@metamask/eth-simple-keyring
Version:
A simple standard interface for a series of Ethereum private keys.
187 lines • 10.4 kB
JavaScript
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 });
}
async signTypedData(address, data, options) {
let { version } = options ?? { version: SignTypedDataVersion.V1 };
// Treat invalid versions as "V1"
if (!version || !isSignTypedDataVersion(version)) {
version = SignTypedDataVersion.V1;
}
const privateKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, options);
return signTypedData({ privateKey, data, 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