SignatureChecker.test.js 16 KB

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