Skip to content

Add Silent Payments for the Liquid Network#37

Open
42Pupusas wants to merge 10 commits into
ElementsProject:mainfrom
42Pupusas:elip-silent-payments-liquid
Open

Add Silent Payments for the Liquid Network#37
42Pupusas wants to merge 10 commits into
ElementsProject:mainfrom
42Pupusas:elip-silent-payments-liquid

Conversation

@42Pupusas

Copy link
Copy Markdown

This ELIP specifies BIP-352 Silent Payments adapted to the Liquid Network's Confidential Transactions. Each normative rule is tagged as BIP-352-derived, a Liquid adaptation, or an open design choice (stated preferentially). Includes worked test vectors and abstract data structures.

A Standards Track draft specifying BIP-352 Silent Payments adapted to the
Liquid Network's Confidential Transactions. Each normative rule is tagged
as BIP-352-derived, a Liquid adaptation, or an open design choice (stated
preferentially). Includes worked test vectors and abstract data structures.
Comment thread elip-silent-payments-liquid.mediawiki Outdated

==Reference Implementation==

A reference implementation demonstrating address encoding, input aggregation,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A reference implementation in python would be nice to review this ELIP

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.

I have working examples using LWK in Rust, is that acceptable? not too familiar with Python

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.

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.

I attempted to wire in everything together in a Python implementation, but the blinding logic for Confidential Transactions exposed on the Rust crate is not as readily available on python tooling, and my skills in python are not good enough to rewire everything to ensure it will work correctly.

I have tried to clean up the Rust implementation and added more comments throughout mapping to the ELIP documentation. I hope that will sufficient?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Have you tried using wallycore? If that doesnt work maybe we can replace LWK with a more lighter elements crate?

Comment thread elip-silent-payments-liquid.mediawiki Outdated
Comment thread elip-silent-payments-liquid.mediawiki Outdated
Comment thread elip-silent-payments-liquid.mediawiki Outdated
Because <code>bk_k</code> and <code>t_k</code> are outputs of a random oracle (a
tagged hash) evaluated on '''disjoint domains''' over the same secret <code>S</code>,
they are independent: knowledge of one does not assist in recovering the other or
<code>S</code>. The domain tag <code>LiquidSilentPayments/Blind</code> MUST differ

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Would a third person who wants to detect silent payments, be able to compute blinding keys for each transaction and then check if it unblinds to identify Silent payments?

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.

It should not be possible as this is using the same hardness assumption from BIP-352 that the blinding key is computed from the sender+receiver Diffie Hellman shared secret, which should never be computable by third parties.

Comment thread elip-silent-payments-liquid.mediawiki Outdated
42Pupusas added 5 commits June 2, 2026 15:22
Trim BIP-352 re-explanation to focus on Liquid-specific differences:
- Shorten abstract and motivation prose
- Reduce BIP-352-compliant sections to brief restatements
- Remove Rationale section (reasoning is stated inline in each
  [Liquid]/[Choice] design section)
The flow is essentially identical to BIP-352; fold the three subsections
(filter omission, server interface) into one paragraph plus the interface
example.
Switch silent-payment outputs from confidential P2WPKH to confidential
Taproot (OP_1 <x_only(P_k)>), with P_k used directly per BIP-352 (no
script tree, no taptweak). This aligns the output, spend path, and
index-server data with BIP-352's x-only conventions verbatim.

- Output is now Taproot-only; eligible inputs keep BIP-352's full set
  (P2TR, P2WPKH, P2SH-P2WPKH, P2PKH), so an SP output is itself eligible
  as a later input and the even-Y rule applies unchanged.
- Spending is an ordinary BIP-340 key-path spend (even-Y normalized).
- Collapse the now-pure-BIP-352 sections (input aggregation, eligible
  inputs, shared secret, output representation) into one consolidated
  'Reused from BIP-352 unchanged' section.
- Update test vectors (Taproot scriptPubKeys) and reference-implementation
  note (verified against LWK, reproduces vectors byte-for-byte).
…ls section

The previous lq/tlq HRP collided with ordinary Liquid CONFIDENTIAL
addresses (blech32 lq/tlq), defeating the distinct-HRP goal. Switch to
lqsp/tlqsp, which differ from every existing Liquid HRP (ex/tex
unconfidential, lq/tlq confidential) and from Bitcoin's sp/tsp. Update
the test-vector address accordingly (verified against LWK).

Also fold the Labels section into the consolidated BIP-352 reused
section (labels need no Liquid adaptation); the one substantive note —
the blinding key is label-independent — moves to the blinding-key section.
Reduce the Reference Implementation paragraph to what the public
reference covers: it reproduces the test vectors byte-for-byte and
demonstrates non-interactive unblinding of the shared-secret-blinded
output. Wallet integration (scanning, signing, transaction building) is
left to implementations, rather than enumerated here.
non-interactively by the receiver. Wallet integration — scanning, signing, and
transaction building — is left to implementations.

==Acknowledgements==

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JAN3 should be in the acknowledgements if this BIP is awarded the bounty

Comment thread elip-silent-payments-liquid.mediawiki Outdated
tweaks, which does not exist in common protocols today; until then, silent-payment
outputs are usable with software signing.

==Reference Implementation==

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The point of the reference implementation in the BIP is like a sort of documentation. I will review your rust code but I feel it should be in python, or something thats close to human readable code. You can check https://github.com/bitcoin/bips/blob/master/bip-0352/reference.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There should also be implementation of the tweak server and decryption by the wallet

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Had a cursory look at https://github.com/42Pupusas/elip-sp-reference/blob/main/src/lib.rs and it looks good. Im just concerned if rust is human readable enough for a BIP.

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.

Ok the wallycore for sure was the easier path in Python. I reused most of the BIP-352 reference implementation that uses libsecp256klab and a bech32 impl from Peter Wuille, then added wallycore blinding functions where appropriate to create a parallel Python reference implementation.

I am not a good judge of whether the Python code is more human readable as I have strong biases against Python and for Rust, but that particular bikeshed is not worth exploring, so I do hope the review is easier with the new Python code.

Comment thread elip-silent-payments-liquid.mediawiki Outdated
# aggregates the private keys of its eligible transaction inputs into a single scalar <code>a</code> and forms <code>A = a·G</code> '''[BIP-352]''';
# computes a transaction-bound <code>input_hash</code> and an ECDH shared secret <code>S</code> with the receiver's scan key '''[BIP-352]''';
# derives, for output index <code>k</code>, a spend public key <code>P_k</code> that only the receiver can later re-derive '''[BIP-352]''';
# places <code>P_k</code> in a Taproot (P2TR) output '''[BIP-352]''', and blinds that output's asset and amount to a blinding key that is itself derived from <code>S</code> '''[Liquid]'''.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actually, we need to think harder on this. On-chain, there is actually some Taproot usage, so SP payments get masked. But on liquid, if there is no usage at all, we might run into issues.

A taproot output will definitely be SP in liquid I think. We need to analyse usage

@42Pupusas 42Pupusas Jun 18, 2026

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.

A quick 500 block scan shows that Taproot usage is very small, around 3% .

So a TR output won't "definitely" be SP, but the anonymity set is quite small.

Is this enough nudge to drop taproot and use p2pkh instead?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I feel P2PKH is better, but I would like to get a second opinion on this. More specifically, understand why the mainchain decided to use Taproot.

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.

I was digging through the old BIP352 threads last weeks to get more clarity for the taproot output being the default.

I was unable to find much discussion around it, but here is some explicit comments on it I found:

Also to consider from the implementation difference is that with TR output , the script is the x-only key, so we only do direct x-coordinate comparison. With P2PKH the scanner has to serialize compressed, HASH160, then compare. The overhead is not immense but should be considered.

All three have their merits I can see, just unsure of the tradeoff with anonymity set size in Liquid.

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.

Also the BIP-352 itself generalizes the choices to just

"^ Why only taproot outputs? Providing too much optionality for the protocol makes it difficult to implement and can be at odds with the goal of providing the best privacy. Limiting to taproot outputs helps simplify the implementation significantly while also putting users in the best eventual anonymity set."

Comment thread elip-silent-payments-liquid.mediawiki Outdated
Throughout this document, every normative rule is tagged to make its origin explicit:

* '''[BIP-352]''' — the rule is taken unchanged from BIP-352. Implementations SHOULD reuse existing, reviewed BIP-352 logic for these parts.
* '''[Liquid]''' — the rule is an adaptation made necessary by a structural difference between Liquid and Bitcoin (most importantly, Confidential Transactions and Liquid's deployed output types). These are the substantive technical contributions of this document.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
* '''[Liquid]''' — the rule is an adaptation made necessary by a structural difference between Liquid and Bitcoin (most importantly, Confidential Transactions and Liquid's deployed output types). These are the substantive technical contributions of this document.
* '''[Liquid]''' — the rule is an adaptation due to Confidential Transactions and Liquid's deployed output types.

Theres still a lot of AI generated verboseness, imo. I feel they can be cleaned up and we dont need to add anything new in this BIP than whats absolutely needed.

For me it feels like ===Output blinding key '''[Liquid]'''=== is the only real specification in this doc and the remaining spec is just to use exisiting BIP 352 logic. Everything can be condensed imo

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.

will push a condensed revision once a path has been settled for TR vs P2PKH

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please do not wait for me to get back to make the changes as I may take a while to reply and another reviewer might want to review this, etc. Try to make the PR as up to date as possible whenever you get time to work on it.

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.

Condensed the ELIP to only the specific changes needed for Liquid, reusing and adoption the notation conventions in BIP-352.

Comment thread elip-silent-payments-liquid.mediawiki Outdated
Comment on lines +155 to +156
bk_k = hashLiquidSilentPayments/Blind( serP(S) || ser32(k) ) (a 32-byte scalar)
BK_k = bk_k·G

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This bit is still not clear to me.

A and S can be computed by a third party. What is k?

Actually what is hashLiquidSilentPayments/Blind( serP(S) || ser32(k) ) ? can we just write the math and not generic fn names

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.

k is the output index counter the sender chooses and keeps for a specific SP address (0 first payment, +1 recurring), not transmitted on-chain.

A third party who knows the receiver's address can compute A and S from public data, but to derive the blinding key for a specific output they'd have to brute-force the gap limit × every address × every transaction.

can we just write the math and not generic fn names

is following the notation style in BIP-352 ok?

so changing to:

tag = "LiquidSilentPayments/Blind"
bk_k = int( SHA256(SHA256(tag)||SHA256(tag) || serP(S) || ser32(k) ) ) mod n

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  • Pin the exact ASCII tag string (LiquidSilentPayments/Blind) verbatim, since interop depends on every implementation hashing the identical bytes. A one-character difference silently breaks unblinding across wallets.

  • Define it the same way BIP-352 defines its own: state the BIP-340 tagged-hash construction once in the conventions section then use the named form hashLiquidSilentPayments/Blind(...). Please note that LiquidSilentPayments/Blind should be in the subscript like BIP-352

Image

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A third party who knows the receiver's address can compute A and S from public data, but to derive the blinding key for a specific output they'd have to brute-force the gap limit × every address × every transaction.

This is wrong. I was wrong earlier, a third party cannot compute S and that's the security. Brute-forcing isn't hard

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 new Privacy section should now be clearer and explicit about where the privacy and security assumptions come from.

Conventions: state the BIP-340 tagged-hash construction once and pin the exact
ASCII tag strings (incl. LiquidSilentPayments/Blind, 26 bytes) in a table;
switch the derivation steps to the named hash_<tag>() subscript form, matching
BIP-352 and removing the define-after-use tag_* inline forms.

Privacy: rewrite the rationale. The previous text wrongly claimed S is
derivable from the public A and B_scan; that is the computational
Diffie-Hellman problem. Privacy rests on the secrecy of b_scan; the address is
not secret and the output index k is not secret.

Align tweak terminology to BIP-352 (partial tweak -> tweak).

README: add the ELIP index row.
@42Pupusas 42Pupusas force-pushed the elip-silent-payments-liquid branch from 276f423 to 28d6c18 Compare June 29, 2026 22:32
spent by an ordinary key-path signature, so existing relay and validation rules apply.
Wallets that don't implement this are unaffected.

==Reference Implementations==

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This should be moved to this repo. Follow the convention in https://github.com/bitcoin/bips/blob/master/bip-0352/reference.py and use MIT license

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Okay ignore my comment about MIT license as other ELIPS are BSD license as well

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