|
@@ -1,31 +1,14 @@
|
|
-const {
|
|
|
|
- AbiCoder,
|
|
|
|
- AbstractSigner,
|
|
|
|
- Signature,
|
|
|
|
- TypedDataEncoder,
|
|
|
|
- assert,
|
|
|
|
- assertArgument,
|
|
|
|
- concat,
|
|
|
|
- dataLength,
|
|
|
|
- decodeBase64,
|
|
|
|
- getBytes,
|
|
|
|
- getBytesCopy,
|
|
|
|
- hashMessage,
|
|
|
|
- hexlify,
|
|
|
|
- sha256,
|
|
|
|
- toBeHex,
|
|
|
|
- keccak256,
|
|
|
|
-} = require('ethers');
|
|
|
|
|
|
+const { ethers } = require('ethers');
|
|
const { secp256r1 } = require('@noble/curves/p256');
|
|
const { secp256r1 } = require('@noble/curves/p256');
|
|
const { generateKeyPairSync, privateEncrypt } = require('crypto');
|
|
const { generateKeyPairSync, privateEncrypt } = require('crypto');
|
|
|
|
|
|
// Lightweight version of BaseWallet
|
|
// Lightweight version of BaseWallet
|
|
-class NonNativeSigner extends AbstractSigner {
|
|
|
|
|
|
+class NonNativeSigner extends ethers.AbstractSigner {
|
|
#signingKey;
|
|
#signingKey;
|
|
|
|
|
|
constructor(privateKey, provider) {
|
|
constructor(privateKey, provider) {
|
|
super(provider);
|
|
super(provider);
|
|
- assertArgument(
|
|
|
|
|
|
+ ethers.assertArgument(
|
|
privateKey && typeof privateKey.sign === 'function',
|
|
privateKey && typeof privateKey.sign === 'function',
|
|
'invalid private key',
|
|
'invalid private key',
|
|
'privateKey',
|
|
'privateKey',
|
|
@@ -54,7 +37,7 @@ class NonNativeSigner extends AbstractSigner {
|
|
}
|
|
}
|
|
|
|
|
|
async signMessage(message /*: string | Uint8Array*/) /*: Promise<string>*/ {
|
|
async signMessage(message /*: string | Uint8Array*/) /*: Promise<string>*/ {
|
|
- return this.signingKey.sign(hashMessage(message)).serialized;
|
|
|
|
|
|
+ return this.signingKey.sign(ethers.hashMessage(message)).serialized;
|
|
}
|
|
}
|
|
|
|
|
|
async signTypedData(
|
|
async signTypedData(
|
|
@@ -63,17 +46,17 @@ class NonNativeSigner extends AbstractSigner {
|
|
value /*: Record<string, any>*/,
|
|
value /*: Record<string, any>*/,
|
|
) /*: Promise<string>*/ {
|
|
) /*: Promise<string>*/ {
|
|
// Populate any ENS names
|
|
// Populate any ENS names
|
|
- const populated = await TypedDataEncoder.resolveNames(domain, types, value, async name => {
|
|
|
|
- assert(this.provider != null, 'cannot resolve ENS names without a provider', 'UNSUPPORTED_OPERATION', {
|
|
|
|
|
|
+ const populated = await ethers.TypedDataEncoder.resolveNames(domain, types, value, async name => {
|
|
|
|
+ ethers.assert(this.provider != null, 'cannot resolve ENS names without a provider', 'UNSUPPORTED_OPERATION', {
|
|
operation: 'resolveName',
|
|
operation: 'resolveName',
|
|
info: { name },
|
|
info: { name },
|
|
});
|
|
});
|
|
const address = await this.provider.resolveName(name);
|
|
const address = await this.provider.resolveName(name);
|
|
- assert(address != null, 'unconfigured ENS name', 'UNCONFIGURED_NAME', { value: name });
|
|
|
|
|
|
+ ethers.assert(address != null, 'unconfigured ENS name', 'UNCONFIGURED_NAME', { value: name });
|
|
return address;
|
|
return address;
|
|
});
|
|
});
|
|
|
|
|
|
- return this.signingKey.sign(TypedDataEncoder.hash(populated.domain, types, populated.value)).serialized;
|
|
|
|
|
|
+ return this.signingKey.sign(ethers.TypedDataEncoder.hash(populated.domain, types, populated.value)).serialized;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -81,7 +64,7 @@ class P256SigningKey {
|
|
#privateKey;
|
|
#privateKey;
|
|
|
|
|
|
constructor(privateKey) {
|
|
constructor(privateKey) {
|
|
- this.#privateKey = getBytes(privateKey);
|
|
|
|
|
|
+ this.#privateKey = ethers.getBytes(privateKey);
|
|
}
|
|
}
|
|
|
|
|
|
static random() {
|
|
static random() {
|
|
@@ -89,20 +72,27 @@ class P256SigningKey {
|
|
}
|
|
}
|
|
|
|
|
|
get privateKey() {
|
|
get privateKey() {
|
|
- return hexlify(this.#privateKey);
|
|
|
|
|
|
+ return ethers.hexlify(this.#privateKey);
|
|
}
|
|
}
|
|
|
|
|
|
get publicKey() {
|
|
get publicKey() {
|
|
const publicKeyBytes = secp256r1.getPublicKey(this.#privateKey, false);
|
|
const publicKeyBytes = secp256r1.getPublicKey(this.#privateKey, false);
|
|
- return { qx: hexlify(publicKeyBytes.slice(0x01, 0x21)), qy: hexlify(publicKeyBytes.slice(0x21, 0x41)) };
|
|
|
|
|
|
+ return {
|
|
|
|
+ qx: ethers.hexlify(publicKeyBytes.slice(0x01, 0x21)),
|
|
|
|
+ qy: ethers.hexlify(publicKeyBytes.slice(0x21, 0x41)),
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
- sign(digest /*: BytesLike*/) /*: Signature*/ {
|
|
|
|
- assertArgument(dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
|
|
+ sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
|
|
|
|
+ ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
|
|
- const sig = secp256r1.sign(getBytesCopy(digest), getBytesCopy(this.#privateKey), { lowS: true });
|
|
|
|
|
|
+ const sig = secp256r1.sign(ethers.getBytesCopy(digest), ethers.getBytesCopy(this.#privateKey), { lowS: true });
|
|
|
|
|
|
- return Signature.from({ r: toBeHex(sig.r, 32), s: toBeHex(sig.s, 32), v: sig.recovery ? 0x1c : 0x1b });
|
|
|
|
|
|
+ return ethers.Signature.from({
|
|
|
|
+ r: ethers.toBeHex(sig.r, 32),
|
|
|
|
+ s: ethers.toBeHex(sig.s, 32),
|
|
|
|
+ v: sig.recovery ? 0x1c : 0x1b,
|
|
|
|
+ });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -113,7 +103,7 @@ class RSASigningKey {
|
|
constructor(keyPair) {
|
|
constructor(keyPair) {
|
|
const jwk = keyPair.publicKey.export({ format: 'jwk' });
|
|
const jwk = keyPair.publicKey.export({ format: 'jwk' });
|
|
this.#privateKey = keyPair.privateKey;
|
|
this.#privateKey = keyPair.privateKey;
|
|
- this.#publicKey = { e: decodeBase64(jwk.e), n: decodeBase64(jwk.n) };
|
|
|
|
|
|
+ this.#publicKey = { e: ethers.decodeBase64(jwk.e), n: ethers.decodeBase64(jwk.n) };
|
|
}
|
|
}
|
|
|
|
|
|
static random(modulusLength = 2048) {
|
|
static random(modulusLength = 2048) {
|
|
@@ -121,28 +111,67 @@ class RSASigningKey {
|
|
}
|
|
}
|
|
|
|
|
|
get privateKey() {
|
|
get privateKey() {
|
|
- return hexlify(this.#privateKey);
|
|
|
|
|
|
+ return ethers.hexlify(this.#privateKey);
|
|
}
|
|
}
|
|
|
|
|
|
get publicKey() {
|
|
get publicKey() {
|
|
- return { e: hexlify(this.#publicKey.e), n: hexlify(this.#publicKey.n) };
|
|
|
|
|
|
+ return { e: ethers.hexlify(this.#publicKey.e), n: ethers.hexlify(this.#publicKey.n) };
|
|
}
|
|
}
|
|
|
|
|
|
- sign(digest /*: BytesLike*/) /*: Signature*/ {
|
|
|
|
- assertArgument(dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
|
|
+ sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
|
|
|
|
+ ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
// SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes)
|
|
// SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes)
|
|
return {
|
|
return {
|
|
- serialized: hexlify(
|
|
|
|
- privateEncrypt(this.#privateKey, getBytes(concat(['0x3031300d060960864801650304020105000420', digest]))),
|
|
|
|
|
|
+ serialized: ethers.hexlify(
|
|
|
|
+ privateEncrypt(
|
|
|
|
+ this.#privateKey,
|
|
|
|
+ ethers.getBytes(ethers.concat(['0x3031300d060960864801650304020105000420', digest])),
|
|
|
|
+ ),
|
|
),
|
|
),
|
|
};
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class RSASHA256SigningKey extends RSASigningKey {
|
|
class RSASHA256SigningKey extends RSASigningKey {
|
|
- sign(digest /*: BytesLike*/) /*: Signature*/ {
|
|
|
|
- assertArgument(dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
- return super.sign(sha256(getBytes(digest)));
|
|
|
|
|
|
+ sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
|
|
|
|
+ ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
+ return super.sign(ethers.sha256(ethers.getBytes(digest)));
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class WebAuthnSigningKey extends P256SigningKey {
|
|
|
|
+ sign(digest /*: BytesLike*/) /*: { serialized: string } */ {
|
|
|
|
+ ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
+
|
|
|
|
+ const clientDataJSON = JSON.stringify({
|
|
|
|
+ type: 'webauthn.get',
|
|
|
|
+ challenge: ethers.encodeBase64(digest).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', ''),
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // Flags 0x05 = AUTH_DATA_FLAGS_UP | AUTH_DATA_FLAGS_UV
|
|
|
|
+ const authenticatorData = ethers.solidityPacked(
|
|
|
|
+ ['bytes32', 'bytes1', 'bytes4'],
|
|
|
|
+ [ethers.ZeroHash, '0x05', '0x00000000'],
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // Regular P256 signature
|
|
|
|
+ const { r, s } = super.sign(
|
|
|
|
+ ethers.sha256(ethers.concat([authenticatorData, ethers.sha256(ethers.toUtf8Bytes(clientDataJSON))])),
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ const serialized = ethers.AbiCoder.defaultAbiCoder().encode(
|
|
|
|
+ ['bytes32', 'bytes32', 'uint256', 'uint256', 'bytes', 'string'],
|
|
|
|
+ [
|
|
|
|
+ r,
|
|
|
|
+ s,
|
|
|
|
+ clientDataJSON.indexOf('"challenge"'),
|
|
|
|
+ clientDataJSON.indexOf('"type"'),
|
|
|
|
+ authenticatorData,
|
|
|
|
+ clientDataJSON,
|
|
|
|
+ ],
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ return { serialized };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -151,7 +180,7 @@ class MultiERC7913SigningKey {
|
|
#signers;
|
|
#signers;
|
|
|
|
|
|
constructor(signers) {
|
|
constructor(signers) {
|
|
- assertArgument(
|
|
|
|
|
|
+ ethers.assertArgument(
|
|
Array.isArray(signers) && signers.length > 0,
|
|
Array.isArray(signers) && signers.length > 0,
|
|
'signers must be a non-empty array',
|
|
'signers must be a non-empty array',
|
|
'signers',
|
|
'signers',
|
|
@@ -159,18 +188,20 @@ class MultiERC7913SigningKey {
|
|
);
|
|
);
|
|
|
|
|
|
// Sorting is done at construction so that it doesn't have to be done in sign()
|
|
// Sorting is done at construction so that it doesn't have to be done in sign()
|
|
- this.#signers = signers.sort((s1, s2) => keccak256(s1.bytes ?? s1.address) - keccak256(s2.bytes ?? s2.address));
|
|
|
|
|
|
+ this.#signers = signers.sort(
|
|
|
|
+ (s1, s2) => ethers.keccak256(s1.bytes ?? s1.address) - ethers.keccak256(s2.bytes ?? s2.address),
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
get signers() {
|
|
get signers() {
|
|
return this.#signers;
|
|
return this.#signers;
|
|
}
|
|
}
|
|
|
|
|
|
- sign(digest /*: BytesLike*/ /*: Signature*/) {
|
|
|
|
- assertArgument(dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
|
|
+ sign(digest /*: BytesLike*/ /*: ethers.Signature*/) {
|
|
|
|
+ ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
|
|
|
|
|
|
return {
|
|
return {
|
|
- serialized: AbiCoder.defaultAbiCoder().encode(
|
|
|
|
|
|
+ serialized: ethers.AbiCoder.defaultAbiCoder().encode(
|
|
['bytes[]', 'bytes[]'],
|
|
['bytes[]', 'bytes[]'],
|
|
[
|
|
[
|
|
this.#signers.map(signer => signer.bytes ?? signer.address),
|
|
this.#signers.map(signer => signer.bytes ?? signer.address),
|
|
@@ -181,4 +212,11 @@ class MultiERC7913SigningKey {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-module.exports = { NonNativeSigner, P256SigningKey, RSASigningKey, RSASHA256SigningKey, MultiERC7913SigningKey };
|
|
|
|
|
|
+module.exports = {
|
|
|
|
+ NonNativeSigner,
|
|
|
|
+ P256SigningKey,
|
|
|
|
+ RSASigningKey,
|
|
|
|
+ RSASHA256SigningKey,
|
|
|
|
+ WebAuthnSigningKey,
|
|
|
|
+ MultiERC7913SigningKey,
|
|
|
|
+};
|