Evidence
Nobody has ever audited a Kaspa covenant — asked in the core developer channels, and the answer was nobody. There is no certificate to buy, so the only honest position is to publish the evidence that does exist and name precisely what it is not. This page is that, including the part where six vulnerabilities were found in here.
Three layers, and one that is missing
A rule can be right in the specification and wrong in the bytecode. Bytecode can be right in an engine and unrelayable on a real network. So the same claim is made three times, in three places that can disagree with each other.
Layer 1 · the rules
606 tests across nine packages. Every verdict any package gives
comes from @warda_protocol/core, so a second copy of the rules cannot drift
from the first. This proves what the limits mean. It proves nothing about the
script that enforces them.
Layer 2 · the bytecode
14 probes run the compiled covenant through
TxScriptEngine from rusty-kaspa — the same engine a node uses, not a
model of it. Eight the covenant must refuse and six it must accept, each
refusal beside its nearest legitimate twin, all green against the deployed version. A
second suite — 33 tests flipping one field at a time from
an accepted baseline — is green too, as of 15 September 2026.
Layer 3 · the chain
Grants created, spent, delegated, settled, revoked and reclaimed on testnet-10, with transaction ids published on the agent pages. This catches what the engine alone cannot: fees, storage mass, relay policy, and every assumption about the network that was never written down.
Headroom · measured, not estimated
Read out of the running engine at the widest allowlist depth: 8,680 bytes of a 1,000,000-byte ceiling, 16 compute units of 65,535, and 118 stack slots of 244. Stack is the tightest at 2.07× and it is flat — the Merkle fold reuses slots, so a 65,536-payee allowlist costs the same stack as a four-payee one.
Layer 4 · formal methods
Absent. The state machine — budget, per-spend cap, epoch headroom, allowlist membership, succession arithmetic — has not been modelled in an SMT solver. Testing samples that space; a solver covers it. Worth saying plainly, because a page like this is where a reader would assume otherwise.
One of these two suites was dead, and writing this page is what found it.
Running covenant/harness to collect figures for the panel above returned
constructor argument count mismatch on 32 of its 33 tests. Its argument list
still announced itself as “v2 constructor order” in a comment nobody
had re-read for two covenant versions. It is repaired and green now — five separate
defects, four of them loud.
The fifth was not loud, and it is the one worth knowing. The successor's accounting was being written into three constructor slots that v4 had repurposed, so every successor compiled successfully at a wrong geometry, its address never matched the continuation the covenant derives, and everything was refused. The three baselines failed and every refusal passed — for reasons unrelated to the rule each was testing. A suite reporting 18 of 33 green would have looked healthy to anyone who did not read which 18.
Nothing was ever wrong on chain; every live grant was built by the tool the probes check. What decayed was the evidence, silently. That is the failure this page exists to talk about, so it stays written down: a figure nobody re-derives is a figure that decays, and a test suite nobody runs is the same thing wearing a green badge. Both suites now run on every push and weekly, which is the actual fix — the decay happened across two covenant versions with nobody touching the covenant in between.
Layer 2, in detail
The script engine collapses every failed require into one opaque error. It never
says which rule rejected the transaction. So a test that asserts this was refused,
against a transaction that never passed in the first place, proves only that something
somewhere was wrong — a malformed signature script included. It would pass just as
happily against a covenant with no rules in it at all.
The suite closes that gap with flip tests. One transaction is established
that the engine returns Ok(()) for. Every other test starts from that exact
transaction and changes one field. Because the baseline is accepted, a
rejection can only have been caused by the field that changed.
The probe suite is built that way: fourteen transactions, each refusal paired with the nearest thing that must succeed. Run against the deployed covenant, every one behaves as required.
| Probe — against the deployed covenant | Required | Engine |
|---|---|---|
| spend an epoch's allowance twice | refuse | refused |
| rewind to an epoch already consumed | refuse | refused |
| move forward to a fresh epoch | accept | accepted |
| spend within the same epoch's headroom | accept | accepted |
| spend after expiry | refuse | refused |
| delegate a narrowed child | accept | accepted |
| narrowed child pays inside its set | accept | accepted |
| narrowed child pays outside its set | refuse | refused |
| reabsorb a child honestly | accept | accepted |
| settle a child to the wrong party | refuse | refused |
| reabsorb a child with live children of its own | refuse | refused |
| reabsorb out of order | refuse | refused |
| revoke honestly | accept | accepted |
| revoke, burning the balance | refuse | refused |
The second suite applies the same idea more narrowly, flipping one field at a time from a single accepted baseline — and the baseline is the load-bearing part, which is why its failure invalidated everything while looking like a partial pass.
| From the accepted baseline, change one thing | Engine |
|---|---|
| nothing — the baseline itself | accepted |
| spend more than the per-spend cap | rejected |
| pay someone absent from the allowlist | rejected |
| name one payee in the proof, send the money elsewhere | rejected |
| spend the money, do not record it in the successor | rejected |
| move the reserve independently of the spend | rejected |
| agent raises its own per-spend cap | rejected |
| agent widens its own allowlist | rejected |
| agent extends its own expiry | rejected |
| agent inflates its own budget | rejected |
| child delegated a wider allowlist than its parent | rejected |
| child outliving its parent | rejected |
| child born already spent | rejected |
| delegation depth extended | rejected |
| delegate without reserving, under-reserving, over-reserving | rejected |
The accepted baseline is the load-bearing part. A suite made only of refusals would be passed by a covenant that refuses everything, which is useless and secure.
Both tables are current, against the deployed covenant.
The part a sales sheet leaves out
Every one was found by building an attack and handing it to the real script engine. That is worth knowing about the ones not yet found: review has a poor record here, so the absence of a finding from reading this covenant is weak evidence about it.
The first three share a shape — the covenant checked what something was, and not how much of it there was. The fifth is the first one again, verbatim, in a code path written after it had been fixed elsewhere: a value check with no destination check. A fix is not a lesson until it is applied to the code written next.
All six are written up in full — mechanism, fingerprint of the affected version, and the fix — in GUARANTEES.md, alongside a section on what the chain cannot enforce.
Limits, not omissions
Each of these is structural rather than a feature waiting to be built, and each is the kind of thing a reader would otherwise assume the other way round.
revoke is the only
immediate stop, and it needs somebody online.What none of this is
No third party has reviewed this covenant. Warda has run on testnet-10 only and has never held real money. A quoted price for external review exists and has not been paid. Nothing on this page should be read as a substitute for that, and the evidence above is offered because it is the most that can be assembled without it — not because it is equivalent.
One distinction worth keeping, because it is where this kind of claim usually goes wrong: a proof about a model of a covenant is not a proof about the covenant that gets deployed. Layer 2 above exists precisely to avoid that gap — it runs the compiled artifact, not a description of it. If layer 4 is ever added, it will be stated as what it is: a proof about a model, sitting beside a proof about the bytecode, which are different claims.
Don't take our word for it
The bytecode layer, in the node's own engine. First build fetches rusty-kaspa and takes a few minutes; after that it is sub-second.
git clone https://github.com/ArtyKOMarkets/warda cd warda/covenant/harness cargo test --test spend -- --nocapture
The adversarial probes — transactions the covenant must refuse, each beside its nearest legitimate twin, because a covenant that refuses everything would pass a suite made only of refusals. Two of them are assembled without the SDK, because an attacker does not use our SDK.
cd warda/sdk && node --experimental-strip-types tools/probes.ts
And the grant behind every claim on this site is live. Its agent's private key is published in full.