feat(walletapi): decoy selection — batch RPC + activity sampler + D6 bins (fixes #88) - #89
Open
liqdmetal wants to merge 3 commits into
Open
feat(walletapi): decoy selection — batch RPC + activity sampler + D6 bins (fixes #88)#89liqdmetal wants to merge 3 commits into
liqdmetal wants to merge 3 commits into
Conversation
This was referenced Aug 22, 2026
liqdmetal
added a commit
to liqdmetal/derohe-improvements-by-liqdmetal
that referenced
this pull request
Aug 23, 2026
… fix Implements spec/decoy-activity-distribution.md D5: decoy sampling that matches the REAL participant activity distribution, so an observer cannot distinguish real sender/receiver from decoys by on-chain activity alone (the OSPEAD analog for DERO's account model). walletapi/decoy_sampler.go: - DecoyModel: published bin table (blocks-since-last-appearance x weight) from the D2 direct estimator (participant density p(x) recovered from ringsize-2 members — no deconvolution noise) - candidateRecency: extracts blocks-since-last from the candidate's embedded NonceBalance (uvarint NonceHeight + ElGamal) - SelectDecoys: weighted Fisher-Yates-style draws without replacement, probability proportional to bin weight; zero-weight candidates never drawn walletapi/wallet_transfer.go: fix the batch-path decode — the tree value is NonceBalance (varint NonceHeight + 66B ElGamal), NOT a bare ElGamal; NonceBalance.Unmarshal panics on malformed input, so it must be recover-guarded (a wallet must never panic on a daemon's malformed batch). Tests (walletapi/decoy_sampler_test.go): - TestCandidateRecency: recency extraction + malformed -> 0 - TestSelectDecoysMatchesModel: sampled distribution matches model weights (recent share ~0.67 vs expected 0.714 on a 10:1 model, within finite-population tolerance); zero-weight candidates excluded Posterior math (spec §3.1): Pr[s=m_i | ring] is uniform iff decoys are drawn from the same distribution as real participants; this module implements that match. Pure client logic — no consensus impact. Builds on the batch RPC (PR DEROFDN#89) — the wallet already has the candidate features (NonceHeight embedded in each candidate's balance).
liqdmetal
force-pushed
the
feature/decoy-batch-rpc
branch
from
August 23, 2026 03:26
362a598 to
7b18a77
Compare
This was referenced Aug 23, 2026
…dor noise) Daemon GetRandomAddressBatch (<=512 real accounts with encrypted balances, one RPC call, 5-block filter removed); wallet Random_ring_members_batch() with CSPRNG Fisher-Yates draw. Effect: no per-decoy round-trips; daemon posterior collapses to 1/C(B,R).
liqdmetal
force-pushed
the
feature/decoy-batch-rpc
branch
from
August 25, 2026 01:30
2a18dfb to
aef5bf9
Compare
…er (one package) Batch RPC: GetRandomAddressBatch (<=512 real accounts, one call, 5-block filter removed) + client-side CSPRNG Fisher-Yates draw. Activity sampler: DecoyModel/DecoyBin quantile model, recency from NonceHeight, SelectDecoys weighted draw without replacement. Effect: decoy distribution tracks observed mainnet activity, daemon posterior collapses to 1/C(B,R).
D2 ringsize-2 direct-estimator table as DefaultDecoyModel(). Starting artifact, not a live feed. Sampler already consumes DecoyModel.
Author
|
Folded local maturity: PublishedMainnetDecoyModel / DefaultDecoyModel() — D2 ringsize-2 direct-estimator bins as the public starting table the sampler consumes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Non-consensus fix (wallet↔daemon protocol only, ships in a normal release): kills the two decoy-selection privacy leaks from the transaction-relation analysis, and publishes the activity model that makes selection track real participants.
The two leaks
K1 — active-account narrowing.
DERO.GetRandomAddresssamples the balance tree but skips any account whose ciphertext changed in the last 5 blocks. Since decoys are guaranteed untouched for 5 blocks, any ring member that was touched is, by construction, sender or receiver. An observer computing per-block balance-tree diffs can read this signal off public state.K2 — daemon-ring-leak. The wallet fetches the encrypted balance of every decoy candidate via
DERO.GetEncryptedBalancewith the address in plaintext. A daemon sees: the wallet's own address, the receiver, and every decoy re-queried milliseconds after serving it — and can reconstruct the ring, often inferring sender/receiver from query order and timing.The fix
Node side = raw material only. New RPC
DERO.GetRandomAddressBatchreturns up to 512 real registered accounts WITH their encrypted balances in one response, sampled from the balance tree:Wallet side = selection. The wallet verifies the batch, then picks the final ring client-side with its own CSPRNG (Fisher-Yates draw,
crypto/rand). The daemon's posterior over the true ring after serving a batch of size B for a ring of size R is 1/C(B,R) — its information advantage is destroyed.Activity-matched sampler + published model (D6, folded in)
Uniform sampling fixes sender-selection statistics but not activity-distribution matching. This revision adds:
walletapi/decoy_sampler.go—DecoyModel(weighted "blocks since last appearance" bins),candidateRecency(NonceHeight from the embedded NonceBalance),SelectDecoys(weighted draw without replacement).walletapi/decoy_model_published.go—PublishedMainnetDecoyModel/DefaultDecoyModel(): the D2 direct-estimator bins (13.6% in 0–5 blocks, 24.7% in 5–10, then 10–50 / 50–200 / 200–1000 / 1000+). Source: ringsize-2 members are BOTH real participants (zero decoys), so the participant density is read straight from their recency histogram. Starting table, not a live feed.The posterior over ring members is uniform iff decoys are drawn from the same distribution as real participants; this closes that gap.
Changes
rpc/daemon_rpc.goGetRandomAddressBatch_Params/Result+Candidatestructscmd/derod/rpc/rpc_dero_getrandomaddress.goGetRandomAddressBatchhandler (cap 512)cmd/derod/rpc/websocket_server.gogetrandomaddressbatchwalletapi/daemon_communication.goRandom_ring_members_batch()+filterBatchCandidates()(fail-closed)walletapi/wallet_transfer.gowalletapi/decoy_sampler.gowalletapi/decoy_model_published.goDefaultDecoyModel()Security argument
Honest limits
Branch:
feature/decoy-batch-rpcin the forkliqdmetal/derohe-improvements-by-liqdmetal. Carries the build-manifest fix (go.mod/go.sum) for fresh-clone builds. Flat oncommunity-dev.