@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
JavaScript
;
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;