Attesto.CNonceStore behaviour (Attesto v1.12.2)

Copy Markdown View Source

Storage seam for server-issued OID4VCI c_nonces.

A server issues an opaque, time-limited c_nonce from the nonce endpoint and requires the wallet to echo it in the proof sent to the credential endpoint. This behaviour is where those nonces live: issue/1 mints one, valid?/1 reports whether a presented nonce is still live (non-consuming, so every proof in one OID4VCI proofs batch can validate against the shared nonce), and consume/1 atomically single-uses it (called once per credential request, after the whole batch verifies, so a captured proof + nonce cannot be replayed for another credential).

Attesto.CNonceStore.ETS is a ready single-node implementation. A multi-node deployment implements this over a shared store so a nonce issued by one node is honoured by another.

Summary

Callbacks

Atomically consume nonce so it cannot be used again.

Mint and store a fresh nonce valid for ttl_seconds.

Returns true iff nonce was issued by this store and has not expired.

Callbacks

consume(nonce)

@callback consume(nonce :: String.t()) :: :ok | {:error, :used | :expired | :unknown}

Atomically consume nonce so it cannot be used again.

Returns :ok to the single caller that wins the consume, and an {:error, _} to everyone else so single-use is enforced (that is what stops a captured proof from being replayed to mint duplicate credentials; a store that cannot implement this MUST NOT be used for issuance). The precise error is best-effort and implementation-dependent: a store that deletes the row on consume (like Attesto.CNonceStore.ETS) reports {:error, :unknown} for an already-consumed nonce because it is indistinguishable from one never issued; a store that records a used_at marker instead can distinguish {:error, :used} from {:error, :expired}/{:error, :unknown}. Callers MUST treat any {:error, _} as "not consumable" and fail closed.

issue(ttl_seconds)

@callback issue(ttl_seconds :: pos_integer()) :: String.t()

Mint and store a fresh nonce valid for ttl_seconds.

valid?(nonce)

@callback valid?(nonce :: String.t()) :: boolean()

Returns true iff nonce was issued by this store and has not expired.