Bridge committees
A handful of keys stands between a locked deposit and a minted claim. Compromise the keys and the vault is empty; the underlying chains were never asked.
~$2.8Bbridge losses since 2022, mostly this layerPost Fiat · pfUSDC on Arc · technical review packet
On 2 September 2026 we deposited 1.000000 USDC into a vault on Arc testnet, proved Arc's own validator quorum finalized it inside a zero-knowledge proof, minted pfUSDC on the PostFiat L1 against that proof, burned it, proved PostFiat finality of the burn under ML-DSA signatures, and released the USDC on Arc against that proof. This page pins every hop to its hash.
What is a NAVCoin. A NAVCoin is a new financial primitive: a token whose value tracks the machine-verified net asset value of a real portfolio, the way a fund share tracks a fund, rather than a peg to a dollar. It lets someone turn USDC into exposure to a portfolio and back again, privately, and without trusting an issuer's quarterly PDF, because the backing, the valuation, and the redemption claim are checked by code and tied to the token's supply. It is built to ride the surge in on-chain stocks, commodities, and other tokenized real-world assets: as those assets move on-chain, portfolios of them can be held, swapped, and redeemed the same way. The design is public in the NAVCoin proposal and the private swap explainer.
Who is asking, and for what. Post Fiat runs its own blockchain, where people and AI agents hold and trade NAVCoins. When someone buys into a NAVCoin they pay in dollars, and when they cash out they get dollars back. Those dollars are USDC, and the copy of that USDC that lives on Post Fiat's chain is called pfUSDC. Every pfUSDC is backed one-for-one by real USDC locked in a vault contract on another blockchain, so the pfUSDC vault is the dollar leg of every NAVCoin subscription and redemption. Post Fiat is applying to Circle's Arc Ecosystem Program. The support requested pays for independent security audits of the contracts and proof programs and for running the proof-generating machines as a reliable service; Post Fiat pays for the engineering itself. This page exists because the proposal promised that every claim would be backed by running software, and this is that software, running.
Why Circle would want this. Arc is Circle's own blockchain, and transaction fees on it are paid in USDC. What Circle needs from early builders is real usage rather than farmed activity, and integrations that show what Arc was built for. Post Fiat's offer is to move its USDC vault onto Arc. After that, every time anyone buys into or cashes out of a NAVCoin, real USDC moves into or out of a vault on Arc, with the fee paid in USDC. That is recurring settlement volume tied to asset management, not a one-off demo. The proposal starts with a capped reserve pilot on Arc once the audit clears, a path to moving most of Post Fiat's reserves there, and a public demonstration of an AI agent doing paid work and being paid in USDC on Arc.
Why the zero-knowledge part matters to Arc. A bridge is the software that lets USDC locked on one chain be represented on another. Almost every bridge relies on a small trusted group, a committee or multisig, to say "yes, that deposit happened," and that trusted group is where most of the billions in bridge hacks came from. This bridge is designed to have no such group, and none was involved in the transactions on this page. One caveat, stated in §04: on this test deployment a single administrative key could still bypass the withdrawal check, and removing it is the first job of the audit. Going in, a zero-knowledge proof, a compact piece of math anyone can check, shows that Arc's own validators confirmed the deposit. Going out, a contract on Arc checks a proof that Post Fiat's validators confirmed the withdrawal. That gives Circle two things to talk about: Arc's confirmations can be verified by other chains with math rather than trust, and Post Fiat's validators sign with post-quantum signatures, the kind designed to survive quantum computers, verified inside a proof on Arc. Several of Arc's founding validators are banks and payment networks with active quantum-risk programs; this is a concrete answer for them.
Why the audit is the point. A demonstration on a test network is a demo. The same system, audited by an independent security firm and then run with a capped amount of real money, is a product Circle can point to. The split is simple: Post Fiat brings the working software, and the grant's audit tranches buy the independent review that makes it something institutions can rely on. The rest of this page is what an auditor would start from rather than a pitch: every step tied to a transaction you can look up, 32 deliberately tampered inputs correctly rejected, and the one known weakness stated up front rather than buried (§04). Closing that weakness before real money is at stake is exactly what the audit money is for.
Both directions of the bridge are authorized by succinct proofs of the other chain's finality, verified on-chain. Ingress verifies Arc's Ed25519 commit certificate over the exact block that carries the deposit receipt, plus an EIP-1186 proof of Arc's validator registry at that exact block so the signing set is proven, not assumed. Egress verifies PostFiat's ML-DSA (FIPS-204) consensus certificates inside an SP1 zkVM, wrapped to a constant-size Groth16 proof that an Arc contract checks. There is no attestation layer and no operator checkpoint write. The design intends no signer fallback either; on this testnet deployment that intent is not yet met on egress, because the SP1 gateway owner key can route around the Groth16 check (§04). The transactions below were all authorized by real proofs; the finding is about what the gateway owner could do, and it is what we want audited first.
Everything below ran on real infrastructure on 2 September 2026: a live Arc testnet deposit, an operator-run Arc node for the registry proofs, an A100 for proving, and the six-validator PostFiat devnet with the Arc-capable release deployed through a six-clone deployment gate earlier the same day. Every transaction and contract on this page links to the Arc explorer; every artifact links to the evidence file committed on the branch.
The depositor calls depositV2 on the immutable vault. The vault pulls exactly 1,000,000 atoms, derives the canonical deposit id, records it in the direct-mode ingress anchor, and emits the deposit log the proof will open.
evidence · ingress/deposit.receipt.json · ingress/deposit.env
From our own Arc v0.8.0 execution + consensus follower (public Arc RPCs do not serve historical eth_getProof; four were probed and all declined), the capture tool pulled the commit certificate for the block, the receipt trie proof, and EIP-1186 account and storage proofs for the validator-registry proxy and its implementation at exactly that block: 2 + 5N storage slots for N = 20 registered validators. The guest re-derives the validator set from proven storage, requires ≥ ⅔ voting power on the certificate, and binds the deposit log byte-for-byte.
evidence · ingress/witness.v2.json · ingress/capture.log · ingress/negative-suite.v2.json · ingress/audit.log
The witness is executed by the pfusdc-arc-ingress guest and wrapped to Groth16. The program identity is the one rebuilt independently in CI earlier today from two clean source trees, byte-identical to the checked-in ELF.
evidence · ingress/proof/proof-report.json · execute-report.json · proof-calldata.bin · public-values.bin · CI rebuild run 33586405990
Governance first bound pfUSDC to the Arc verifier: the Arc proof profile was registered (943), the NAV asset re-registered onto it (944), and the epoch-9 route profile with its Arc finality bootstrap activated by a six-validator signed batch (945). The relay bundle carried the Groth16 proof and public values; the PostFiat node's bounded verifier checked it against the pinned vkey and the governance-pinned Arc finality state, then propose (946) and finalize + claim (947) executed as certified rounds.
evidence · devnet/arc-profile-register-round.finality.json · relay-bundle.report.json · arc-propose.report.json · arc-finalize-claim.report.json · ingress/route-profile.v2.json
The holder burns the full amount with destination evm-erc20:5042002:0x0995876e5a97c036c0fda8846f8f47b57b6d2bfc. Issued supply returns to exactly 303,700,595. The redemption is recorded with its withdrawal packet and EVM digest — the values the egress proof commits to.
evidence · the accepted burn receipt and the withdrawal packet are carried literally inside egress/egress-witness.json
The egress witness carries three things: the 16-block finality ancestry (blocks 932–947) that links the verifier's pinned checkpoint at height 931 to the burn block; the burn block 948 itself with its consensus-v2 commit certificate (committee epoch 3, ML-DSA-65 signatures from the six-validator committee, quorum 5); and the burn receipt with its Merkle path plus the withdrawal packet. Captured independently on validators 0 and 3; identical bytes (sha256 e4d5202be88f8411671c9a97228f7f307aa3b2078f96a0806bab673c6fd5975a).
evidence · egress/egress-witness.json · egress/egress-negative-suite.json (20 cases, 20 rejected, 0 accepted) · egress/egress-audit.log
The pfusdc-egress guest verifies every ML-DSA-65 certificate in the ancestry and on the burn block 948, the burn's inclusion, and the withdrawal packet, and commits the recipient, amount, withdrawal and burn commitments, and packet digest as public values.
evidence · egress/proof/proof-report.json · execute-report.json · proof-calldata.bin · public-values.bin · cuda-prover.log
withdrawWithProof(publicValues, proof): the finality verifier at 0x1D436908516D15E3c55A936899B47a885e047F27 verified the Groth16 proof through the SP1 v6.1.0 gateway, checked the checkpoint lineage from height 931, and consumed the proof nullifier, withdrawal-id and burn-tx commitments; the vault transferred the amount to the recipient encoded in the proof and asserted both balance deltas exactly. Re-submitting the same proof reverts with ProofAlreadyConsumed(nullifier) (selector 0xfab54c75).
evidence · egress/withdraw.receipt.json · withdraw.balances.txt · withdraw.replay.txt · withdraw.preflight.txt
| Object | Address / identity | Binding |
|---|---|---|
| SP1 v6.1.0 Groth16 verifier | 0xd3b199D0C643dab3F28E5C97D0067763e28187b2 | VERIFIER_HASH 0x4388a21c687fdd5f218d7e3d13190cac4c5355818d3605fd5fb811df468ee696; runtime code hash 0xc26a6452cb4fb09bc555e9ba44384da0267da540ec8700a87f8f4801520b2fa1 matches pinned sp1-contracts v6.1.0 |
| Arc-local SP1 gateway | 0x532D3a8035b87646a92245eCaa01e6602a13654e | route 0x4388a21c → 0xd3b199D0…, not frozen; owner() = 0xdB9b78C87F76054b204188109b35cE4614d03814 (deployer EOA). The finality verifier's immutable sp1Verifier points here. Owner powers and mitigation in §04. |
| One-shot deployment factory | 0xC4932b2d2E2E7A97c90c1595a13A5913C70cab81 | CREATE nonces 1 and 2 predict anchor and vault; no setter, no upgrade path |
| PFTL finality verifier | 0x1D436908516D15E3c55A936899B47a885e047F27 | no owner function; egress vkey 0x0036cbe7d36bbfe1118a3c544eeba74f3791d2a19bf5ec59b972f72d36416852; route epoch 9; checkpoint block id 8e3639ee748255636d09adb8b3fe70f20f8ae0aa9388b024eaffb98b7a94f3c7c4b0e5710f0d8c2479b08d9bc8665c4b (height 931); committee root ca8da61a23bf9d1d1e9ddf57edfe8ee7e26a33ac04c8ff6bee44c3e419083e03b1ecfd3e3299db3f9c6a8b0f5471f6c4 |
| Ingress anchor | 0x92390D3a2102CB74E4746c05B4d91F61093475D0 | direct mode; pinned vault; route binding 0xd9e0cd409c5d1e118d65c78ee059adcbba937616353e9675e350d52ee8d498b2 |
| Vault | 0x160307f3EfeAd79B6A3629c4b8D90E8301fC250F | token 0x3600000000000000000000000000000000000000 (Arc USDC); runtime code hash 0x8d15bb9dc20c416db72c724eeeb725b8b395cb850a889cde130645dcc752764e equals the immutable pinned in the verifier; owner() = 0x0995876e5A97C036C0fDa8846F8F47B57B6d2BFc (pause only) |
| Arc ingress guest | vkey 0x0050a8b0daed2fa75f44d4102b42204c34668a03d311cb727cc6ca3f8df5cf16 | ELF sha256 7660fcc58677cfec433a5f2337b9fe46fa96374a1e3c4bd6fba625b982d881bc; two clean CI rebuilds byte-identical (GitHub Actions run 33586405990) |
| PFTL egress guest | vkey 0x0036cbe7d36bbfe1118a3c544eeba74f3791d2a19bf5ec59b972f72d36416852 | ELF sha256 8b0f266a035a432ef3c0e4233672d8a15b913cefd4c85ff07f91f008601bb744; same CI rebuild |
| PFTL release | arc-current-v2-a699d560 (git a699d560a9275ce769d25e5196d819a069a7285b) · binary sha256 7f4288f3b5182933d253c19661fd563d85c5ca94764497d727b37d6f991f9c0b | signed manifest, verified by every validator's unit at start; six-clone gate passed before rollout |
Trust is Arc's own consensus: a ≥ ⅔ voting-power quorum of the validator set proven from registry storage at the exact block. A two-thirds collusion of that permissioned cohort could fabricate an ingress fact. Nothing else can: no relayer, no RPC provider (a dishonest provider can only withhold), no operator key.
The registry proof is mandatory even when the set does not change; the guest fails closed without it.
Trust is PostFiat consensus soundness (ML-DSA-65 committee certificates, quorum 5 of 6 on this devnet) plus SP1/Groth16 soundness. The Arc verifier holds a checkpoint that advances only by proof.
Replay is excluded twice: the withdrawal-id commitment and the burn-tx commitment are both consumed on first use.
Vault setPaused, held by EOA 0x0995876e5A97C036C0fDa8846F8F47B57B6d2BFc. Liveness only: pause halts deposits and withdrawals; it cannot move funds, alter the verifier, or admit a proof.
SP1 gateway owner(), EOA 0xdB9b78C87F76054b204188109b35cE4614d03814. This is the finding we want reviewed first. The gateway's addRoute reverts RouteAlreadyExists for selector 0x4388a21c, so the owner cannot swap the Groth16 verifier behind today's proofs. The owner can, however, (i) freezeRoute(0x4388a21c), which halts egress (liveness), and (ii) register a route for a different selector to any contract exposing VERIFIER_HASH(). Because PFTLFinalityVerifierV1 forwards proofBytes to the gateway without pinning the four-byte selector, a proof prefixed with such a selector bypasses the Groth16 check. On this testnet that key is therefore a soundness assumption on egress, not only a liveness one. Ingress is unaffected: the PostFiat-side verifier is in-node and governance-pinned.
Reproduced, not just asserted. Four tests in PFUSDCTier4.t.sol deploy the unmodified verifier behind a gateway replica with the exact Arc topology and show: the registered 0x4388a21c route cannot be replaced (RouteAlreadyExists); a forged proof under 0x4388a21c is rejected; freezeRoute halts egress; and, the PoC, a route added under selector 0xdeadbeef lets a bare four-byte "proof" release funds. forge test --root crates/ethereum-contracts --match-contract PFUSDCTier4Test --match-test Gateway.
Mitigation, planned and not yet deployed: the finality verifier binds its immutable sp1Verifier directly to the SP1 v6.1.0 Groth16 verifier at 0xd3b199D0… and rejects any proofBytes whose selector is not 0x4388a21c; the gateway leaves the trust base entirely. The contracts here are immutable, so this is a redeploy of the pair (factory → anchor + vault + verifier), which is why it is scheduled with the audit rather than patched in place. Until then the precise statement is: egress soundness = PostFiat consensus + SP1/Groth16 + non-misuse of the gateway owner key.
Deposits use Arc's transparent path because the proof opens the deposit log.
Everything is open source at github.com/postfiatorg/postfiatl1v2, branch integrate/arc-tier4-current-v2-20260901. Evidence and source links on this page are pinned to commit 1c136a17 so they cannot drift; the evidence directory is docs/evidence/arc-mvp-20260828/devnet-20260902 (49 files). The claim-classification packet maps every statement in the grant proposal to verified / demonstrated / implemented / planned / aspirational, and the audit scope lists the components, threat model, and deliverables we are requesting. Contracts: crates/ethereum-contracts/src. Guests: programs/pfusdc-arc-ingress, programs/pfusdc-egress. Prover and audit tool: tools/pfusdc-tier4-prover.
# conformance against pinned arc-node source (certificates, receipts, negatives)
cargo test -p arc-conformance --locked
# vault, anchor, factory, finality verifier (22 tests, mock SP1 verifier; 4 of them are the §04 gateway PoC)
forge test --root crates/ethereum-contracts --match-contract PFUSDCTier4Test -vv
# re-run both negative suites against today's witnesses
pfusdc-tier4-prover arc-ingress-audit --witness witness.v2.json --output out.json
pfusdc-tier4-prover egress-audit --witness egress-witness.json --output out.json
# verify today's Groth16 proofs against the pinned vkeys
pfusdc-tier4-prover arc-ingress --witness witness.v2.json --output-dir ./x # executes; --prove to reproduceWhat this page does not claim: