signers.js 5.1 KB

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