UNPKG

@metamask/eth-simple-keyring

Version:

A simple standard interface for a series of Ethereum private keys.

188 lines 10.5 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _SimpleKeyring_instances, _SimpleKeyring_wallets, _SimpleKeyring_getPrivateKeyFor, _SimpleKeyring_getWalletForAccount; function $importDefault(module) { if (module?.__esModule) { return module.default; } return module; } import { ecsign, isValidPrivate, privateToPublic, publicToAddress, stripHexPrefix } from "@ethereumjs/util"; import { concatSig, decrypt, getEncryptionPublicKey, normalize, personalSign, signEIP7702Authorization, signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util"; import { add0x, bigIntToBytes, bytesToHex } from "@metamask/utils"; import { keccak256 } from "ethereum-cryptography/keccak"; import $randombytes from "randombytes"; const randombytes = $importDefault($randombytes); const TYPE = 'Simple Key Pair'; // FIXME: This should not be exported as default. class SimpleKeyring { constructor(privateKeys = []) { _SimpleKeyring_instances.add(this); _SimpleKeyring_wallets.set(this, void 0); this.type = TYPE; __classPrivateFieldSet(this, _SimpleKeyring_wallets, [], "f"); /* istanbul ignore next: It's not possible to write a unit test for this, because a constructor isn't allowed * to be async. Jest can't await the constructor, and when the error gets thrown, Jest can't catch it. */ this.deserialize(privateKeys).catch((error) => { throw new Error(`Problem deserializing SimpleKeyring ${error.message}`); }); } async serialize() { return __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").map((a) => a.privateKey.toString('hex')); } async deserialize(privateKeys) { __classPrivateFieldSet(this, _SimpleKeyring_wallets, privateKeys.map((hexPrivateKey) => { const strippedHexPrivateKey = stripHexPrefix(hexPrivateKey); const privateKey = Buffer.from(strippedHexPrivateKey, 'hex'); const publicKey = Buffer.from(privateToPublic(privateKey)); return { privateKey, publicKey }; }), "f"); } async addAccounts(numAccounts = 1) { const newWallets = []; for (let i = 0; i < numAccounts; i++) { const privateKey = generateKey(); const publicKey = Buffer.from(privateToPublic(privateKey)); newWallets.push({ privateKey, publicKey }); } __classPrivateFieldSet(this, _SimpleKeyring_wallets, __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").concat(newWallets), "f"); const hexWallets = newWallets.map(({ publicKey }) => add0x(bytesToHex(publicToAddress(publicKey)))); return hexWallets; } async getAccounts() { return __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").map(({ publicKey }) => add0x(bytesToHex(publicToAddress(publicKey)))); } async signTransaction(address, transaction, opts = {}) { const privKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, opts); const signedTx = transaction.sign(privKey); // Newer versions of Ethereumjs-tx are immutable and return a new tx object return signedTx ?? transaction; } async signEip7702Authorization(address, authorization, opts = {}) { const privateKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, opts); return signEIP7702Authorization({ privateKey, authorization }); } // For eth_sign, we need to sign arbitrary data: async signMessage(address, data, opts = { withAppKeyOrigin: '', validateMessage: true }) { const message = stripHexPrefix(data); if (opts.validateMessage && (message.length === 0 || !message.match(/^[a-fA-F0-9]*$/u))) { throw new Error('Cannot sign invalid message'); } const privKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, opts); const msgSig = ecsign(Buffer.from(message, 'hex'), privKey); const rawMsgSig = concatSig(Buffer.from(bigIntToBytes(msgSig.v)), Buffer.from(msgSig.r), Buffer.from(msgSig.s)); return rawMsgSig; } // For personal_sign, we need to prefix the message: async signPersonalMessage(address, msgHex, opts = { withAppKeyOrigin: '' }) { const privKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, opts); return personalSign({ privateKey: privKey, data: msgHex }); } // For eth_decryptMessage: async decryptMessage(withAccount, encryptedData) { const wallet = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getWalletForAccount).call(this, withAccount); const privateKey = wallet.privateKey.toString('hex'); return decrypt({ privateKey, encryptedData }); } // personal_signTypedData, signs data along with the schema async signTypedData(address, typedData, opts = { version: SignTypedDataVersion.V1 }) { // Treat invalid versions as "V1" let version = SignTypedDataVersion.V1; if (opts.version && isSignTypedDataVersion(opts.version)) { version = SignTypedDataVersion[opts.version]; } const privateKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, opts); return signTypedData({ privateKey, data: typedData, version }); } // get public key for nacl async getEncryptionPublicKey(withAccount, opts) { const privKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, withAccount, opts); const publicKey = getEncryptionPublicKey(privKey.toString('hex')); return publicKey; } // returns an address specific to an app async getAppKeyAddress(address, origin) { if (!origin || typeof origin !== 'string') { throw new Error(`'origin' must be a non-empty string`); } const wallet = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getWalletForAccount).call(this, address, { withAppKeyOrigin: origin, }); const appKeyAddress = add0x(bytesToHex(publicToAddress(wallet.publicKey))); return appKeyAddress; } // exportAccount should return a hex-encoded private key: async exportAccount(address, opts = { withAppKeyOrigin: '' }) { const wallet = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getWalletForAccount).call(this, address, opts); return wallet.privateKey.toString('hex'); } removeAccount(address) { if (!__classPrivateFieldGet(this, _SimpleKeyring_wallets, "f") .map(({ publicKey }) => bytesToHex(publicToAddress(publicKey)).toLowerCase()) .includes(address.toLowerCase())) { throw new Error(`Address ${address} not found in this keyring`); } __classPrivateFieldSet(this, _SimpleKeyring_wallets, __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").filter(({ publicKey }) => bytesToHex(publicToAddress(publicKey)).toLowerCase() !== address.toLowerCase()), "f"); } } _SimpleKeyring_wallets = new WeakMap(), _SimpleKeyring_instances = new WeakSet(), _SimpleKeyring_getPrivateKeyFor = function _SimpleKeyring_getPrivateKeyFor(address, opts = { withAppKeyOrigin: '' }) { if (!address) { throw new Error('Must specify address.'); } const wallet = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getWalletForAccount).call(this, address, opts); return wallet.privateKey; }, _SimpleKeyring_getWalletForAccount = function _SimpleKeyring_getWalletForAccount(account, opts = {}) { const address = normalize(account); let wallet = __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").find(({ publicKey }) => bytesToHex(publicToAddress(publicKey)) === address); if (!wallet) { throw new Error('Simple Keyring - Unable to find matching address.'); } if (opts.withAppKeyOrigin) { const { privateKey } = wallet; const appKeyOriginBuffer = Buffer.from(opts.withAppKeyOrigin, 'utf8'); const appKeyBuffer = Buffer.concat([privateKey, appKeyOriginBuffer]); const appKeyPrivateKey = keccak256(appKeyBuffer); const appKeyPublicKey = privateToPublic(appKeyPrivateKey); wallet = { privateKey: Buffer.from(appKeyPrivateKey), publicKey: Buffer.from(appKeyPublicKey), }; } return wallet; }; SimpleKeyring.type = TYPE; export default SimpleKeyring; /** * Generate and validate a new random key of 32 bytes. * * @returns Buffer The generated key. */ function generateKey() { const privateKey = randombytes(32); if (!isValidPrivate(privateKey)) { throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)'); } return privateKey; } /** * Type predicate type guard to check if a string is in the enum SignTypedDataVersion. * * @param version - The string to check. * @returns Whether it's in the enum. */ // TODO: Put this in @metamask/eth-sig-util function isSignTypedDataVersion(version) { return version in SignTypedDataVersion; } //# sourceMappingURL=simple-keyring.mjs.map