aldabra/audits/2026-05-09-escrow-e2e.md
Kayos 93f11ecef0 docs: rewrite for users — drop internal infra context
README + supporting docs were written for ourselves (deployment paths,
internal product comparisons, internal task lists, build pipeline
artifacts) instead of for users of the software. This pass refocuses
them on what the software is, how to install, configure, and use it.

- README.md: full rewrite. New shape — What it does / Architecture /
  Build / Run / Configuration / MCP tools / Security model / Status /
  License / Dependencies. Drops the internal "why we built it"
  narrative, drops phase-status claims that drifted stale, drops
  internal deployment paths.
- ROADMAP.md: deleted. Was an internal task-list with [x]/[ ] items
  showing incremental private development. The README's Status
  section now communicates what's actually shipped.
- docs/architecture.md: scrub cross-project comparisons referencing
  unrelated internal Sulkta codebases.
- aiken-escrow/README.md: drop reference to a non-existent spec file;
  rewrite the Status checklist to reflect what's actually done
  rather than what was open at the time of writing.
- audits/2026-05-09-escrow-e2e.md: scrub internal image names +
  container paths; the audit findings (chain hashes, validator hash,
  what each tx proved) are the public-useful part and stay.
- audits/2026-05-09-escrow-internal-audit.md: drop references to
  feature-flag-gated branches that no longer exist.
- Dockerfile: drop the dead `escrow_wip surface` phrase from comments.
- Cargo.toml: drop the cross-project comparison comment that named
  an unrelated internal service.
- crates/aldabra-{core,dao}: scrub internal preprod-test naming from
  source comments — same technical content, generic phrasing.
2026-05-10 20:56:25 -07:00

5.9 KiB

Escrow v1 — preprod E2E results, 2026-05-09

End-to-end execution of the escrow surface on Cardano preprod, post- internal-audit (validator hash a8081acef26935d9b5f44b92052178e17301b6d6e6808c91c5b56f5d).

Setup

  • Validator script address: addr_test1wz5qsxkw7f5ntkd4739eypfp0rshxqdk6mngpry3ck6k7hgcgah4g
  • Validator size: 8414 hex chars (~4 KB binary)
  • party_a: 4cd61bd67ed72c1cec160bf7de6103c6bddb397da6a500dc4ff805f8 (addr_test1qpxdvx7k...)
  • party_b / recipient: fc7533eb154263a154fb40f1acc4fc40e847cac98fe45da4e948c4f6 (addr_test1qr782vltz...)

Veto path (4 of 6 builders)

Step tx hash What it proves
Open 0972963e8eb46597b37dfa7e1fa15c97fea4e39d55b29c54f723ba1b155bc7cb escrow_open builder; deposits=[(preprod, 5 ADA)] at script
Deposit (bob) 0519cb7dbb8c2c1632db5baee90d5440d2905c151f910a085c78d9050c2d6175 first Plutus V3 spend on chain. Validator's Deposit branch ran — including the new HIGH-1 value_geq_value(new_value, in_value) check. Continuing-output deposits-list update verified via cbor.serialise byte-equality.
Agree f6664079d96c453e1a6333c33c65744984953c474e57cdb331e78ad3e5429cc7 both-party multi-sig (driver=preprod, co-signer=bob). State Open→Agreed{at=upper}. validity_upper ≤ open_deadline_ms enforced.
Veto 14a4be9f233ab02e518356e5963d30517986984f6fd3520151c924057db9c661 HIGH-2 fix active: value_eq(deposits_to_value(d.deposits), in_value). Multi-output refund (5 ADA each to enterprise addresses of party_a + party_b).

Total tADA cycled through validator: 10 ADA (5 from each party). Refunded to enterprise addresses of both contributors.

Refund-timeout path (5th builder)

Step tx hash What it proves
Open (short deadline) 0669ef61b8695bedcb4eb4c38fda2bd66c68c9384b57839329a01a745db75305 escrow_open with open_deadline = tip_ms + 5 min, initial_lovelace=2 ADA
(wait) tip elapsed past open_deadline (~165s past) before next step
Refund-timeout 41590ac6ed069586e650da58858436cfe6be51a865069a7a4b40f795dfcdbff9 escrow_refund_timeout builder; lower > open_deadline_ms strict-> time gate proven; HIGH-2 deposits-sum invariant proven on a single-deposit escrow; single-output refund (2 ADA back to party_a's enterprise address).

Settle path (6th builder)

Step tx hash What it proves
Open a878900c09022381f332ca2cea1b4624202ebdbd6f3a83fd9de07475bb98bd6b escrow_open with deadline=tip+1h, lock=30min, recipient=bob
Deposit (bob) ef8910101e88b63abb28ec9b511616e3465075b8d34d5eeb9703efe1876a62bf bob adds 5 ADA, deposits=[(preprod,5),(bob,5)], second time the V3 validator's Deposit branch ran on chain
Agree bbfd57c3acb68ddb76d6b92c0dbe8ba9cb21ca88ad6370d19f00822c3b69d655 both-party multi-sig (driver=preprod, co-signer=bob); state Open→Agreed{at=1778381375000}
(wait) tip elapsed past agreed_at + lock_period_ms (~1778383175000) — actual tip at settle: 1778383733 = 558s past
Settle 4b52312ce264dba74a6fde6c2ccb597696022c8919470f23670e2746db10d1ff escrow_settle builder; lower > agreed_at + lock_period strict-> time gate proven; MED-2/3 fix held a second time under different timing; recipient (bob) gets 10 ADA at his enterprise address; no party signer required (preprod drove as fee-payer only)

All 6 builders proven on chain.

What this E2E proved on chain

  • Plutus V3 validator deploys at the bech32-derived script address per validator hash + testnet header byte.
  • Inline V3 validator CBOR (~8 KB) attaches as the spending script witness; the MCP validator_script_path arg correctly resolves it from whatever path the operator deploys the blueprint to.
  • Conway-era inline-datum + script-output min-utxo (2 ADA floor) matches MED-5 fix expectations.
  • HIGH-1 fix (value_geq_value(new_value, in_value) on Deposit) ran on chain — validator accepted a clean deposit, no token drain attempt was made (v1 ADA-only).
  • HIGH-2 fix (value_eq(deposits_to_value(d.deposits), in_value) on Veto and Refund) ran on chain — the new helper functions deposits_to_value and value_eq execute within the per-tx UPLC budget for a 1-deposit and a 2-deposit escrow.
  • Multi-party signing flow works end-to-end: driver signs partial → co-signer signs partial → submit. wallet_sign_partial appends a fresh VKeyWitness without disturbing existing ones.
  • Slot↔ms math at the strict-> validator boundary (Refund branch) didn't burn fees — the MED-2/3 fix to derive validity_lower_ms from slot_to_posix_ms(network, slot) instead of Koios block_time*1000 held up under a real preprod chain race.

v2 / future TODO (still open)

  • Settle path on-chain validation.
  • External third-party audit (TxPipe / Anastasia Labs / MLabs / Tweag).
  • Reference-script deployment for the validator (~8 KB inline per spend tx is non-trivial; ref-script optimization halves tx size).
  • aiken/fuzz property tests in escrow.ak covering each redeemer branch's invariant violations.
  • Multi-asset escrows (the v2 frontier — needs the foldr-canonicality golden test for Pairs<> serialisation, plus widening the spend builders to take escrow_in_assets and emit non-empty asset entries on every output).
  • Read-side MCP tools (escrow_show, escrow_list).

Wallet movement summary

party_a: pre-flow ~9844 tADA → post-flow ≈ 9844 - 100 (sent to party_b) - ~5 (fees + topups) ≈ ~9739 tADA. party_b: pre-flow 0 → post-funding 100 → minus ~5 ADA fee/lockup → ≈ 95 tADA at end of flow.

Refunds landed at enterprise (null-stake) addresses, NOT the base addresses of the wallets — this is by validator design (pkh_to_base_address hard-codes stake_credential: None). The funds are spendable via the same payment key; just a different bech32 prefix.