@metamask/eth-simple-keyring
Version:
A simple standard interface for a series of Ethereum private keys.
186 lines • 10.9 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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _SimpleKeyring_instances, _SimpleKeyring_wallets, _SimpleKeyring_getPrivateKeyFor, _SimpleKeyring_getWalletForAccount;
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("@ethereumjs/util");
const eth_sig_util_1 = require("@metamask/eth-sig-util");
const utils_1 = require("@metamask/utils");
const keccak_1 = require("ethereum-cryptography/keccak");
const randombytes_1 = __importDefault(require("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 = (0, util_1.stripHexPrefix)(hexPrivateKey);
const privateKey = Buffer.from(strippedHexPrivateKey, 'hex');
const publicKey = Buffer.from((0, util_1.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((0, util_1.privateToPublic)(privateKey));
newWallets.push({ privateKey, publicKey });
}
__classPrivateFieldSet(this, _SimpleKeyring_wallets, __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").concat(newWallets), "f");
const hexWallets = newWallets.map(({ publicKey }) => (0, utils_1.add0x)((0, utils_1.bytesToHex)((0, util_1.publicToAddress)(publicKey))));
return hexWallets;
}
async getAccounts() {
return __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").map(({ publicKey }) => (0, utils_1.add0x)((0, utils_1.bytesToHex)((0, util_1.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 (0, eth_sig_util_1.signEIP7702Authorization)({ privateKey, authorization });
}
// For eth_sign, we need to sign arbitrary data:
async signMessage(address, data, opts = { withAppKeyOrigin: '', validateMessage: true }) {
const message = (0, util_1.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 = (0, util_1.ecsign)(Buffer.from(message, 'hex'), privKey);
const rawMsgSig = (0, eth_sig_util_1.concatSig)(Buffer.from((0, utils_1.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 (0, eth_sig_util_1.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 (0, eth_sig_util_1.decrypt)({ privateKey, encryptedData });
}
// personal_signTypedData, signs data along with the schema
async signTypedData(address, typedData, opts = { version: eth_sig_util_1.SignTypedDataVersion.V1 }) {
// Treat invalid versions as "V1"
let version = eth_sig_util_1.SignTypedDataVersion.V1;
if (opts.version && isSignTypedDataVersion(opts.version)) {
version = eth_sig_util_1.SignTypedDataVersion[opts.version];
}
const privateKey = __classPrivateFieldGet(this, _SimpleKeyring_instances, "m", _SimpleKeyring_getPrivateKeyFor).call(this, address, opts);
return (0, eth_sig_util_1.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 = (0, eth_sig_util_1.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 = (0, utils_1.add0x)((0, utils_1.bytesToHex)((0, util_1.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 }) => (0, utils_1.bytesToHex)((0, util_1.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 }) => (0, utils_1.bytesToHex)((0, util_1.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 = (0, eth_sig_util_1.normalize)(account);
let wallet = __classPrivateFieldGet(this, _SimpleKeyring_wallets, "f").find(({ publicKey }) => (0, utils_1.bytesToHex)((0, util_1.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 = (0, keccak_1.keccak256)(appKeyBuffer);
const appKeyPublicKey = (0, util_1.privateToPublic)(appKeyPrivateKey);
wallet = {
privateKey: Buffer.from(appKeyPrivateKey),
publicKey: Buffer.from(appKeyPublicKey),
};
}
return wallet;
};
SimpleKeyring.type = TYPE;
exports.default = SimpleKeyring;
/**
* Generate and validate a new random key of 32 bytes.
*
* @returns Buffer The generated key.
*/
function generateKey() {
const privateKey = (0, randombytes_1.default)(32);
if (!(0, util_1.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 eth_sig_util_1.SignTypedDataVersion;
}
//# sourceMappingURL=simple-keyring.cjs.map