Skip to content

NUT-CTF-settle: Multi-Party Atomic Settlement - #2

Open
joemphilips wants to merge 27 commits into
mainfrom
nut-ctf-settle
Open

NUT-CTF-settle: Multi-Party Atomic Settlement#2
joemphilips wants to merge 27 commits into
mainfrom
nut-ctf-settle

Conversation

@joemphilips

Copy link
Copy Markdown
Owner

This PR adds multi-party settlement for CTF tokens defined in cashubtc#337. It also depends on cashubtc#410.

Add NUT-CTF (Conditional Token Framework), NUT-CTF-split-merge (split/merge operations), and NUT-CTF-numeric (numeric outcome conditions) specs with test vectors, supplementary material, error codes, and README entries.

Squashed from 9 commits after rebasing onto upstream/main to resolve NUT-28/29 filename conflicts (upstream now uses those numbers for P2BK and Batched Mint).
The docs.gnosis.io/conditionaltokens/ URL returns 404. Replace with the official readthedocs site (rendered from the same gnosis/conditional-tokens-contracts repo) and link the contracts repo alongside as a stable fallback. Add Polymarket as a real-world production example of the same model in CTF.md's Terminology section.
Replace POST /v1/ctf/split and /v1/ctf/merge with a single POST /v1/ctf/convert that performs any payoff-preserving rebalance of conditional positions within one condition at one nesting level. Split, merge, recombine, and negative-risk-style conversion all become special cases.

Core rule: for every outcome o in Omega, out(o) == in(o) - F, valid only if in(o) >= F, where F = ceil(sum input_fee_ppk / 1000). The flat fee is retained non-contingently on every outcome (real collateral the mint keeps regardless of the attested outcome), which bounds free operations.

Collateral is the reserved map key '*' (regular keyset at root, parent conditional keyset when nested). Coverage is computed from registered keyset metadata, not request map keys. Canonical collection encoding fixed (enum: announcements[0] outcome index; numeric: [HI,LO]). Partitions must have >=2 elements with no full-set conditional collection. Issuance invariant: every conditional token backed by >= its face in collateral. Adds error codes 13041/13042/13043; updates CTF.md, CTF-numeric.md, error_codes, suppl, and test vectors accordingly.

Design and post-implementation passes adversarially reviewed via Codex (verdict: sound to merge).
Remove partition registration from the CTF specs and describe condition-time keyset creation, managed default policies, root-only convert scope, and updated numeric behavior.
Add NUT-08-style blank outputs and change signatures for condition registration fees so mints retain only the authoritative required fee.
Use a distinct 13047 error for invalid registration fee change outputs and disambiguate the idempotency cross-reference.
Advertise registration fee schedules by collateral unit and default missing schedules to free registration.
Treat registration_fees entries as the allowlist for CTF collateral units and reject non-advertised units instead of defaulting them to free registration.
Closes a partition-based over-redemption gap. HI = floor(amount*hi_payout_ratio), LO = floor(amount*lo_payout_ratio), and the mint retains amount - HI - LO as dust. HI+LO <= amount always, and because floor is subaddible, splitting or merging proofs before redemption can only reduce a holder's payout, never increase it. Trades exact conservation (a full set can redeem slightly under collateral; dust is mint revenue) for partition-invariant solvency.
Extension of both NUT-Exchange (cashubtc#410) and NUT-CTF-split-merge (cashubtc#337).

Delta document specifying only the differences from both parent NUTs.

P0 fixes from adversarial review (gpt-5.6-sol xhigh):

- corrected H_recv and refund domain separators

- explicit inherited (rules 1-7,9,11) vs replaced (rules 8,10)

- request_digest binds condition_id + parent_collection_id

- active-keyset cutoff inherited from split-merge

- coverage requirement is multi-party-only, not inherited

- removed false NUT-03 COMPLEMENTARY claim

- attestation atomicity scoped to all /v1/ctf/convert commits
- Rule 10: rewrite to explain WHY uncovered outcomes are degenerate

- Rule 4: add DoS rationale for zero-fee rejection

- Rule 5: remove (redundant with exchange rule 11)

- request_digest: add idempotency purpose

- Attestation atomicity: rewrite in plain language

- Liability accounting: add colloquial explanation before formal proof
These files are needed because CTF-settle depends on NUT-Exchange

concepts (PAY_TO_UNLOCK, H_recv, participants request shape).
Dropping offer_keyset was incorrect: collateral keysets are regular

NUT-02 keysets that may share verification keys. Without the tag

inside the signed Proof.secret, a coordinator could relabel Proof.id

to a different keyset sharing the same key. Condition is now identical

to NUT-Exchange (3 tags: offer_keyset, expiry, refund).
P0-1: amounts encoded as decimal strings in canonical form (avoids

  IEEE-754 precision loss above 2^53 in JCS)

P0-2: unique per-proof nonce, shared meaningful fields (allows multiple

  proofs per record without duplicate secrets)

P0-3: resolved by P0-2 (micro-lots with unique nonces, same H_recv)

P1-8: change determined by aggregate conservation, not per-participant

P1-11: fix field name id (was keyset_id) in sorting
Also: clarify CTF rejects optional exchange tags (alt_outputs,

allow_change, min_output_amount), per-proof nonce wording.
Applied to participant_canonical and refund preimage (was only H_recv).

Also: field name fix (id not keyset_id), max_alt_outputs MUST enforce,

min_output_amount canonical grammar, removed duplicate bullet.
Fix P0-4: handle absent final_expiry via max_expiry_seconds fallback
Stopgap before the planned nut-atomic-exchange -> main merge + rebase of this branch.

- Add exchange-partial-fill.md placeholder (latest codex-reviewed version: error codes 15011-15014, Repair B worked example, decimal-string PoolEntry canonicalization, gross-debit fee clarification, canonical pool_selection bitmap, NUT-09 discard safety, mixed pool/standard participants, pool-mode tag grammar)

- Update error_codes.md placeholder to latest (adds 15011-15014 + partial-fill link)

- exchange.md is byte-identical between branches (no-op); README.md and all nine CTF files untouched
CTF convert now supports pool-mode range orders (PAY_TO_UNLOCK with rate_n/rate_d) inherited from NUT-Exchange-partial-fill, replacing the micro-lot pattern.

- Mode-dispatched validation: standard participants inherit base rules 1-7,9,11-12; pool participants inherit 6p/7p/9p + per-participant 8p clauses; the request-global two-keyset clause of 8p and rule 10p are replaced by CTF rule 8 and per-outcome rule 10

- Endpoint binding: manifest hashed under Cashu/ctf/convert/manifest (not base PAY_TO_UNLOCK/manifest), preventing /v1/exchange replay at /v1/ctf/convert

- Rational limit-price encoding: buy at p=a/b -> rate_n=b, rate_d=a; sell reverses; fractional prices representable

- request_digest includes pool_manifest/pool_selection for pool participants; Mint info gains partial_fill + max_pool_entries; max_request_bytes covers manifests

- Reconcile pool-mode change with per-outcome conservation (safety = per-outcome equality, not auth/price); clarify fair-pricing FAQ (mint enforces owner limit, not best execution)

- Re-point exchange-spec references to PR cashubtc#410; remove local exchange.md and exchange-partial-fill.md placeholders; depends on now lists NUT-Exchange and NUT-Exchange-partial-fill
Canonical: condition_id and parent_collection_id are 64 lowercase hex decoded to 32 raw bytes (omitted parent = 32 zero bytes); pool participant canonical = JCS({inputs,outputs,pool_manifest}) || hex_decode(pool_selection). Coordinator auth inherited from NUT-Exchange with digest domain Cashu/ctf/convert/coordinator; coordinator_pubkey permitted in standard and pool mode; one key per request (v1).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant