UNPKG

eth-onekey-bridge-keyring

Version:
474 lines 22.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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 _OneKeyKeyring_instances, _OneKeyKeyring_normalize, _OneKeyKeyring_signTransaction, _OneKeyKeyring_getPage, _OneKeyKeyring_accountDetailsFromAddress, _OneKeyKeyring_addressFromIndex, _OneKeyKeyring_getDerivePath, _OneKeyKeyring_getBasePath, _OneKeyKeyring_getPathForIndex, _OneKeyKeyring_isLedgerLiveHdPath, _OneKeyKeyring_isLedgerLegacyHdPath, _OneKeyKeyring_isStandardBip44HdPath, _OneKeyKeyring_isSameHdPath; Object.defineProperty(exports, "__esModule", { value: true }); exports.OneKeyKeyring = void 0; const tx_1 = require("@ethereumjs/tx"); const ethUtil = __importStar(require("@ethereumjs/util")); const eth_sig_util_1 = require("@metamask/eth-sig-util"); // eslint-disable-next-line @typescript-eslint/no-shadow, n/prefer-global/buffer const buffer_1 = require("buffer"); // eslint-disable-next-line @typescript-eslint/naming-convention const hdkey_1 = __importDefault(require("hdkey")); const pathBase = 'm'; const defaultHdPath = `${pathBase}/44'/60'/0'/0`; const keyringType = 'OneKey Hardware'; const hdPathString = `m/44'/60'/0'/0/x`; const ledgerLegacyHdPathString = `m/44'/60'/0'/x`; const ALLOWED_HD_PATHS = { [defaultHdPath]: true, [hdPathString]: true, [ledgerLegacyHdPathString]: true, }; /** * Check if the given value has a hex prefix. * * @param value - The value to check. * @returns Returns `true` if the value has a hex prefix. */ function hasHexPrefix(value) { return value.startsWith('0x'); } /** * Add a hex prefix to the given value. * * @param value - The value to add a hex prefix to. * @returns Returns the value with a hex prefix. */ function addHexPrefix(value) { if (hasHexPrefix(value)) { return value; } return `0x${value}`; } /** * Check if the passphrase state is empty. * * @param passphraseState - The passphrase state to check. * @returns Returns `true` if the passphrase state is empty. */ function isEmptyPassphrase(passphraseState) { return (passphraseState === null || passphraseState === undefined || passphraseState === ''); } class OneKeyKeyring { constructor({ bridge }) { _OneKeyKeyring_instances.add(this); this.type = keyringType; this.page = 0; this.perPage = 5; this.unlockedAccount = 0; this.hdk = new hdkey_1.default(); this.accounts = []; this.accountDetails = {}; this.hdPath = defaultHdPath; if (!bridge) { throw new Error('Bridge is a required dependency for the keyring'); } this.bridge = bridge; } async init() { return this.bridge.init(); } async destroy() { return this.bridge.dispose(); } async serialize() { return { hdPath: this.hdPath, accounts: [...this.accounts], accountDetails: Object.assign({}, this.accountDetails), page: this.page, }; } async deserialize(state) { var _a, _b, _c, _d; this.hdPath = (_a = state.hdPath) !== null && _a !== void 0 ? _a : defaultHdPath; this.accounts = (_b = state.accounts) !== null && _b !== void 0 ? _b : []; this.accountDetails = (_c = state.accountDetails) !== null && _c !== void 0 ? _c : {}; this.page = (_d = state.page) !== null && _d !== void 0 ? _d : 0; } getModel() { return this.bridge.model; } setAccountToUnlock(index) { this.unlockedAccount = index; } setHdPath(hdPath) { if (!ALLOWED_HD_PATHS[hdPath]) { throw new Error('Unknown HD path'); } // Reset HDKey if the path changes if (!__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isSameHdPath).call(this, hdPath)) { this.hdk = new hdkey_1.default(); this.accounts = []; this.page = 0; this.perPage = 5; this.unlockedAccount = 0; this.accountDetails = {}; } this.hdPath = hdPath; } lock() { this.hdk = new hdkey_1.default(); } isUnlocked() { var _a; return Boolean((_a = this.hdk) === null || _a === void 0 ? void 0 : _a.publicKey); } async unlock() { if (this.isUnlocked()) { return 'already unlocked'; } return new Promise((resolve, reject) => { // eslint-disable-next-line no-void void this.bridge .getPassphraseState() .then((passphraseResponse) => { var _a, _b; if (!passphraseResponse.success) { throw new Error(((_a = passphraseResponse.payload) === null || _a === void 0 ? void 0 : _a.error) || 'Unknown error'); } this.passphraseState = passphraseResponse.payload; // eslint-disable-next-line no-void void this.bridge .getPublicKey({ showOnOneKey: false, chainId: 1, path: __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_getBasePath).call(this), passphraseState: (_b = this.passphraseState) !== null && _b !== void 0 ? _b : '', }) .then(async (res) => { if (res.success) { this.hdk.publicKey = buffer_1.Buffer.from(res.payload.publicKey, 'hex'); this.hdk.chainCode = buffer_1.Buffer.from(res.payload.chainCode, 'hex'); resolve('just unlocked'); } else { reject(new Error('getPublicKey failed')); } }) .catch((error) => { reject(new Error((error === null || error === void 0 ? void 0 : error.toString()) || 'Unknown error')); }); }) .catch((error) => { reject(new Error((error === null || error === void 0 ? void 0 : error.toString()) || 'Unknown error')); }); }); } async addAccounts(numberOfAccounts = 1) { await this.unlock(); const from = this.unlockedAccount; const to = from + numberOfAccounts; const newAccounts = []; for (let i = from; i < to; i++) { const address = __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_addressFromIndex).call(this, i); const hdPath = __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_getPathForIndex).call(this, i); if (typeof address === 'undefined') { throw new Error('Unknown error'); } if (!this.accounts.includes(address)) { this.accounts = [...this.accounts, address]; newAccounts.push(address); } if (!this.accountDetails[address]) { this.accountDetails[address] = { index: i, hdPath, passphraseState: this.passphraseState, }; } this.page = 0; } return newAccounts; } getName() { return keyringType; } async getFirstPage() { this.page = 0; return __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_getPage).call(this, 1); } async getNextPage() { return __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_getPage).call(this, 1); } async getPreviousPage() { return __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_getPage).call(this, -1); } async getAccounts() { return Promise.resolve(this.accounts.slice()); } removeAccount(address) { const filteredAccounts = this.accounts.filter((a) => a.toLowerCase() !== address.toLowerCase()); if (filteredAccounts.length === this.accounts.length) { throw new Error(`Address ${address} not found in this keyring`); } this.accounts = filteredAccounts; delete this.accountDetails[ethUtil.toChecksumAddress(address)]; } async updateTransportMethod(transportType) { return this.bridge.updateTransportMethod(transportType); } /** * Signs a transaction using OneKey. * * Accepts either an ethereumjs-tx or @ethereumjs/tx transaction, and returns * the same type. * * @param address - Hex string address. * @param tx - Instance of either new-style or old-style ethereumjs transaction. * @returns The signed transaction, an instance of either new-style or old-style * ethereumjs transaction. */ async signTransaction(address, tx) { return __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_signTransaction).call(this, address, Number(tx.common.chainId()), tx, (payload) => { // Because tx will be immutable, first get a plain javascript object that // represents the transaction. Using txData here as it aligns with the // nomenclature of ethereumjs/tx. const txData = tx.toJSON(); // The fromTxData utility expects a type to support transactions with a type other than 0 txData.type = tx.type; // The fromTxData utility expects v,r and s to be hex prefixed txData.v = ethUtil.addHexPrefix(payload.v); txData.r = ethUtil.addHexPrefix(payload.r); txData.s = ethUtil.addHexPrefix(payload.s); // Adopt the 'common' option from the original transaction and set the // returned object to be frozen if the original is frozen. return tx_1.TransactionFactory.fromTxData(txData, { common: tx.common, freeze: Object.isFrozen(tx), }); }); } async signMessage(withAccount, data) { return this.signPersonalMessage(withAccount, data); } // For personal_sign, we need to prefix the message: async signPersonalMessage(withAccount, message) { return new Promise((resolve, reject) => { var _a; const details = __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_accountDetailsFromAddress).call(this, withAccount); this.bridge .ethereumSignMessage({ path: details.hdPath, passphraseState: (_a = details.passphraseState) !== null && _a !== void 0 ? _a : '', useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), messageHex: ethUtil.stripHexPrefix(message), }) .then((response) => { var _a; if (response.success) { const signature = addHexPrefix(response.payload.signature); const addressSignedWith = (0, eth_sig_util_1.recoverPersonalSignature)({ data: message, signature, }); if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { reject(new Error('signature doesnt match the right address')); } // eslint-disable-next-line promise/no-multiple-resolved resolve(signature); } else { reject(new Error(((_a = response.payload) === null || _a === void 0 ? void 0 : _a.error) || 'Unknown error')); } }) .catch((error) => { reject(new Error((error === null || error === void 0 ? void 0 : error.toString()) || 'Unknown error')); }); }); } // EIP-712 Sign Typed Data async signTypedData(address, data, { version }) { var _a, _b; // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison const useV4 = version === eth_sig_util_1.SignTypedDataVersion.V4; const dataVersion = useV4 ? eth_sig_util_1.SignTypedDataVersion.V4 : eth_sig_util_1.SignTypedDataVersion.V3; const typedData = eth_sig_util_1.TypedDataUtils.sanitizeData(data); const domainHash = eth_sig_util_1.TypedDataUtils.hashStruct('EIP712Domain', typedData.domain, typedData.types, dataVersion).toString('hex'); const messageHash = eth_sig_util_1.TypedDataUtils.hashStruct(typedData.primaryType, typedData.message, typedData.types, dataVersion).toString('hex'); const details = __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_accountDetailsFromAddress).call(this, address); const response = await this.bridge.ethereumSignTypedData({ path: details.hdPath, passphraseState: (_a = details.passphraseState) !== null && _a !== void 0 ? _a : '', useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), data: data, domainHash, messageHash, metamaskV4Compat: Boolean(useV4), // eslint-disable-line camelcase }); if (response.success) { const signature = addHexPrefix(response.payload.signature); const addressSignedWith = (0, eth_sig_util_1.recoverTypedSignature)({ data: typedData, signature, version: dataVersion, }); if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(address)) { throw new Error('signature doesnt match the right address'); } return signature; } throw new Error(((_b = response.payload) === null || _b === void 0 ? void 0 : _b.error) || 'Unknown error'); } forgetDevice() { this.hdk = new hdkey_1.default(); this.accounts = []; this.page = 0; this.unlockedAccount = 0; this.accountDetails = {}; this.passphraseState = undefined; } } exports.OneKeyKeyring = OneKeyKeyring; _OneKeyKeyring_instances = new WeakSet(), _OneKeyKeyring_normalize = function _OneKeyKeyring_normalize(buffer) { return ethUtil.bytesToHex(new Uint8Array(buffer)); }, _OneKeyKeyring_signTransaction = async function _OneKeyKeyring_signTransaction(address, chainId, tx, handleSigning) { var _a, _b, _c, _d, _e; // new-style transaction from @ethereumjs/tx package // we can just copy tx.toJSON() for everything except chainId, which must be a number const transaction = Object.assign(Object.assign({}, tx.toJSON()), { chainId, to: __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_normalize).call(this, buffer_1.Buffer.from((_b = (_a = tx.to) === null || _a === void 0 ? void 0 : _a.bytes) !== null && _b !== void 0 ? _b : [])) }); try { const details = __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_accountDetailsFromAddress).call(this, address); const response = await this.bridge.ethereumSignTransaction({ path: details.hdPath, passphraseState: (_c = details.passphraseState) !== null && _c !== void 0 ? _c : '', useEmptyPassphrase: isEmptyPassphrase(details.passphraseState), transaction, }); if (response.success) { const newOrMutatedTx = handleSigning(response.payload); const addressSignedWith = ethUtil.toChecksumAddress(ethUtil.addHexPrefix(newOrMutatedTx.getSenderAddress().toString())); const correctAddress = ethUtil.toChecksumAddress(address); if (addressSignedWith !== correctAddress) { throw new Error("signature doesn't match the right address"); } return newOrMutatedTx; } throw new Error(((_d = response.payload) === null || _d === void 0 ? void 0 : _d.error) || 'Unknown error'); } catch (error) { throw new Error((_e = error === null || error === void 0 ? void 0 : error.toString()) !== null && _e !== void 0 ? _e : 'Unknown error'); } }, _OneKeyKeyring_getPage = async function _OneKeyKeyring_getPage(increment) { this.page += increment; if (this.page <= 0) { this.page = 1; } return new Promise((resolve, reject) => { const from = (this.page - 1) * this.perPage; const to = from + this.perPage; const accounts = []; this.unlock() .then(async () => { for (let i = from; i < to; i++) { const address = __classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_addressFromIndex).call(this, i); if (typeof address === 'undefined') { throw new Error('Unknown error'); } accounts.push({ index: i, address, balance: null, }); } resolve(accounts); }) .catch((error) => { // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(error); }); }); }, _OneKeyKeyring_accountDetailsFromAddress = function _OneKeyKeyring_accountDetailsFromAddress(address) { const checksummedAddress = ethUtil.toChecksumAddress(address); const accountDetails = this.accountDetails[checksummedAddress]; if (typeof accountDetails === 'undefined') { throw new Error('Unknown address'); } return accountDetails; }, _OneKeyKeyring_addressFromIndex = function _OneKeyKeyring_addressFromIndex(i) { const dkey = this.hdk.derive(__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_getDerivePath).call(this, i)); const address = ethUtil.bytesToHex(ethUtil.publicToAddress(new Uint8Array(dkey.publicKey), true)); return ethUtil.toChecksumAddress(address); }, _OneKeyKeyring_getDerivePath = function _OneKeyKeyring_getDerivePath(index) { if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isLedgerLiveHdPath).call(this)) { throw new Error('Ledger Live is not supported'); } if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isStandardBip44HdPath).call(this)) { return `${pathBase}/0/${index}`; } return `${pathBase}/${index}`; }, _OneKeyKeyring_getBasePath = function _OneKeyKeyring_getBasePath() { if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isLedgerLiveHdPath).call(this)) { throw new Error('Ledger Live is not supported'); } return "m/44'/60'/0'"; }, _OneKeyKeyring_getPathForIndex = function _OneKeyKeyring_getPathForIndex(index) { // Check if the path is BIP 44 (Ledger Live) if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isLedgerLiveHdPath).call(this)) { return `m/44'/60'/${index}'/0/0`; } if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isLedgerLegacyHdPath).call(this)) { return `m/44'/60'/0'/${index}`; } if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isStandardBip44HdPath).call(this)) { return `m/44'/60'/0'/0/${index}`; } // default path: m/44'/60'/0'/0/x return `${this.hdPath}/${index}`; }, _OneKeyKeyring_isLedgerLiveHdPath = function _OneKeyKeyring_isLedgerLiveHdPath() { return this.hdPath === `m/44'/60'/x'/0/0`; }, _OneKeyKeyring_isLedgerLegacyHdPath = function _OneKeyKeyring_isLedgerLegacyHdPath() { return this.hdPath === `m/44'/60'/0'/x`; }, _OneKeyKeyring_isStandardBip44HdPath = function _OneKeyKeyring_isStandardBip44HdPath() { return this.hdPath === `m/44'/60'/0'/0/x` || this.hdPath === defaultHdPath; }, _OneKeyKeyring_isSameHdPath = function _OneKeyKeyring_isSameHdPath(newHdPath) { if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isLedgerLiveHdPath).call(this)) { return newHdPath === `m/44'/60'/x'/0/0`; } if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isLedgerLegacyHdPath).call(this)) { return newHdPath === `m/44'/60'/0'/x`; } if (__classPrivateFieldGet(this, _OneKeyKeyring_instances, "m", _OneKeyKeyring_isStandardBip44HdPath).call(this)) { return newHdPath === `m/44'/60'/0'/0/x` || newHdPath === defaultHdPath; } return this.hdPath === newHdPath; }; OneKeyKeyring.type = keyringType; //# sourceMappingURL=onekey-keyring.cjs.map