UNPKG

@thirdweb-dev/wallets

Version:

<p align="center"> <br /> <a href="https://thirdweb.com"><img src="https://github.com/thirdweb-dev/js/blob/main/legacy_packages/sdk/logo.svg?raw=true" width="200" alt=""/></a> <br /> </p> <h1 align="center">thirdweb Wallet SDK</h1> <p align="center"> <a h

297 lines (278 loc) • 11.6 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var evm_wallets_abstract_dist_thirdwebDevWalletsEvmWalletsAbstract = require('../../abstract/dist/thirdweb-dev-wallets-evm-wallets-abstract.cjs.dev.js'); var ethSigUtil = require('@metamask/eth-sig-util'); var ethers = require('ethers'); var ethereumjsUtil = require('ethereumjs-util'); var kms = require('@google-cloud/kms'); var asn1 = require('asn1.js'); var BN = require('bn.js'); var KeyEncoder = require('key-encoder'); require('../../../../dist/defineProperty-9051a5d3.cjs.dev.js'); require('eventemitter3'); require('@thirdweb-dev/sdk'); function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var asn1__namespace = /*#__PURE__*/_interopNamespace(asn1); var BN__default = /*#__PURE__*/_interopDefault(BN); var KeyEncoder__default = /*#__PURE__*/_interopDefault(KeyEncoder); const keyEncoder = new KeyEncoder__default["default"]("secp256k1"); /* this asn1.js library has some funky things going on */ /* eslint-disable func-names */ const EcdsaSigAsnParse = asn1__namespace.define("EcdsaSig", function () { // parsing this according to https://tools.ietf.org/html/rfc3279#section-2.2.3 this.seq().obj(this.key("r").int(), this.key("s").int()); }); // eslint-disable-next-line better-tree-shaking/no-top-level-side-effects const EcdsaPubKey = asn1__namespace.define("EcdsaPubKey", function () { // parsing this according to https://tools.ietf.org/html/rfc5480#section-2 this.seq().obj(this.key("algo").seq().obj(this.key("a").objid(), this.key("b").objid()), this.key("pubKey").bitstr()); }); /* eslint-enable func-names */ function getClientCredentials(kmsCredentials) { if (kmsCredentials.applicationCredentialEmail && kmsCredentials.applicationCredentialPrivateKey) { return { credentials: { client_email: kmsCredentials.applicationCredentialEmail, private_key: kmsCredentials.applicationCredentialPrivateKey.replace(/\\n/gm, "\n") } }; } const applicationCredentialEmail = // eslint-disable-next-line turbo/no-undeclared-env-vars process.env.GOOGLE_APPLICATION_CREDENTIAL_EMAIL; const applicationCredentialPrivateKey = // eslint-disable-next-line turbo/no-undeclared-env-vars process.env.GOOGLE_APPLICATION_CREDENTIAL_PRIVATE_KEY; return applicationCredentialEmail && applicationCredentialPrivateKey ? { credentials: { client_email: applicationCredentialEmail, private_key: applicationCredentialPrivateKey.replace(/\\n/gm, "\n") } } : {}; } async function sign(digest, kmsCredentials) { const kms$1 = new kms.KeyManagementServiceClient(getClientCredentials(kmsCredentials)); const versionName = kms$1.cryptoKeyVersionPath(kmsCredentials.projectId, kmsCredentials.locationId, kmsCredentials.keyRingId, kmsCredentials.keyId, kmsCredentials.keyVersion); const [asymmetricSignResponse] = await kms$1.asymmetricSign({ name: versionName, digest: { sha256: digest } }); return asymmetricSignResponse; } const getPublicKey = async kmsCredentials => { const kms$1 = new kms.KeyManagementServiceClient(getClientCredentials(kmsCredentials)); const versionName = kms$1.cryptoKeyVersionPath(kmsCredentials.projectId, kmsCredentials.locationId, kmsCredentials.keyRingId, kmsCredentials.keyId, kmsCredentials.keyVersion); const [publicKey] = await kms$1.getPublicKey({ name: versionName }); if (!publicKey || !publicKey.pem) { throw new Error(`Can not find key: ${kmsCredentials.keyId}`); } // GCP KMS returns the public key in pem format, // so we need to encode it to der format, and return the hex buffer. const der = keyEncoder.encodePublic(publicKey.pem, "pem", "der"); return Buffer.from(der, "hex"); }; function getEthereumAddress(publicKey) { // The public key here is a hex der ASN1 encoded in a format according to // https://tools.ietf.org/html/rfc5480#section-2 // I used https://lapo.it/asn1js to figure out how to parse this // and defined the schema in the EcdsaPubKey object. const res = EcdsaPubKey.decode(publicKey, "der"); const pubKeyBuffer = res.pubKey.data; // The raw format public key starts with a `04` prefix that needs to be removed // more info: https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html // const pubStr = publicKey.toString(); const pubFormatted = pubKeyBuffer.slice(1, pubKeyBuffer.length); // keccak256 hash of publicKey const address = ethers.ethers.utils.keccak256(pubFormatted); // take last 20 bytes as ethereum address const EthAddr = `0x${address.slice(-40)}`; return EthAddr; } function findEthereumSig(signature) { const decoded = EcdsaSigAsnParse.decode(signature, "der"); const { r, s } = decoded; const secp256k1N = new BN__default["default"]("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); // max value on the curve const secp256k1halfN = secp256k1N.div(new BN__default["default"](2)); // half of the curve // Because of EIP-2 not all elliptic curve signatures are accepted // the value of s needs to be SMALLER than half of the curve // i.e. we need to flip s if it's greater than half of the curve // if s is less than half of the curve, we're on the "good" side of the curve, we can just return return { r, s: s.gt(secp256k1halfN) ? secp256k1N.sub(s) : s }; } async function requestKmsSignature(plaintext, kmsCredentials) { const response = await sign(plaintext, kmsCredentials); if (!response || !response.signature) { throw new Error(`GCP KMS call failed`); } return findEthereumSig(response.signature); } function recoverPubKeyFromSig(msg, r, s, v) { return ethers.ethers.utils.recoverAddress(`0x${msg.toString("hex")}`, { r: `0x${r.toString("hex")}`, s: `0x${s.toString("hex")}`, v }); } function determineCorrectV(msg, r, s, expectedEthAddr) { // This is the wrapper function to find the right v value // There are two matching signatures on the elliptic curve // we need to find the one that matches to our public key // it can be v = 27 or v = 28 let v = 27; let pubKey = recoverPubKeyFromSig(msg, r, s, v); if (pubKey.toLowerCase() !== expectedEthAddr.toLowerCase()) { // if the pub key for v = 27 does not match // it has to be v = 28 v = 28; pubKey = recoverPubKeyFromSig(msg, r, s, v); } return { pubKey, v }; } /** * Validate that the given value is a valid version string. * * @param version - The version value to validate. * @param allowedVersions - A list of allowed versions. If omitted, all versions are assumed to be * allowed. */ function validateVersion(version, allowedVersions) { if (!Object.keys(ethSigUtil.SignTypedDataVersion).includes(version)) { throw new Error(`Invalid version: '${version}'`); } else if (allowedVersions && !allowedVersions.includes(version)) { throw new Error(`SignTypedDataVersion not allowed: '${version}'. Allowed versions are: ${allowedVersions.join(", ")}`); } } /** * All code forked from ethers-gcp-kms-signer repository: * https://github.com/openlawteam/ethers-gcp-kms-signer/tree/master * * We had to fork this code because the repository forces GOOGLE_APPLICATION_CREDENTIALS * to be set with environment variables, but we need a path to pass them directly. */ class GcpKmsSigner extends ethers.ethers.Signer { // @ts-expect-error Allow defineReadOnly to set this property // @ts-expect-error Allow defineReadOnly to set this property constructor(kmsCredentials, provider) { super(); // @ts-expect-error Allow passing null here ethers.ethers.utils.defineReadOnly(this, "provider", provider || null); ethers.ethers.utils.defineReadOnly(this, "kmsCredentials", kmsCredentials); } async getAddress() { if (this.ethereumAddress === undefined) { const key = await getPublicKey(this.kmsCredentials); this.ethereumAddress = getEthereumAddress(key); } return Promise.resolve(this.ethereumAddress); } async _signDigest(digestString) { const digestBuffer = Buffer.from(ethers.ethers.utils.arrayify(digestString)); const sig = await requestKmsSignature(digestBuffer, this.kmsCredentials); const ethAddr = await this.getAddress(); const { v } = determineCorrectV(digestBuffer, sig.r, sig.s, ethAddr); return ethers.ethers.utils.joinSignature({ v, r: `0x${sig.r.toString("hex")}`, s: `0x${sig.s.toString("hex")}` }); } async signMessage(message) { return this._signDigest(ethers.ethers.utils.hashMessage(message)); } /** * Original implementation takes into account the private key, but here we use the private * key from the GCP KMS, so we don't need to provide the PK as signature option. * Source code: https://github.com/MetaMask/eth-sig-util/blob/main/src/sign-typed-data.ts#L510 * . * Sign typed data according to EIP-712. The signing differs based upon the `version`. * * V1 is based upon [an early version of EIP-712](https://github.com/ethereum/EIPs/pull/712/commits/21abe254fe0452d8583d5b132b1d7be87c0439ca) * that lacked some later security improvements, and should generally be neglected in favor of * later versions. * * V3 is based on [EIP-712](https://eips.ethereum.org/EIPS/eip-712), except that arrays and * recursive data structures are not supported. * * V4 is based on [EIP-712](https://eips.ethereum.org/EIPS/eip-712), and includes full support of * arrays and recursive data structures. * * @param options - The signing options. * @returns The '0x'-prefixed hex encoded signature. */ async signTypedData(_ref) { let { data, version } = _ref; validateVersion(version); if (data === null || data === undefined) { throw new Error("Missing data parameter"); } let messageSignature; if (version === ethSigUtil.SignTypedDataVersion.V1) { messageSignature = this._signDigest(ethSigUtil.typedSignatureHash(data)); } else { const eip712Hash = ethSigUtil.TypedDataUtils.eip712Hash(data, version); messageSignature = this._signDigest(ethereumjsUtil.bufferToHex(eip712Hash)); } return messageSignature; } async _signTypedData(domain, types, value) { const hash = ethers.ethers.utils._TypedDataEncoder.hash(domain, types, value); return this._signDigest(hash); } async signTransaction(transaction) { const unsignedTx = await ethers.ethers.utils.resolveProperties(transaction); const serializedTx = ethers.ethers.utils.serializeTransaction(unsignedTx); const transactionSignature = await this._signDigest(ethers.ethers.utils.keccak256(serializedTx)); return ethers.ethers.utils.serializeTransaction(unsignedTx, transactionSignature); } connect(provider) { return new GcpKmsSigner(this.kmsCredentials, provider); } } /** * @wallet */ class GcpKmsWallet extends evm_wallets_abstract_dist_thirdwebDevWalletsEvmWalletsAbstract.AbstractWallet { constructor(options) { super(); this._options = options; } async getSigner() { return new GcpKmsSigner(this._options); } } exports.GcpKmsWallet = GcpKmsWallet;