Skip to content

feat: EIP-712 structured signing + on-chain signature verification for mintOriginal#39

Open
pradhyum6144 wants to merge 1 commit intoc2siorg:mainfrom
pradhyum6144:feat/eip712-signature-verification
Open

feat: EIP-712 structured signing + on-chain signature verification for mintOriginal#39
pradhyum6144 wants to merge 1 commit intoc2siorg:mainfrom
pradhyum6144:feat/eip712-signature-verification

Conversation

@pradhyum6144
Copy link
Copy Markdown

Summary

  • Adds on-chain EIP-712 signature verification to mintOriginal() in LensMintERC1155.sol, fixing the core vulnerability where the signature parameter was stored but never verified — allowing anyone with an active device address to mint with a fake signature string like "0xsignature123"
  • Adds replay protection via mapping(bytes32 => bool) usedImageHashes (prevents the same image from being minted twice) and per-device nonces (prevents signature reuse)
  • Updates the backend (web3Service.js) to use ethers.Wallet.signTypedData() with the matching EIP-712 domain, and updates the ABI to the new mintOriginal(address, string, bytes32, uint256, uint8, bytes32, bytes32) signature

What changed

Contract (LensMintERC1155.sol)

  • Inherits OpenZeppelin's EIP712("LensMintERC1155", "1")
  • Defines MINT_ORIGINAL_TYPEHASH for the structured type: MintOriginal(address to, string ipfsHash, bytes32 imageHash, uint256 maxEditions, uint256 nonce)
  • mintOriginal() now accepts (uint8 v, bytes32 r, bytes32 s) instead of string _signature, reconstructs the EIP-712 digest on-chain, and verifies ECDSA.recover(digest, v, r, s) == msg.sender
  • imageHash changed from string to bytes32 for gas efficiency and proper cryptographic comparison
  • Exposes domainSeparator() view function for off-chain signing compatibility

Tests (LensMintEIP712.t.sol — 15 new tests)

Test What it verifies
testMintOriginalWithValidSignature Happy path — valid EIP-712 sig mints successfully
testNonceIncrementsAfterMint Nonce increments per device after each mint
testMintTwoDifferentImages Sequential mints with different images work
testEditionMintStillWorks Edition minting is unaffected by the change
testRevertOnDuplicateImageHash Same image cannot be minted twice
testRevertOnFakeSignature Garbage v/r/s values are rejected
testRevertOnWrongSignerKey Signature from wrong private key is rejected
testRevertOnSignatureFromDifferentDevice Cross-device signature replay blocked
testRevertOnTamperedParams Changed recipient detected via digest mismatch
testRevertOnTamperedIpfsHash Changed IPFS CID detected
testRevertOnWrongNonce Stale nonce signature rejected
testRevertOnUnregisteredDevice Unregistered device still blocked
testRevertOnDeactivatedDevice Inactive device still blocked
testDomainSeparatorIsConsistent Domain separator is deterministic
testUsedImageHashTracking usedImageHashes mapping updates correctly

Backend (web3Service.js)

  • New signMintOriginal() method builds the EIP-712 domain + types and calls wallet.signTypedData()
  • mintOriginal() now signs before calling the contract with (v, r, s) params
  • ABI updated to match new contract interface

Config (foundry.toml)

Why EIP-712 over EIP-191

EIP-191 (personal_sign) EIP-712 (this PR)
What user sees Raw hex blob Structured fields: to, ipfsHash, imageHash
Domain binding None Tied to contract address + chain ID
Cross-chain safety Same sig works on any chain Domain separator prevents cross-chain replay
Replay protection Must be added separately Nonce is part of the signed struct

Closes #2

Test plan

  • All 18 Foundry tests pass (forge test — 15 new + 3 updated existing)
  • Deploy to Sepolia testnet and verify minting flow end-to-end
  • Verify hardware camera app signing integration (Python sign_hash → EIP-712 format)

…ion to mintOriginal

The mintOriginal() function previously accepted a plain string signature
parameter and stored it without any on-chain verification, allowing anyone
with an active device address to mint with a fake signature. This commit
adds cryptographic proof that the device actually signed the mint request.

Changes:
- Inherit OpenZeppelin EIP712 in LensMintERC1155 with domain "LensMintERC1155" v1
- Add ECDSA.recover verification against EIP-712 typed data digest in mintOriginal()
- Add mapping(bytes32 => bool) usedImageHashes to prevent replay of the same image
- Add per-device nonce tracking for additional replay protection
- Change mintOriginal() signature to accept (bytes32 imageHash, uint8 v, bytes32 r, bytes32 s)
- Expose domainSeparator() view for off-chain signing compatibility
- Add 15 Foundry tests covering valid signing, replay rejection, tampered params,
  wrong signer, wrong nonce, unregistered/deactivated device scenarios
- Update existing MintEditionDebug tests to use EIP-712 vm.sign flow
- Update web3Service.js with EIP-712 signTypedData and new contract ABI
- Fix evm_version paris -> cancun in foundry.toml (resolves mcopy errors)

Closes c2siorg#2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing ECDSA signature verification on image upload and minting permits spoofing

1 participant