Cryptography in use
The component pages say what each part guarantees. This page says how, at the level of which group, which generator, which transcript label, which equation the verifier checks and how many bytes cross the wire. Everything here was read from the code on 2026-09-07, with the file named, and where the code and the older design documents disagree the code is reported and the disagreement listed at the end.
source: zkpi/rust/zkfmi-zk (pedersen.rs, sigma.rs, range.rs, bitrange.rs, oneofmany.rs, or_dleq.rs, adaptor.rs, shamir.rs; on defmi main until 2026-09-07) · defmi/rust/defmi (ledger.rs, notes.rs, note_membership.rs, netting.rs, credit.rs, reconcile.rs, viewing.rs, vetting.rs, assets.rs, pvp.rs) · zkpi, qomm-proofs · zkpi, qomm, oclob, dekyx, zkfmi-crypto · locked versions from Cargo.lock
Group, generators, hashing
Every commitment, proof and classical signature lives in ristretto255, the prime-order group over Curve25519, through curve25519-dalek pinned at 4.1.3. The floor is stated in the workspace manifest: 4.1.3 carries the fix for a timing leak the compiler introduced into scalar subtraction (RUSTSEC-2024-0344), and the dalek family was audited by Quarkslab in 2019 together with the Bulletproofs crate. Ristretto was chosen over the raw curve for a reason recorded in the code: an earlier version hashed to a point and did not clear the cofactor, and 39% of the encodings a well-known library accepted were not of prime order. Ristretto has no cofactor to get wrong.
The two Pedersen generators are not chosen by this stack. They are taken from the Bulletproofs crate, g = B (the Ristretto basepoint) and h = hash-to-group(SHA3-512, B), on purpose: a range proof commits under the generators that crate picks, and a settlement compares those commitments against commitments the ledger made itself. If the two generator sets differed, every equation would still typecheck and every binding between them would be vacuous. A second, independent generator pair derived with SHA-512 exists for uses that never meet a range proof.
Per-asset generators, the asset tags, are hash-to-group of a fixed label and the asset identifier:
A_a = hash_to_group(SHA-512, "qomm:defmi:asset:" || be32(asset_id))
A balance in asset a is committed under A_a in place of g, with h unchanged, so proofs that speak only about h stay valid across a change of value generator. The same hash-to-group pattern produces decoy seats for the vetting roll (label qomm:defmi:vetting:decoy:v1, so that no one, the operator included, holds an opening for a decoy), the scope generator of the or-DLEQ proof, and the point substituted for ineligible ring members in note proofs (DEFMI:NOTE:INELIGIBLE:v2).
Challenges are never a hand-rolled hash. Every sigma protocol runs over a Merlin transcript (STROBE over Keccak-f[1600]): points are appended compressed under a label, and the challenge is 64 bytes reduced to a scalar. Three hash families therefore appear, each in one role: Merlin for proof challenges, SHA-512 for hash-to-group and key derivation, SHA-256 for digests and wire identifiers.
| construction | transcript framing | challenge |
|---|---|---|
| opening proof | dom = "qomm/opening", then C, T | c |
| zero-opening (value pinned to 0) | relation = "zero-opening:v2" before the opening framing | c |
| cross-generator equality | dom = "qomm/xgen", then g1 g2 c1 c2 t1 t2 | c |
| product | dom = "qomm/product", then Ca Cb Cc Tf Tp | c |
| bit (OR proof) | dom = "qomm/bit", then C T0 T1 | c |
| Groth–Kohlweiss one-of-many | dom = "qomm/gk", then the whole set C and cl ca cb gk, each length-prefixed | x |
| or-DLEQ (credential scope) | qomm:or-dleq:v1, then registry scope context N P A B | c |
| adaptor Schnorr | qomm:zk:adaptor:v1, then R X m | c |
| bit-decomposition range | qomm:bitrange:v1 + context, sub-contexts :bit:i, :link, |above, |below | per component |
| reconciliation | qomm:defmi:reconcile, then the SHA-256 attestation body (register, account, asset, as-of, total) | c |
| vetting membership | qomm:defmi:vetting:v1, then context, cohort, group, epoch, every envelope, the handle | x |
| note ring (Triptych) | DEFMI:NOTE:MEMBERSHIP:v2 + a context hashing the output commitments and note bindings | library |
Two of those rows carry an argument. The one-of-many transcript includes the set, not only the proof, because otherwise an attacker could move mass between two members and leave the sum untouched. The vetting transcript includes every envelope, because naming the epoch alone would let a roll rewrite an entry without bumping it.
Commitments and conservation
A commitment is C = g·v + h·r in additive notation, or H·q + h·r under a blinded asset tag H = A_a + h·γ. Conservation on the account ledger is checked with no proof and no opening, by one equation over the whole state:
Σ accounts + Σ escrowed(pending PvP legs) == minted
Escrow is counted explicitly; leaving it out would make every prepared leg look like value destroyed and every commit like value created. Per transfer, the ledger enforces the homomorphism directly and then asks for one range proof:
balance − amount_commitment == remainder_commitment // arithmetic, no proof
remainder_commitment ∈ [0, 2^bits) // Bulletproof
That pair is the whole of "nothing negative, nothing created". Under an asset tag the blinding bookkeeping is the only subtlety: the payer's remainder blinding is r_payer − r_amount − γ·balance, because H^(v−q) h^t has to land on balance / amount, which costs γ·v. The netting rail has the same shape with positions in place of balances.
Six sigma protocols
The zk crate implements its own sigma protocols because the crates that exist for this either say they are not production-ready or carry no audit. Each is a three-move proof made non-interactive over Merlin, in the shape commit first-move point, append, challenge, respond with z = k + c·witness. Compressed points and scalars are 32 bytes each.
| proof | relation | wire | used for |
|---|---|---|---|
| opening | knows (v, r) with C = g·v + h·r | 1 point + 2 scalars, 96 B | building block; linkage in the bit-decomposition baseline |
| zero-opening | C = h·r, value forced to 0 and rejected otherwise | 96 B | note spend balance (inputs minus outputs), linear relations |
| cross-generator equality | same v in C₁ = G₁·v + h·r₁ and C₂ = G₂·v + h·r₂ | 2 points + 3 scalars, 160 B | instruction amount ↔ securities leg; cash reference ↔ cash leg |
| product | C_c commits to a·b given C_a, C_b, via C_a·b = g·ab + h·r_a·b so C_c − C_a·b is a pure multiple of h | 2 points + 3 scalars, 160 B | cash leg = quantity × price; waterfall ordering |
| linear | Σ coeff_i·v_i = constant, collapsed to a zero-opening of Σ coeff_i·C_i − g·constant | 96 B | attested cycles, credit arithmetic |
| bit | C opens to 0 or 1, Chaum–Pedersen OR with the two challenges summing to the transcript's | 2 points + 3 scalars, 160 B | each bit of the bit-decomposition baseline |
At a DvP settlement, three of these are checked together: one cross-generator proof that the securities leg moves the instruction's committed amount, one product proof that price × amount equals the committed cash reference, and one cross-generator proof that the cash leg moves that reference. They are pushed into one batch and settled with a single multiscalar multiplication against the identity. Two soundness rules are written into the code next to that batch. Batch weights are drawn by the verifier, never from the transcript, because a transcript-derived weight is something the prover can compute too and then solve the single aggregate relation. And an unbatched verifier must check the two halves of a product proof separately: a summed check once accepted a proof that 2 × 3 = 8, and the test that forges it is kept in the crate.
Range proofs: Bulletproofs in production, bits as the baseline
Every range proof on a production path is a Bulletproof (bulletproofs 5.0.0), at 8, 16, 32 or 64 bits, aggregated in powers of two with zero-valued padding and with out-of-width values rejected before proving because the crate does not reject them. A single 64-bit proof is 672 bytes: seven elements plus an inner-product argument of 2·log₂(n·m) points and two scalars. Verification is one multiscalar multiplication, which is why the settlement package barely moves with width (3,424 B at 64 bits) and why the "settle" time is 7.02 ms.
The bit-decomposition construction is kept as the measured baseline, not as a switchable backend. It commits each little-endian bit under a fresh blinding, proves each with a bit proof under its own sub-transcript, and links the weighted sum back to the original commitment: C − Σ 2^j·C_j must be a pure multiple of h. The cost is 192 bytes per bit (32 for the bit commitment, 160 for its proof) plus the linkage, 896 B/bit on the wire once the rest of the package is counted, and 0.80 ms/bit to verify. The only production caller is the measurement harness that produces the "linear backend" rows of the charts.
Its threshold form, however, is on a production path. When a quorum proves a range over a value none of its members holds, the per-bit OR proof cannot be produced from shares, so the threshold range proof keeps the bit commitments and the linkage but replaces the OR with the field equation b·b = b, proved jointly. It is what the threshold DvP package uses for the two reservation remainders and what zkPI carries as its two threshold range proofs. Bounded intervals are two range proofs at width ⌈log₂(high − low)⌉, and the verifier also checks the inner widths; the attack that prevents is written in the code: for the interval [−4000, 4000] and value 4001, value − low is 8001 and honest at 13 bits while high − value is −1, which is ℓ − 1 and honest at 253 bits.
One-of-many: Groth–Kohlweiss for tags and vetting, Triptych for notes
The Groth–Kohlweiss proof shows that one commitment in a set opens to zero without saying which. The implementation is fixed at base 2: the set size must be a power of two, m = log₂ N, and the prover commits to the bits of its index (cl_j), to random masks (ca_j), to their products (cb_j), publishes m polynomial-coefficient points G_k = Σ_i p_i[k]·C_i + h·ρ_k, and answers the challenge with f_j, za_j, zb_j and one aggregate zd. Proof size is exactly 32·(7m + 1) bytes, so a set of 16 costs 928 B and every doubling adds 224 B. Verification grows about 1.4× per doubling rather than 2×, the batched multiscalar multiplication showing through, which is the miss that moved the default vetting crowd from 16 to 128.
It is used in two places. At account issuance, to show that a blinded tag H is one of the registered asset generators, over the quotients H − A_i (which equals h·γ exactly at the registered index); the registry is padded to a power of two so that a set which grew whenever an asset was listed would not leak the listing, and the proof is paid once per account, not per settlement. And in vetting, below.
The note ring is not Groth–Kohlweiss any more. Version 2 of the note rail adapts Tari's Triptych, a parallel one-of-many proof that binds the same ring index to a one-time owner key and to a value commitment and produces an unlinkable linking tag. Parameters are base 2 with exponent max(2, log₂ ring), a ring cap of 4,096 and a proof cap of 1,800 bytes, canonical encoding re-checked on decode. The library is pinned by commit and its own notice is repeated in the adapter: the upstream implementation is experimental, and this is not an audit claim.
Notes, serials, nullifiers
An address is a pair (A, B) = (g·a, g·b), view key a and spend key b, split at the wallet so that a view key finds notes and cannot spend them; there is no method to refuse because the scalar is simply not there. The payer draws an ephemeral e, publishes E = g·e, and forms the note's one-time point g·H(A·e) + B. The payee recomputes the shared scalar from its view key and the serial from its spend key:
shared = H_512("qomm:defmi:note:v1:" || "shared" || E·a)
S = shared + b
masks = H_512(… "mask:value" …), H_512(… "mask:blinding" …)
Value and blinding travel masked by those two scalars, so scanning is one Diffie–Hellman and two hashes per note (0.05 ms). The note publishes its g·S point and its value commitment A_a·v + h·r separately, because folded into one point a settlement would have nothing to compare the proof against; the note's identity is a SHA-256 of the whole body under DEFMI:NOTE:BODY:v2, a hash rather than a sum precisely so that a malicious recipient cannot change its decomposition.
On spend, what is published is the Triptych linking tag U·S⁻¹, where U is the library's third generator, and never g·S or the scalar. That was defect N2 in the September 2025 note-proof review: publishing the searchable one-time key would have linked the spend to the payment. A spend proof carries the tag, a re-blinded pseudo-commitment to the input value, the Triptych ring proof, the output commitments and note bindings, one aggregated Bulletproof over the outputs, and a zero-opening proof that pseudo − Σ outputs is a pure multiple of h (defect N3 was a general opening here; the fixed-zero variant now rejects any nonzero value response). Double spend is a set insert on the compressed tag; spent notes stay in the pool, because removing them would say which one went. The pool's state root is a rolling SHA-256, extended once per append and once per spend, after walking the pool was measured at 61 ms of a settlement against about 8 ms of cryptography.
Decoy selection has two implementations. ring_for draws uniformly over the pool with a fixed-seed shuffle so the real note is not always first. ring_recent draws from the newest notes, because a settlement pays with a note it was recently paid and a uniform decoy usually is not recent; the measured attack shows the difference, 0.359 against a nominal 0.062 at ring 16 with sixteen settlements of other traffic. Only the uniform rule is wired into the harness and tests today; the recency rule exists in the benchmarks, and the ledger's own status note says so.
Adaptor signatures for PvP
Two legs on two ledgers that share no state are tied by a Schnorr adaptor signature over ristretto255:
pre-sign R = g·r, c = H(R + Y, X, m), s' = r + c·x → (R, s')
verify g·s' == R + c·X
adapt s = s' + y → (R + Y, s)
extract y = s − s'
The design reason is written in the module: a hash lock leaks a preimage that appears in the clear on both ledgers and links the two legs for anyone who reads both; an adaptor leaks a scalar, and only to the holder of the pre-signature. What lands on either ledger is an ordinary Schnorr signature whose nonce points differ by Y, and Y never appears. The protocol is proposer and responder each preparing an escrowed leg and exchanging pre-signatures; the first claim publishes a signature, the second party extracts y from it and claims the other leg. The escrow records the release key at prepare time and refuses a commit after its deadline; an unwind needs no signature, the deadline is the whole authority; leg names are burned for the ledger's lifetime. The safety condition is an inequality between the two deadlines and the time to notice and react, and the reaction is 0.06 ms, so the exposure window is set by the two ledgers' finality and by nothing in this code.
Reconciliation against a register, 96 bytes
Commitments are homomorphic, so the sum of an account's balance commitments is a commitment to the sum:
Σ C_i = A_a·(Σ v_i) + h·(Σ r_i)
residual = Σ C_i − A_a·N // N is the register's figure
If the register is right, the residual is a pure multiple of h, and a 96-byte proof of that says the totals agree and nothing else. The register's figure, account, asset and as-of date are hashed into the challenge, so a proof cannot be replayed against another attestation; the attestation itself is ed25519-signed by the registrar; nobody need hold the aggregate blinding, because a 3-of-7 quorum can assemble the response. Localising a break costs disclosure, by design: bisection makes 2·log₂ n + 1 sub-proofs and reports every range it made public.
(v, r) with residual = g·v + h·r; the holder of the openings has such a proof for any N. A test written while preparing this page produced an accepted reconciliation for a total of 6,500 against balances summing to 6,000, and a second test passed a commitment to 300 through an 8-bit range proof by re-proving the bit-decomposition linkage, which had the same shape. The same pattern was on the linkage of the threshold range proof carried by zkPI and the threshold DvP package, on the quote proof's "winner opens to the revealed value" step, and on the rule audit's equality step. All five now use the fixed-zero opening the note spend already used; in the threshold assembly no node's nonce carries a value component, so the assembled response's value part is zero exactly when the statement holds. The two forgeries are regression tests in the owning crates in zkpi. The finding note in the settlement repository has the details.Scoped viewing
Scoping is in the address, not in the key. A wallet holds two 32-byte seeds and derives, per scope (an instrument, a quarter, a mandate),
view_s = H_512("qomm:defmi:view:v1" : "view" : seed_v : scope) // the auditor gets this
spend_s = H_512("qomm:defmi:view:v1" : "spend" : seed_s : scope) // it does not
address = (g·view_s, g·spend_s)
A viewing grant names the grantee, the address, the view key and a validity window, and is ed25519-signed; checking it verifies the signature, that the key opens the address it names, and the dates. With the key an auditor scans notes (both the one-time point and the value commitment must reconstruct before a note is accepted as the wallet's), receives value and blinding but no serial, and can run the reconciliation above over the scope. Three limits are stated rather than hidden: a grant cannot be revoked, revocation is address rotation; a view key sees incoming value only, because a serial belongs to an address exactly when S − H(E·a) = b and the hash puts that outside what a sigma protocol can prove; and scoping depends on payers using the current address. Outflows are therefore signed disclosures, attribution rather than verification, and the code refuses to combine a view key with serials for the same scope, because that combination is the wallet.
Vetting: one handle, one seat, a crowd of 128
A vetting roll holds sealed envelopes C_i = a_i·G + r_i·h. To show that a handle A was vetted, the holder proves one-of-many that some C_i − A is a commitment to zero, over a group fixed for the life of the handle so rings cannot be intersected, and a Schnorr proof that A = a·G is a bare power of the base point. The second half is load-bearing: the one-of-many proof alone says C_ℓ − A is a multiple of h, and A + δ·h satisfies that for any δ; pinning A to the base point is what makes "one vetting, one handle" mean something for the per-firm caps. Decoy seats are hashed to points nobody can open, so the roll's count of vetted members is honest; slots are assigned at random so position does not encode onboarding order; the group type cannot be constructed outside the crate, so a verifier must compare the roll digest with what the chain published. Crowd 128 costs 3.80 ms to verify and 1,676 bytes; at 16 the membership is 1,004 bytes, of which 928 is the ring, 64 the control proof and 12 the group and epoch. Hiding in thousands would need a Merkle tree checked inside a circuit, a different proof system from the one this stack is built on.
Netting, the intraday limit, the waterfall
A net rail accumulates with no proof at all: one delta, one payer, one payee, position_payer −= Δ, position_payee += Δ, deliberately not a debit/credit pair so conservation holds by construction rather than by a check that could be forgotten. On a gross rail a cover proof per order is required; on a net rail it is refused. At the close, every handle proves coverage once:
headroom = position + limit // both commitments
Bulletproof(headroom ∈ [0, 2^bits)) under transcript "qomm:cycle:close" || rail || handle
The verifier reconstructs the statement, position_commitment + cap_commitment, rather than being told it, and refuses a proof about any other point. Because the cap enters as a commitment and its blinding is added to the position's, the proof is about position + cap and never reveals which side of zero the position was on; the offset trick that hiding a signed position would otherwise need is not required anywhere. Headroom arithmetic is done in 128-bit integers because in a release build a wrap is silent, and a wrapped headroom is a proof that a position is covered when it is not.
Granting the limit proves collateral·(10,000 − haircut_bp) − cap·10,000 ≥ 0 as a range proof on the scaled slack, so the integer division never has to be proved; the width grows by the fourteen bits of the scale, paid once at grant rather than per order. The default waterfall is a chain of product proofs, draw_k × remaining_{k−1} = 0 with the product commitment pinned to the identity: a later tranche can be drawn only when the earlier one is exhausted. Attested cycles skip per-trade verification and instead require an ed25519 signature by the quorum over a digest that is length-prefixed by cycle identifier, because an attestation for one quiet cycle once settled the next quiet cycle. The measured consequence is on the DeFMI page: 400 ms gross-gross, 247.5 ms net-net, 20.6 ms attested, at 64 trades and 8 participants.
zkPI: what the wire carries and what the quorum signs
The instruction's version 2 wire is generated from the encoder, so the layout in the repository's wire document cannot drift from the code. Big-endian throughout:
| field | bytes | kind |
|---|---|---|
magic QOMMZKPI, version 2 | 8 + 2 | tag |
| amount, price, asset commitments | 3 × 32 | Pedersen, compressed Ristretto |
| payer handle, payee handle | 2 × 32 | public points g·s_V |
| deadline, nonce | 8 + 32 | Unix seconds; nullifier input |
| quote proof digest | 32 | SHA-256 of the complete public quote proof |
| quorum signature | 64 | FROST over ristretto255 |
| amount range proof, price range proof | 4 + n, 4 + m | threshold bit-decomposition proofs, length-prefixed |
A threshold range proof is 2 + bits × (32 + 5 × 32) + 3 × 32 bytes: a width, one commitment and one five-element product proof per bit, and the 96-byte linkage. At the product vector's bounds of 16-bit quantity and 32-bit price that is 3,170 and 6,242 bytes, and the whole instruction is 306 + 4 + 3,170 + 4 + 6,242 = 9,726 bytes. The figure is bounds-dependent: the default bounds are 32/32 and the QOMM product circuit runs 64-bit amounts, which give different totals. Version 1 (a packed quote key and two Bulletproofs, 1,572 bytes) is a migration format; an unknown version is refused, never guessed, because a misparsed commitment is still a valid point.
The nullifier is derived, not carried: the first 32 bytes of SHA-512("QOMM:ZKPI:NUL:v1" || nonce || payer_handle || payee_handle). The deadline is bounded on both ends at the venue, no further than a configurable horizon (86,400 s by default) and not already past.
The FROST signature (frost-ristretto255 3.0, the Zcash Foundation's threshold Schnorr, partially audited by NCC Group in 2023) is 3-of-7 over a SHA-512 digest of the public fields:
SHA-512( "QOMM:ZKPI:v2" || len(domain) || domain
|| amount_c || price_c || asset_c || payer_handle || payee_handle
|| deadline || nonce
|| "QOMM:QUOTE-PROOF-DIGEST:v1" || quote_proof_digest )
Range evidence is deliberately outside the signed digest; the venue verifies the ranges separately and then the signature, and the domain is what stops replay across rails. Keys come from a three-round FROST DKG over the authenticated coordinator, which relays only signed identities, public broadcasts and recipient-encrypted round-two packages, so no party ever holds the group secret; the trusted-dealer constructor exists and is labelled a fixture, not a deployment. Signing is the standard two rounds, one-time nonce commitments then partial signatures, aggregated to 64 bytes. A verifier that took only the group key could not tell one quorum from another that aggregates to the same point, so the venue holds the full public key package. The typed zkPI used by applications adds a second FROST signature over operation, scope, direction, venue and ledger identifiers, three handles, reservation identifiers and sequences, the RFQ nullifier, mandate and receipt digests and the prior state root; both signatures must verify.
Handles are one 32-byte seed per participant and one scalar per venue, s_V = H_512("QOMM:ZKPI:HANDLE:v1" || seed || len(venue) || venue), H_V = g·s_V. The multiplicative alternative g·(a·h(V)) was rejected and the rejection is a test: it is publicly linkable, because H_A·h(B) = H_B·h(A) exactly when the two handles belong to one firm, and measured it let an observer join the two legs of a cross-venue exchange with certainty. Cross-domain DvP projects one instruction onto two ledgers with different digests, so the two public records share no trade identifier; the body requires two distinct ledger deployments, an escrow distinct from source and destination, and arm < claim < refund deadlines.
The verifier's exit codes are 0 accept, 1 reject, 2 could not be asked. Without a quorum key and a clock it checks the layout only and says so in words, because a tool that printed "ok" for a well-formed unsigned instruction would be worse than none. An accepted test vector must decode and re-encode to the same bytes, a stronger statement than "it parsed".
Threshold proofs: sigma responses are affine, so shares combine
The reason a quorum can produce a proof about a value none of its members holds is one line of algebra. A sigma response is z = k + c·w, affine in the witness, so each node computes z_i = k_i + c·w_i from its own Shamir share and the pieces Lagrange-combine into z in the scalar field of the group. That is only true if the shares live in the group's scalar field, which is the matched field below. Per-bit range proofs cannot use the OR construction, because choosing a branch from the bit is not affine; the threshold form proves b·b = b as a product relation instead, whose responses are affine.
The protocol is commit, reveal, challenge, respond, with the commitments sealed first: each node publishes a SHA-256 seal of its nonce points under QOMM:THRESHOLD-RANGE:ROUND1:v1 before any reveal, so an adaptive node cannot cancel an honest nonce. Reveals must match seals exactly, the quorum exactly, and the context digest. Each node holds only its value share, blinding share and per-bit triples plus the public VSS coefficient commitments, and checks its own share against the public VSS statement before using it; the public statement is reconstructed only in the exponent, and no scalar share or range value is accepted by that function. Verification is the ordinary public verifier with no shares, quorum or setup as inputs. A venue can additionally refuse mathematically valid single-prover Bulletproof evidence as a provenance policy, because a single prover would have been a cleartext reconstruction point.
The MPC: malicious Shamir over the group order
The venues run MP-SPDZ's malicious-shamir-party.x, seven parties, threshold two, and refuse to run at all if the binary is absent rather than fall back to a clear matcher. The prime passed to the virtual machine is
ℓ = 7237005577332262213973186563042994240857116359379907606001950938285454250989
= 2^252 + 27742317777372353535851937790883648493
which is exactly the order of ristretto255. This is the matched field, and it is enforced, not assumed: the proof assembler checks the circuit's prime against the group order and refuses with the message that the circuit wrote in a field of one width and the commitments live in one of 253 bits. The one-line circuit change is Σ LAGRANGE[p]·input_p in place of Σ input_p, public times secret being local, so rounds and input count are untouched. The price is exactly 2.00× traffic, because the element grows from 16 to 32 bytes and nothing else, and 1.06 to 1.07× wall clock. The measurement record also keeps its own error: the first version reported 2.02× rounds and 14.3× traffic, and the whole difference was one compile flag whose warning the framework printed on every run.
Blindings are drawn inside the MPC. The generated circuit calls sint.get_random() for the price blinding and every bit blinding, so no party learns r; the winner is selected obliviously by multiplying each maker's settlement price by an equality flag against the best key; and each node's persistence file receives only its own Shamir evaluation. Each node then publishes Com(share_i, blinding_i), and the public commitment C = g·price + h·r is reconstructed by Lagrange interpolation in the exponent. No later issuer is asked to recreate anything from a clear quote, and the constructor that builds an instruction from threshold ranges accepts no opening by its API, which prevents an issuer process from becoming a hidden reconstruction point.
The quote proof instantiates Baum–Damgård–Orlandi's publicly auditable MPC: input providers publish Pedersen commitments, and the online phase is linear operations an auditor replays on the commitments. Per maker it proves
depth_i = slope_i × qty product proof
skew_i = invcoef_i × inv_i product proof
ask_i = level_i + depth_i + skew_i linear, free
bid_i = level_i − spread_i − depth_i + skew_i
fits_i = maxqty_i − qty ≥ 0 range proof
fresh_i = expiry_i − now ≥ 0 range proof
ok_i is a bit and gates the cost
key_i = cost_i × M + i linear, free
and over the set: the winner's commitment opens to the revealed value, and key_i − v ≥ 0 for every i. Minimality plus membership is exactly "v is the minimum", so an incorrect winner cannot be proved; six forgery controls (a non-minimal winner, an expired maker, an unfillable request, a switched-off winner, swapped minimality proofs, minimality for a false winner) are recorded as rejected. A witness carrying no registered policy blindings is refused, so the minimum is taken over registered commitments, not ones the prover invented. Each maker's two eligibility ranges share one aggregated Bulletproof, as do the minimality ranges. Measured on host-a with seven nodes: 4 makers 62 ms to prove and 19 ms to verify, 16 makers 246 ms and 71 ms; the joint assembly by a quorum of three takes 4.8 ms with no node holding the witness and the ordinary verifier accepting the result.
Binding what was computed to what was committed. A node can check its dealt share against the dealer's commitment; nothing forces the value it then feeds the MPC to be that share. The sound per-party input check closes this: the circuit stores its shares as it reads them, opens one random ρ once every input is in, and checks Σ ρ^k·share_k against the same combination of the commitments. Soundness is m/p by Schwartz–Zippel, about 2⁻²⁴⁵ at 166 values in a 253-bit field, replacing 2⁻⁴² from seven repetitions of 6-bit coefficients on the old narrow field; the cost is one extra round and 0.39% more traffic. The dealer side is bound by identity rather than comparison: the party that commits to a value is the party that shares it, in the one pass that writes the party files, and a dealer that deals what it did not commit is caught at the first mismatching position before anything is computed.
Naming a liar. Shamir shares of a degree-t secret are a Reed–Solomon codeword RS[n, t+1], so Berlekamp–Welch corrects up to ⌊(n − t − 1)/2⌋ wrong shares and returns the error locator whose roots are the evaluation points of the parties that lied. At n = 7, t = 2 the capacity is 2 on a fresh sharing and 1 on a degree-2t product, which is where n ≥ 4t + 1 comes from and why the robust configuration is nine nodes; measured, 300 of 300 trials name the culprits exactly up to capacity and 0 of 300 beyond it, at 40% more share traffic and no extra rounds. The framework itself only detects: its consistency failure carries no party information, and four runs with one flipped byte gave the same string for two different culprits. Detected faults feed Ed25519-signed receipts (QOMM:AUDIT:v1, one per node per slot) and a bond ledger with a fixed schedule, equivocation 1,000,000, forked state and omitted makers 500,000, stale state and bad signature 250,000, missing receipt 50,000, in relative units. The accountability page has the five-rung ladder; the deployed engine is rung 1.
Differential privacy. Published counts carry noise sampled inside the MPC: 64 secret-shared random bits form a uniform 64-bit u, compared against the quantised CDF of a truncated two-sided geometric, and the number of thresholds passed minus the support is the noise. The mechanism is (ε, δ), not pure DP, because the support is finite and the folded tails give adjacent inputs supports offset by the sensitivity; δ is computed as a hockey-stick divergence at e^ε rather than by a closed form, because the closed forms understate it. At ε = 0.5 and support 8 the measured release is δ ≈ 2.1 × 10⁻⁴, 28 rounds and 63.9 MB across seven parties, and the noise is 93% of the rounds. An exact unbounded sampler is still required before a pure-DP claim, and a cheaper divisible construction is known and not implemented.
Transport. Input shares travel in constant-size 495-byte frames (QOMMWIRE v4, fourteen field elements) drawn in the execution field ℓ, after an earlier version that shared over 2²⁵⁵ − 19 reconstructed correctly in tests and produced a different value once the framework reduced in its scalar field. Envelopes to a node or a winner are X25519 to the recipient's key with a fresh ephemeral per envelope, an HMAC-SHA-256 extract-and-expand over the shared secret with the ephemeral key and quote digest in the info, and ChaCha20-Poly1305 with the header as associated data. Node-to-node service traffic is mutually authenticated TLS. This is a P0 combiner, not a handshake protocol, and the post-quantum page covers what replaces it.
OCLOB: shares made at the participant, arrivals certified by five
A corporate participant makes degree-two Shamir shares of six fields (side, limit, quantity, time-in-force, expiry, a GTC flag) before any coordinator sees the order, with hiding Pedersen VSS commitments so every node can reject an inconsistent share without learning the small price or quantity, and so that the commitments cannot be brute-forced like a bare g·price. Each share is sealed to one node: a fixed 8,192-byte clear, a fresh X25519 ephemeral, a derived envelope key, ChaCha20-Poly1305 with the envelope header as associated data, delivered directly over mutual TLS. The coordinator does not receive order contents.
Admission order is fixed before matching by an arrival certificate: five of seven nodes sign, with Ed25519, the same statement
SHA-256( "OCLOB:ORDER-VOTE:v1" || len(market) || market || sequence
|| order_commitment || previous_certificate || expires_at )
A node that votes twice for different statements at one sequence is an equivocation; a certificate needs at least five distinct nodes with identical statement digests and strict signature verification; the log requires each certificate to name its predecessor, so the chain is canonical and a repeated commitment is a replay. The policy constants are frozen, seven nodes, two corruptible, reconstruction quorum three, ordering quorum five, and the validator refuses any other shape or an ordering quorum that does not exceed twice the corruption bound. Matching is price-time priority inside the same malicious-Shamir MPC over ℓ, and for each fill the seven nodes jointly prove, from their 616 secret-shared proof wires, the quantity and price ranges, quantity times price, and non-negative cash, securities and maker-reservation remainders.
The settlement authority for an order is a fixed 65,536-byte sealed envelope under a per-order key that is Feldman-shared 3-of-7; reconstruction from any three participant-signed, VSS-verified node releases interpolates the scalar and then checks it against the published commitment, two shares always fail, and the opener zeroes the clear buffer and re-checks the recovered order against the manifest. A node releases its share only over the settlement-specific mutual-TLS connection after persisting a valid MPC result, and the release is permitted only once that result shows the order matched or arrived with remainder; there is no single pre-match decryption key. What remains a P0 item is the legacy browser demo, whose coordinator still sees the clear order before sharing; the native path does not.
DeKYX: an Ed25519-signed commitment, a Schnorr presentation, a scope nullifier
The credential is not BBS+ or CL. It is an Ed25519 signature by the issuer over a SHA-256 digest (DEKYX:CREDENTIAL:v1) of a Pedersen-committed subject secret, a Merkle root of attributes, scope and policy digests, epochs and validity. The subject commitment is g·s + H_b·r with H_b = hash-to-group("DEKYX:SUBJECT-BLINDING-GENERATOR:v1"); the holder generates s, proves possession at issuance with an Okamoto proof of the opening, and the issuer sees only the commitment and proof, never either scalar. Attributes are Merkle leaves, so selective disclosure is revealing the leaves a policy requires and their paths.
A presentation is a two-base Schnorr AND-proof under one challenge:
g·z_s + H_b·z_r == A_c + c·commitment
H_scope·z_s == A_n + c·nullifier where H_scope = hash-to-group("DEKYX:SCOPE-NULLIFIER-GENERATOR:v1" || scope)
c = H_512("DEKYX:ANONYMOUS-PRESENTATION:v1" || credential || context || nullifier || A_c || A_n || disclosed leaves and paths)
so it proves knowledge of the opening of the signed commitment, that the published nullifier is H_scope·s for the same secret, and, through the challenge, that the disclosed leaves sit under the signed root. The context (scope, audience, action, request digest, nonce, expiry) is in the challenge, so a transcript for one operation cannot be moved to another, and the ledger of consumed (nullifier, context) pairs must live in authenticated storage because a rollback re-enables a replay. The nullifier N_scope = H_scope·s is deterministic and linkable within one scope and unlinkable across scopes, since the bases differ. The stable subject line is a SHA-256 of issuer, scope, policy and nullifier, and survives issuer key rotation, so an application can enforce one line or one limit per subject without learning who the subject is.
Revocation lists are Ed25519-signed under DEKYX:STATUS-LIST:v1, sorted and strictly increasing, bound to the issuer key epoch, valid only inside their window; a missing or stale list fails closed and an older list cannot replace a newer one. Key rotation registers a strictly higher epoch and bounds earlier epochs by a grace instant. The stated limit is on the DeKYX page and repeated here: the scheme is scope-pseudonymous, not issuer-unlinkable, because the signed commitment is stable within one credential and can be correlated with the issuance record; issuer-unlinkability needs a rerandomisable credential such as a reviewed BBS+ or CL adapter.
The post-quantum hybrids
The foundation crate pins ed25519-dalek 2.2.0, ml-dsa 0.1.1, ml-kem 0.3.2 and x25519-dalek 2.0.1 exactly, records the upstream commits and tarball hashes, and repeats the upstream statement that the two lattice crates have not been independently audited.
Signature, Ed25519 + ML-DSA-65. The hybrid signature is the plain concatenation ed25519(64) || ml_dsa_65(3309), 3,373 bytes, public key 32 + 1952; decode accepts the exact length only. Both components are produced in the hybrid suite and both must verify, and a lattice-side error is never converted into classical-only acceptance. The shared context is "ZKFMI:SIGNATURE:v1" || purpose(u16) || suite(4); ML-DSA receives it as its FIPS 204 context and signs randomised, while Ed25519, which has no context input, signs a separate domain "ZKFMI:ED25519-CONTEXT:v1" || len(context) || context || len(message) || message and is verified strictly. Suite identifiers are stable wire discriminants, never inferred from declaration order; the hybrid is 0x202.
Key exchange, X25519 + ML-KEM-768. The combiner is
HKDF-SHA256( salt = "ZKFMI:HYBRID-KEM:v1",
ikm = ss_x25519(32) || ss_mlkem(32) || ct_x25519(32) || ct_mlkem(1088) || suite(4),
info = "ZKFMI:HYBRID-KEM:SESSION:v1", L = 32 )
Public key 1,216 bytes, ciphertext 1,120, the classical half of the ciphertext being the sender's ephemeral X25519 key. Non-contributory X25519 results are rejected on both sides, secrets are zeroised, there is no classical-only fallback, and ML-KEM's implicit rejection means a tampered ciphertext yields a different secret rather than an error. It is a P0 combiner: it authenticates no peer and is not a handshake.
The canonical preimage ZKFMI:CANONICAL:v1 fixes what a signature is over: protocol name and version, network, deployment and contract identifiers, a purpose code, the list of object identifiers, a sequence or nonce, an exclusive expiry, the suite and a 32-byte body hash, all length-prefixed big-endian with no Unicode normalisation or sorting; the body encoding is the protocol's own, hashed with SHA-256 before it enters. Key records carry participant, purpose, suite, a monotonic key version, validity and revocation instants; rotation is signed in both directions, the old key over ZKFMI:KEY-ROTATION:AUTHORIZE:v1 and the new over ACKNOWLEDGE, each covering both bindings, and a change of participant or purpose, a skipped generation, key reuse or a rollback from hybrid to classical is refused.
What the hybrids leave classical is stated in the suite table itself: FROST quorum signatures, Pedersen commitments, Bulletproofs and classical TLS transport carry post_quantum = false. The flag classifies an algorithm and certifies no implementation. The venue's own analysis of why the sigma layer cannot simply be swapped to lattices names three obstacles: size (4,960 bytes a sigma step against kilobytes to tens of kilobytes), rejection sampling (the abort test is on the combined response, so a rejection would arrive after the leak it exists to prevent), and the structural one, that shortness does not survive Shamir, because a Lagrange-combined response is short things multiplied by large coefficients and reduced. The computation itself is already information-theoretically private; what a quantum adversary breaks is the binding of the commitments, and the integration status of the signature and KEM hybrids is on the post-quantum page.
What is classical, what is audited, what is not claimed
- Every commitment, range proof, one-of-many proof, adaptor signature and quorum signature rests on the discrete logarithm in ristretto255. Pedersen commitments are only computationally binding, and a maker's policy commitment sits for the life of the policy, which the position paper names as the strongest argument for the post-quantum direction here: it needs no quantum computer, only time.
- The hybrids on the post-quantum page cover signatures and key exchange. They do not make a Pedersen commitment binding against a discrete-log adversary, while the existing relations remain classical and a separate PQC proof path is under active research.
- Audited components: curve25519-dalek and bulletproofs (Quarkslab 2019), frost-ristretto255 (NCC Group 2023, partial, v0.6.0). Not audited: the sigma protocols, the Groth–Kohlweiss implementation, the Triptych adapter and its upstream, the note rail, the netting and credit proofs, the threshold constructions, and every host. Two internal review rounds are documented on the prior-art and security pages; they are not an audit.
- Nothing here proves the MPC. Validators verify what is submitted to them; the share-to-proof handoff is bound, not proved, and the binding page measures what that costs.
Where the older documents and the code disagree
Read from the code on 2026-09-07. The component pages on this site have been aligned; the repositories' design documents still carry the older statements in the places noted.
| document says | code does | where |
|---|---|---|
| the note ring is a Groth–Kohlweiss proof that one of the ring holds this serial | version 2 uses Triptych with an unlinkable linking tag; Groth–Kohlweiss remains for asset-tag membership and vetting | DEFMI.md §5 vs defmi/src/note_membership.rs |
a note is one point C = g^S · A_a^v · h^r | the one-time point and the value commitment are published separately; the note's identity is a SHA-256 of the body | DEFMI.md §5, notes.rs header vs notes.rs struct |
| the nullifier is derived from the serial and published on spend | the spend publishes the Triptych linking tag U·S⁻¹, never g·S | note_membership.rs, review finding N2 |
| the settlement proof is a bit decomposition of the balance range | every production range proof is a Bulletproof; bit decomposition is the measured baseline and the threshold form | DEFMI.md §2 vs ledger.rs, notes.rs, netting.rs |
| recency-matched decoys close the ring gap | implemented and measured, called only from the benchmarks; production paths use uniform decoys, as DEFMI.md §9 states | notes.rs, benches/rings.rs |
| the reconciliation proof shows the committed balances sum to the register's figure | the verifier checked a general opening of the residual and a forged total was accepted; fixed the same day, see the finding above | reconcile.rs, bitrange.rs, threshold_range.rs, quote_proof.rs, rule_audit.rs |
From the technical deck to implementation
The 37-topic cryptography catalogue maps purpose, equations, actors and limitations. Follow the settlement lifecycle for state transitions and optimistic assurance for the two demos and their native settlement boundary.