Skip to main content

Prepare phase

The Prepare relation proves correct parsing and issuer validation of the SD-JWT–style credential and produces the policy-independent proof the wallet can cache. The Rust stack calls this prepare; the SDK exposes it as precompute (OpenAC.precompute).

What is proven (conceptual)

Per Paper — construction / Prepare: issuer signature verification, disclosure digest consistency, and a commitment interface to the message vector used later in Show.

Inputs the SDK expects

PrecomputeRequest (types.ts):

  • jwt: compact serialization (header.payload.signature).
  • disclosures: raw base64url disclosure strings (SD-JWT [salt, name, value] triples).
  • issuerPublicKey: P-256 JWK (kty: EC, crv: P-256) or PEM wrapper.
  • keys: KeySet with prepare + show proving/verifying keys.
  • Optional: jwtParams, birthdayClaimIndex, decodeFlags, additionalMatches, claimFormats.

The credential must carry a device binding key:

get deviceBindingKey(): EcdsaPublicKey | null {
const cnf = this.payload.cnf as { jwk?: EcdsaPublicKey } | undefined;
if (!cnf?.jwk) return null;
const jwk = cnf.jwk;
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
return null;
}
return jwk;
}

Pedersen / curve notation (paper-aligned)

Prepare ends by committing to the parsed attribute vector m=(m1,,mn)\mathbf{m}=(m_1,\ldots,m_n) using a Pedersen vector commitment, exactly as defined in Paper §Preliminaries — Commitments and Proof Interface. With public generators (g1,,gn,h)(g_1,\ldots,g_n,h) in a cyclic group G\mathbb{G} of prime order qq and randomness rFr \leftarrow \mathbb{F}:

C(j)=i=1ngimihr(j)    G.C^{(j)} = \prod_{i=1}^{n} g_i^{\,m_i} \cdot h^{\,r^{(j)}} \;\in\; \mathbb{G}.

The Prepare relation πprep(j)\pi_{\mathrm{prep}}^{(j)} certifies that the SD-JWT parses to m\mathbf{m}, that the issuer signature σI\sigma_I verifies, and that C(j)C^{(j)} commits to m\mathbf{m} under randomness r(j)r^{(j)}. The same C(j)C^{(j)} is referenced by Show — the verifier checks consistency on C(j)C^{(j)} to link the two halves without learning m\mathbf{m}.

In the PoC backend, G\mathbb{G} is the paper's Tom256 (T256) proving curve — configured in code under the alias secq256r1 (see wallet-unit-poc/circom/circomkit.json). ECDSA over P-256 / secp256r1 is used for issuer and device signatures. See Security — assumptions for the full curve breakdown.

JwtCircuitInputs (low-level)

Field-aligned inputs for Circom are built by buildJwtCircuitInputs — see JwtCircuitInputs in types.ts (sig_r, sig_s_inverse, message limbs, matchesCount, claims, …).

DEFAULT_JWT_PARAMS

Values exported from types.ts:

FieldDefaultMeaning
maxMessageLength1920Maximum JWT length (header.payload) in bytes the circuit handles.
maxB64PayloadLength1900Maximum base64url payload length.
maxMatches4Maximum number of substring extractions per JWT.
maxSubstringLength50Maximum length of each substring match.
maxClaimLength128Maximum length of each extracted claim value.

The 1k / 2k / 4k / 8k proving-key sets baked into scripts/keys/ correspond to scaled maxMessageLength variants of the JWT circuit (jwt, jwt_1k, jwt_2k, …). Choose a VcSize that fits your worst-case JWT.

PrecomputeRequest.decodeFlags and PrecomputeRequest.claimFormats are optional; when omitted, the input builder fills them with zeros (UTF-8 string format, no special decoding) up to maxMatches entries. Set them explicitly if you have integer claims (claimFormats[i] = 1) or need URL-decoding.

Version

Defaults track SDK 0.1.0.