Use a window, not a counter
The obvious design islast_seq with a seq > last_seq check. It is wrong, and it fails on the common case rather than an edge one.
Nelo keeps a 128-slot sliding bitmap instead — the same primitive IPsec uses for anti-replay. Sixteen bytes.
The rules
1
Reject below the window
seq < seq_base → SequenceTooOld. It has fallen out of the window and can no longer be redeemed.2
Reject above the window
seq >= seq_base + 128 → SequenceTooFarAhead. The payer is too far ahead of what has settled.3
Reject an already-set bit
The sequence has been redeemed →
SequenceAlreadyRedeemed. This is where a double-spend dies.4
Otherwise set it, and advance
Set the bit, then advance
seq_base while the low bit is set — sliding the window forward over the contiguous settled prefix.The conflict freeze
A replay is one thing. Payer fraud is another, and it has a different signature: two different vouchers carrying the same sequence, both validly signed by the enrolled device. An honest secure element never produces that pair. So producing it is proof, and the proof is permissionless — anyone holding both vouchers can submit them:What it refuses to treat as a conflict
The same voucher submitted twice
The same voucher submitted twice
That is a replay, not payer fraud — and replaying a voucher you legitimately hold would otherwise be a denial-of-service against the payer.
NotAConflict.Two vouchers at different sequences
Two vouchers at different sequences
Ordinary trading.
NotSameSequence.A forged signature
A forged signature
The precompile introspection catches it before the conflict logic runs.
What the freeze does
Redemption stays open deliberately. The freeze blocks the payer’s exit, not the payees — merchants holding good vouchers must still claim against locked collateral. Freezing them out would punish the victims.
Staking is blocked in both directions: stake is first-loss capital against exactly the event that froze the vault, and capital that can leave after the loss is not collateral.
The gap this does not close
That shortfall is precisely what the platform guarantee covers, and what the reserve model prices:report_conflict should also accept an inconsistent-remaining_after pair as proof is an open design decision. It would shorten the window in which a compromised device keeps trading. It is flagged rather than taken, because it changes the fraud model rather than fixing a bug.