Skip to content

CHIP-0057: Silent Payments - #198

Open
kdc2000 wants to merge 7 commits into
Chia-Network:mainfrom
kdc2000:main
Open

CHIP-0057: Silent Payments#198
kdc2000 wants to merge 7 commits into
Chia-Network:mainfrom
kdc2000:main

Conversation

@kdc2000

@kdc2000 kdc2000 commented Mar 31, 2026

Copy link
Copy Markdown

Note

Low Risk
This PR only adds a specification document to the CHIPs repo; it does not change consensus, puzzles, or wallet code. Future implementation mistakes in ECDH, grouping, or key handling would be security-sensitive, but that risk is outside this diff.

Overview
Adds CHIP-0057 (CHIPs/chip-0057.md), a new Review-status process/tooling CHIP that specifies silent payments on Chia—unlinkable one-time outputs from a static spxch1/tspxch1 address, adapted from Bitcoin BIP-352.

The document defines BLS12-381 ECDH using the sender’s synthetic public key from standard p2_delegated_puzzle_or_hidden_puzzle reveals, EIP-2333 scan/spend paths (m/12381/8444/12/0 and 13/0), tagged hashes, input hashing from smallest coin ID plus aggregated sender key, output indexing, labeling via forward puzzle-hash checks, and multi-input spend grouping via cyclic ASSERT_CONCURRENT_SPEND (opcode 64) SCCs. It includes send/scan/spend pseudocode, K_max, security and compatibility notes (no consensus changes), four full test vectors, a reference implementation link, and appendices on light clients and ECDH vs pairings.

Reviewed by Cursor Bugbot for commit 4be6090. Bugbot is set up for automated code reviews on this repo. Configure here.

@danieljperry danieljperry changed the title chip-kdc2000-silent_payments CHIP-0057: Silent Payments Mar 31, 2026
@danieljperry

Copy link
Copy Markdown
Contributor

This CHIP has been assigned as CHIP-57 and is a Draft. It describes a method to support Unlinkable on-chain payments to a static address using ECDH on BLS12-381.

Please leave your reviews here, and feel free to comment in the #chips channel of our Discord.

@danieljperry

Copy link
Copy Markdown
Contributor

The recording of the discussion of this CHIP is now available on Youtube.

@danieljperry

Copy link
Copy Markdown
Contributor

This CHIP is now in Review. Please leave your reviews here, and be sure to watch the video discussion, linked above.

… fixes

Reconcile the spec to the simplified single-Pass-2 scanning model (v1.5):
- Collapse Pass 2a (puzzle-hash grouping) + Pass 2b into a single Pass 2
  (opcode-64 ASSERT_CONCURRENT_SPEND directed-SCC); drop puzzle-hash grouping.
- Reword the stale "Spend group" glossary definition to concurrent-spend linkage.
- Correct Appendix A bandwidth for Chia's ~1-in-3 transaction-block frequency
  (~1,536 tx blocks/day; ~7.5-9 MB/day total).
- Remove Appendix C (ECDH scalar multiplication).
- Genericize evolving reference-implementation file references.

Status unchanged (Review). Test Vector 4 cryptographic values unchanged.
@kdc2000

kdc2000 commented Jun 3, 2026

Copy link
Copy Markdown
Author

Update on the multi-input grouping, plus a few editorial corrections.

Grouping: strongly connected components over ASSERT_CONCURRENT_SPEND

While implementing silent payments in the chia-wallet-sdk, I adopted strongly-connected-component (SCC) grouping over explicit opcode-64 ASSERT_CONCURRENT_SPEND bindings, and updated the CHIP to match. The scanner builds a directed graph from the ASSERT_CONCURRENT_SPEND conditions in each removal's solution and treats each strongly connected component as a multi-input group.

This replaces the earlier approach, which combined two heuristics: grouping coins that share a puzzle hash, and linking coins via coin announcements (opcode 60/61, CREATE_COIN_ANNOUNCEMENT / ASSERT_COIN_ANNOUNCEMENT). The single SCC mechanism is the one I validated against the SDK implementation.

Test Vector 4's cryptographic values are unchanged.

Editorial corrections

  • Appendix A (bandwidth): corrected for Chia's transaction-block frequency — only ~1 in 3 blocks carries spends (~1,536 tx blocks/day, not ~4,608); daily figures recomputed (~7.5–9 MB/day total).
  • Appendix C (ECDH scalar multiplication): removed — an implementation performance note rather than protocol content.
  • Glossary: reworded the "Spend group" definition.
  • Reference Implementation: framed as illustrative example code (not normative); dropped specific filenames since the example layout evolves.
  • Minor wording in the Indistinguishability section.

Status unchanged (Review).

@danieljperry

Copy link
Copy Markdown
Contributor

Status unchanged (Review).

Absent any new reviews, do you foresee yourself needing to make additional changes to your CHIP? If not, then I could move it to Last Call soon.

@kdc2000

kdc2000 commented Aug 12, 2026

Copy link
Copy Markdown
Author

Status unchanged (Review).

Absent any new reviews, do you foresee yourself needing to make additional changes to your CHIP? If not, then I could move it to Last Call soon.

I've just reviewed this again, and I have a few small changes I would like to make. They should be done by the end of this week.

…ent model

The appendix predated multi-input scanning: it served per-spend (PK, coin ID)
entries with client-side ECDH, which cannot reproduce Pass-2 spend-group
formation (grouping requires executing puzzles/solutions for opcode-64
conditions). Rewritten to server-side grouping serving one precomputed
48-byte tweak point (input_hash * A_sum) per spend group, group-level
transaction cut-through, and bandwidth estimates recomputed at 48 B/group.
The address data part now carries a leading 5-bit version character. v0 =
the 96-byte scan||spend payload (exact length enforced); v1-30 are
forward-compatible (senders use the first 96 payload bytes and ignore
trailing data); v31 is reserved as a must-reject sentinel for
backward-incompatible upgrades. Also specifies the 1,023-character length
allowance (SP addresses exceed bech32's 90-char limit).

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4be6090. Configure here.

Comment thread CHIPs/chip-0057.md
**Verification:**
- Scanner using *b*<sub>scan</sub> + *A* produces the same puzzle hash: YES
- Recipient derives one-time SK = (*b*<sub>spend</sub> + *t*<sub>0</sub>) mod *r* = `3c399c61ae130724903b3b650e936ff042b7646764289a33519e17100a89db37`
- One-time SK * G matches P_0: YES

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test vector spend key mismatch

Medium Severity

Test Vector 1 lists a one-time spend key that is not equal to (b_spend + t_0) mod r from the same vector. The stated verification that this key’s public point matches P_0 cannot hold, so implementations that check or use this hex will fail spend tests.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4be6090. Configure here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive — the arithmetic checks out, including the mod-r reduction:

b_spend + t_0 = 0x53d140b3…b087 + 0x5c560301…2ab1
              = 0xb02743b4d7b0846cc375136d183547f59675086a6426f632519e170f0a89db38

The raw sum exceeds the group order r, and reducing mod r (one subtraction of r) gives exactly the value printed in the vector:

(b_spend + t_0) mod r = 0x3c399c61ae130724903b3b650e936ff042b7646764289a33519e17100a89db37

This is also verified end-to-end by the automated suites that generate these vectors: b_spend is derived from the TV1 mnemonic, and multiplying the resulting one-time SK by G (BLS12-381 scalar multiplication via chia_rs/blst) reproduces the printed P_0 = b671487c…e964d byte-for-byte, in both the Python and Rust reference test suites. No change needed.

Comment thread CHIPs/chip-0057.md
* Let *group*: `list[CoinSpend]` = *removals* whose coin_id is in *scc*
* For each *removal* in *group*: let *pk*<sub>i</sub> = extract_synthetic_pk(*removal*.puzzle_reveal); skip if None
* Let *A*<sub>sum</sub>: `G1` = *pk*<sub>1</sub> + *pk*<sub>2</sub> + ... + *pk*<sub>n</sub>
* Let *group_coin_ids*: `list[bytes32]` = coin IDs of all *removals* in *group*

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass 2 coin set mismatch

High Severity

Pass 2 builds A_sum only from extractable synthetic keys (skip if None) but sets group_coin_ids to every coin in the SCC. Security also requires cycling every input coin. If a non-standard coin joins that cycle, coin_id_L can diverge from the sender’s eligible set and multi-input payments are silently missed.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4be6090. Configure here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The premise requires a non-standard coin inside the opcode-64 binding cycle, which can't arise for a conforming sender and has no recipient-side impact:

  1. No third party can inject a coin into a group. That's precisely what directed-SCC selection provides: an outside spend asserting a victim's coin ID creates a one-way edge and lands in its own trivial SCC (see Scanner Grouping Strategies). Only the sender constructs the cycle.
  2. A conforming sender's cycle contains only standard coins. The Sending procedure takes one synthetic secret key per spent coin — every coin participating in the derivation is a standard-puzzle coin by construction, so within a conforming group skip if None never fires and the scanner's key set and coin-ID set coincide exactly with the sender's. The skip if None branch is defensive handling for nonconforming input, per the Non-Standard Puzzles section.
  3. A nonconforming sender only harms itself. If a sender cycles in a coin it didn't include in its own derivation, the resulting mismatch means that sender's payment isn't detected — a self-inflicted correctness failure with no security or privacy impact on recipients, and no risk to funds (the coin still exists; it's simply not detected as a silent payment).

Given the practical severity is "a sender deviating from the spec breaks its own payment," we don't think a spec change is warranted; the reference implementation additionally rejects mixed-asset bundles outright, so it cannot produce such a group.

@danieljperry

Copy link
Copy Markdown
Contributor

Humans, please leave your further reviews here before the spec is finalized. Discussions can also happen in the #chips channel of our Discord.

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.

2 participants