Sigil’s public read-only RPC requires no authentication. Transaction submission requires an Ed25519 signature embedded in the transaction envelope. Premium RPC tiers and validator-internal methods require a JWT.

Transaction signing

Every transaction is signed by the originator’s Ed25519 key. The signature covers the canonicalised transaction envelope:
signature = ed25519_sign(
  signing_key,
  blake3("sigil/tx/v1\n" || jcs(tx_envelope))
)
Where:
  • jcs is JSON Canonicalisation Scheme (RFC 8785).
  • tx_envelope is the typed transaction payload with all fields populated.
  • The domain separator sigil/tx/v1 prevents cross-protocol signature reuse.

Envelope shape

{
  "from": "did:oas:sigil:agent:alice",
  "nonce": 42,
  "fee": "1000",
  "expires_at_height": 1024533,
  "tx": {
    "kind": "Transfer",
    "data": {
      "to": "did:oas:sigil:agent:bob",
      "amount": "1000000"
    }
  }
}
The signed transaction is then submitted via sigil_sendTransaction with the signature attached:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "sigil_sendTransaction",
  "params": {
    "envelope": { /* as above */ },
    "signature": "ed25519:Bx9...",
    "pubkey": "ed25519:Ax2..."
  }
}
The node verifies:
  1. The signature matches the envelope under blake3("sigil/tx/v1\n" || jcs(envelope)).
  2. The public key resolves to the from DID via the OAS resolver.
  3. The nonce matches the next expected nonce for the DID.
  4. The fee meets the local fee market for the transaction class.
  5. The expires_at_height is in the future and within the admission window.

Multi-human roots

Transactions originating from did:oas:sigil:mhr:... DIDs are signed with a FROST t-of-n threshold signature. The aggregated signature is verifiable as a standard Ed25519 signature against the group public key; the verifier does not need to know which signers participated. The MHR registration transaction RegisterMhrRoot commits the group public key, the threshold, and the participant DIDs. Subsequent transactions reference the MHR DID and are verified against the registered group key.

JWT for premium RPC

Premium RPC tiers (higher rate limit, validator-internal methods, identity-bound subscriptions) require a Bearer JWT in the Authorization header:
POST /rpc HTTP/1.1
Host: rpc.sigil.ml
Authorization: Bearer eyJhbGciOiJFZERTQSIs...
Content-Type: application/json
JWTs are issued by the validator cluster’s auth service after a challenge-response flow:
  1. Client requests a challenge: POST /auth/challenge → returns a 32-byte nonce.
  2. Client signs the nonce with their DID’s Ed25519 key.
  3. Client exchanges the signed nonce: POST /auth/exchange → returns a JWT.
  4. JWT is valid for 24 hours; refresh by repeating the flow.
JWT claims include the issuing DID, the rate-limit tier, and the allowed method list. The validator cluster verifies the JWT on every request.

Validator-internal methods

A small set of methods (block production, peer management, log inspection) is reachable only on the validator-internal port and requires a JWT issued by the validator’s own auth service. These methods are not exposed on public RPC and return MethodNotFound from the public profile. See WS private subscriptions for the identity-bound WebSocket variant.

Wallet signing patterns

Wallet typeSigning flow
Browser extensionSigns in-extension under a user-confirmed prompt; never exports raw key.
CLI (sigil-cli)Decrypts encrypted keystore on demand; signs in-process.
Hardware wallet (Ledger)App on the device signs the BLAKE3 digest; host never sees the seed.
Validator HSMSigning API exposes only signature output; raw key isolated in the HSM.
FROST MHREach participant signs a share; the aggregator combines into the final signature.
The reference signing implementation across all SDKs is the Rust WASM crypto module in oas/oas-sdk-rust/src/sign.rs, compiled to WASM and linked from TypeScript, Go, Python, Swift, and Kotlin SDKs.