eth-onekey-bridge-keyring
Version:
A MetaMask compatible keyring, for OneKey hardware wallets
474 lines • 22.4 kB
JavaScript
;
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