SignatureChecker.test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const precompile = require('../../helpers/precompiles');
  5. const { P256SigningKey, NonNativeSigner } = require('../../helpers/signers');
  6. const TEST_MESSAGE = ethers.id('OpenZeppelin');
  7. const TEST_MESSAGE_HASH = ethers.hashMessage(TEST_MESSAGE);
  8. const WRONG_MESSAGE = ethers.id('Nope');
  9. const WRONG_MESSAGE_HASH = ethers.hashMessage(WRONG_MESSAGE);
  10. const aliceP256 = new NonNativeSigner(P256SigningKey.random());
  11. const bobP256 = new NonNativeSigner(P256SigningKey.random());
  12. async function fixture() {
  13. const [signer, extraSigner, other] = await ethers.getSigners();
  14. const mock = await ethers.deployContract('$SignatureChecker');
  15. const wallet = await ethers.deployContract('ERC1271WalletMock', [signer]);
  16. const wallet2 = await ethers.deployContract('ERC1271WalletMock', [extraSigner]);
  17. const malicious = await ethers.deployContract('ERC1271MaliciousMock');
  18. const signature = await signer.signMessage(TEST_MESSAGE);
  19. const verifier = await ethers.deployContract('ERC7913P256Verifier');
  20. return { signer, other, extraSigner, mock, wallet, wallet2, malicious, signature, verifier };
  21. }
  22. describe('SignatureChecker (ERC1271)', function () {
  23. before('deploying', async function () {
  24. Object.assign(this, await loadFixture(fixture));
  25. });
  26. describe('EOA account', function () {
  27. it('with matching signer and signature', async function () {
  28. await expect(
  29. this.mock.$isValidSignatureNow(ethers.Typed.address(this.signer.address), TEST_MESSAGE_HASH, this.signature),
  30. ).to.eventually.be.true;
  31. });
  32. it('with invalid signer', async function () {
  33. await expect(
  34. this.mock.$isValidSignatureNow(ethers.Typed.address(this.other.address), TEST_MESSAGE_HASH, this.signature),
  35. ).to.eventually.be.false;
  36. });
  37. it('with invalid signature', async function () {
  38. await expect(
  39. this.mock.$isValidSignatureNow(ethers.Typed.address(this.signer.address), WRONG_MESSAGE_HASH, this.signature),
  40. ).to.eventually.be.false;
  41. });
  42. });
  43. describe('ERC1271 wallet', function () {
  44. for (const fn of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) {
  45. describe(fn, function () {
  46. it('with matching signer and signature', async function () {
  47. await expect(
  48. this.mock.getFunction(`$${fn}`)(
  49. ethers.Typed.address(this.wallet.target),
  50. TEST_MESSAGE_HASH,
  51. this.signature,
  52. ),
  53. ).to.eventually.be.true;
  54. });
  55. it('with invalid signer', async function () {
  56. await expect(
  57. this.mock.getFunction(`$${fn}`)(ethers.Typed.address(this.mock.target), TEST_MESSAGE_HASH, this.signature),
  58. ).to.eventually.be.false;
  59. });
  60. it('with identity precompile', async function () {
  61. await expect(
  62. this.mock.getFunction(`$${fn}`)(
  63. ethers.Typed.address(precompile.identity),
  64. TEST_MESSAGE_HASH,
  65. this.signature,
  66. ),
  67. ).to.eventually.be.false;
  68. });
  69. it('with invalid signature', async function () {
  70. await expect(
  71. this.mock.getFunction(`$${fn}`)(
  72. ethers.Typed.address(this.wallet.target),
  73. WRONG_MESSAGE_HASH,
  74. this.signature,
  75. ),
  76. ).to.eventually.be.false;
  77. });
  78. it('with malicious wallet', async function () {
  79. await expect(
  80. this.mock.getFunction(`$${fn}`)(
  81. ethers.Typed.address(this.malicious.target),
  82. TEST_MESSAGE_HASH,
  83. this.signature,
  84. ),
  85. ).to.eventually.be.false;
  86. });
  87. });
  88. }
  89. });
  90. describe('ERC7913', function () {
  91. describe('isValidSignatureNow', function () {
  92. describe('with EOA signer', function () {
  93. it('with matching signer and signature', async function () {
  94. const eoaSigner = ethers.zeroPadValue(this.signer.address, 20);
  95. const signature = await this.signer.signMessage(TEST_MESSAGE);
  96. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(eoaSigner), TEST_MESSAGE_HASH, signature)).to
  97. .eventually.be.true;
  98. });
  99. it('with invalid signer', async function () {
  100. const eoaSigner = ethers.zeroPadValue(this.other.address, 20);
  101. const signature = await this.signer.signMessage(TEST_MESSAGE);
  102. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(eoaSigner), TEST_MESSAGE_HASH, signature)).to
  103. .eventually.be.false;
  104. });
  105. it('with invalid signature', async function () {
  106. const eoaSigner = ethers.zeroPadValue(this.signer.address, 20);
  107. const signature = await this.signer.signMessage(TEST_MESSAGE);
  108. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(eoaSigner), WRONG_MESSAGE_HASH, signature)).to
  109. .eventually.be.false;
  110. });
  111. });
  112. describe('with ERC-1271 wallet', function () {
  113. it('with matching signer and signature', async function () {
  114. const walletSigner = ethers.zeroPadValue(this.wallet.target, 20);
  115. const signature = await this.signer.signMessage(TEST_MESSAGE);
  116. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(walletSigner), TEST_MESSAGE_HASH, signature))
  117. .to.eventually.be.true;
  118. });
  119. it('with invalid signer', async function () {
  120. const walletSigner = ethers.zeroPadValue(this.mock.target, 20);
  121. const signature = await this.signer.signMessage(TEST_MESSAGE);
  122. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(walletSigner), TEST_MESSAGE_HASH, signature))
  123. .to.eventually.be.false;
  124. });
  125. it('with invalid signature', async function () {
  126. const walletSigner = ethers.zeroPadValue(this.wallet.target, 20);
  127. const signature = await this.signer.signMessage(TEST_MESSAGE);
  128. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(walletSigner), WRONG_MESSAGE_HASH, signature))
  129. .to.eventually.be.false;
  130. });
  131. });
  132. describe('with ERC-7913 verifier', function () {
  133. it('with matching signer and signature', async function () {
  134. const signer = ethers.concat([
  135. this.verifier.target,
  136. aliceP256.signingKey.publicKey.qx,
  137. aliceP256.signingKey.publicKey.qy,
  138. ]);
  139. const signature = await aliceP256.signMessage(TEST_MESSAGE);
  140. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(signer), TEST_MESSAGE_HASH, signature)).to
  141. .eventually.be.true;
  142. });
  143. it('with invalid verifier', async function () {
  144. const signer = ethers.concat([
  145. this.mock.target, // invalid verifier
  146. aliceP256.signingKey.publicKey.qx,
  147. aliceP256.signingKey.publicKey.qy,
  148. ]);
  149. const signature = await aliceP256.signMessage(TEST_MESSAGE);
  150. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(signer), TEST_MESSAGE_HASH, signature)).to
  151. .eventually.be.false;
  152. });
  153. it('with invalid key', async function () {
  154. const signer = ethers.concat([this.verifier.target, ethers.randomBytes(32)]);
  155. const signature = await aliceP256.signMessage(TEST_MESSAGE);
  156. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(signer), TEST_MESSAGE_HASH, signature)).to
  157. .eventually.be.false;
  158. });
  159. it('with invalid signature', async function () {
  160. const signer = ethers.concat([
  161. this.verifier.target,
  162. aliceP256.signingKey.publicKey.qx,
  163. aliceP256.signingKey.publicKey.qy,
  164. ]);
  165. const signature = ethers.randomBytes(65); // invalid (random) signature
  166. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(signer), TEST_MESSAGE_HASH, signature)).to
  167. .eventually.be.false;
  168. });
  169. it('with signer too short', async function () {
  170. const signer = ethers.randomBytes(19); // too short
  171. const signature = await aliceP256.signMessage(TEST_MESSAGE);
  172. await expect(this.mock.$isValidSignatureNow(ethers.Typed.bytes(signer), TEST_MESSAGE_HASH, signature)).to
  173. .eventually.be.false;
  174. });
  175. });
  176. });
  177. describe('areValidSignaturesNow', function () {
  178. const sortSigners = (...signers) =>
  179. signers.sort(({ signer: a }, { signer: b }) => ethers.keccak256(b) - ethers.keccak256(a));
  180. it('should validate a single signature', async function () {
  181. const signer = ethers.zeroPadValue(this.signer.address, 20);
  182. const signature = await this.signer.signMessage(TEST_MESSAGE);
  183. await expect(this.mock.$areValidSignaturesNow(TEST_MESSAGE_HASH, [signer], [signature])).to.eventually.be.true;
  184. });
  185. it('should validate multiple signatures with different signer types', async function () {
  186. const signers = sortSigners(
  187. {
  188. signer: ethers.zeroPadValue(this.signer.address, 20),
  189. signature: await this.signer.signMessage(TEST_MESSAGE),
  190. },
  191. {
  192. signer: ethers.zeroPadValue(this.wallet.target, 20),
  193. signature: await this.signer.signMessage(TEST_MESSAGE),
  194. },
  195. {
  196. signer: ethers.concat([
  197. this.verifier.target,
  198. aliceP256.signingKey.publicKey.qx,
  199. aliceP256.signingKey.publicKey.qy,
  200. ]),
  201. signature: await aliceP256.signMessage(TEST_MESSAGE),
  202. },
  203. );
  204. await expect(
  205. this.mock.$areValidSignaturesNow(
  206. TEST_MESSAGE_HASH,
  207. signers.map(({ signer }) => signer),
  208. signers.map(({ signature }) => signature),
  209. ),
  210. ).to.eventually.be.true;
  211. });
  212. it('should validate multiple EOA signatures', async function () {
  213. const signers = sortSigners(
  214. {
  215. signer: ethers.zeroPadValue(this.signer.address, 20),
  216. signature: await this.signer.signMessage(TEST_MESSAGE),
  217. },
  218. {
  219. signer: ethers.zeroPadValue(this.extraSigner.address, 20),
  220. signature: await this.extraSigner.signMessage(TEST_MESSAGE),
  221. },
  222. );
  223. await expect(
  224. this.mock.$areValidSignaturesNow(
  225. TEST_MESSAGE_HASH,
  226. signers.map(({ signer }) => signer),
  227. signers.map(({ signature }) => signature),
  228. ),
  229. ).to.eventually.be.true;
  230. });
  231. it('should validate multiple ERC-1271 wallet signatures', async function () {
  232. const signers = sortSigners(
  233. {
  234. signer: ethers.zeroPadValue(this.wallet.target, 20),
  235. signature: await this.signer.signMessage(TEST_MESSAGE),
  236. },
  237. {
  238. signer: ethers.zeroPadValue(this.wallet2.target, 20),
  239. signature: await this.extraSigner.signMessage(TEST_MESSAGE),
  240. },
  241. );
  242. await expect(
  243. this.mock.$areValidSignaturesNow(
  244. TEST_MESSAGE_HASH,
  245. signers.map(({ signer }) => signer),
  246. signers.map(({ signature }) => signature),
  247. ),
  248. ).to.eventually.be.true;
  249. });
  250. it('should validate multiple ERC-7913 signatures (ordered by ID)', async function () {
  251. const signers = sortSigners(
  252. {
  253. signer: ethers.concat([
  254. this.verifier.target,
  255. aliceP256.signingKey.publicKey.qx,
  256. aliceP256.signingKey.publicKey.qy,
  257. ]),
  258. signature: await aliceP256.signMessage(TEST_MESSAGE),
  259. },
  260. {
  261. signer: ethers.concat([
  262. this.verifier.target,
  263. bobP256.signingKey.publicKey.qx,
  264. bobP256.signingKey.publicKey.qy,
  265. ]),
  266. signature: await bobP256.signMessage(TEST_MESSAGE),
  267. },
  268. );
  269. await expect(
  270. this.mock.$areValidSignaturesNow(
  271. TEST_MESSAGE_HASH,
  272. signers.map(({ signer }) => signer),
  273. signers.map(({ signature }) => signature),
  274. ),
  275. ).to.eventually.be.true;
  276. });
  277. it('should validate multiple ERC-7913 signatures (unordered)', async function () {
  278. const signers = sortSigners(
  279. {
  280. signer: ethers.concat([
  281. this.verifier.target,
  282. aliceP256.signingKey.publicKey.qx,
  283. aliceP256.signingKey.publicKey.qy,
  284. ]),
  285. signature: await aliceP256.signMessage(TEST_MESSAGE),
  286. },
  287. {
  288. signer: ethers.concat([
  289. this.verifier.target,
  290. bobP256.signingKey.publicKey.qx,
  291. bobP256.signingKey.publicKey.qy,
  292. ]),
  293. signature: await bobP256.signMessage(TEST_MESSAGE),
  294. },
  295. ).reverse(); // reverse
  296. await expect(
  297. this.mock.$areValidSignaturesNow(
  298. TEST_MESSAGE_HASH,
  299. signers.map(({ signer }) => signer),
  300. signers.map(({ signature }) => signature),
  301. ),
  302. ).to.eventually.be.true;
  303. });
  304. it('should return false if any signature is invalid', async function () {
  305. const signers = sortSigners(
  306. {
  307. signer: ethers.zeroPadValue(this.signer.address, 20),
  308. signature: await this.signer.signMessage(TEST_MESSAGE),
  309. },
  310. {
  311. signer: ethers.zeroPadValue(this.extraSigner.address, 20),
  312. signature: await this.extraSigner.signMessage(WRONG_MESSAGE),
  313. },
  314. );
  315. await expect(
  316. this.mock.$areValidSignaturesNow(
  317. TEST_MESSAGE_HASH,
  318. signers.map(({ signer }) => signer),
  319. signers.map(({ signature }) => signature),
  320. ),
  321. ).to.eventually.be.false;
  322. });
  323. it('should return false if there are duplicate signers', async function () {
  324. const signers = sortSigners(
  325. {
  326. signer: ethers.zeroPadValue(this.signer.address, 20),
  327. signature: await this.signer.signMessage(TEST_MESSAGE),
  328. },
  329. {
  330. signer: ethers.zeroPadValue(this.signer.address, 20),
  331. signature: await this.signer.signMessage(TEST_MESSAGE),
  332. },
  333. );
  334. await expect(
  335. this.mock.$areValidSignaturesNow(
  336. TEST_MESSAGE_HASH,
  337. signers.map(({ signer }) => signer),
  338. signers.map(({ signature }) => signature),
  339. ),
  340. ).to.eventually.be.false;
  341. });
  342. it('should return false if signatures array length does not match signers array length', async function () {
  343. const signers = sortSigners(
  344. {
  345. signer: ethers.zeroPadValue(this.signer.address, 20),
  346. signature: await this.signer.signMessage(TEST_MESSAGE),
  347. },
  348. {
  349. signer: ethers.zeroPadValue(this.extraSigner.address, 20),
  350. signature: await this.extraSigner.signMessage(TEST_MESSAGE),
  351. },
  352. );
  353. await expect(
  354. this.mock.$areValidSignaturesNow(
  355. TEST_MESSAGE_HASH,
  356. signers.map(({ signer }) => signer),
  357. signers.map(({ signature }) => signature).slice(1),
  358. ),
  359. ).to.eventually.be.false;
  360. });
  361. it('should pass with empty arrays', async function () {
  362. await expect(this.mock.$areValidSignaturesNow(TEST_MESSAGE_HASH, [], [])).to.eventually.be.true;
  363. });
  364. });
  365. });
  366. });