Skip to content
Open
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d092ef7
Add Silent Payments for the Liquid Network draft
42Pupusas Jun 2, 2026
ab997b4
Condense Silent Payments for Liquid ELIP per reviewer feedback
42Pupusas Jun 2, 2026
ab0c3d6
Condense light-client receive section to a single paragraph
42Pupusas Jun 2, 2026
5f2a62c
Make Taproot the output representation, matching BIP-352 exactly
42Pupusas Jun 2, 2026
0124bbb
Fix SP address HRP collision with native Liquid; drop standalone Labe…
42Pupusas Jun 2, 2026
8d634b1
Scope down Reference Implementation section
42Pupusas Jun 3, 2026
c40601e
Silent Payments for Liquid: pin hash tags, fix Privacy rationale
42Pupusas Jun 29, 2026
520320e
Condense spec to BIP-352 style; fix Privacy, refs, and prose
42Pupusas Jun 29, 2026
237599c
Link both reference implementations (Rust and Python)
42Pupusas Jun 29, 2026
28d6c18
Tighten Differences-from-BIP-352 section and surrounding prose
42Pupusas Jun 29, 2026
89fa834
Add reference implementations in-repo, per ELIPs maintainer request
42Pupusas Jun 30, 2026
d5c61a1
Address reviewer comments: outpoint byte order, bk_k range handling, …
42Pupusas Jul 6, 2026
1639a4c
Address review feedback: t_k semantics, surjection proof, bk_k skip, …
42Pupusas Jul 7, 2026
2bfe65b
Generate real asset surjection proofs in both references
42Pupusas Jul 7, 2026
e0d76e7
Fix wallycore surjectionproof call: the wrapper allocates the buffer …
42Pupusas Jul 7, 2026
c9a8c3a
elip-silent-payments-liquid: add Copyright section
42Pupusas Jul 17, 2026
5842092
Address latest review round: flatten Python reference, drop Rust refe…
42Pupusas Jul 29, 2026
059ff40
Add .gitignore for Python reference-implementation artifacts
42Pupusas Jul 29, 2026
d9e2de3
Drop stale [BIP-352] tags from new Tweak Server section for consistency
42Pupusas Jul 29, 2026
7d2e04e
Condense Tweak Server back into the light-client scanning bullet
42Pupusas Jul 29, 2026
218d909
Tighten tweak server bullet to match doc's symbolic enumeration style
42Pupusas Jul 29, 2026
44be5b9
Address verified findings from second independent review
42Pupusas Jul 29, 2026
c36639d
Extend test vectors: labels, peg-in eligibility, testnet/regtest addr…
42Pupusas Jul 29, 2026
f56b278
Remove incorrect BIP-158 light-client bullet
42Pupusas Jul 29, 2026
ffc5c46
Re-add light-client scanning bullet with correct rationale
42Pupusas Jul 29, 2026
bc8bb5c
Remove redundant reference-impl README
42Pupusas Jul 29, 2026
9a323ad
Remove .gitignore — shouldn't have been committed to the repo
42Pupusas Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
343 changes: 343 additions & 0 deletions elip-silent-payments-liquid.mediawiki
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
<pre>
ELIP: ?
Layer: Applications
Title: Silent Payments for the Liquid Network
Author: 42pupusas
Comments-Summary: No comments yet.
Comments-URI: https://github.com/ElementsProject/elips/wiki/Comments:ELIP-????
Status: Draft
Type: Standards Track
Created: 2026-06-01
License: BSD-3-Clause
</pre>

==Introduction==

===Abstract===

This document specifies Silent Payments for the Liquid Network, building on
[https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki BIP-352]. It assumes
familiarity with BIP-352 and describes only what Liquid requires to differ: the
BIP-352 key-derivation core is reused unchanged, while three things are adapted to
Confidential Transactions (CT) and Liquid's deployed output types — the output
representation, a per-output blinding key derived from the silent-payment shared
secret (so a confidential output can be discovered and unblinded non-interactively),
and a light-client receive flow following the "tweak server" model of the
[https://github.com/silent-payments/BIP0352-index-server-specification BIP-352 index server specification].

===Copyright===

This document is licensed under the 3-clause BSD license.

===Motivation===

Confidential Transactions hide a Liquid output's asset and amount, but its script —
and so any reused address — is public. A receiver accepting many payments to one
published address must today either reuse an address, linking those payments, or run
an interactive protocol to hand out fresh ones.

Silent Payments remove this trade-off, and compose naturally with CT: the receiver
publishes one static address, each sender independently derives a distinct output
only the receiver can recognize, and the payment graph gains the unlinkability of
Silent Payments while amounts keep the confidentiality of CT. This document defines
how to construct and recognize such outputs on Liquid so that independent
implementations interoperate.

==Conventions and Provenance of Each Rule==

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.

* '''[Choice]''' — the rule reflects a design decision for which alternatives exist. Where this document states a value or behavior under a [Choice] tag, that value is the '''preferred''' option of this draft. These are the points on which reviewer input is most actively sought.

Notation follows BIP-352:

* <code>G</code> is the secp256k1 generator; <code>n</code> the curve order.
* Lowercase letters denote scalars (private keys); uppercase letters the corresponding points, e.g. <code>A = a·G</code>.
* <code>serP(P)</code> is the 33-byte compressed encoding of a point <code>P</code>; <code>ser32(i)</code> the 4-byte big-endian encoding of an integer <code>i</code>.
* <code>·</code> is scalar–point multiplication and <code>+</code> is point addition or scalar addition mod <code>n</code> as appropriate.
* <code>hash_tag(m)</code> is the BIP-340 tagged hash <code>SHA256(SHA256(tag) || SHA256(tag) || m)</code> with ASCII tag <code>tag</code>.

==Design==

===Overview===

A receiver holds two key pairs: a '''scan''' key pair <code>(b_scan, B_scan)</code> and
a '''spend''' key pair <code>(b_spend, B_spend)</code>. The static silent-payment
address encodes the two public keys.

To pay the address, a sender:

# 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."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO it's more important that SP for elements/liquid be directly derived from BIP-352. This increases security and it inherits all the review from upstream and can help with re-use of tooling etc.

Currently there is much more P2PKH than TR, but that will likely/hopefully change (as it will on Bitcoin with e.g. more usage of L2 systems using TR addresses and LN using TR addresses).

Also, the anonymity of SP addresses within the larger set of TR addresses is a nice-to-have additional feature, and not the intended privacy goal of SP.


To receive, the receiver re-derives the candidate spend keys and output scripts from
<code>input_hash·A</code> and its scan key, matches them against the transaction's
outputs, and on a match derives the blinding key to unblind and the spend key to spend.

===Receiver keys and address===

The scan and spend key pairs are independent secp256k1 key pairs as in BIP-352. '''[BIP-352]'''
Seed derivation is left to the wallet (a BIP-32 scheme analogous to BIP-352's is
RECOMMENDED), since only the public keys are transmitted. '''[Choice]'''

The address is the Bech32m '''[BIP-352]''' encoding of a version symbol followed by
the payload <code>serP(B_scan) || serP(B_spend)</code> (66 bytes), with a
network-specific human-readable part. '''[Liquid] [Choice]''' This draft uses:

{| class="wikitable"
! Network !! HRP
|-
| Liquid (mainnet) || <code>lqsp</code>
|-
| Liquid testnet / regtest || <code>tlqsp</code>
|}

and version symbol <code>q</code> (the Bech32 character for value 0), denoting
version 0. As in BIP-352, the 90-character Bech32 length limit does '''not''' apply
to silent-payment addresses. '''[BIP-352]'''

A distinct, network-specific HRP is required: it must differ both from Bitcoin's
silent-payment HRP (<code>sp</code>/<code>tsp</code>) '''and''' from the HRPs of
ordinary Liquid addresses — in particular Liquid's '''confidential''' addresses use
<code>lq</code>/<code>tlq</code> (blech32), so a silent-payment HRP of <code>lq</code>
would collide with them. The <code>lqsp</code>/<code>tlqsp</code> HRPs are distinct
from every existing Liquid address HRP (<code>ex</code>/<code>tex</code> for
unconfidential, <code>lq</code>/<code>tlq</code> for confidential) as well as from
Bitcoin's, so a silent-payment address can never be confused with a Bitcoin one, with
a native Liquid one, nor a mainnet address with a testnet one.

===Reused from BIP-352 unchanged '''[BIP-352]'''===

Because silent-payment outputs are Taproot, the entire derivation and output path is
BIP-352 verbatim; the symbols below are restated only because the Liquid blinding key
(next section) is built from <code>S</code> and <code>k</code>:

<pre>
input aggregation: a = Σ a_i, A = a·G (eligible inputs per BIP-352)
input_hash = int(hashBIP0352/Inputs( outpoint_L || serP(A) )) mod n
S = input_hash · a · B_scan (sender) = input_hash · b_scan · A (receiver)
t_k = int(hashBIP0352/SharedSecret( serP(S) || ser32(k) )) mod n
P_k = B_spend + t_k·G
scriptPubKey = OP_1 <x_only(P_k)> (P_k used directly; no script tree, no taptweak)
</pre>

The eligible-input set (P2TR key-path, P2WPKH, P2SH-P2WPKH, P2PKH), the even-Y rule for
Taproot keys, gap-limited scanning, labels (including the change label <code>m = 0</code>),
and the x-only output key are all exactly as in BIP-352 — so a silent-payment output is
itself eligible as an input to a later one.
Two Liquid notes only: <code>outpoint_L</code> uses the Elements consensus outpoint
encoding (32-byte txid, internal order, then 4-byte little-endian vout) '''[Liquid]''',
and the asset and amount are blinded as in any CT output '''[Liquid]''' — see the next
section. The scriptPubKey itself is identical to a BIP-352 output.

===Output blinding key '''[Liquid]'''===

This is the central adaptation required by Confidential Transactions and has no
counterpart in BIP-352.

On Liquid, an output is blinded to a '''blinding key''': the sender places an ECDH
nonce in the output, and the holder of the corresponding blinding private key can
recover the asset, amount, and their blinding factors. For ordinary addresses the
blinding key is derived from the output script (e.g. SLIP-77 or
[https://github.com/ElementsProject/ELIPs/blob/main/elip-0151.mediawiki ELIP-151]).
A silent-payment output's script, however, is not known to the receiver in advance —
it is discovered by scanning — so a script-derived blinding key cannot be used.

This document specifies that the blinding key of a silent-payment output is derived
deterministically from the silent-payment shared secret, in a '''dedicated hash
domain''' disjoint from the spend-key derivation:

<pre>
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.

</pre>

The sender blinds the output to <code>BK_k</code> (i.e. uses <code>BK_k</code> as the
receiver blinding public key when constructing the output's CT nonce, range proof,
and commitments, exactly as for any confidential output). The receiver, having
recomputed <code>S</code>, derives <code>bk_k</code> and unblinds the output. No
out-of-band exchange and no additional interaction are required: the same shared
secret that yields the spend key also yields the blinding key.

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.

from the BIP-352 spend domain <code>BIP0352/SharedSecret</code>. The blinding key is
also unaffected by BIP-352 labels: it depends only on <code>S</code> and <code>k</code>,
not on the labeled spend key.

Two alternatives were rejected. Publishing a single fixed blinding key in the
address would link all of a receiver's outputs through a common blinding key,
negating the unlinkability Silent Payments provides. Exchanging a per-output blinding
key out of band would reintroduce the interaction Silent Payments is designed to
eliminate.

==Light-client receive: the tweak server model==

The light-client receive flow follows the tweak-server model of the
[https://github.com/silent-payments/BIP0352-index-server-specification BIP-352 index server specification]
unchanged: the server publishes a per-transaction '''partial tweak'''
<code>T = input_hash · A</code> (no scan key needed), and the client completes
<code>S = b_scan · T</code> and runs the BIP-352 gap-limit match. '''[BIP-352]''' On a
match it derives <code>bk_k</code> to unblind and <code>b_spend + t_k</code> to spend.
The only divergence is that the BIP-158 compact-filter step is unnecessary on Liquid
'''[Liquid] [Choice]''': Confidential Transactions blind an output's asset, amount,
and nonce but '''not its scriptPubKey''', so a client matches its derived candidate
scripts directly against the public output scripts it already retrieves. Filters MAY
still be used as an optimization but are not part of the protocol. The protocol
therefore requires only that, per block height, a client can obtain that block's
partial tweaks; the concrete wire format is left to a companion specification or
existing Liquid indexing infrastructure. '''[Choice]'''

<pre>
tweaks(block_height) -> [ serP(T_1), serP(T_2), ... ]
</pre>

==Spending a received output '''[BIP-352]'''==

Spending is an ordinary BIP-340 Taproot key-path spend with
<code>d = b_spend + t_k (+ label_tweak_m)</code> (even-Y normalized), exactly as in
BIP-352. Since <code>d</code> is not a BIP-32-derivable key, the only signer-side
requirement is a signer that accepts a base key plus an additive tweak rather than only
keys at a derivation path — a software-signer capability, sufficient to send, receive,
and spend silent-payment outputs.

Hardware-signer support is a separate matter. Common hardware-signer protocols
identify the signing key by a registered descriptor or derivation path and expose no
channel for an additive per-input tweak; signing a silent-payment output on such
devices therefore requires firmware-level support for key tweaks, which is out of
scope for this version and is noted as future work. Hardware support for Silent
Payments is nascent on Bitcoin as well.

==Abstract data structures==

The following abstract structures summarize the values exchanged or derived; field
encodings are as defined above. They are illustrative, not an API.

A silent-payment address:

<pre>
SilentPaymentAddress {
scan_pubkey: serP(B_scan) // 33 bytes
spend_pubkey: serP(B_spend) // 33 bytes; B_spend,m for a labeled address
}
// wire: Bech32m( hrp, version=0, scan_pubkey || spend_pubkey )
</pre>

Aggregated input data computed by a sender:

<pre>
AggregatedInputs {
a: scalar // sender only
A: point // = a·G
input_hash: scalar // = H_Inputs(outpoint_L || serP(A))
}
</pre>

A derived silent-payment output (for index k):

<pre>
SilentPaymentOutput {
spend_pubkey: P_k = B_spend + t_k·G
blinding_pubkey: BK_k = bk_k·G
// scriptPubKey = OP_1 <x_only(P_k)>, asset/amount blinded to BK_k
}
</pre>

The light-client view per eligible transaction:

<pre>
PartialTweak = serP( input_hash · A ) // published by the index/tweak server
</pre>

==Test Vectors==

The following worked example fixes all inputs and lists every intermediate and final
value, so that an independent implementation can reproduce the construction
byte-for-byte. All byte strings are hex.

Receiver keys (32-byte scalars):

<pre>
b_scan = 1111111111111111111111111111111111111111111111111111111111111111
b_spend = 2222222222222222222222222222222222222222222222222222222222222222
</pre>

Two eligible inputs (any BIP-352-eligible type), with private keys and outpoints:

<pre>
input 0: priv = 3131...31 (0x31 x32), outpoint txid = 1010...10 (0x10 x32), vout = 0
input 1: priv = 3232...32 (0x32 x32), outpoint txid = 2020...20 (0x20 x32), vout = 1
</pre>

Aggregated input values (outpoint_L is input 0, the lexicographically smaller):

<pre>
A = 031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99
input_hash = d392922c00280a7e8d282182f5026f2fddbc74c1e1de18b4822128b2b77ec641
</pre>

Per-output derived values:

<pre>
k = 0:
P_k (spend pubkey) = 02a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931
BK_k (blinding pub) = 0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc
bk_k (blinding priv) = 70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0
spend priv (b_spend + t_k) = f03c436d2cd67ae1fecf7d88a38aa3a03c0abea43feaf6da8eb71e2e3a866bda
scriptPubKey = 5120a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931

k = 1:
P_k (spend pubkey) = 0229d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
BK_k (blinding pub) = 03efdeda770ccdbe8bf466fba48bfd2b2c436ab0c04658fc6d6c277de5078129fa
bk_k (blinding priv) = 945ba73a9804f62089c7d2ffdc079031031f0aebab372cec17ef9c110ebceb10
spend priv (b_spend + t_k) = 9eff3472230fc83ef5ea8f8c80401c4eecd595a048bd2482a107d3a49baa5a58
scriptPubKey = 512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
</pre>

The unlabeled mainnet (HRP <code>lqsp</code>) address for these keys:

<pre>
lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe
</pre>

A conforming implementation MUST reproduce <code>A</code>, <code>input_hash</code>,
and for each <code>k</code> the values <code>P_k</code>, <code>BK_k</code>,
<code>bk_k</code>, the spend private key, and the scriptPubKey, and MUST produce the
address above. Because the asset and amount blinding factors are randomized per
output, the full blinded output is not byte-reproducible; the recovery property is
instead stated as: an output blinded to <code>BK_k</code> by the construction above
unblinds correctly under <code>bk_k</code>, and fails to unblind under any other key.

==Backwards Compatibility==

This document defines a new, opt-in address type and output convention. It introduces
no consensus change and does not affect existing addresses, descriptors, or
transactions. Wallets that do not implement it are unaffected; a silent-payment
output, once created, is an ordinary confidential Taproot output on the chain and is
spent by an ordinary key-path signature, so existing relay and validation rules apply
unchanged.

Discovering and spending silent-payment outputs requires wallet support (scanning and
tweak-aware signing). Hardware signers require firmware support for additive key
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.


A reference implementation, built on the cryptographic primitives of the Liquid Wallet
Kit (LWK), reproduces the test vectors in this document byte-for-byte and demonstrates
that a confidential output blinded to the shared-secret-derived key can be unblinded
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


This specification builds directly on BIP-352 and the BIP-352 index server
specification, and on the Confidential Transactions and CT-descriptor work of the
Elements Project.