NFT sale-lock protocol

This commit is contained in:
2026-10-20 15:38:37 -04:00
parent c254c4c537
commit 84a505c4a5
5 changed files with 550 additions and 0 deletions
+2
View File
@@ -80,6 +80,8 @@ Acceptance: indexed-vs-full-replay differential tests; adversarial reorg/crash/c
Blocked on P0 ADR; use P1 amount types and P2 durable events. Consensus work is a separately reviewed change.
**Candidate increment, 2026-10-20:** [ADR 004 — sale locks](sale-lock-candidate.md) now specifies and implements an offline deterministic signed lock/completion/cancel/expiry model, exact height boundaries, native transfer/trade lock guards, replay-resistant sequences, state commitments and a non-posting financial observation policy. A pure bridge checks terms drafted from actual sandbox reservations. Final build and 162 offline tests passed. Active v6 consensus still rejects the candidate and no host signer or balance-posting integration is enabled. Independent review, policy approval, versioned network activation, actual PoW/peer/index integration and PostgreSQL terminal workflow remain open; M3 is not accepted.
- If a reviewed trustless design is selected: implement its actual cross-chain validity/evidence rules, compatible cryptographic primitives, both-party failure/refund paths and commission semantics. Run maintained reference vectors, negative tests, fuzz/property tests and specialist audit before claiming atomicity.
- If a custodial/attested fallback is explicitly approved: define chain-enforced NFT lock, order identity, authorized releaser, cancellation/refund/expiry and stale-signature rules. Separate host control keys from seller wallet and processor payout keys. Reserve/refund procedures must not permit both buyer refund and later seller delivery/release. Describe who can steal, censor or lock assets and how recovery is authorized.
- Completed-sale inclusion must require the approved evidence that payment is sufficiently confirmed. A peer must not insert a completed sale merely because its local database says paid. Define threshold/policy version, proof/attestation authorization and deterministic validation; no external HTTP calls in consensus validation.
+76
View File
@@ -0,0 +1,76 @@
# ADR 004 — Custodial NFT sale-lock candidate
Status: **offline executable candidate; NOT activated or independently reviewed**. Evidence: 2026-10-20. Related: [production plan](production-plan.md), [custodial model](custodial-market-design.md), [PostgreSQL storage](postgres-financial-storage.md).
## Purpose and activation boundary
Specify the missing asset-control rules before enabling real custodial purchases. An ordinary signed NFT transfer can survive a website refund deadline; balance reservations alone cannot prevent that late delivery. This candidate couples sale authorization to explicit canonical locks and terminal outcomes.
[Signed schemas/messages](../shared/sale-candidate.ts) and [deterministic state machine](../server/testnet/sale-candidate.ts) are deliberately separate from active consensus. The existing transaction schema rejects all candidate transactions. The candidate domain derives from the existing chain identity, explicit policy and candidate version; it is an offline signature domain, **not a reviewed network manifest/genesis**. No current chain identity, miner, peer endpoint, database balance or custody signing capability is changed.
The pure `termsForCustodyOrder()` bridge drafts terms from an actual awaiting-lock reservation and checks exact seller/author/host allocations. It does not sign, persist, relay or debit anything. Tests exercise this bridge against the current custody engine. Public custody orders continue stopping at `awaiting-lock`.
## Signed authorization
Terms bind candidate protocol, network, chain/policy digest, custodian/order IDs, exact card/sequence, seller, buyer, host signing identity, native-XMR gross, hosting rate, fixed author policy, lock-admission deadline and final expiry height. Independent downward commission rounding remains unchanged. No production threshold is invented: maturity/depth/window limits are explicit policy inputs committed by the candidate digest.
- **Lock:** both seller and buyer sign the complete terms. Host signing identity must differ from both trading identities. Distinct keys are role separation, not proof of independent humans/operators.
- **Completion:** the terms' exact host signs the sale ID plus a reservation digest, attesting that sufficiently confirmed/unlocked funds were reserved under the approved policy. Host cannot select another card/recipient/price. Validators would verify the host's authorization, **not Monero truth**. The reservation digest is an accounting commitment, not a cryptographic proof of backing or solvency.
- **Cancellation:** host and buyer sign the sale ID. Neither can unilaterally cancel early. Seller already consented to this branch in signed lock terms; seller retains card ownership on cancellation.
- **Expiry:** permissionless explicit transaction, without a private key, once the canonical expiry height is reached. Website clocks/RPC errors never cause implicit release.
## State transitions and exact heights
Let $h$ be the block executing a transaction, $L$ the accepted lock height, $A$ the last lock-admission height, $E$ the expiry height, $d_L$ the lock-depth policy and $d_O$ the outcome-depth policy.
| Transition | Validity / result |
| --- | --- |
| Unlocked → locked | Both signatures, mature current seller ownership/sequence, no existing lock, unused custodian/order; $h\le A$, $A+d_L<E$, and bounded remaining window. Ownership unchanged; sequence advances once. |
| Locked → completed | Exact host signature, $h-L\ge d_L$ and $h<E$. Ownership moves only to the terms' buyer; sequence advances again. |
| Locked → cancelled | Host+buyer signatures and $h<E$. Ownership remains seller; sequence advances again. |
| Locked → expired | Explicit permissionless transaction with $h\ge E$. Ownership remains seller; sequence advances again. |
| Free transfer/trade | Every affected NFT must be unlocked, correctly owned/sequenced, mature and signed. A locked card in any trade leg rejects the entire transition. |
Only one terminal branch can succeed on a canonical history. An invalid transaction rejects the entire candidate block, not just that transaction; two conflicting outcomes in one block therefore reject the block. Block assembly/mempool policy will eventually need to filter these conflicts before proposing a block.
Lock and terminal transitions both consume sequence numbers, invalidating ordinary transfers signed before/during a lock. Admission requires room for **two** increments so terminal release cannot overflow. Expired/cancelled order-use tombstones prevent a fresh signature from recycling the same custodian/order ID. A new order can relist at the fresh sequence. A withheld lock authorization cannot be accepted after $A$, closing the never-submitted-lock refund hazard.
Candidate state commitment includes NFT entries, active locks, full signed-term messages, lock/terminal heights, reservation commitments and permanent order-use tombstones. Ownership alone would be an insufficient state commitment. Snapshot restore/replay is deterministic. This uses full cloned state and tombstone retention for an auditable model, **not a production-optimized overlay or storage design**.
## Financial observation and reorg discipline
`saleDecision()` is a pure non-posting recommendation based on a caller-supplied canonical candidate state:
- Keep the gross reserved while a lock could still be admitted, while it is active, or before a terminal outcome reaches $h-\text{outcomeHeight}+1\ge d_O$.
- Recommend seller/commission settlement only after mature canonical completion.
- Recommend buyer release only after mature canonical cancellation/expiry. If no lock was ever canonical, require $h-A\ge d_O$ before recommending release.
- Already posted outcomes never post twice. A conflicting or immature/missing previously posted outcome requires `review`, not another settlement/refund. Existing review remains review pending evidence-backed resolution.
This does not independently prove the supplied canonical state or observe intermediate forks. Integration must consume the existing durable connect/disconnect stream and retain a review hold even if an outcome reappears before polling. Deep reorgs can invalidate previously mature results and external XMR withdrawals cannot be undone. Loss/reserve/compensation procedures remain mandatory.
An already mature terminal record remains usable evidence if the NFT is later gifted/traded; current owner alone must not erase completed sale evidence. Conversely, a gift is not payment evidence. Cancel/expiry can be followed by a freshly signed free transfer at the new sequence; this is ordinary owner authority, not resurrection of the cancelled sale.
## Failure behavior and trust
| Failure | Candidate behavior / remaining risk |
| --- | --- |
| Seller lists elsewhere or withholds lock | Competing canonical transfer/lock wins by ordinary sequence rules; funds remain reserved until safe cancellation/non-admission boundary. No cross-host database claim is treated as a chain lock. |
| Buyer disappears after signing lock | Host may complete only to the signed buyer; otherwise eventual expiry restores seller control. Financial operator still owes customer funds. |
| Host disappears or loses key | Anyone can submit expiry after the height boundary. A stalled chain delays expiry; this does not recover custodial XMR keys. |
| Host lies about funding | Valid host signature can authorize delivery despite absent reserves. This is explicit custody/attestation trust; the protocol cannot enforce the honesty of an internal balance claim. |
| Cancel and complete race | Only canonical sequence/lock state chooses a winner; no refund before terminal evidence matures. Mempool order is not settlement. |
| Reorg after posting or withdrawal | Review/quarantine, never imaginary reversal of XMR. Operator loss handling and funded limits needed. |
| Host key compromise/rotation | Existing locks bind an exact key. Rotation/revocation and emergency authority are NOT implemented; do not silently redirect existing authority. |
## Evidence and next release gates
TypeScript/Vite Docker build passed. **162 offline tests passed, zero failures/skips**, including 13 candidate tests in [tests/sale-candidate.test.ts](../tests/sale-candidate.test.ts). Coverage includes current-consensus rejection, field/signature tampering, exact height boundaries, sequence exhaustion, native transfer/trade lock bypass, competing-market/reused-order locks, atomic conflicting-outcome rejection, reorg financial decisions, deterministic snapshot restore, state commitments, actual custody-order drafting and enumerated adversarial histories through expiry. One initial enumeration-count expectation was corrected by extending its horizon through the expiry boundary; no protocol guarantee was inferred from an arbitrary test count.
**This does not close M3.** Required next steps:
1. Independent protocol/consensus review and operator approval of authority, cancellation and numeric policy parameters; final specification and test vectors.
2. Versioned network/manifest and explicit migration/activation strategy. No enabling the candidate under the existing v6 chain identity.
3. Integrate new transaction encodings/state commitments with mint/transfer/trade validation, canonical SQL undo/indexes/events, actual RandomX blocks, peer sync, restart and multi-host fork/partition tests. The current model supplies no PoW, issuance or fork-choice implementation.
4. Durable PostgreSQL sale witness/terms storage, financially reserved host-signing workflow, outbox fencing, canonical observer/inbox, atomic terminal postings and reorg holds. Existing sandbox signatures do not authorize the new sale protocol. No signing before final exact order consent.
5. Publication/review UI, key management/rotation, privacy and public-price disclosure, window/griefing/storage limits and operational recovery. Public terms would reveal sale linkage and amounts; privacy acceptance is a product decision.
6. Monero confirmation/unlock observation, real treasury controls, coordinated financial/node recovery and independently reviewed production release gates. No live wallet, funds or credentials were used in this increment.