diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d540081 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Run Tests + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "npm" + + - name: Install dependencies + working-directory: ./tests + run: npm ci + + - name: Run tests + working-directory: ./tests + run: npm test diff --git a/packages/core/helpers/siwe/siwe.ts b/packages/core/helpers/siwe/siwe.ts index 71fee51..26b9f95 100644 --- a/packages/core/helpers/siwe/siwe.ts +++ b/packages/core/helpers/siwe/siwe.ts @@ -6,6 +6,7 @@ import { http, getContract, Client, + recoverAddress, } from "viem"; import { worldchain } from "viem/chains"; @@ -127,15 +128,23 @@ export const generateSiweMessage = (siweMessageData: SiweMessage) => { export const SAFE_CONTRACT_ABI = [ { - name: "checkSignatures", - type: "function", - stateMutability: "view", inputs: [ - { name: "dataHash", type: "bytes32" }, - { name: "data", type: "bytes" }, - { name: "signature", type: "bytes" }, + { + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "isOwner", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, ], - outputs: [], + stateMutability: "view", + type: "function", }, ]; @@ -192,7 +201,6 @@ export const verifySiweMessage = async ( userProvider || createPublicClient({ chain: worldchain, transport: http() }); const signedMessage = `${ERC_191_PREFIX}${message.length}${message}`; - const messageBytes = Buffer.from(signedMessage, "utf8").toString("hex"); const hashedMessage = hashMessage(signedMessage); const contract = getContract({ address: address as `0x${string}`, @@ -201,11 +209,19 @@ export const verifySiweMessage = async ( }); try { - await contract.read.checkSignatures([ - hashedMessage, - `0x${messageBytes}`, - `0x${signature}`, - ]); + const recoveredAddress = await recoverAddress({ + hash: hashedMessage, + signature: signature as `0x${string}`, + }); + + if (recoveredAddress !== address) { + throw new Error("Signature verification failed"); + } + + const isOwner = await contract.read.isOwner([recoveredAddress]); + if (!isOwner) { + throw new Error("Signature verification failed"); + } } catch (error) { throw new Error("Signature verification failed"); } diff --git a/tests/package.json b/tests/package.json index 7cccfe6..8fa881f 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,14 +1,6 @@ { - "name": "minikit-tests", - "version": "1.0.0", - "description": "", - "main": "siwe.test.js", - "scripts": { - "test": "jest" - }, - "keywords": [], "author": "", - "license": "ISC", + "description": "", "devDependencies": { "@babel/core": "^7.24.5", "@babel/preset-env": "^7.24.5", @@ -16,5 +8,13 @@ "@worldcoin/minikit-js": "workspace:*", "babel-jest": "^29.7.0", "jest": "^29.7.0" - } + }, + "keywords": [], + "license": "ISC", + "main": "siwe.test.js", + "name": "minikit-tests", + "scripts": { + "test": "jest --watchAll" + }, + "version": "1.0.0" } diff --git a/tests/siwe.test.js b/tests/siwe.test.js index 3d9a20c..ffb5b94 100644 --- a/tests/siwe.test.js +++ b/tests/siwe.test.js @@ -8,8 +8,12 @@ Version: 1\n\ Chain ID: 10\n\ Nonce: 12345678\n\ Issued At: ${new Date().toISOString()}\n\ -Expiration Time: 2024-05-03T00:00:00Z\n\ -Not Before: 2024-05-03T00:00:00Z\n\ +Expiration Time: ${new Date( + new Date().getTime() + 1000 * 60 * 60 * 24 * 7 +).toISOString()}\n\ +Not Before: ${new Date( + new Date().getTime() - 1000 * 60 * 60 * 24 * 7 +).toISOString()}\n\ Request ID: 0`; const incompleteSiweMessage = `https://test.com wants you to sign in with your Ethereum account:\n\ @@ -31,8 +35,13 @@ Issued At: ${new Date().toISOString()}\n\ Expiration Time: 2024-05-03T00:00:00Z\n\ Request ID: 0`; -const signatureSiweMessage = - "http://localhost:3000 wants you to sign in with your Ethereum account:\n0xd809de3086ea4f53ed3979cead25e1ff72b564a3\n\n\nURI: http://localhost:3000/\nVersion: 1\nChain ID: 10\nNonce: 814434bd-ed2c-412e-aa2c-c4b266a42027\nIssued At: 2024-05-22T17:49:52.075Z\nExpiration Time: 2024-05-29T17:49:52.074Z\nNot Before: 2024-05-21T17:49:52.074Z\nRequest ID: 0\n"; +const signatureSiweMessage = ( + issuedAt = new Date(), + expirationDays = 7, + notBeforeDays = -1 +) => + `http://localhost:3000 wants you to sign in with your Ethereum account:\n0xd809de3086ea4f53ed3979cead25e1ff72b564a3\n\n\nURI: http://localhost:3000/\nVersion: 1\nChain ID: 10\nNonce: 814434bd-ed2c-412e-aa2c-c4b266a42027\nIssued At: ${issuedAt.toISOString()}\nExpiration Time: ${new Date(issuedAt.getTime() + 1000 * 60 * 60 * 24 * expirationDays).toISOString()}\nNot Before: ${new Date(issuedAt.getTime() + 1000 * 60 * 60 * 24 * notBeforeDays).toISOString()}\nRequest ID: 0\n`; + const signature = "f75530590312f5b36b6ef0003800003ba0af04640c72838580f76a3883d2365f397670d785475c39514629345cec307bcbe8c81fb85430da0dc3ef43c9a946d91b"; @@ -51,15 +60,35 @@ describe("Test SIWE Message Parsing", () => { "Missing 'Nonce: '" ); }); +}); +describe("Test SIWE Message Verification", () => { test("Verify SIWE Message", () => { + // TODO: Implement this test + }); + + test("Verify SIWE Message with invalid signature", async () => { const payload = { status: "success", - message: signatureSiweMessage, - signature: signature, + message: signatureSiweMessage(new Date(), 7, -1), + signature: "random_signature", address: "0xd809de3086ea4f53ed3979cead25e1ff72b564a3", }; + await expect( + verifySiweMessage(payload, "814434bd-ed2c-412e-aa2c-c4b266a42027") + ).rejects.toThrow("Signature verification failed"); + }); + + test("Verify SIWE Message with invalid address", async () => { + const payload = { + status: "success", + message: signatureSiweMessage(new Date(), 7, -1), + signature: signature, + address: "0x0000000000000000000000000000000000000000", + }; - verifySiweMessage(payload, "814434bd-ed2c-412e-aa2c-c4b266a42027"); + await expect( + verifySiweMessage(payload, "814434bd-ed2c-412e-aa2c-c4b266a42027") + ).rejects.toThrow("Signature verification failed"); }); });