Privacy
Mixdepths
Mixdepths are isolated wallet accounts. Inputs for one CoinJoin come from one mixdepth, equal CoinJoin outputs move to the next mixdepth, and change remains in the source mixdepth. This separation prevents the wallet from immediately merging an equal output with its linkable change.
An INTERNAL destination uses the next mixdepth and wraps from the final
mixdepth to mixdepth 0. Explicit destinations are used as provided and may be in
another wallet.
Makers can choose how to select a source when several mixdepths can fill the
same request. The default balanced policy spends from the largest eligible
balance. This keeps all configured compartments meaningfully active, but tends
to reduce the largest balance, and therefore the maker's maximum offer, over
time. The optional concentrated policy ports the reference
yg-privacyenhanced cyclic-gap heuristic. It keeps eligible funds in a compact
run around the mixdepth cycle, generally preserving larger offers after the
final mixdepth wraps to mixdepth 0.
concentrated is a liquidity policy, not a privacy enhancement. Concentrating
funds in fewer active mixdepths reduces the opportunity to obscure relationships
between equal CoinJoin outputs and the change left in prior rounds. Both policies
are deterministic and may be classified by an authenticated probing taker over
repeated requests. This does not create a pre-authentication mixdepth oracle: the
source is selected only after PoDLE authentication, and every policy must reveal
the selected inputs and output addresses in !ioauth.
The maker keeps that cyclic routing without treating every mixdepth 0 coin as interchangeable. Its default md0 merge pool contains exact protocol CoinJoin outputs and CoinJoin change only when authoritative local history proves, recursively, that every wallet input was already in that pool. Deposits, deposit-derived change, ordinary-send change, mixed ancestry, reconstructed history, and incomplete legacy history remain single-UTXO only. This keeps terminal maker funds liquid after they wrap to md0 without merging them with deposit or withdrawal ancestry.
This lineage rule is compartment hygiene, not a claim that maker change is unlinkable. Change remains identifiable and its later reuse links rounds; input consolidation can reveal additional common ownership. Long-running makers should still minimize unnecessary inputs and avoid distinctive fee settings.
Treat mixdepth boundaries as privacy boundaries when making manual sends. For the exact HD paths, address branches, and wallet behavior, see Technical Wallet Notes.
Automatic fixed-amount direct sends stay within one mixdepth. When the source is not pinned, they use the highest mixdepth with a fee-sufficient admissible selection. Selection minimizes script clusters before input count and excess value, and treats every UTXO sharing a script as one atomic cluster. Frozen or unconfirmed members prevent automatic partial spending of that cluster. In mixdepth 0, more than one cluster may be consolidated only when every selected outpoint has exact CoinJoin provenance. Sweeps require an explicit mixdepth; manual and explicit input selection remain authoritative.
PoDLE
Proof of Discrete Log Equivalence (PoDLE) prevents cost-free probing of maker UTXOs. Without it, a taker could repeatedly request transactions, collect maker inputs, and abort before paying any CoinJoin cost.
Protocol Flow
- The taker commits to
C = SHA256(P2), whereP2 = k * J. - The maker accepts the commitment and sends its encryption key.
- The taker reveals
P = k * G,P2, and a Schnorr-like proof. - The maker verifies the commitment, proof, UTXO, and ownership binding.
- The maker blacklists the commitment immediately.
- After
!ioauth, the maker relays!hp2through fresh directory connections, a random nick, and an isolated Tor stream.
Relay happens after every maker in the current transaction has processed the
commitment. Relaying earlier could make peers reject the same taker's !auth.
The ephemeral relay identity keeps the public blacklist broadcast separate from
the maker that consumed the commitment.
Proof
The proof shows that P = k * G and P2 = k * J have the same unknown scalar
k:
- Compute nonce commitments
KG = r * GandKJ = r * J. - Compute
e = SHA256(KG || KJ || P || P2). - Compute
s = r + e * k mod n. - Reveal
(P, P2, s, e).
The maker reconstructs:
KG = s * G - e * P
KJ = s * J - e * P2
and checks the challenge hash and SHA256(P2) = C.
The nonce is derived with a domain-separated RFC 6979-style HMAC-SHA256
construction keyed by the UTXO private key. Its transcript binds the UTXO
reference, NUMS index, P, and P2, preventing nonce reuse across distinct
proofs. Secret response multiplication and addition are delegated to
libsecp256k1 key-tweak operations rather than Python bigint arithmetic.
NUMS Points And Reuse
J is one of 256 deterministic Nothing-Up-My-Sleeve points with no known
discrete-log relation to Bitcoin's generator G. The construction hashes the
compressed and uncompressed encodings of G, the one-byte index, and a counter
until the result encodes a valid curve point. The implementation and vectors
live in jmcore/src/jmcore/podle.py.
One UTXO can produce commitments with multiple NUMS indices:
- index 0 is the first use,
- indices 1 and 2 cover normal retries,
- higher indices require a maker configured with a larger
taker_utxo_retriesallowance.
Takers track used commitments in cmtdata/commitments.json. Makers maintain
the relayed blacklist in cmtdata/commitmentlist.
By default, a PoDLE UTXO needs five confirmations and value of at least 20% of the CoinJoin amount. Eligible coins are prioritized by confirmations and then value. Automatic taker coin selection also requires at least one selected input to have an unused, non-blacklisted commitment index. If the normal funding choice is exhausted, the taker reselects with a fresh eligible UTXO and spends that UTXO in the CoinJoin rather than revealing a separate unspent coin. Manual and explicit input selection remain authoritative and can fail when the chosen set has no fresh commitment.
Fidelity Bonds
Fidelity bonds let makers prove that they have locked bitcoin. Takers can use the time-value of that locked UTXO when selecting makers, making large-scale maker identities costly to create.
Privacy Properties
The bond proof publishes one exact UTXO and associates it with the maker's identity. Its transaction history, funding source, amount, and later spend are therefore public linkage points.
Prepare bond funds with coin control and CoinJoin them before locking. Do not merge unrelated deposits with funds that a probing taker can cause the maker to spend alongside an advertised bond. A bond's timelock protects availability, not anonymity.
The locked UTXO cannot participate directly in CoinJoins. After expiry it must first be redeemed to a regular output.
Script And Bond Value
The bond is P2WSH with this witness script:
<locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey> OP_CHECKSIG
Bond value increases with amount and the committed interval from confirmation to locktime. It remains constant before expiry and decays afterward. One bond is one UTXO. If an address receives multiple payments, only the largest UTXO is announced as the bond; additional locked UTXOs do not combine with it.
CLTV validity follows chain median-time-past. Local wall-clock time alone does not make a bond spendable.
Proof And Certificate Chain
The 252-byte fidelity-bond proof contains:
| Field | Size | Purpose |
|---|---|---|
| Nick signature | 72 | Certificate key signs the taker/maker nick pair |
| Certificate signature | 72 | Bond key delegates to the certificate key |
| Certificate public key | 33 | Online key used for nick proofs |
| Certificate expiry | 2 | Absolute 2016-block period, little-endian |
| Bond public key | 33 | Key committed by the CLTV script |
| Transaction ID | 32 | Bond outpoint transaction ID in display order |
| Output index | 4 | Bond outpoint index, little-endian |
| Timelock | 4 | CLTV timestamp, little-endian |
DER signatures are left-padded with 0xff to fixed 72-byte fields. The
certificate message binds its public key and absolute expiry period:
fidelity-bond-cert|<certificate-pubkey>|<absolute-expiry-period>
The trust chain is:
bond key (optionally cold) -> certificate key (hot) -> maker nick proof
Separating these keys keeps the bond-spending key offline during normal maker operation. Compromise of the hot certificate key can permit impersonation while takers accept the bond proof, but does not spend the bond funds. Reference and JoinMarket NG takers enforce the authenticated certificate expiry field against chain height.
Certificate issuance chooses an absolute period, often by adding a configured number of periods to the current chain period. The signed field is not a relative duration on the wire.
Cold Wallet Setup
Cold-key setup, backend use, signer compatibility, certificate renewal, redemption, migration, and the public hardware-wallet test vector are operational procedures. See Fidelity Bond Operations for the maintained workflow.
Cryptographic Parameters
JoinMarket uses secp256k1, the same curve used by Bitcoin:
y^2 = x^3 + 7 mod p
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
Bitcoin's generator point G is defined by SEC 2. PoDLE's NUMS points are
alternative generators derived transparently from G; knowing a discrete-log
relation between a NUMS point and G would break the proof's soundness.
References: