Spending in the Crowd — Hiding Received Notes by Time, Split, and Change

2025-08-29 · 6,551 words · Singular Grit Substack · View on Substack

How a wallet can auto-drizzle income into ordinary transactions so even the payer can’t trace your spending

Keywords: Bitcoin, privacy at scale, spending privacy, payroll unlinkability, note scheduling, disjoint inputs, per-note change, IP-to-IP, pacing, receipts, audit

Thesis statement:

Privacy after you receive money is achieved by dissolving those receipts into the ordinary traffic of the ledger: the wallet automatically schedules spends over Δt ∈ [1 hour, 7 days], splits values into many bounded notes, ensures disjoint inputs and per-note change with zero address reuse, and negotiates each payment directly with third parties. Given this, even the original payer (e.g., your employer) cannot reliably link their outbound notes to your later spending; outsiders face a sea of small, independent events. This essay builds on yesterday’s essay (receipt-side privacy) to show spend-side unlinkability, end to end.Subscribe

Introduction: from receipt privacy to spend privacy

Yesterday’s essay made the case that privacy at receipt is earned by scale: split a total into many small notes, each independently valid, each with its own recipient key and (if needed) its own change key, and you deny outsiders the structural hooks that make one big transfer easy to follow. That solved the “how to be paid discreetly” side of the problem. Today’s problem is the other half: once funds arrive, naïve spending can re-expose you. If you consolidate notes into a few large outputs, reuse addresses, or blast payments in tidy bursts from a single origin, you recreate the very patterns analysts rely on. Privacy gained at receipt can be squandered in spend.

The aim here is simple and practical: show how a wallet can turn incoming notes into unremarkable outgoing activity over a window of roughly 1 hour to 7 days, with strict non-reuse of addresses and change, and with inputs reserved so no note’s inputs overlap another’s within a spend set. The wallet automates three habits that matter: (1) time diversity, by scheduling spends across the window with bounded jitter rather than emitting a single spike; (2) amount diversity, by splitting intended payments into many bounded notes that match ambient small-value traffic; and (3) structural independence, by deriving per-note recipient keys and per-note sender change keys so neither address nor change is ever reused. Each outgoing note is ordinary on-chain—standard scripts, standard sizes—but the aggregate lacks the tidy signatures that invite linkage.

The context is end-to-end: yesterday’s receipt-side method provides the substrate (many small notes under strict scope and labelling); today’s spend-side method preserves that advantage by refusing to collapse back into conspicuous lumps. The wallet’s role is not to invent new cryptography but to enforce discipline automatically: disjoint input reservation, unique change per note, deterministic yet varied pacing, and either-side broadcast when counterparties accept it. With those in place, even a well-informed payer (an employer who knows exactly what they sent) cannot reliably trace how you spend later; what they and any passive observer see is a crowd of small, independent events spread across time and routes. The lesson carries through: privacy is not a one-off act at receipt, but a continuing practice in how you spend.

Threat model and observables

We consider four observer classes with distinct vantage points and limits.

E — Employer. Knows exactly what they sent: the set E = {txid, amount, time-window} for their outbound notes, plus any internal payroll metadata. They can watch the ledger and mempool after payday. They do not know your private scalars, your per-note spend schedule, your per-merchant bounds, or your IP→IP dialogues with counterparties. They do not control your anchors or wallet policy. Cryptographic assumptions hold (preimage/collision resistance of SHA-256/RIPEMD-160; hardness of discrete log on secp256k1).

A₁ — Passive chain analyst. Parses blocks and mempools at scale; sees scripts, amounts, and timings; runs standard clustering heuristics (overlapping inputs, shared change, address reuse, tight timing bursts, denomination ladders). Cannot tamper with transactions or defeat the cryptographic assumptions.

A₂ — Passive network watcher. Records first-seen announcements and gossip paths; infers origin peers probabilistically. Cannot force routing, cannot read authenticated off-chain messages, and cannot bind IP-layer hints to identities with certainty when either side can broadcast.

A₃ — Data broker. Correlates public off-chain artefacts (merchant receipts, screenshots, leaked manifests) with on-chain activity. Lacks keys or transcripts unless you leak them.

What they see. The employer and outside observers all see the employer’s outbound notes land on-chain. Later, they see your outgoing transactions—small, ordinary spends—appearing over Δt ∈ [1 h, 7 d]. They may infer rough fee levels and observe occasional change outputs. They do not see: (i) your per-note timing draws and pacing jitter; (ii) the deterministic derivations binding your recipient/change keys to each spend; (iii) the reservation table that enforces disjoint inputs; (iv) the IP→IP negotiations with third parties; (v) any address reuse—because there is none.

Goal. Deny reliable linkage from the employer’s outputs → your later spends. Concretely: for each employer note, an external observer should face a large candidate set of small outputs in the same bands and bins; across all notes and hours, the assignment problem should explode combinatorially. The wallet achieves this by (1) time diversity (randomised scheduling across 1 h–7 d; either-side broadcast when accepted by counterparties); (2) amount diversity (bounded notes matching ambient traffic; permutation to break index↔size correlation); (3) structural independence (no overlapping inputs across spends, unique per-note change, zero address reuse). Under these conditions, the employer’s knowledge does not collapse uncertainty: they can confirm that they paid you, but they cannot reconstruct what you spent, where, or when—while you retain the ability to prove narrowly (via selective receipts) exactly what you choose to disclose.

Wallet automation: the drizzle engine

The wallet’s drizzle engine turns incoming notes into ordinary outgoing activity without manual micromanagement. It is parameterised by user-visible policy and drives a deterministic, privacy-preserving schedule over a window Δt ∈ [1 h, 7 d].

Policy inputs. The user sets: (i) a spend window Δt with lower/upper bounds; (ii) amount bands per merchant class (e.g., groceries 0.50–3.00, transit 0.25–1.50), plus optional global caps; (iii) a fee-rate floor/ceiling pair to stabilise construction; (iv) max notes per day and max concurrent notes to avoid bursts; (v) guardrails (minimum reserve balance; per-day budget; quiet hours). These policies are canonical JSON, signed by the identity key, and versioned.

Scheduler. A seeded PRNG (seed := H(user_scope ∥ date ∥ “drizzle”)) selects spend timestamps and counts inside Δt. Time is partitioned into bins (e.g., 5–15 min buckets); for each target payment the engine draws a feasible N with N_min ≤ N ≤ N_max and derives a vector a[0…N−1] with Σ aᵢ = target, using prefix clamping within the class band, then permutes a so indices do not correlate with sizes. It then assigns each aᵢ to a bin with bounded jitter, respecting per-day and per-hour caps, and ensuring that Σ scheduled aᵢ across Δt equals the intended budget (carry-over adjustments are applied to the final few notes if needed). To avoid metronomic patterns, the scheduler enforces: minimum gap ≥ g_min, maximum gap ≤ g_max, randomised first announcer when counterparties accept either-side broadcast, and route diversity across peers.

Safety rails. Dust limits are enforced strictly: any candidate that would produce change ∈ (0, dust) is reseated (add an input or redraw the amount within the band) or postponed. Balance guards maintain a reserve Rₘᵢₙ; if projected balance after a scheduled note would fall below Rₘᵢₙ, the engine pauses and reschedules within Δt. A “don’t spend below X until Y” rule blocks spends under a threshold until a timestamp or confirmation condition is met (useful after large receipts or during fee spikes). A global pause/resume switch freezes the queue without destroying determinism (paused notes retain their indices; only timestamps are re-drawn within the remaining window). Fee ceilings protect against surges: if the effective feerate would exceed the ceiling, affected notes are marked “hold” and reissued later with the same indices and addresses but new inputs, preserving the non-overlap guarantees.

Determinism and logs. Given the same policy, scope, and UTXO snapshot, two instances produce the same schedule (bins, counts, amounts) modulo time-jitter draws recorded in the log. Every decision (draws, reseats, pauses, reissues) is persisted as canonical JSON with detached signatures and a hash-chain, enabling full replay. The result is automated, varied spending that stays within user limits while denying observers the tidy bursts and structural reuse that make linkage easy.

Preparing the substrate: granularity on receipt

The drizzle engine works best when your wallet already holds many small, independent notes. Best case: income arrives as dozens to thousands of tiny credits using yesterday’s method—bounded amounts, per-note recipient keys, and per-note change. In that case, you can enable spending immediately; the reservation table will find exact or near-exact matches for most outgoing notes without creating tell-tale change.

If income lands as a few coarse UTXOs, create granularity once with a preparatory fan-out (you→you). The goal is to turn each large input into a bouquet of small, standard outputs in the bands your routine spending will use (illustrative only: groceries 0.50–3.00; transit 0.25–1.50; utilities 1.00–5.00). The fan-out is outside any spend set and is marked "funding_only": true in the log; it never pays third parties and never reuses addresses. Derive outputs to unique per-note change addresses under the “snd” domain so none of the new UTXOs share a destination. Use a deterministic but varied template: for each source input, choose a feasible count N_f and amounts b[0…N_f−1] that sum to the input less fees, clamp to dust limits, then permute so index ≠ size. This avoids tidy ladders (e.g., 1.00, 1.00, 1.00 …) that can be fingerprinted.

Operational discipline matters. Build the fan-out with a fee at or above your floor, estimate size conservatively, and prefer single-input fan-outs to keep semantics simple: one big in → many small out. If a single fan-out cannot reach target granularity, stop; the rule is at most one fan-out per pay cycle to avoid creating a recognizable cascade. Record canonical JSON for the transaction plan (intended bands, counts, amounts), the actual outputs (txid, vout, value, address payload), and the signature over the canonical bytes. Wait for confirmation or accept the intended risk per policy before enabling drizzle.

The result is a pool U₀ of small, spendable notes distributed across realistic bands, each at a unique address, none reused. From here, the drizzle engine can reserve disjoint inputs for each outgoing note, minimize or eliminate change, and schedule spends over Δt ∈ [1 h, 7 d] without first consolidating or splitting on the fly. In effect, the one-time fan-out buys you months of low-friction, low-linkage spending: ordinary notes in, ordinary notes out, with no structural reuse to stitch them together.

Spend shaping: amounts, splits, and merchants

Every outgoing purchase begins with bounds that make your notes blend with ambient traffic. For a target amount T to a specific merchant, choose a band [v_min, v_max] that reflects what the ledger already emits for that merchant class (illustrative only: groceries 0.50–3.00; transit 0.25–1.50; utilities 1.00–5.00). Check feasibility with the simple inequalities N_min = ⌈T ÷ v_max⌉ and N_max = ⌊T ÷ v_min⌋, and pick an N in the closed interval [N_min, N_max]. If N_min > N_max, adjust the band or defer: forcing an infeasible split is how patterns slip in.

With N fixed, derive a vector a[0…N−1] such that ∑ aᵢ = T and v_min ≤ aᵢ ≤ v_max for all i. Use prefix clamping to guarantee feasibility at every step: at position i, compute the remaining slots and clamp the draw for aᵢ to the interval that still allows the remainder to be expressed by the remaining notes. This prevents the common failure where a few early large draws leave a stranded remainder. When the vector is complete, permute it (deterministic shuffle) so that index does not correlate with size; the payment has no “big-first, small-last” tell.

Per-merchant bands matter because cover traffic is local. Transit systems tend to throw off tight clusters of sub-unit fares; cafés and groceries produce dense bands around small whole numbers; utilities generate a spread of low, mid, and occasional larger notes. By selecting a band that already exists at scale for the class you’re paying, each note you emit enjoys a bigger crowd to hide in. Resist the temptation to camp at the edges of a band (always v_min or v_max); use the full interval so your histogram looks like everyone else’s.

If you are paying many small vendors in the same day or week, interleave their invoices. Rather than completing A end-to-end and then B, schedule overlapping splits for A, B, C so that the outgoing stream is a composite of notes from several targets. Interleaving raises cover traffic for each merchant-specific band and shrinks the chances of a neat cluster pointing to a single counterparty. The wallet can enforce soft caps like “no more than β notes per merchant per hour” and “no more than γ notes within any 5-minute bin” to avoid bursts.

Finally, prefer exact or near-exact funding to minimize change (and send any change to a unique per-note change address). Where the reservation cannot hit exact sums, vary which notes carry small change and avoid repeated round remainders. Together—class-appropriate bounds, feasible N, prefix-clamped draws, permutation, interleaving, and conservative change—you shape spends into the ordinary background, where privacy lives.

Spend shaping: amounts, splits, and merchants

Begin every outgoing purchase by fixing bounds that make each note look ordinary within its merchant’s ambient traffic. For a target T to a given counterparty, choose a band [v_min, v_max] characteristic of that class (illustrative only: groceries 0.50–3.00; transit 0.25–1.50; utilities 1.00–5.00). Check feasibility with the simple inequalities N_min = ⌈T ÷ v_max⌉ and N_max = ⌊T ÷ v_min⌋. If N_min > N_max, the band is too tight for T; widen the interval or defer the payment. Otherwise pick an integer N with N_min ≤ N ≤ N_max. Favor interior values when many are feasible so you do not always hug the extremes.

With N fixed, derive a vector a[0…N−1] satisfying v_min ≤ aᵢ ≤ v_max and ∑ aᵢ = T. Use prefix clamping so the remainder always stays feasible: at step i with remainder rem and slots s = N − 1 − i, clamp the draw to

low := max(v_min, rem − v_max × s) and high := min(v_max, rem − v_min × s).

Draw aᵢ uniformly from [low, high], set rem ← rem − aᵢ, and repeat until the last slot, which takes a_{N−1} := rem. This guarantees termination without backtracking while avoiding stranded remainders. Once the amounts are chosen, permute a under a deterministic shuffle seeded for this invoice so index ↔ size correlation disappears. The merchant will still see the same multiset of values; an outside observer loses the “big-first, small-last” tell.

Select per-merchant bands that match what the ledger already produces at scale. Small retailers and cafés throw off dense clusters near small whole numbers; transit systems generate tight sub-unit fares; billers show a spread with occasional outliers. Staying inside those bands lets each aᵢ hide among many peers. Avoid edge bias (always v_min or always v_max); use the full span so your histogram mirrors the crowd.

If you are paying many small vendors, interleave their invoices. Instead of finishing A and then B, slice both (and C, …) and schedule notes from each across the same window. Interleaving raises cover traffic for every band simultaneously and blurs merchant-specific bursts. Enforce soft caps like “≤ β notes per merchant per hour” and “≤ γ notes in any 5-minute bin” to prevent spikes, and stagger first announcers when counterparties accept either-side broadcast.

Finally, minimize change without creating patterns. Prefer exact-match inputs for aᵢ; when change is unavoidable, let it occur on varied notes and always send it to the unique per-note change address. Do not reuse change intra-invoice. Together—feasible bounds, prefix-clamped draws, deterministic permutation, class-appropriate bands, and cross-merchant interleaving—shape your spends into the ordinary background where privacy lives.

Disjoint input reservation for spends

Spends begin with a reservation pass that assigns exclusive funding to each outgoing note. Take a snapshot of your spendable UTXOs U₀ at the start of the window and build a table R mapping index i ↦ Sᵢ, where Sᵢ is the set of inputs reserved for note i. The rule is strict: for all i ≠ j, Sᵢ ∩ Sⱼ = ∅. Once an outpoint enters Sᵢ it is logically locked to that note until the note confirms, is cancelled, or is reissued; it is removed from the working pool so no two notes ever contend for the same coin.

Selection is deterministic. Iterate UTXOs in a fixed order (e.g., value ↑, then txid ↑, then vout ↑). Process note targets in a fixed order (e.g., amounts ↓, then index ↑). For each target aᵢ, choose inputs with a bounded-knapsack heuristic and a clear preference ladder: (1) exact match (Σ value = aᵢ + fee₁, with one output to the merchant); (2) single near-over (one input slightly above aᵢ + fee₂, yielding valid change ≥ dust to the per-note change address); (3) fewest inputs that minimise overshoot while keeping change ≥ dust. Here fee₁/fee₂ come from the standard estimator size_bytes(m,n) ≈ 10 + 148·m + 34·n with n ∈ {1,2} and a negotiated floor feerate. Reject any candidate that would produce change ∈ (0, dust) or violate the fee floor; reseat by adding an input or trying the next candidate. On success, write Sᵢ to R and delete its members from the live pool.

Two hygiene constraints are non-negotiable. First, no intra-invoice/window change reuse: if note i creates change, that output goes only to Addrᴬ,ᵢ and is barred from funding any other note j within the same spend set. Second, no cross-note overlap: if a later pass would pick an input already placed in an earlier Sᵢ, the builder must fail fast and reseat—overlap is never “fixed later.” These rules remove the two most powerful link hooks (overlapping inputs and shared change) across the outgoing notes.

Conflicts are handled cleanly. If any reserved outpoint is seen spent elsewhere (external wallet mutation, race), mark the affected note “conflict,” invalidate R, take a fresh snapshot U′, and rebuild the entire reservation table deterministically. Partial salvage is not allowed—it would reintroduce ambiguity.

Everything is logged. Each reservation decision includes {i, inputs, sums, fee model, change (if any)} in canonical JSON with a detached signature and a prev_hash to chain records. Given U₀, policy, and the log, an auditor can replay selection byte-for-byte and reproduce R. Operationally, the payoff is immediate: when the drizzle engine schedules a note, its inputs are already exclusive, change hygiene is enforced by construction, and broadcast can proceed without last-minute consolidation or split patterns that leak structure.

Time and route diversity

Timing is a fingerprint unless you blur it. The scheduler spreads notes over a window Δt ∈ [1 h, 7 d] using jittered gaps rather than a metronome. Each target gets a nominal placement in a coarse bin (e.g., 5–15-minute buckets), then a small random offset is added so emits never land exactly on the minute. Enforce soft caps like “≤ β notes per merchant per hour” and “≤ γ notes in any 5-minute bin,” and apply minimum/maximum inter-note gaps (g_min ≤ gap ≤ g_max) to avoid both drips and floods. Some merchants should see bursts (a handful of notes in a short span) when that matches ambient traffic; others should receive a trickle (single notes spaced widely). Above all, avoid top-of-hour and other human-friendly rhythms; cron-like regularity is a tell.

Route diversity complements time diversity. Where counterparties accept it, enable either-side broadcast: sometimes you announce first, sometimes the merchant does, sometimes both. Vary entry peers so the same relay is not the first hop for every note. If a counterparty cannot broadcast, you still vary your own entry points across sessions. Periodic rebroadcast, when needed, should be phase-shifted with small jitter so it never creates a heartbeat signature.

The effect is to degrade two common clustering heuristics. First-seen attribution becomes unreliable when some notes first appear from you and others from the recipient, entering through different peers at different times; the “all notes from X” story no longer fits. Sliding-window grouping weakens when the stream is spread across many bins with jitter and soft caps; the characteristic “everything at once” spike disappears, replaced by ordinary-looking background traffic. Combined with structural independence (no overlapping inputs, no shared change, no address reuse), the lack of tight timing and path uniformity leaves few handles for an outside observer.

Determinism and audit are preserved. The nominal placements and route choices are drawn from a seeded PRNG under user scope, so two compliant wallets reproduce the same schedule given the same inputs, while the actual emission times include bounded jitter recorded in the log. If the network environment changes (fee spike, peer failure), the scheduler re-draws within the remaining window without violating caps or reuse rules. In short: vary when and where notes enter, keep the variation inside documented bounds, and you turn timing and route from fingerprints into ordinary noise.

Why your employer cannot trace your spending

Your employer’s vantage point is narrow. They know the set E of what they sent—{txid, amount, timestamp} for each outbound note—and they can watch the chain afterward. They do not know your drizzle schedule (when you plan to spend), your split vectors (how you decompose each purchase), or your per-note keys (the unique recipient and change addresses you derive per spend). They lack your reservation table, so they cannot see which inputs fund which note; they lack your off-chain dialogues with merchants, so they cannot read invoice numbers, order IDs, or any IP→IP details.

What your later activity looks like from the outside is a scatter of independent small notes. Each spend uses a disjoint input set Sᵢ (no overlap across notes), pays a bounded amount to a unique merchant address, and—if change exists—returns it to a unique per-note change address. No address is ever reused. Notes are paced across many bins within Δt ∈ [1 h, 7 d]; some arrive in tiny bursts where that is normal for the merchant class, others trickle; either party may broadcast. There is no “everything at once” spike, no shared-change ladder, no overlapping inputs across notes—i.e., none of the classic link hooks.

The analytical burden becomes combinatorial. Partition time into bins (e.g., 5–15 minutes). In bin t, let mₜ be the number of small outputs on the ledger that fall inside the ambient band you use for that merchant class. If you place kₜ notes into that bin, a crude lower bound on the observer’s assignment uncertainty is

uncertainty ≈ Σₜ kₜ · log₂(mₜ).

Large crowds (mₜ big) and broad distribution (many bins with nonzero kₜ) push this sum up fast. And that is before you account for path diversity (some notes first-seen from you, some from the merchant, via different peers) and amount diversity (notes drawn across the whole band, then permuted). With no address reuse and no shared change, the usual “stitch two transactions together” move is gone; the analyst is left guessing across a sea of near-identical candidates.

Crucially, there is no shared processor trail to mine. Each payment is negotiated directly with the counterparty over an authenticated channel; there is no third-party gateway emitting consistent order tokens that can be cross-referenced. Your employer cannot subpoena a processor to ask “where did these outputs go?” because there isn’t one in the loop. They can confirm that they paid you (they already know E); they cannot reconstruct your grocery trips, transit taps, or café stops from that knowledge.

Could side channels still bite? Yes—merchant loyalty programs, reused shipping profiles, or posting screenshots will deanonymize anyone. But within the on-chain and network observables, the disciplines here—disjoint inputs, unique per-note change, zero address reuse, jittered timing, mixed announcers—deny reliable linkage from E → your spends. Outsiders see ordinary noise; you retain selective proof when you choose to disclose.

Case slice (illustrative only) — payroll week

Income. Two credits arrive this week: Mon 09:30 and Wed 09:30, each £1 200. Best case, they land as many small notes already; otherwise a single preparatory fan-out (you→you) creates a pool U₀ of spendable UTXOs in the £0.50–£3.00 band, each to a unique address, no reuse, then drizzle is enabled.

Outgoing over 7 days. The wallet schedules routine spend across Δt = 7 d with jittered gaps and soft caps. Per merchant class it selects bounds and splits targets into notes:

• Groceries (illustrative only): £220 in [0.50, 3.00] → N ≈ 120 notes, permuted; small bursts near store visits, otherwise trickle.

• Transit: £60 in [0.25, 1.50] → N ≈ 80 notes, interleaved across commute windows; no top-of-hour spikes.

• Dining: £90 in [0.50, 2.50] → N ≈ 60 notes, scattered evenings; some notes yield small change to unique per-note change addresses.

• Utilities: £180 in [1.00, 5.00] → N ≈ 70 notes, paced mid-week; exact-match preference to minimise change.

• Miscellaneous: £50 in [0.50, 2.00] → N ≈ 40 notes, sprinkled through idle bins.

Totals here (~£600, ~370 notes) are illustrative only; the engine may schedule several hundred to a thousand notes, depending on your bands and habits. For each note i the wallet reserves disjoint inputs Sᵢ, derives a unique recipient key (merchant’s anchor, “recv”), and a unique sender-change key (“snd”). If a note produces change, it returns only to its per-note change address and is never reused within this spend set. Inputs never overlap across notes.

Timing and routes. The scheduler uses 5–15 min bins with bounded jitter (gaps g_min ≤ gap ≤ g_max), caps like “≤ β notes/merchant/hour” and “≤ γ notes/any 5 min bin,” and varies the first announcer where counterparties accept it. Some notes are broadcast by you, others by the merchant; entry peers vary. There is no “everything at once” blast.

What outsiders see. A week of small, ordinary transactions in common bands, dribbling in at irregular intervals. No address reuse. No shared-change ladders. No overlapping inputs across notes. First-seen attribution splits between different peers and both endpoints. In any 15-minute bin t, there are m_t other outputs in the same band; your k_t notes add to that crowd. The analyst’s assignment uncertainty grows roughly like Σ k_t·log₂(m_t) across bins—large crowds and wide distribution push that sum up quickly. With no structural hooks (overlap, reuse, tight bursts), the set resists clustering.

What you can later prove. For any spend you choose—say, Friday’s utility payment—you hold a per-invoice Merkle root M and a manifest of (i, txid). You can reveal only those leaves and paths that correspond to the utility notes; a verifier recomputes M and is satisfied without learning about groceries, transit, dining, or misc. Conversely, if asked to demonstrate that payroll was received, you can point to yesterday’s receipt-side root and its selective proofs—still without exposing how you later spent. The public story remains a mosaic of small, unremarkable notes; the private story is replayable from signed logs and receipts when and only when you decide to show it.

Receipts and selective disclosure for spends

Each spend set (one merchant, one invoice, one window) is bound by a per-invoice Merkle root M. For every note index i in that set, the wallet constructs a binary leaf Lᵢ that commits to the essentials: i, txidᵢ, amountᵢ, and the address payload used on-chain. Leaves are ordered deterministically by i, then folded left-to-right to produce M. Alongside M, the wallet records a compact manifest that lists just (i, txidᵢ) for the set plus the invoice fingerprint that scopes it. All artefacts—policy snapshot, manifest, and M—are stored as canonical JSON with detached signatures and a prev_hash field so the journal is append-only and replayable.

Selective disclosure is straightforward. Suppose you need to prove just the rent payment (illustrative only). You reveal: (1) the leaf fields for the rent subset (i, txidᵢ, amountᵢ, address payload), and (2) the corresponding Merkle paths πᵢ (sibling hashes and left/right positions) up to M. A verifier recomputes each Lᵢ from the disclosed fields, folds the paths in order, and checks that the result equals M. The rest of the spend set remains opaque: undisclosed amounts, addresses, and indices never leave your control, and the verifier learns nothing beyond the fact that the rent notes are members of the committed set.

The same mechanism works at finer or coarser granularity. You can prove a single note (one i), a class of notes (all utilities this month), or a union across invoices, simply by packaging the needed leaves and paths. Because the leaf contents are byte-exact and the ordering is fixed, any attempt to reorder, insert, or delete notes breaks verification immediately. Because M is tied to the invoice fingerprint, cross-invoice reuse is impossible.

Operationally, this provides audit without surveillance. You can satisfy narrow evidentiary requests—“show rent”—without exposing groceries, transit, or dining. Counterparties can archive M and the manifest to reconstruct intent internally, while outsiders see only what you elect to disclose. The result is spending privacy by default, verifiability on demand.

Risks, limits, and operator guidance

Privacy fails in practice through habits, not cryptography. The principal re-identify risks are off-chain and behavioral: (i) merchant reuse with strong, persistent identifiers (customer accounts, email addresses) lets vendors correlate visits regardless of on-chain hygiene; (ii) recurring time slots (always paying certain bills at 09:00 Mondays) create rhythmic fingerprints; (iii) loyalty IDs and coupons bind you to a profile that can later be matched to ledger activity; (iv) shipping metadata (names, addresses, tracking numbers) leaks through carriers and merchants and can be sold or subpoenaed. On-chain, the classic errors reappear if discipline slips: address reuse, shared change, overlapping inputs, and neat ladders of amounts or bursts in time.

Mitigation is operational and continuous. Rotate anchors on a policy cadence (e.g., monthly or per-employer/merchant compartment), publishing the new public point while retiring the old; this limits any long-lived graph growth. Vary bands per merchant class and per period so your histograms do not calcify; use the full interval rather than hugging v_min or v_max, and suppress neat sequences (no long runs of 1.00, no arithmetic progressions). Avoid synchronized autopay times; introduce bounded jitter and soft caps so no day, hour, or minute becomes “your” time. Compartmentalize identities off-chain: separate email/phone handles, loyalty accounts, and delivery profiles for unrelated merchant clusters; prefer in-person pickup or minimal-data checkout when possible. Inside the wallet, keep the hard rules inviolable: no address reuse, no intra-invoice change reuse, Sᵢ ∩ Sⱼ = ∅ across notes, deterministic ordering, and reject-zero on all scalar derivations.

Operational guidance under changing conditions: treat fee changes as reissue-before-broadcast events. Rebuild the affected note(s) with new disjoint inputs at the updated feerate; preserve index i and the derived recipient/change addresses; mark old bytes “superseded” off-chain. If a conflict is detected (a reserved outpoint appears spent elsewhere), abort the current build, take a fresh snapshot, and rebuild the entire reservation table deterministically—partial salvage is not permitted. Honor expiry: when an invoice/window expires, cancel queued notes, release reservations, and continue passive monitoring only for already-broadcast notes. Log everything as canonical JSON with detached signatures and a hash-chain so audits replay exactly.

Bottom line: privacy here is a discipline. Keep identities compartmentalized, timing irregular within bounds, structures independent, and operations deterministic. The cryptography holds; habits decide.

Compliance and ethics

Privacy here is not evasion; it is restraint. The design minimizes what is exposed by default while preserving the ability to prove narrowly what is required. That balance is ethical and operational: disclose no more than necessary, yet be able to substantiate every material claim.

Concretely, keep internal records: canonical JSON logs for policy, invoices, reservations, transactions, and lifecycle events; per-invoice Merkle roots and manifests; timestamped journal entries chained by prev_hash; detached signatures by the relevant identity key. These artefacts let you replay intent, reconstruct settlements, and generate selective proofs (e.g., “show rent”) without revealing unrelated purchases. Configure retention with a clear policy (what is kept, for how long, who can sign) so obligations can be met without casual leakage.

When law or contract demands evidence, respond precisely: present only the minimal leaves and paths that verify the specific items, along with signatures that establish authenticity. Do not publish entire journals or raw address sets unless required; doing so converts disciplined privacy into self-surveillance. Maintain role separation inside the software (“operate” vs “audit” views) so routine spending cannot accidentally expose audit trails.

Avoid misuse. The same mechanisms that protect ordinary discretion must not be framed as tools for hiding criminal activity. Enforce dust, fee, and expiry policies; forbid address reuse and intra-invoice change reuse; monitor and log conflicts; refuse to proceed when invariants are violated. Educate users that privacy depends as much on behavior (no loyalty IDs, no synchronized autopays, compartmentalized accounts) as on cryptography.

Finally, keep it ordinary: standard transactions, standard hashes, standard keys. Extraordinary claims should not be required to verify ordinary life. Privacy by default; proof on demand.

Conclusion: spending privacy = scale × independence × diversity

Spending privacy is not a trick; it is a practice built from ordinary parts. Scale comes from splitting each intended purchase into many bounded notes and drizzling them across time—Δt ∈ [1 h, 7 d]—so no single transfer becomes a lighthouse. Independence comes from structure: per-note recipient keys (“recv”), per-note sender-change keys (“snd”), strict non-reuse of addresses, and disjoint input reservation with Sᵢ ∩ Sⱼ = ∅. Diversity comes from behavior: vary amounts within realistic bands, permute indices so size ≠ order, mix first announcers and entry peers, and avoid metronomic schedules. Put together, the recipe is simple to state and powerful in effect:

• drizzle over time rather than burst;

• split into many notes with v_min ≤ aᵢ ≤ v_max and ∑aᵢ = T;

• keep inputs disjoint and change unique per note;

• vary time and route;

• negotiate directly with counterparties over authenticated channels.

The outcome is asymmetrical. Outsiders—including the employer—cannot stitch a reliable story from their own outbound txids to your later activity: what they face is a sea of small, standard events spread across bins and paths, with no address reuse, no shared change, and no input overlap to grab. Insiders retain deterministic audit: from canonical logs, scope {Z, H_I}, reservations, and per-invoice Merkle roots, every artefact can be recomputed; every selective proof can be produced without exposing the whole. Privacy follows from doing more of the ordinary thing, independently and with varied timing; accountability follows from identical bytes and signed records.

Appendix

Appendix A — Configuration templates (illustrative only)

Safe defaults (balanced privacy/latency)

{

"policy_type": "spend_drizzle_v1",

"unit": "satoshi",

"created_at": "2025-08-29T00:00:00Z",

"drizzle": {

"window_min_s": 3600,

"window_max_s": 604800,

"g_min_s": 20,

"g_max_s": 600,

"max_parallel_notes": 12,

"max_notes_per_day": 800,

"per_merchant_hour_cap": 120,

"per_5min_bin_cap": 20,

"fee_floor_sat_per_byte": 1,

"fee_ceiling_sat_per_byte": 4,

"reserve_min_sat": 200000

},

"bands": {

"groceries": { "v_min": 5000, "v_max": 30000 },

"transit": { "v_min": 2500, "v_max": 15000 },

"dining": { "v_min": 5000, "v_max": 25000 },

"utilities": { "v_min": 10000, "v_max": 50000 },

"misc": { "v_min": 5000, "v_max": 20000 }

},

"guards": {

"quiet_hours_utc": ["00:00-06:00"],

"dont_spend_below_sat": 100000,

"until_iso8601": "2025-09-01T00:00:00Z"

},

"sign": { "sig_key": "", "sig_alg": "secp256k1-sha256", "sig": "" }

}

“Paranoid” profile (maximize cover; slower, wider, stricter)

{

"policy_type": "spend_drizzle_v1",

"unit": "satoshi",

"created_at": "2025-08-29T00:00:00Z",

"drizzle": {

"window_min_s": 14400,

"window_max_s": 1209600,

"g_min_s": 45,

"g_max_s": 900,

"max_parallel_notes": 6,

"max_notes_per_day": 400,

"per_merchant_hour_cap": 60,

"per_5min_bin_cap": 8,

"fee_floor_sat_per_byte": 1,

"fee_ceiling_sat_per_byte": 2,

"reserve_min_sat": 500000

},

"bands": {

"groceries": { "v_min": 4000, "v_max": 20000 },

"transit": { "v_min": 2000, "v_max": 12000 },

"dining": { "v_min": 4000, "v_max": 20000 },

"utilities": { "v_min": 8000, "v_max": 40000 },

"misc": { "v_min": 4000, "v_max": 16000 }

},

"guards": {

"quiet_hours_utc": ["00:00-07:00"],

"dont_spend_below_sat": 300000,

"until_iso8601": "2025-09-15T00:00:00Z"

},

"sign": { "sig_key": "", "sig_alg": "secp256k1-sha256", "sig": "" }

}

Notes: values are placeholders; keys are in canonical order where hashed; times are UTC; caps and bands should be tuned to ambient traffic.

Appendix B — Scheduler pseudocode (illustrative only)

Inputs (policy):

Δt = [t0, t1] spend window; g_min, g_max inter-note gaps

bin_min_s, bin_max_s (e.g., 300…900); max_parallel_notes

caps: per_merchant_hour_cap, per_5min_bin_cap

fee_floor, fee_ceiling; reserve_min; quiet_hours; until_ts

bands[merchant] = [v_min, v_max]

seed := H(user_scope ∥ invoice_id ∥ "drizzle")

def schedule(invoice, targets): # targets: list of {merchant, T}

rng = PRNG(seed)

bins = make_bins(t0, t1, rng.uniform(bin_min_s, bin_max_s))

Q = [] # scheduled notes

state = init_state()

for tgt in targets: # per outgoing purchase

v_min, v_max = bands[tgt.merchant]

Nmin = ceil(tgt.T / v_max); Nmax = floor(tgt.T / v_min)

N = choose_count(rng, Nmin, Nmax) # interior-biased

a = bounded_split(rng, tgt.T, v_min, v_max, N) # prefix-clamped

a = permute_in_place(rng, a) # index ≠ size

burst or trickle mode by merchant profile

mode = choose(rng, ["burst","trickle"], weights=tgt.profile)

group = burst_groups(rng, len(a)) if mode=="burst" else [[i] for i in range(len(a))]

for grp in group:

b = pick_bin_with_caps(rng, bins, tgt.merchant, caps, quiet_hours)

for i in grp:

t_jitter = rng.uniform(0, bins[b].span)

t_sched = clamp(bins[b].start + t_jitter, now(), until_ts)

guardrails

if violates_quiet(t_sched, quiet_hours): b = shift_bin(rng, bins, b); continue

if projected_balance(state) - a[i] < reserve_min: defer(state, i); continue

if fee_estimate(a[i]) > fee_ceiling: hold_for_reissue(state, i); continue

reservation + change hygiene

S_i = select_inputs_disjoint(state.U, a[i], fee_floor)

if produces_dust_change(S_i, a[i], fee_floor): S_i = reseat_inputs(state.U, a[i])

if S_i is None: mark_insufficient(state, i); continue

announcer = choose_announcer(rng, capability=tgt.counterparty)

peer = pick_entry_peer(rng)

Q.append({

"i": note_index(state),

"merchant": tgt.merchant,

"amount": a[i],

"inputs": S_i,

"t_sched": t_sched,

"announcer": announcer,

"peer": peer

})

enforce_inter_note_gap(state, t_sched, g_min, g_max)

apply_caps(state, tgt.merchant, b, caps)

lock_inputs(state, S_i)

ensure Σ scheduled amounts == Σ targets (minor tail adjust if needed)

Q = reconcile_tail(Q, targets)

deterministic ordering for audit; jitter is recorded, not re-drawn

return sort_by(Q, keys=["t_sched","merchant","i"])

Notes:

• make_bins partitions Δt; caps enforced per hour and per 5-min bin.

• burst_groups draws run-lengths (e.g., geometric) for clustered merchants.

• choose_announcer flips between you and counterparty if either-side broadcast is allowed.

• All decisions (draws, caps hits, reseats, defers, holds) are logged as canonical JSON with detached signatures.

16. Appendix C — Negative examples (illustrative only)

Single blast at 09:00 daily. A metronomic dump of notes at a human-friendly time creates a time fingerprint; sliding-window clustering will bind them. Spread with jitter across Δt, enforce g_min ≤ gap ≤ g_max, and avoid top-of-hour cadence.

Address reuse. Reusing any recipient or change address collapses unlinkability. Derive per-note keys with explicit domains (“recv”, “snd”), enforce reject-zero, and forbid reuse in policy and code.

Pulling change into concurrent spends. Funding spend B with change from spend A inside the same invoice/window recreates the shared-change hook. Enforce Sᵢ ∩ Sⱼ = ∅ and “no intra-invoice change reuse.”

Round-number ladders. Long runs such as 1.00, 1.00, 1.00 … or arithmetic staircases 0.50, 1.00, 1.50 … invite pattern detection. Use prefix clamping within [v_min, v_max], then permute a so index ≠ size.

Single announcer, single route. If every note is first-seen from the same peer, first-seen heuristics bind the set. Where feasible, mix announcers (either side) and vary entry peers.

Consolidation before spend. Merging many small UTXOs into a lump before paying creates a conspicuous lighthouse. Prefer exact/near-exact matches; when granularity is lacking, do one fan-out (you→you) and stop.


Deliverables checklist for drafting-

Use Unicode maths (e.g., ∑, ≤, ≥) inline; no LaTeX.

-

Numbered headers; small code blocks only in appendices.

-

Mark all examples “illustrative only.”

-

Link back to yesterday’s essay for receipt-side derivations; do not restate the full math.

-

Keep tone precise, academic, and readable; avoid undefined external jargon.


← Back to Substack Archive