Sign-In With Tenzro
Authenticate a Tenzro identity to any website or API with an EIP-4361-shaped message signed by a hardware-rooted key.
Sign-In With Tenzro (SIWT) lets a website, API or agent authenticate a Tenzro identity. The user signs a short, human-readable message scoped to the requesting domain, and the relying party checks the signature against the identity's registered keys. The message format follows EIP-4361 (Sign-In with Ethereum) byte for byte, so existing SIWE parsers can read it.
The signature comes from a hardware-rooted key: a passkey with user verification for a person, a TPM 2.0 or Secure Enclave key for a machine. Like every Tenzro signature it is hybrid (classical plus ML-DSA-65). See Hardware-rooted keys.
The message
example.com wants you to sign in with your Tenzro account:
0x1234567890abcdef1234567890abcdef12345678
Sign in to Example
URI: https://example.com/login
Version: 1
Chain ID: <Network 1 chain id>
Nonce: abc123XYZ
Issued At: 2026-10-01T12:00:00Z
Expiration Time: 2026-10-01T12:05:00Z
Request ID: req-001
Resources:
- https://example.com/r1| Field | Notes |
|---|---|
| Domain | The host asking for the sign-in. Wallets should show it to the user and refuse one that does not match the page's origin. |
| Address | The Tenzro account. The EVM-shaped 20-byte form is accepted, as EIP-4361 requires. |
| Statement | Optional text shown to the user. |
| URI | The resource being signed in to. A tenzro:// URI is allowed. |
| Version | 1. |
| Chain ID | The value eth_chainId returns on Network 1. |
| Nonce | At least eight random alphanumeric characters, issued by the relying party. |
| Issued At, Expiration Time, Not Before | RFC 3339 timestamps. |
| Request ID, Resources | Optional. |
Always read the chain id from the network (eth_chainId or tenzro_caip2) rather than hard-coding it.
Flow
- Issue a nonce. The relying party generates a random nonce and stores it against the pending sign-in.
- Build the message. Fill in the fields and render the canonical text, locally or with
tenzro_siwtBuildMessage. - Sign. The user's wallet shows the domain and statement and signs the exact canonical bytes with their hardware-rooted key.
- Verify. The relying party parses the message, checks the domain, nonce and time window, resolves the DID for the address, and verifies the hybrid signature against the identity's registered keys.
- Consume the nonce. Mark it used so the same signed message cannot be replayed.
Verification is stateless on the network side: replay protection comes from the relying party's nonce store.
Build and parse
Both methods are open. Read the chain id first, then build the message:
CHAIN_ID=$(printf '%d' "$(curl -s https://rpc.tenzro.xyz \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}' | jq -r .result)")
curl -s https://rpc.tenzro.xyz \
-H 'content-type: application/json' \
-d @- <<JSON
{
"jsonrpc": "2.0", "id": 2,
"method": "tenzro_siwtBuildMessage",
"params": {
"domain": "example.com",
"address": "0x1234567890abcdef1234567890abcdef12345678",
"statement": "Sign in to Example",
"uri": "https://example.com/login",
"version": "1",
"chain_id": $CHAIN_ID,
"nonce": "abc123XYZ",
"issued_at": "2026-10-01T12:00:00Z",
"expiration_time": "2026-10-01T12:05:00Z"
}
}
JSONtenzro_siwtBuildMessage returns the canonical string to sign. tenzro_siwtParseMessage takes a message string and returns its fields, and fails if a required field such as URI is missing.
From the CLI:
tenzro siwt build --json "{\"domain\":\"example.com\",\"address\":\"0x...\",\"uri\":\"https://example.com/login\",\"version\":\"1\",\"chain_id\":$CHAIN_ID,\"nonce\":\"abc123XYZ\",\"issued_at\":\"2026-10-01T12:00:00Z\"}"
tenzro siwt parse --message "$(cat message.txt)"Checking the time window
A verifier rejects a message whose Expiration Time has passed or whose Not Before is still in the future. Keep expiry short, a few minutes, and issue a fresh nonce for every attempt.
Related
- Identity: resolving the DID behind an address
- Console and passkey wallet
- RPC access: how signed requests authorise owner methods