From 4051e4d2acf585ff022e96a7b21fe0bcdade19b0 Mon Sep 17 00:00:00 2001 From: Derek Cofausper <256792747+decofe@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:27:22 +0000 Subject: [PATCH] docs: add SignatureVerifier guide under sdk/foundry and link from T3 Co-authored-by: 0xrusowsky <90208954+0xrusowsky@users.noreply.github.com> Amp-Thread-ID: https://ampcode.com/threads/T-019d9a8c-11a7-7198-9566-b3aa796afa45 --- src/pages/protocol/upgrades/t3.mdx | 2 + src/pages/sdk/foundry/signature-verifier.mdx | 141 +++++++++++++++++++ vocs.config.ts | 4 + 3 files changed, 147 insertions(+) create mode 100644 src/pages/sdk/foundry/signature-verifier.mdx diff --git a/src/pages/protocol/upgrades/t3.mdx b/src/pages/protocol/upgrades/t3.mdx index ec872d37..a23585e5 100644 --- a/src/pages/protocol/upgrades/t3.mdx +++ b/src/pages/protocol/upgrades/t3.mdx @@ -87,6 +87,8 @@ Guides about TIPs coming soon. [TIP-1020](/protocol/tips/tip-1020) adds a single precompile for verifying secp256k1, P256, and WebAuthn signatures onchain. This gives smart contract teams, wallet builders, and account integrators a standard verification surface for passkey wallets, multisigs, governance approvals, subscription authorization, and other signature-driven flows without deploying and maintaining custom verifier contracts for each signature type. This change is additive for existing partners: nothing breaks if you keep your current verifier setup, but teams that want simpler integrations or forward-compatible support for Tempo signature types can adopt the precompile. Keychain signatures still need `AccountKeychain` for key resolution, so this precompile covers the underlying signature schemes rather than the full keychain authorization flow. +The Foundry project template for Tempo includes a working example that uses the `SignatureVerifier` precompile to verify P256 signatures inside a smart contract. See the [Signature Verification with Foundry](/sdk/foundry/signature-verifier) guide to get started. + ### TIP-1022: Virtual Addresses for TIP-20 Deposit Forwarding [TIP-1022](/protocol/tips/tip-1022) lets partners issue per-user deposit addresses that forward TIP-20 deposits to a registered master wallet at the protocol level. This is useful for exchanges, ramps, custodians, and payment processors that want one deposit address per customer for reconciliation, attribution, or account crediting, but do not want to maintain sweep jobs or fund separate onchain wallets for every user. For existing partners, nothing changes unless you adopt virtual addresses, but any explorer, indexer, or backend system that surfaces TIP-20 deposit activity should handle forwarded deposits correctly. Teams that adopt virtual addresses should treat those forwarded deposits as deposits to the registered master wallet. Forwarding applies only to TIP-20 transfer paths. diff --git a/src/pages/sdk/foundry/signature-verifier.mdx b/src/pages/sdk/foundry/signature-verifier.mdx new file mode 100644 index 00000000..36c59fa9 --- /dev/null +++ b/src/pages/sdk/foundry/signature-verifier.mdx @@ -0,0 +1,141 @@ +--- +title: Signature Verification in Foundry +description: Verify secp256k1, P256, and WebAuthn signatures in smart contracts using the TIP-1020 SignatureVerifier precompile with Foundry. +--- + +# Signature Verification with Foundry + +The [TIP-1020](/protocol/tips/tip-1020) `SignatureVerifier` precompile lets contracts verify secp256k1, P256, and WebAuthn signatures through a single interface — no custom verifier contracts needed. This is available after the [T3 network upgrade](/protocol/upgrades/t3). + +The Foundry project template for Tempo ships with a working example that demonstrates signature verification in a relayed mail contract. Initialize it with: + +```bash +forge init --template tempo my-project && cd my-project +``` + +## How it works + +The template's `Mail` contract supports two modes: + +1. **Direct** — call `sendMail()` yourself (`msg.sender` is the sender). +2. **Relayed** — sign a mail off-chain and let anyone deliver it on-chain. + +Relayed mode uses the `SignatureVerifier` precompile to verify the sender's signature. Unlike Ethereum's `ecrecover`, the precompile: + +- Supports secp256k1, P256, and WebAuthn signature types +- Reverts on invalid signatures instead of returning `address(0)` +- Maintains forward compatibility with future Tempo account types + +## Contract example + +The key pattern is a single `verify()` or `recover()` call on the precompile: + +```solidity +import {StdPrecompiles} from "tempo-std/StdPrecompiles.sol"; + +// Option 1: verify — returns true/false, reverts on malformed signatures +require( + StdPrecompiles.SIGNATURE_VERIFIER.verify(from, hash, signature), + "invalid signature" +); + +// Option 2: recover — returns the signer address, reverts on malformed signatures +require( + StdPrecompiles.SIGNATURE_VERIFIER.recover(hash, signature) == from, + "invalid signature" +); +``` + +The full `Mail` contract in the template combines this with a per-sender nonce to prevent replay: + +```solidity +contract Mail { + ITIP20 public token; + mapping(address => uint256) public nonces; + + /// @notice Send mail on behalf of `from` using their off-chain Tempo signature. + function sendMail( + address from, + address to, + string memory message, + Attachment memory attachment, + bytes calldata signature + ) external { + bytes32 hash = getDigest(from, to, message, attachment); + require( + StdPrecompiles.SIGNATURE_VERIFIER.verify(from, hash, signature), + "invalid signature" + ); + nonces[from]++; + token.transferFromWithMemo(from, to, attachment.amount, attachment.memo); + emit MailSent(from, to, message, attachment); + } + + function getDigest(address from, address to, string memory message, Attachment memory attachment) + public view returns (bytes32) + { + return keccak256( + abi.encode(address(this), block.chainid, from, to, message, attachment, nonces[from]) + ); + } +} +``` + +## Testing + +The template includes tests for both signature types. Set `hardfork = "tempo:T3"` in inline config or `foundry.toml` to enable the precompile locally: + +### secp256k1 + +```solidity +/// forge-config: default.hardfork = "tempo:T3" +contract MailRelayTest is MailTest { + uint256 internal constant ALICE_PK = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + + function test_SendMailWithSecp256k1Signature() public { + bytes32 digest = mail.getDigest(ALICE, BOB, message, attachment); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, digest); + + mail.sendMail(ALICE, BOB, message, attachment, abi.encodePacked(r, s, v)); + assertEq(mail.nonces(ALICE), 1); + } +} +``` + +### P256 + +```solidity +uint256 internal constant CAROL_P256_PK = 0x1; + +function setUp() public override { + super.setUp(); + (uint256 x, uint256 y) = vm.publicKeyP256(CAROL_P256_PK); + carolPubX = bytes32(x); + carolPubY = bytes32(y); + CAROL = address(uint160(uint256(keccak256(abi.encodePacked(x, y))))); +} + +function test_SendMailWithP256Signature() public { + bytes32 digest = mail.getDigest(CAROL, BOB, message, attachment); + (bytes32 r, bytes32 s) = vm.signP256(CAROL_P256_PK, digest); + s = _normalizeP256S(s); // low-s normalization required by the precompile + + bytes memory sig = abi.encodePacked(carolPubX, carolPubY, r, s); + mail.sendMail(CAROL, BOB, message, attachment, sig); + assertEq(mail.nonces(CAROL), 1); +} +``` + +## Run the tests + +```bash +forge test -vvv +``` + +The secp256k1 and P256 relay tests run against the T3 hardfork automatically via the inline config directive. + +## Related + +- [TIP-1020: Signature Verification Precompile](/protocol/tips/tip-1020) +- [T3 Network Upgrade](/protocol/upgrades/t3) +- [Foundry for Tempo](/sdk/foundry) diff --git a/vocs.config.ts b/vocs.config.ts index 6e4cb9f0..c65bb9b5 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -769,6 +769,10 @@ export default defineConfig({ text: 'Use MPP with Foundry', link: '/sdk/foundry/mpp', }, + { + text: 'Signature Verification', + link: '/sdk/foundry/signature-verifier', + }, ], }, {