The wrapper W
Publics (flat, in this order):
chain_id, pool_address, payment_root, phase_class, time_bucket, registry_root,
fi_index, counterparty_fi_index, state_in, state_out, attestation_in, attestation_out
Witness: phase (constrained by phase_class), FI leaf opening
(fi_id, mpk, epk, vk_root, params_commitment, path), vk_salt, the three
per-phase VK hashes, the selected policy VK (full), the policy proof, and a
Schnorr signature under mpk over all publics.
Checks:
phase_class ∈ {0, 1}. If0,phase == 0; elsephase ∈ {1, 2}. Which of accept or reject was proven is never a public.- Leaf opens under
registry_rootatfi_index. vk_root == H(FI_VKS, vk_salt, vk0, vk1, vk2);selected = [vk0,vk1,vk2][phase].hash(policy_vk) == selected.context_root = H(CTX_PHASE, payment_root, merkle_16(phase leaves))where the phase leaves are built from the publics and the witnessedphase.- Recursive verification of the policy proof with inner publics
(context_root, leaf.params_commitment, state_in, state_out, attestation_out). - Schnorr verify under
mpkover all publics, includingfi_index. The signature is the FI vouching; it binds the proof to this FI, this hold, this bucket.
One VK_W for every FI and every phase.
Authority is separate from proving. Only the signature needs msk. The
policy proof and the wrapper proof can be produced by a proving service or a
relayer that holds the envelope and the FI's params and state preimages.
Compromise of that service loses privacy (it sees plaintexts) but cannot
move value or vouch for a transition, because it never holds msk.