signers.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. const { ethers } = require('ethers');
  2. const { secp256r1 } = require('@noble/curves/p256');
  3. const { generateKeyPairSync, privateEncrypt } = require('crypto');
  4. // Lightweight version of BaseWallet
  5. class NonNativeSigner extends ethers.AbstractSigner {
  6. #signingKey;
  7. constructor(privateKey, provider) {
  8. super(provider);
  9. ethers.assertArgument(
  10. privateKey && typeof privateKey.sign === 'function',
  11. 'invalid private key',
  12. 'privateKey',
  13. '[ REDACTED ]',
  14. );
  15. this.#signingKey = privateKey;
  16. }
  17. get signingKey() {
  18. return this.#signingKey;
  19. }
  20. get privateKey() {
  21. return this.signingKey.privateKey;
  22. }
  23. async getAddress() {
  24. throw new Error("NonNativeSigner doesn't have an address");
  25. }
  26. connect(provider) {
  27. return new NonNativeSigner(this.#signingKey, provider);
  28. }
  29. async signTransaction(/*tx: TransactionRequest*/) {
  30. throw new Error('NonNativeSigner cannot send transactions');
  31. }
  32. async signMessage(message /*: string | Uint8Array*/) /*: Promise<string>*/ {
  33. return this.signingKey.sign(ethers.hashMessage(message)).serialized;
  34. }
  35. async signTypedData(
  36. domain /*: TypedDataDomain*/,
  37. types /*: Record<string, Array<TypedDataField>>*/,
  38. value /*: Record<string, any>*/,
  39. ) /*: Promise<string>*/ {
  40. // Populate any ENS names
  41. const populated = await ethers.TypedDataEncoder.resolveNames(domain, types, value, async name => {
  42. ethers.assert(this.provider != null, 'cannot resolve ENS names without a provider', 'UNSUPPORTED_OPERATION', {
  43. operation: 'resolveName',
  44. info: { name },
  45. });
  46. const address = await this.provider.resolveName(name);
  47. ethers.assert(address != null, 'unconfigured ENS name', 'UNCONFIGURED_NAME', { value: name });
  48. return address;
  49. });
  50. return this.signingKey.sign(ethers.TypedDataEncoder.hash(populated.domain, types, populated.value)).serialized;
  51. }
  52. }
  53. class P256SigningKey {
  54. #privateKey;
  55. constructor(privateKey) {
  56. this.#privateKey = ethers.getBytes(privateKey);
  57. }
  58. static random() {
  59. return new this(secp256r1.utils.randomPrivateKey());
  60. }
  61. get privateKey() {
  62. return ethers.hexlify(this.#privateKey);
  63. }
  64. get publicKey() {
  65. const publicKeyBytes = secp256r1.getPublicKey(this.#privateKey, false);
  66. return {
  67. qx: ethers.hexlify(publicKeyBytes.slice(0x01, 0x21)),
  68. qy: ethers.hexlify(publicKeyBytes.slice(0x21, 0x41)),
  69. };
  70. }
  71. sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
  72. ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
  73. const sig = secp256r1.sign(ethers.getBytesCopy(digest), ethers.getBytesCopy(this.#privateKey), { lowS: true });
  74. return ethers.Signature.from({
  75. r: ethers.toBeHex(sig.r, 32),
  76. s: ethers.toBeHex(sig.s, 32),
  77. v: sig.recovery ? 0x1c : 0x1b,
  78. });
  79. }
  80. }
  81. class RSASigningKey {
  82. #privateKey;
  83. #publicKey;
  84. constructor(keyPair) {
  85. const jwk = keyPair.publicKey.export({ format: 'jwk' });
  86. this.#privateKey = keyPair.privateKey;
  87. this.#publicKey = { e: ethers.decodeBase64(jwk.e), n: ethers.decodeBase64(jwk.n) };
  88. }
  89. static random(modulusLength = 2048) {
  90. return new this(generateKeyPairSync('rsa', { modulusLength }));
  91. }
  92. get privateKey() {
  93. return ethers.hexlify(this.#privateKey);
  94. }
  95. get publicKey() {
  96. return { e: ethers.hexlify(this.#publicKey.e), n: ethers.hexlify(this.#publicKey.n) };
  97. }
  98. sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
  99. ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
  100. // SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes)
  101. return {
  102. serialized: ethers.hexlify(
  103. privateEncrypt(
  104. this.#privateKey,
  105. ethers.getBytes(ethers.concat(['0x3031300d060960864801650304020105000420', digest])),
  106. ),
  107. ),
  108. };
  109. }
  110. }
  111. class RSASHA256SigningKey extends RSASigningKey {
  112. sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
  113. ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
  114. return super.sign(ethers.sha256(ethers.getBytes(digest)));
  115. }
  116. }
  117. class WebAuthnSigningKey extends P256SigningKey {
  118. sign(digest /*: BytesLike*/) /*: { serialized: string } */ {
  119. ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
  120. const clientDataJSON = JSON.stringify({
  121. type: 'webauthn.get',
  122. challenge: ethers.encodeBase64(digest).replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', ''),
  123. });
  124. // Flags 0x05 = AUTH_DATA_FLAGS_UP | AUTH_DATA_FLAGS_UV
  125. const authenticatorData = ethers.solidityPacked(
  126. ['bytes32', 'bytes1', 'bytes4'],
  127. [ethers.ZeroHash, '0x05', '0x00000000'],
  128. );
  129. // Regular P256 signature
  130. const { r, s } = super.sign(
  131. ethers.sha256(ethers.concat([authenticatorData, ethers.sha256(ethers.toUtf8Bytes(clientDataJSON))])),
  132. );
  133. const serialized = ethers.AbiCoder.defaultAbiCoder().encode(
  134. ['bytes32', 'bytes32', 'uint256', 'uint256', 'bytes', 'string'],
  135. [
  136. r,
  137. s,
  138. clientDataJSON.indexOf('"challenge"'),
  139. clientDataJSON.indexOf('"type"'),
  140. authenticatorData,
  141. clientDataJSON,
  142. ],
  143. );
  144. return { serialized };
  145. }
  146. }
  147. class MultiERC7913SigningKey {
  148. // this is a sorted array of objects that contain {signer, weight}
  149. #signers;
  150. constructor(signers) {
  151. ethers.assertArgument(
  152. Array.isArray(signers) && signers.length > 0,
  153. 'signers must be a non-empty array',
  154. 'signers',
  155. signers.length,
  156. );
  157. // Sorting is done at construction so that it doesn't have to be done in sign()
  158. this.#signers = signers.sort(
  159. (s1, s2) => ethers.keccak256(s1.bytes ?? s1.address) - ethers.keccak256(s2.bytes ?? s2.address),
  160. );
  161. }
  162. get signers() {
  163. return this.#signers;
  164. }
  165. sign(digest /*: BytesLike*/ /*: ethers.Signature*/) {
  166. ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
  167. return {
  168. serialized: ethers.AbiCoder.defaultAbiCoder().encode(
  169. ['bytes[]', 'bytes[]'],
  170. [
  171. this.#signers.map(signer => signer.bytes ?? signer.address),
  172. this.#signers.map(signer => signer.signingKey.sign(digest).serialized),
  173. ],
  174. ),
  175. };
  176. }
  177. }
  178. module.exports = {
  179. NonNativeSigner,
  180. P256SigningKey,
  181. RSASigningKey,
  182. RSASHA256SigningKey,
  183. WebAuthnSigningKey,
  184. MultiERC7913SigningKey,
  185. };