SignatureChecker.test.js 15 KB

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