What happened
Mint without burn.
SPV (Simplified Payment Verification) is a class of proof that lets one chain verify a transaction occurred on another chain without downloading the full source-chain history. Cross-chain bridges that move native tokens between chains use SPV proofs as the authorization primitive for minting: the source chain records a burn; the burn event is encoded into an SPV proof; the destination chain validates the proof and mints equivalent tokens.
The conservation property this flow should enforce is:
For every token minted on the destination, a valid proof of a corresponding burn on the source must have been accepted first.
The Syscoin bridge’s SPV validation did not hold this property under malformed inputs. A proof that should have failed validation (because the proof fields did not correspond to a real burn event on the source chain) was instead accepted. The destination contract minted approximately 5 billion SYS without a matching burn. Supply on the destination side diverged from supply on the source side. The bridge’s conservation invariant was broken.
The Nomad bridge exploit in August 2022 (~$190M) is the closest
prior-art incident in this class. Nomad’s replicas stored an
acceptableRoot hash used to validate bridge messages.
An initialization error set acceptableRoot to
0x00. Any message with a proof rooted at
0x00, including messages with a zeroed-out or empty
proof field, passed validation. The contract then processed those
messages as legitimate, allowing anyone to forge claims and drain
the bridge. Same pattern: proof validation admits inputs that
should be rejected → tokens minted or transferred without
genuine backing → conservation broken.
The specific proof field that was malformed in Syscoin’s case, and the code path that admitted it, is documented in the Halborn postmortem. This writeup stays on the invariant class.
The pattern
Supply divergence via proof validation bypass.
Cross-chain bridges in the burn-then-mint family have a core conservation invariant that spans both sides of the bridge:
total_minted_on_destination ≤ total_burned_on_source (for valid burns)
When proof validation is the gate that enforces “this burn was valid,” any bypass of proof validation is a bypass of the conservation invariant. The bridge’s correctness depends on the proof gate being sound, not merely present.
This is the pattern the conservation invariant class
is built to catch: a state change (minting) that should require an
authorizing condition (valid burn proof) proceeding without that
condition being genuinely satisfied. The state change here is a
token supply increase on the destination chain without a valid
corresponding commitment on the source chain.
What the invariant asserts
The property and the violating sequence.
The property the bridge should have enforced is:
After any mint operation, the total tokens outstanding on the destination side must not exceed the total tokens proven burned on the source side via proofs that pass a sound validation.
Expressed as a stateful test property:
-
Track a running counter of
valid_burns_provenandtokens_minted. -
After every call to the mint entry point, assert:
tokens_minted ≤ valid_burns_proven. - The attacker’s move (submitting a malformed SPV proof to the mint function) is in the callspace of the stateful test.
-
If the validation function admits malformed proofs, the test
reaches a state where
tokens_minted > valid_burns_proven, and the invariant fires.
The violating sequence is short: craft a malformed proof, call the mint function, observe the counter diverge. A stateful fuzzer that tracks the two counters, without being told the specific malformed proof field, will find this shape by exploring the mint entry point with random proof bytes. Some random byte vectors will trigger the admitted-but-malformed path; the invariant fires when they do.
This is the same shape as the Jito
claim_amount_conservation invariant in our
CI-verified reference suite: total claimed must not exceed total
allocated per merkle root. The merkle root is the authorization
primitive for claims in Jito’s tip distribution; the SPV
proof is the authorization primitive for mints in a bridge. The
invariant structure is the same.
CI-verified coverage
Map to CI-verified reference pairs.
Three artifacts in the CaliperForge toolkit carry a
conservation reference pair verified in CI today. All
are CI-verified per
agents/build_squad_lead/proof_register.md. The
conservation property is rail-agnostic; the Anchor/Solana and
Cairo/Starknet reference pairs are the CI-verified evidence for
the class on those rails.
cf-invariants-anchor (Anchor / Solana),
balance_conservation class
(vault_ref / vault_ref_planted).
The clean variant enforces that the vault’s token supply can
only increase via calls with a valid deposit commitment; total
outstanding cannot exceed total committed. The planted variant
removes the supply cap check, allowing the minted/issued balance
to exceed the committed backing. The conservation invariant then
fires with at least one marker. Clean = 0 markers, planted
≥ 1 marker. CI-verified, commit aee4136. Repo:
github.com/caliperforge/cf-invariants-anchor.
The planted vault_ref drops the commitment check
before incrementing the balance. That is structurally equivalent
to what the Syscoin mint function did when it accepted a proof
without validating it: the branch that should gate the supply
increment was bypassed. The invariant catches both by tracking the
same divergence between “authorized amount” and
“minted amount.”
cf-invariants-jito (Anchor / Solana),
claim_amount_conservation +
no_double_claim classes on the real Jito
tip-distribution program.
claim_amount_conservation asserts that total claimed
across all recipients cannot exceed the merkle root’s total
allocation. no_double_claim asserts that no recipient
can receive more than their single allocation. Both planted twins
remove the accounting check, allowing the invariant to fire.
CI-verified at commit e683c5a. Repo:
github.com/caliperforge/cf-invariants-jito.
The Jito conservation harness ran against a real on-chain program’s accounting logic, not a synthetic example. That is the framing a Syscoin-specific harness would need: instrument the real mint function’s proof-validation and supply-accounting paths → track the two-counter property → plant the validation bypass → show the invariant fires on malformed inputs.
cf-invariants (Cairo / Starknet), 12-class matrix
including conservation. The 12-class reference suite
covers supply conservation across ERC-20, ERC-4626, lending,
staking, and vesting contracts. Each reference has a planted bug
that breaks the relevant conservation property; each compile-twins
to a clean variant via a clean scarb feature; each
asserts planted ≥ 1 AND clean = 0 in
CI. PR #2 squash-merged 2026-06-07 at commit 003e33c,
CI run 27075213962 26/26 green. All twelve references
deployed and source-verified on Starknet Sepolia. Repo:
github.com/caliperforge/cf-invariants.
Developer cookbook (public since 2026-06-11, PR #3 squash-merged
at 7f94b08):
github.com/caliperforge/cf-invariants/tree/main/docs/cookbook.
Harness design
What a Syscoin-specific invariant harness would look like.
The exercise is design rather than a shipped harness. Syscoin runs its own UTXO-based chain with an Ethereum-compatible execution layer (NEVM); the bridge contract lives in that EVM context. A Syscoin-specific conservation harness would target the bridge’s mint entry point in a Foundry or Hardhat test environment, not the Anchor or Cairo rails our CI-verified reference pairs use.
The invariant design is clear regardless of rail:
-
Identify the two counters:
valid_burns_proven(incremented only when SPV proof validation succeeds on a non-malformed proof) andtokens_minted(incremented on every successful mint call). -
Express the property: after every call to the mint entry
point,
tokens_minted ≤ valid_burns_proven. - Test setup: include the mint function in the callspace; include a proof-generation helper that can emit both well-formed and malformed proofs. The stateful test should explore the full input space of the proof fields.
-
Assert after every transition: if
tokens_mintedincremented,valid_burns_provenmust have incremented by the same amount in the same call.
The violating sequence in step 4 is the exploit: mint called with a malformed proof that the validation function admits, counters diverge, invariant fires. A stateful fuzzer reaches this without being told which proof field is malformed; it only needs to know the property and the callspace.
The design above is a reference for teams building conservation harnesses against bridge contracts in the EVM context.
Sources
Primary analysis and CaliperForge artifacts.
- rekt.news writeup (incident coverage, loss figure, exploit summary): rekt.news/syscoin-rekt
- Halborn postmortem (primary technical analysis: proof structure, validation bypass, on-chain evidence): halborn.com/blog/post/explained-the-syscoin-bridge-hack-june-2026
Prior-art reference:
-
Nomad bridge exploit (Aug 2022, ~$190M):
acceptableRootinitialized to0x00, any zeroed proof accepted as valid, messages forged. Immunefi hack analysis: immunefi.com/blog/bug-fix-reviews/hack-analysis-nomad-bridge-august-2022/
CaliperForge artifacts cited:
-
cf-invariants-anchor
, Anchor / Solana,
balance_conservationreference pair, CI-verifiedaee4136. -
cf-invariants-jito
, Anchor / Solana,
claim_amount_conservation+no_double_claimclasses on real Jito tip-distribution, CI-verifiede683c5a. -
cf-invariants
, Cairo / Starknet, 12-class matrix including
conservation, CI-verified
003e33c, run27075213962. -
cf-invariants developer cookbook
, public since 2026-06-11,
7f94b08.