Thanks for making the time to take a look at this today SatsAndSports (nprofile…gu0q) !
Chiefmonkey (nprofile…t202) it looks like we have a solution to address the biggest pain points of auctions with a moving deadline. I'll run it by Schlaus Kwab (nprofile…t5pj) soon.
Here is an LLM summary of SatsAndSport's idea:
# Moving-Bound Auctions with 1-of-2 P2PK: Symmetric Oracle Paths for Trustless Early Refunds
**Date:** May 22, 2026
**Companion to:** "Extending Auctions Without Imposing a Hard End Time"
**Credit:** SatsAndSports identified the 1-of-2 P2PK approach as a solution to the early-refund and self-rebidding challenges raised in the original article.
---
## The Problem Recap
The moving-bound auction design (described in the parent article) removes the seller-imposed `max_end_at` and replaces it with a bound that moves forward with each new bid. This creates two friction points that the original article identified as open challenges:
1. **Refund latency:** Losing bidders must wait for the full locktime (grace period) to expire before reclaiming their ecash, even if the seller settles within seconds.
2. **Self-rebidding capital waste:** A bidder who raises their own bid must lock the full new amount while the old amount remains locked, temporarily tying up 2x-3x the actual commitment.
Both problems stem from the same root cause: the current lock profile is 1-of-1 P2PK with a timelocked refund. The only way to unlock ecash before `locktime` is via the seller's derived child private key. Losing bidders have no early exit.
---
## The Insight: Symmetric HD Paths with 1-of-2 P2PK
Cashu's NUT-11 already supports M-of-N P2PK locks. A `1-of-2` lock means the ecash can be spent by either of two specified pubkeys — no multisig coordination required, either key alone suffices.
The proposal: **the oracle issues derivation paths for both the seller and the bidder, and the ecash is locked to both of their derived pubkeys as a 1-of-2.**
This is the same path-oracle pattern already used in v1, applied symmetrically.
---
## Current Design: 1-of-1 P2PK + Timelocked Refund
```
┌─────────────────────────────────────────┐
│ Locked ecash (N sats) │
│ │
│ Spendable by: │
│ • seller_child_pubkey (anytime) │
│ derivation path held by oracle │
│ │
│ • bidder_refund_key (after │
│ locktime only) │
│ bidder holds this key directly │
│ │
│ locktime = max_end_at + grace_period │
└─────────────────────────────────────────┘
```
The oracle generates one derivation path per bid:
```
[seller xpub]
│
path: m/.../n
│
seller_child_pubkey
```
- **Seller** has the `xpriv`. Once the oracle reveals the derivation path, the seller derives the child private key and redeems the ecash.
- **Bidder** has a refund key but can only use it after `locktime` expires.
- **Oracle** holds only the derivation path (a sequence of indices). No private key material. Cannot spend.
The problem: the bidder's only early-exit mechanism is the locktime. In the common case (seller settles quickly), the bidder's funds are unnecessarily trapped for the full grace period.
---
## Proposed Design: 1-of-2 P2PK with Symmetric Paths
```
┌─────────────────────────────────────────┐
│ Locked ecash (N sats) │
│ │
│ Spendable by EITHER (1-of-2): │
│ • seller_child_pubkey │
│ (oracle path A) │
│ • bidder_child_pubkey │
│ (oracle path B) │
│ │
│ Fallback (if oracle disappears): │
│ • bidder_refund_key after locktime │
└─────────────────────────────────────────┘
```
The oracle now generates two derivation paths per bid — one against each party's HD key:
```
Oracle's HD key tree (per bid):
[oracle xpub]
/ \
path A: m/.../0 path B: m/.../1
/ \
seller_child_pubkey bidder_child_pubkey
```
### Key derivation for each party
- **Seller** holds their own `xpriv`. When the oracle reveals path A, the seller derives `seller_child_privkey = derive(xpriv, path_A)` and can spend.
- **Bidder** holds their own `xpriv`. When the oracle reveals path B, the bidder derives `bidder_child_privkey = derive(xpriv, path_B)` and can spend.
- **Oracle** holds only the path indices (e.g., `m/30408'/0'/0'` and `m/30408'/0'/1'`). No `xpriv` material from either party. **Cannot derive any private key. Cannot spend. Cannot steal.**
### The trust model
```
┌──────────────┐
│ ORACLE │
│ │
│ Knows: paths │
│ Can't: spend │
└──────┬───────┘
╱ ╲
path A ╱ ╲ path B
╱ ╲
┌──────┴──────┐ ┌──────┴──────┐
│ SELLER │ │ BIDDER │
│ │ │ │
│ Has: xpriv │ │ Has: xpriv │
│ Needs: A │ │ Needs: B │
└──────────────┘ └──────────────┘
```
The oracle's role is purely **informational**: it decides *which* path to reveal to *which* party. It controls the routing of funds without ever being able to take them.
**What the oracle CAN do:**
- Reveal path A to seller → seller claims the ecash (settlement)
- Reveal path B to bidder → bidder reclaims the ecash (early refund)
- Refuse to reveal any path → parties wait for locktime fallback (DoS, not theft)
- Reveal the "wrong" path → misroutes funds (seller gets refund, or bidder gets payment)
**What the oracle CANNOT do:**
- Derive either party's private key (holds no `xpriv`)
- Spend the locked ecash (no key material)
- Steal funds (can only redirect them to one of the two intended parties)
- Create or destroy ecash (bearer asset, mint-controlled)
This is the same threat model as v1. The oracle is trusted for **liveness** (it must reveal paths) and **correctness** (it reveals the right path to the right party), but NOT for **custody** (it cannot take funds).
---
## How This Solves the Open Challenges
### 1. Instant refunds for losing bidders
```
Auction ends. Winner determined.
For each LOSING bid:
Oracle reveals path B to the bidder
→ Bidder derives child privkey from xpriv + path B
→ Bidder redeems ecash from mint
→ Refund is INSTANT. No locktime wait.
For the WINNING bid:
Oracle reveals path A to the seller
→ Seller derives child privkey from xpriv + path A
→ Seller redeems ecash from mint
→ Same as today.
```
The locktime + refund key still exists as a **fallback**. If the oracle disappears or is unresponsive, every bidder can still reclaim after locktime expires. But in the normal case (oracle online), refunds happen immediately.
This means the grace period can be set generously (hours or even days) to handle seller downtime or mint downtime, **without penalizing losing bidders**. In the happy path, everyone gets their funds quickly regardless of the grace period length.
### 2. Capital-efficient self-rebidding
```
Bidder has a 5,000-sat bid (lock 1, 1-of-2 with paths A1/B1).
Bidder wants to raise to 8,000 sats.
1. Bidder requests new grant from oracle
→ Oracle generates paths A2/B2 for the new bid
2. Bidder locks 8,000 sats in new 1-of-2 lock (lock 2)
3. Bidder publishes new bid event
4. Oracle sees the new bid supersedes the old one
5. Oracle reveals path B1 for the OLD lock to the bidder
→ Bidder instantly reclaims the 5,000 sats
Net result: 5,000 recovered + 8,000 locked = only 8,000 outstanding.
Old lock is resolved immediately, no locktime wait.
```
The oracle's knowledge of which bids are superseded allows it to release the bidder's path for old locks as soon as a higher bid is confirmed. The bidder gets their capital back immediately and can reuse it.
### 3. Generous grace periods without penalty
In v1, the grace period directly determines how long losing bidders wait. A 1-hour grace means a 1-hour wait. This creates a tension: short grace = risky for seller, long grace = painful for bidders.
With 1-of-2 P2PK, the grace period is a **worst-case fallback**, not the normal-case latency:
- **Normal case** (oracle online): losing bidders get refunds via path B within seconds of settlement. Grace period length is irrelevant.
- **Worst case** (oracle offline): losing bidders wait for locktime (same as today). A longer grace period provides more safety for the seller without making the common case worse.
---
## Comparison: v1 vs. Proposed
| Aspect | v1 (current) | 1-of-2 proposal |
|--------|-------------|-----------------|
| Lock type | 1-of-1 P2PK | 1-of-2 P2PK |
| Pubkeys in lock | seller_child only | seller_child + bidder_child |
| Bidder refund trigger | locktime expiry only | oracle-revealed path B (instant) OR locktime (fallback) |
| Refund latency (normal case) | full grace period (1+ hour) | seconds |
| Self-rebid capital efficiency | must lock full new amount, old lock trapped | oracle releases old lock path B immediately |
| Grace period tradeoff | long grace = long bidder wait | long grace = safe fallback only, no normal-case penalty |
| Oracle trust | trusted for liveness + correctness, not custody | **identical** — same threat model |
| Bidder key requirement | single refund key (any secp256k1) | HD key (xpriv) for path derivation |
| Fallback if oracle disappears | bidder reclaims via locktime + refund key | **same** — locktime + refund key still in lock |
| Cashu mint compatibility | NUT-11 with P2PK + locktime + refund | NUT-11 with multi-pubkey P2PK + locktime + refund |
---
## What Changes in the Implementation
### NUT-11 lock construction
Current (v1):
```json
[
"P2PK",
{
"nonce": "<random>",
"data": "<seller_child_pubkey>",
"tags": [
["sigflag", "SIG_INPUTS"],
["locktime", "<locktime_unix_seconds>"],
["refund", "<bidder_refund_pubkey>"],
["n_sigs_refund", "1"]
]
}
]
```
Proposed (1-of-2):
```json
[
"P2PK",
{
"nonce": "<random>",
"data": "<seller_child_pubkey>,<bidder_child_pubkey>",
"tags": [
["sigflag", "SIG_INPUTS"],
["n_sigs", "1"],
["locktime", "<locktime_unix_seconds>"],
["refund", "<bidder_fallback_pubkey>"],
["n_sigs_refund", "1"]
]
}
]
```
Key differences:
- `data` field contains both pubkeys (comma-separated or as per NUT-11 multi-pubkey convention)
- `n_sigs: 1` explicitly set (1-of-2: either key suffices)
- `locktime` and `refund` remain as the oracle-disappears fallback
### Oracle grant flow
The oracle's `request_path` response expands from:
```json
{
"derivationPath": "m/30408'/0'/0'",
"childPubkey": "<seller_child>",
"grantId": "..."
}
```
To:
```json
{
"derivationPathA": "m/30408'/0'/0'",
"sellerChildPubkey": "<seller_child>",
"derivationPathB": "m/30408'/0'/1'",
"bidderChildPubkey": "<bidder_child>",
"grantId": "..."
}
```
The oracle still reveals paths only at settlement time:
- **Settlement:** oracle reveals `derivationPathA` to seller (same as today)
- **Refund:** oracle reveals `derivationPathB` to the losing bidder (new capability)
- **Rebid:** oracle reveals `derivationPathB` for the superseded bid to the bidder (new capability)
### Bidder key setup
In v1, bidders only need a refund pubkey (any secp256k1 key). In the 1-of-2 design, bidders need an HD key (`xpriv`/`xpub`) so they can derive child private keys from oracle-revealed paths.
This could be:
- The bidder's Nostr key (if it's an HD key)
- A dedicated auction HD key generated at first bid
- Derived from the bidder's NIP-60 wallet key material
The auction event would carry a `bidder_p2pk_xpub` in addition to the existing `p2pk_xpub` (seller), or a convention could be established where the bidder's xpub is communicated during the path request.
---
## Open Questions
1. **Bidder xpub source:** Where does the bidder's HD key come from? Options include: derived from Nostr key, derived from NIP-60 wallet, generated per-auction, or generated per-bid. The tradeoff is between privacy (fresh key per bid) and recoverability (deterministic from existing key material).
2. **Oracle path revelation timing:** When exactly does the oracle reveal path B to losers? Options: immediately after settlement, on request from the bidder, or batch-revealed for all losers at once.
3. **Rebid race condition:** If a bidder submits a rebid and the oracle releases path B for the old lock, what prevents the bidder from reclaiming the old funds AND cancelling the new bid before it's confirmed? The oracle should only release path B after the new bid is published and verified.
4. **Mint compatibility:** Not all Cashu mints may support multi-pubkey P2PK locks. The auction's trusted mint list should be validated for 1-of-2 support before the auction is created.
5. **Settlement policy versioning:** This would likely ship as `cashu_p2pk_path_oracle_v2`, coexisting with v1 auctions on the same relays. V1 auctions continue to settle under v1 rules.
---
## Summary
The 1-of-2 P2PK pattern applies the existing path-oracle trust model symmetrically: the oracle creates derivation paths for both the seller and the bidder, and the ecash is locked to both derived pubkeys. The oracle controls *who* can access the funds by choosing which path to reveal, but can never *take* the funds itself.
This solves the three biggest friction points in the moving-bound auction design:
- **Losing bidders** get instant refunds (no locktime wait in the normal case)
- **Self-rebidders** recover old locks immediately (no capital waste)
- **Grace periods** can be generous for safety without penalizing anyone in the happy path
The trust model is unchanged: the oracle is trusted for liveness and correctness, but never for custody. If the oracle disappears, the locktime + refund fallback works exactly as in v1.