Skip to content
Tenzro
Documentation menu
Keys and identity

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
FieldNotes
DomainThe host asking for the sign-in. Wallets should show it to the user and refuse one that does not match the page's origin.
AddressThe Tenzro account. The EVM-shaped 20-byte form is accepted, as EIP-4361 requires.
StatementOptional text shown to the user.
URIThe resource being signed in to. A tenzro:// URI is allowed.
Version1.
Chain IDThe value eth_chainId returns on Network 1.
NonceAt least eight random alphanumeric characters, issued by the relying party.
Issued At, Expiration Time, Not BeforeRFC 3339 timestamps.
Request ID, ResourcesOptional.

Always read the chain id from the network (eth_chainId or tenzro_caip2) rather than hard-coding it.

Flow

  1. Issue a nonce. The relying party generates a random nonce and stores it against the pending sign-in.
  2. Build the message. Fill in the fields and render the canonical text, locally or with tenzro_siwtBuildMessage.
  3. Sign. The user's wallet shows the domain and statement and signs the exact canonical bytes with their hardware-rooted key.
  4. 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.
  5. 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:

bash
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"
  }
}
JSON

tenzro_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:

bash
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.