Docs/Concepts/Proof Binding

Proof Binding

In short

By default the SDK may reuse a recent buffered proof for a predicate call. Three optional parameters change that: sessionNonce binds a proof to one login, decisionRef binds it to one authorization decision, and forceFresh just mints a new proof with no binding.

The freshness buffer

A predicate call can be answered from a buffered proof that is still inside its validity window, rather than generating a new one every time. That keeps repeated checks cheap. When you need a proof tied to a specific request, or a proof made at this exact moment, use one of the three controls below. Each is an optional parameter on isWithin, isOutside, and contains.

Session binding — sessionNonce

Bind a proof to a single login. Your login backend issues a one-time nonce; you pass it as sessionNonce (opaque bytes, 1 to 512). The SDK commits a hash of the nonce inside the signed proof as a sessionBinding stage, so a verifier can confirm the proof was made for that login. Only the hash rides the proof, never the raw nonce. Passing a nonce forces a fresh proof (it bypasses the buffer). An empty or over-512-byte nonce returns INDETERMINATE / INVALID_SESSION_NONCE with no proof.

let nonce = try await loginBackend.issueNonce()
let v = await sdk.loc.isWithin(region: .country(isoCode: "US"), sessionNonce: nonce)
if v.result == .yes { send(v.proof!, to: loginBackend) }
val nonce: ByteArray = loginBackend.issueNonce()
val v = sdk.loc.isWithin(OctetRegion.country("US"), sessionNonce = nonce)
if (v.result == OctetVerdict.Result.YES) send(v.proof!!, loginBackend)

Decision binding — decisionRef

Bind a proof to a specific authorization decision. Pass an opaque, host-minted decision identifier (1 to 256 characters) as decisionRef. The SDK fetches a decision-scoped upload nonce (POST /v1/proofs/challenge with a decision_ref body) so the proofs backend stamps a trusted challenge_decision_ref anchor, and it tags the upload envelope with two opaque top-level fields: decision_ref (the find key you gave) and region_ref (the proof's own claimed region in canonical form, for example country(US)). A relying party can then fetch and trust exactly the proof for one decision. An over-256-character ref returns INDETERMINATE / INVALID_DECISION_REF with no proof and no network call.

The difference from sessionNonce: a session nonce binds the proof to a login and commits a hash inside the signed proof; a decision ref binds the uploaded proof to a decision through the upload envelope, and is the key a backend fetches it by.

Force fresh — forceFresh

Set forceFresh: true to bypass the buffer and mint a proof for this call, with none of the binding semantics above: no committed nonce, no decision-scoped challenge, no envelope tags. Use it when a proof must reflect the device's location right now rather than a cached fix that is still valid, for example a "refresh" button. It does not loosen any spoof check: a fresh proof still runs the full pipeline, and a call that cannot generate one returns noFix, never a cached YES.

Combining forceFresh with a binding is redundant but harmless, because a binding already bypasses the buffer.

Choosing between them

Goal Use
Prove for one login, verifiable in the signed proof sessionNonce
Bind an uploaded proof to one authorization decision a backend fetches decisionRef
A proof of the device's location right now, no binding forceFresh
Cheap repeated checks none (let the buffer serve)

Where to go next