> Feedback: If these docs are stale, missing, or confusing, post sanitized feedback to `https://docs.tempo.xyz/api/feedback` with `source: "mcp"`, a short `message`, and any relevant `toolName`, `relatedResource`, or `client`.
# Signature Verification with Foundry

The [TIP-1020](https://github.com/tempoxyz/tempo/blob/main/tips/tip-1020.md) `SignatureVerifier` precompile is available on Tempo. It lets contracts verify secp256k1, P256, and WebAuthn signatures through a single interface — no custom verifier contracts needed.

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

:::info\[T6 keychain verification]
On T6 networks, `SignatureVerifier` also exposes `verifyKeychain(account, digest, signature)` and `verifyKeychainAdmin(account, digest, signature)` for contracts that need to check whether a signature came from an active access key, root key, or admin key for an account. Include chain ID, contract address, and account address in the digest you ask users or keys to sign.
:::

## 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. Tempo support is enabled by the template's Foundry config. If you copy this pattern into an existing project, make sure `foundry.toml` enables Tempo mode:

```toml
[profile.default]
tempo = true
```

### secp256k1

```solidity
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 use Tempo mode through the template's Foundry config.

## Related

* [TIP-1020: Signature Verification Precompile](https://github.com/tempoxyz/tempo/blob/main/tips/tip-1020.md)
* [T6 Network Upgrade](/docs/protocol/upgrades/t6)
* [Foundry for Tempo](/docs/sdk/foundry)
