Add Silent Payments for the Liquid Network#37
Conversation
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.
|
|
||
| ==Reference Implementation== | ||
|
|
||
| A reference implementation demonstrating address encoding, input aggregation, |
There was a problem hiding this comment.
A reference implementation in python would be nice to review this ELIP
There was a problem hiding this comment.
I have working examples using LWK in Rust, is that acceptable? not too familiar with Python
There was a problem hiding this comment.
There was a problem hiding this comment.
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?
| 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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== |
There was a problem hiding this comment.
JAN3 should be in the acknowledgements if this BIP is awarded the bounty
| tweaks, which does not exist in common protocols today; until then, silent-payment | ||
| outputs are usable with software signing. | ||
|
|
||
| ==Reference Implementation== |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
There should also be implementation of the tweak server and decryption by the wallet
| # 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]'''. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
| 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. |
There was a problem hiding this comment.
| * '''[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
There was a problem hiding this comment.
will push a condensed revision once a path has been settled for TR vs P2PKH
| bk_k = hashLiquidSilentPayments/Blind( serP(S) || ser32(k) ) (a 32-byte scalar) | ||
| BK_k = bk_k·G |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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.