From d092ef7f1950cbdc1d16463a20982c93dcb66092 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Mon, 1 Jun 2026 19:58:19 -0600 Subject: [PATCH 01/10] Add Silent Payments for the Liquid Network draft 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. --- elip-silent-payments-liquid.mediawiki | 515 ++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 elip-silent-payments-liquid.mediawiki diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki new file mode 100644 index 0000000..a9907b3 --- /dev/null +++ b/elip-silent-payments-liquid.mediawiki @@ -0,0 +1,515 @@ +
+  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
+
+ +==Introduction== + +===Abstract=== + +This document specifies Silent Payments for the Liquid Network. Silent Payments, +defined for Bitcoin in [https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki BIP-352], +allow a receiver to publish a single static address from which a sender derives a +fresh, unlinkable on-chain output for every payment, without any interaction and +without leaving a reusable public identifier on the chain. + +Applying this construction to Liquid requires reconciling it with Confidential +Transactions (CT), in which every output additionally commits to a blinded asset +and amount and carries an ECDH nonce that allows the intended receiver to recover +those values. This document reuses the BIP-352 key-derivation core unchanged, adapts +the output representation to Liquid's deployed output types, and introduces a +deterministic derivation of the per-output blinding key from the silent-payment +shared secret so that a confidential output can be both discovered and unblinded by +the receiver without any additional round trip. It further specifies 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=== + +Address reuse harms the privacy of all participants on a transparent or +semi-transparent ledger. Confidential Transactions hide the asset and amount of a +Liquid output, but the output script — and therefore an address that is reused — is +public. A receiver who wishes to accept many payments to a published address +(donations, a point-of-sale identifier, a recurring counterparty) must today either +reuse an address, linking those payments, or run an interactive protocol to hand out +fresh addresses. + +Silent Payments remove this trade-off: the receiver publishes one static address, +and each sender independently derives a distinct output that only the receiver can +recognize. On Liquid this is particularly valuable, because it composes naturally +with CT — the payment graph gains the unlinkability of Silent Payments while the +amounts retain 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. +* '''[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; the rationale and the alternatives considered are recorded in the [[#Rationale|Rationale]] section. These are the points on which reviewer input is most actively sought. + +Notation follows BIP-352: + +* G is the secp256k1 generator; n the curve order. +* Lowercase letters denote scalars (private keys); uppercase letters the corresponding points, e.g. A = a·G. +* serP(P) is the 33-byte compressed encoding of a point P; ser32(i) the 4-byte big-endian encoding of an integer i. +* · is scalar–point multiplication and + is point addition or scalar addition mod n as appropriate. +* hash_tag(m) is the BIP-340 tagged hash SHA256(SHA256(tag) || SHA256(tag) || m) with ASCII tag tag. + +==Design== + +===Overview=== + +A receiver holds two key pairs: a '''scan''' key pair (b_scan, B_scan) and +a '''spend''' key pair (b_spend, B_spend). 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 a and forms A = a·G '''[BIP-352]'''; +# computes a transaction-bound input_hash and an ECDH shared secret S with the receiver's scan key '''[BIP-352]'''; +# derives, for output index k, a spend public key P_k that only the receiver can later re-derive '''[BIP-352]'''; +# places P_k in a Liquid output of the deployed type '''[Liquid]''', and blinds that output's asset and amount to a blinding key that is itself derived from S '''[Liquid]'''. + +To receive, the receiver (or a light client acting on its behalf) obtains, for each +candidate transaction, the value input_hash·A, completes the shared +secret with its scan key, re-derives the candidate spend keys and their output +scripts, matches them against the transaction's outputs, and — on a match — derives +the same blinding key to unblind the asset and amount and the spend key to later +spend the output. + +The remainder of this section defines each step. + +===Receiver keys and address=== + +The receiver's scan and spend key pairs are independent secp256k1 key pairs. '''[BIP-352]''' +Their derivation from a seed is left to the wallet; a deterministic scheme analogous +to BIP-352's (a dedicated purpose under BIP-32) is RECOMMENDED but out of scope for +interoperability, since only the public keys are transmitted. '''[Choice]''' + +The address is the Bech32m '''[BIP-352]''' encoding of a version symbol followed by +the payload serP(B_scan) || serP(B_spend) (66 bytes), with a +network-specific human-readable part. '''[Liquid] [Choice]''' This draft uses: + +{| class="wikitable" +! Network !! HRP +|- +| Liquid (mainnet) || lq +|- +| Liquid testnet / regtest || tlq +|} + +and version symbol q (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 (rather than Bitcoin's sp/tsp) +ensures a Liquid silent-payment address can never be confused with a Bitcoin one, +nor a mainnet address with a testnet one. + +===Input aggregation and the input hash=== + +A sender forms the aggregated input private key by summing the private keys of all +'''eligible''' inputs (defined below): '''[BIP-352]''' + +
+a = a_1 + a_2 + ... + a_u   (mod n)
+A = a·G
+
+ +If a = 0 the sender MUST abort: no payment is defined. '''[BIP-352]''' + +Let outpoint_L be the lexicographically smallest input outpoint, encoded +as in a transaction (32-byte txid in internal byte order followed by the 4-byte +little-endian index — identical to the Elements consensus encoding of an outpoint). +The input hash is: '''[BIP-352]''' + +
+input_hash = int(hashBIP0352/Inputs( outpoint_L || serP(A) )) mod n
+
+ +====Eligible inputs '''[Liquid] [Choice]'''==== + +An input is eligible to contribute to a (sender) and A +(scanner) if and only if it is spent by a single public key whose value is +unambiguously recoverable from the input, and it is not an issuance, reissuance, or +peg-in input. Concretely this draft includes: + +* P2WPKH (elwpkh) — the dominant Liquid output type — taking the public key from the witness; +* P2SH-wrapped P2WPKH — likewise; +* P2PKH — taking the public key from the scriptSig. + +and excludes issuance/reissuance inputs, peg-in inputs, the fee output (which is +never an input), and any input whose signing public key is not uniquely determined. + +This mirrors BIP-352's eligible-input set with two Liquid-specific notes. First, +Liquid blinds the asset and amount of an input's spent output, but the '''signing +public key''' used here is the one revealed in the witness/scriptSig and is +independent of blinding; aggregation therefore proceeds exactly as on Bitcoin. +Second, BIP-352's requirement to negate Taproot input keys to an even Y-coordinate +before summing does '''not''' apply here, because the eligible types above are not +Taproot key-path spends; keys are summed directly. Should Liquid Taproot key-path +spends become eligible in a future version, the BIP-352 even-Y rule would apply to +them unchanged. + +===Shared secret and output spend key=== + +Both parties compute the same ECDH shared secret: '''[BIP-352]''' + +
+sender:    S = input_hash · a · B_scan
+receiver:  S = input_hash · b_scan · A
+
+ +For each output index k = 0, 1, 2, ... paid to the same recipient in the +same transaction: '''[BIP-352]''' + +
+t_k = int(hashBIP0352/SharedSecret( serP(S) || ser32(k) )) mod n
+P_k = B_spend + t_k·G
+
+ +The receiver, upon recognizing an output at index k, can spend it with +the private key b_spend + t_k (mod n). '''[BIP-352]''' + +===Output representation '''[Liquid] [Choice]'''=== + +In BIP-352 the output is a P2TR output whose key is P_k, visible in the +clear. Liquid does not deploy Taproot in general wallet usage, and the overwhelmingly +common Liquid output type is elwpkh (version-0 witness public-key hash). +This draft therefore defines a silent-payment output as a '''confidential +version-0 P2WPKH output''' whose witness program is HASH160(serP(P_k)): + +
+scriptPubKey = OP_0 
+
+ +with the asset and amount blinded as in any CT output (see the next section). + +A consequence is that the value an index server publishes per transaction is a +'''compressed''' point (or equivalently the candidate scripts derived from it), +rather than the x-only key used in the Bitcoin index-server model. + +Emulating Taproot via Liquid's tapscript support was considered and is deferred: +it adds deployment surface for negligible benefit given current Liquid usage. A +future version MAY define a Taproot output type, at which point the BIP-352 x-only +conventions would apply to it directly. + +===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: + +
+bk_k = hashLiquidSilentPayments/Blind( serP(S) || ser32(k) )   (a 32-byte scalar)
+BK_k = bk_k·G
+
+ +The sender blinds the output to BK_k (i.e. uses BK_k 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 S, derives bk_k 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 bk_k and t_k are outputs of a random oracle (a +tagged hash) evaluated on '''disjoint domains''' over the same secret S, +they are independent: knowledge of one does not assist in recovering the other or +S. The domain tag LiquidSilentPayments/Blind MUST differ +from the BIP-352 spend domain BIP0352/SharedSecret. + +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. + +===Labels '''[BIP-352]'''=== + +Labels are supported exactly as in BIP-352. For an integer label m: + +
+label_tweak_m = int(hashBIP0352/Label( ser256(b_scan) || ser32(m) )) mod n
+B_spend,m     = B_spend + label_tweak_m·G
+
+ +A labeled address encodes B_spend,m in place of B_spend; the +scan key is unchanged. The output spend key becomes B_spend,m + t_k·G and +is spendable with b_spend + t_k + label_tweak_m (mod n). Label +m = 0 is reserved to mark change. The output blinding key is unaffected +by labels: it depends only on S and k. + +Implementations MAY support only the change label (m = 0) in a first +version. '''[Choice]''' + +==Light-client receive: the tweak server model== + +The receive bounty for this work requires following the tweak-server model of the +[https://github.com/silent-payments/BIP0352-index-server-specification BIP-352 index server specification]. +That model has a server compute, per silent-payment-eligible transaction, the +'''partial tweak''' '''[BIP-352]''': + +
+T = input_hash · A
+
+ +a single public key that requires no scan key to compute. A light client fetches the +partial tweaks for a block height, completes the shared secret locally with its scan +key, and matches: + +
+S = b_scan · T   (= input_hash · b_scan · A)
+
+ +For each k, the client derives P_k and the candidate output +script and checks it against the transaction's outputs, advancing k +until a configurable number of consecutive misses (a gap limit), since multiple +outputs may be paid to the same address. '''[BIP-352]''' On a match the client +derives bk_k to unblind and b_spend + t_k to spend. + +===Matching on Liquid omits compact filters '''[Liquid] [Choice]'''=== + +In the Bitcoin index-server model the client cannot recompute an output's +scriptPubKey from chain data without first guessing the tweak, so it uses +[https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki BIP-158] compact +block filters to test candidate scripts and then downloads only matching blocks. + +On Liquid this intermediate filtering step is unnecessary: Confidential Transactions +blind the asset, amount, and nonce of an output but '''not its scriptPubKey''', so +output scripts are available in the clear from ordinary block or transaction data. +A client that has derived a candidate script can therefore match it directly against +the transaction outputs it already retrieves. Accordingly this draft does not require +BIP-158 filters; an implementation MAY still use a filter or other index as an +optimization, but it is not part of the protocol. This is the one substantive +deviation from the Bitcoin index-server model, and it is forced by — and made safe +by — Liquid's public output scripts. + +===Server interface '''[Choice]'''=== + +The protocol requires only that, for each block height, a client can obtain the list +of partial tweaks T for that block's silent-payment-eligible +transactions. An abstract client interface is therefore minimal: + +
+tweaks(block_height) -> [ serP(T_1), serP(T_2), ... ]
+
+ +A concrete wire format is intentionally left to a companion specification or to +existing Liquid indexing infrastructure; for example a REST endpoint returning an +array of compressed points per height, or an extension of an existing +descriptor-oriented indexing service. The cryptographic protocol in this document is +independent of that choice. + +==Spending a received output '''[Liquid]'''== + +A received silent-payment output is controlled by the scalar +d = b_spend + t_k (+ label_tweak_m), which is generally '''not''' a +BIP-32-derivable key. Signing such an input therefore requires a signer that accepts +a base key and an additive tweak (or equivalently the tweaked key directly), rather +than only keys located at a derivation path. + +This is a software-signer capability: deriving d and producing an +ordinary signature for the corresponding P2WPKH input is straightforward and uses no +new cryptography. It is the only signer-side change Silent Payments require on +Liquid, and it is sufficient for a wallet to send to, receive from, 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: + +
+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 )
+
+ +Aggregated input data computed by a sender: + +
+AggregatedInputs {
+    a:          scalar             // sender only
+    A:          point              // = a·G
+    input_hash: scalar             // = H_Inputs(outpoint_L || serP(A))
+}
+
+ +A derived silent-payment output (for index k): + +
+SilentPaymentOutput {
+    spend_pubkey:  P_k = B_spend + t_k·G
+    blinding_pubkey: BK_k = bk_k·G
+    // scriptPubKey = OP_0 , asset/amount blinded to BK_k
+}
+
+ +The light-client view per eligible transaction: + +
+PartialTweak = serP( input_hash · A )      // published by the index/tweak server
+
+ +==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): + +
+b_scan  = 1111111111111111111111111111111111111111111111111111111111111111
+b_spend = 2222222222222222222222222222222222222222222222222222222222222222
+
+ +Two eligible inputs, each a P2WPKH spend, with private keys and outpoints: + +
+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
+
+ +Aggregated input values (outpoint_L is input 0, the lexicographically smaller): + +
+A          = 031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99
+input_hash = d392922c00280a7e8d282182f5026f2fddbc74c1e1de18b4822128b2b77ec641
+
+ +Per-output derived values: + +
+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 = 0014aad1065758c02efbf32e797f108007741068eb25
+
+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 = 001481bc88e345e0a585da4ee2157eef9e18bff416cf
+
+ +The unlabeled mainnet (HRP lq) address for these keys: + +
+lq1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyu7836wf
+
+ +A conforming implementation MUST reproduce A, input_hash, +and for each k the values P_k, BK_k, +bk_k, 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 BK_k by the construction above +unblinds correctly under bk_k, and fails to unblind under any other key. + +==Rationale== + +'''Why reuse the BIP-352 key derivation unchanged.''' The spend-key path +(a, A, input_hash, S, +t_k, P_k, labels) is the reviewed cryptographic core of +Silent Payments. Reusing it verbatim maximizes interoperability with existing +implementations and tooling and confines the novel surface to the parts Liquid +actually forces to differ. + +'''Why a confidential P2WPKH output rather than P2TR ([Choice]).''' Liquid wallets +overwhelmingly use elwpkh; using the deployed type avoids a Taproot +dependency and matches existing relay and wallet behavior. The cost is divergence +from BIP-352's x-only convention and a compressed-point value in the index-server +data. A future Taproot output type can be added without disturbing the rest of this +specification. + +'''Why derive the blinding key from the shared secret ([Liquid]).''' This is the +mechanism that lets a confidential output be both discovered and unblinded +non-interactively. A dedicated hash domain keeps the blinding key independent of the +spend key. The security of the scheme reduces to the random-oracle treatment of the +tagged hash and the secrecy of S (which only the sender and the holder +of b_scan can compute); a formal security argument and adversarial +analysis are expected to accompany review. The construction has been validated by a +reference implementation against the test vectors above, including adversarial cases +(an observer holding all public data but not b_scan cannot unblind; +incorrect keys never unblind; spend and blinding values are independent across many +randomized cases). + +'''Why omit BIP-158 on Liquid ([Choice]).''' The index-server filter step exists to +let a client test candidate scripts without downloading every block. Because Liquid +output scripts are public, a client can match candidate scripts directly, so the +filter step is redundant. This is a deliberate, narrowly-scoped deviation from the +Bitcoin index-server model, justified entirely by Liquid's public scripts; it does +not change the cryptographic protocol. + +'''Why a distinct, network-specific HRP ([Choice]).''' To make cross-chain and +cross-network address confusion impossible. + +==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 P2WPKH output on the chain and is +spent by an ordinary 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== + +A reference implementation demonstrating address encoding, input aggregation, +sender output derivation, the shared-secret-derived blinding key, confidential output +construction and recovery, the tweak-server scan flow, labels, and tweak-aware +signing, together with the test vectors and adversarial tests referenced above, is +expected to accompany this proposal prior to a status of Final. + +==Acknowledgements== + +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. From ab997b4a94945bf0bdc73ccbd040bb1b581f5eb9 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Tue, 2 Jun 2026 15:22:48 -0600 Subject: [PATCH 02/10] Condense Silent Payments for Liquid ELIP per reviewer feedback 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) --- elip-silent-payments-liquid.mediawiki | 210 +++++++------------------- 1 file changed, 55 insertions(+), 155 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index a9907b3..d02363f 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -15,21 +15,14 @@ ===Abstract=== -This document specifies Silent Payments for the Liquid Network. Silent Payments, -defined for Bitcoin in [https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki BIP-352], -allow a receiver to publish a single static address from which a sender derives a -fresh, unlinkable on-chain output for every payment, without any interaction and -without leaving a reusable public identifier on the chain. - -Applying this construction to Liquid requires reconciling it with Confidential -Transactions (CT), in which every output additionally commits to a blinded asset -and amount and carries an ECDH nonce that allows the intended receiver to recover -those values. This document reuses the BIP-352 key-derivation core unchanged, adapts -the output representation to Liquid's deployed output types, and introduces a -deterministic derivation of the per-output blinding key from the silent-payment -shared secret so that a confidential output can be both discovered and unblinded by -the receiver without any additional round trip. It further specifies a light-client -receive flow following the "tweak server" model of the +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=== @@ -38,22 +31,17 @@ This document is licensed under the 3-clause BSD license. ===Motivation=== -Address reuse harms the privacy of all participants on a transparent or -semi-transparent ledger. Confidential Transactions hide the asset and amount of a -Liquid output, but the output script — and therefore an address that is reused — is -public. A receiver who wishes to accept many payments to a published address -(donations, a point-of-sale identifier, a recurring counterparty) must today either -reuse an address, linking those payments, or run an interactive protocol to hand out -fresh addresses. +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: the receiver publishes one static address, -and each sender independently derives a distinct output that only the receiver can -recognize. On Liquid this is particularly valuable, because it composes naturally -with CT — the payment graph gains the unlinkability of Silent Payments while the -amounts retain the confidentiality of CT. - -This document defines how to construct and recognize such outputs on Liquid so that -independent implementations interoperate. +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== @@ -61,7 +49,7 @@ Throughout this document, every normative rule is tagged to make its origin expl * '''[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. -* '''[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; the rationale and the alternatives considered are recorded in the [[#Rationale|Rationale]] section. These are the points on which reviewer input is most actively sought. +* '''[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: @@ -86,21 +74,15 @@ To pay the address, a sender: # derives, for output index k, a spend public key P_k that only the receiver can later re-derive '''[BIP-352]'''; # places P_k in a Liquid output of the deployed type '''[Liquid]''', and blinds that output's asset and amount to a blinding key that is itself derived from S '''[Liquid]'''. -To receive, the receiver (or a light client acting on its behalf) obtains, for each -candidate transaction, the value input_hash·A, completes the shared -secret with its scan key, re-derives the candidate spend keys and their output -scripts, matches them against the transaction's outputs, and — on a match — derives -the same blinding key to unblind the asset and amount and the spend key to later -spend the output. - -The remainder of this section defines each step. +To receive, the receiver re-derives the candidate spend keys and output scripts from +input_hash·A 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 receiver's scan and spend key pairs are independent secp256k1 key pairs. '''[BIP-352]''' -Their derivation from a seed is left to the wallet; a deterministic scheme analogous -to BIP-352's (a dedicated purpose under BIP-32) is RECOMMENDED but out of scope for -interoperability, since only the public keys are transmitted. '''[Choice]''' +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 serP(B_scan) || serP(B_spend) (66 bytes), with a @@ -124,24 +106,12 @@ nor a mainnet address with a testnet one. ===Input aggregation and the input hash=== -A sender forms the aggregated input private key by summing the private keys of all -'''eligible''' inputs (defined below): '''[BIP-352]''' - -
-a = a_1 + a_2 + ... + a_u   (mod n)
-A = a·G
-
- -If a = 0 the sender MUST abort: no payment is defined. '''[BIP-352]''' - -Let outpoint_L be the lexicographically smallest input outpoint, encoded -as in a transaction (32-byte txid in internal byte order followed by the 4-byte -little-endian index — identical to the Elements consensus encoding of an outpoint). -The input hash is: '''[BIP-352]''' - -
-input_hash = int(hashBIP0352/Inputs( outpoint_L || serP(A) )) mod n
-
+Input aggregation (a, A) and the input_hash are +computed exactly as in BIP-352, over the '''eligible''' inputs defined below. '''[BIP-352]''' +The only Liquid-specific note on encoding is that outpoint_L — the +lexicographically smallest input outpoint used in the input hash — is encoded as in a +transaction (32-byte txid in internal byte order followed by the 4-byte little-endian +index), which is identical to the Elements consensus encoding of an outpoint. ====Eligible inputs '''[Liquid] [Choice]'''==== @@ -157,36 +127,28 @@ peg-in input. Concretely this draft includes: and excludes issuance/reissuance inputs, peg-in inputs, the fee output (which is never an input), and any input whose signing public key is not uniquely determined. -This mirrors BIP-352's eligible-input set with two Liquid-specific notes. First, -Liquid blinds the asset and amount of an input's spent output, but the '''signing -public key''' used here is the one revealed in the witness/scriptSig and is -independent of blinding; aggregation therefore proceeds exactly as on Bitcoin. -Second, BIP-352's requirement to negate Taproot input keys to an even Y-coordinate -before summing does '''not''' apply here, because the eligible types above are not -Taproot key-path spends; keys are summed directly. Should Liquid Taproot key-path -spends become eligible in a future version, the BIP-352 even-Y rule would apply to -them unchanged. +This mirrors BIP-352's eligible-input set, with two Liquid-specific notes. First, the +signing public key used here is the one revealed in the witness/scriptSig and is +independent of CT blinding, so aggregation proceeds exactly as on Bitcoin. Second, +BIP-352's even-Y negation of Taproot input keys does '''not''' apply, since none of +the eligible types above are Taproot key-path spends; keys are summed directly. The +rule would apply unchanged should Liquid Taproot key-path spends become eligible in a +future version. -===Shared secret and output spend key=== +===Shared secret and output spend key '''[BIP-352]'''=== -Both parties compute the same ECDH shared secret: '''[BIP-352]''' - -
-sender:    S = input_hash · a · B_scan
-receiver:  S = input_hash · b_scan · A
-
- -For each output index k = 0, 1, 2, ... paid to the same recipient in the -same transaction: '''[BIP-352]''' +The ECDH shared secret S, the per-output tweak t_k, and the +output spend key P_k = B_spend + t_k·G are computed exactly as in BIP-352; +the receiver spends a recognized output at index k with the private key +b_spend + t_k (mod n). The symbols S and k are +restated here only because the Liquid-specific blinding key below is derived from them:
+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
 
-The receiver, upon recognizing an output at index k, can spend it with -the private key b_spend + t_k (mod n). '''[BIP-352]''' - ===Output representation '''[Liquid] [Choice]'''=== In BIP-352 the output is a P2TR output whose key is P_k, visible in the @@ -253,46 +215,22 @@ eliminate. ===Labels '''[BIP-352]'''=== -Labels are supported exactly as in BIP-352. For an integer label m: - -
-label_tweak_m = int(hashBIP0352/Label( ser256(b_scan) || ser32(m) )) mod n
-B_spend,m     = B_spend + label_tweak_m·G
-
- -A labeled address encodes B_spend,m in place of B_spend; the -scan key is unchanged. The output spend key becomes B_spend,m + t_k·G and -is spendable with b_spend + t_k + label_tweak_m (mod n). Label -m = 0 is reserved to mark change. The output blinding key is unaffected -by labels: it depends only on S and k. +Labels are supported exactly as in BIP-352 and need no Liquid adaptation. The only +Liquid-specific note is that the output blinding key (below) is unaffected by labels: +it depends only on S and k, not on the labeled spend key. Implementations MAY support only the change label (m = 0) in a first version. '''[Choice]''' ==Light-client receive: the tweak server model== -The receive bounty for this work requires following the tweak-server model of the -[https://github.com/silent-payments/BIP0352-index-server-specification BIP-352 index server specification]. -That model has a server compute, per silent-payment-eligible transaction, the -'''partial tweak''' '''[BIP-352]''': - -
-T = input_hash · A
-
- -a single public key that requires no scan key to compute. A light client fetches the -partial tweaks for a block height, completes the shared secret locally with its scan -key, and matches: - -
-S = b_scan · T   (= input_hash · b_scan · A)
-
- -For each k, the client derives P_k and the candidate output -script and checks it against the transaction's outputs, advancing k -until a configurable number of consecutive misses (a gap limit), since multiple -outputs may be paid to the same address. '''[BIP-352]''' On a match the client -derives bk_k to unblind and b_spend + t_k to spend. +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''' +T = input_hash · A (no scan key needed), and the client completes +S = b_scan · T and runs the BIP-352 gap-limit match. '''[BIP-352]''' On a +match it derives bk_k to unblind and b_spend + t_k to spend. +The Liquid-specific divergences are below. ===Matching on Liquid omits compact filters '''[Liquid] [Choice]'''=== @@ -448,44 +386,6 @@ output, the full blinded output is not byte-reproducible; the recovery property instead stated as: an output blinded to BK_k by the construction above unblinds correctly under bk_k, and fails to unblind under any other key. -==Rationale== - -'''Why reuse the BIP-352 key derivation unchanged.''' The spend-key path -(a, A, input_hash, S, -t_k, P_k, labels) is the reviewed cryptographic core of -Silent Payments. Reusing it verbatim maximizes interoperability with existing -implementations and tooling and confines the novel surface to the parts Liquid -actually forces to differ. - -'''Why a confidential P2WPKH output rather than P2TR ([Choice]).''' Liquid wallets -overwhelmingly use elwpkh; using the deployed type avoids a Taproot -dependency and matches existing relay and wallet behavior. The cost is divergence -from BIP-352's x-only convention and a compressed-point value in the index-server -data. A future Taproot output type can be added without disturbing the rest of this -specification. - -'''Why derive the blinding key from the shared secret ([Liquid]).''' This is the -mechanism that lets a confidential output be both discovered and unblinded -non-interactively. A dedicated hash domain keeps the blinding key independent of the -spend key. The security of the scheme reduces to the random-oracle treatment of the -tagged hash and the secrecy of S (which only the sender and the holder -of b_scan can compute); a formal security argument and adversarial -analysis are expected to accompany review. The construction has been validated by a -reference implementation against the test vectors above, including adversarial cases -(an observer holding all public data but not b_scan cannot unblind; -incorrect keys never unblind; spend and blinding values are independent across many -randomized cases). - -'''Why omit BIP-158 on Liquid ([Choice]).''' The index-server filter step exists to -let a client test candidate scripts without downloading every block. Because Liquid -output scripts are public, a client can match candidate scripts directly, so the -filter step is redundant. This is a deliberate, narrowly-scoped deviation from the -Bitcoin index-server model, justified entirely by Liquid's public scripts; it does -not change the cryptographic protocol. - -'''Why a distinct, network-specific HRP ([Choice]).''' To make cross-chain and -cross-network address confusion impossible. - ==Backwards Compatibility== This document defines a new, opt-in address type and output convention. It introduces From ab0c3d604d5139be6833a6da8c54b51263024752 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Tue, 2 Jun 2026 15:27:44 -0600 Subject: [PATCH 03/10] Condense light-client receive section to a single paragraph The flow is essentially identical to BIP-352; fold the three subsections (filter omission, server interface) into one paragraph plus the interface example. --- elip-silent-payments-liquid.mediawiki | 38 ++++++--------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index d02363f..27d55b3 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -230,41 +230,19 @@ unchanged: the server publishes a per-transaction '''partial tweak''' T = input_hash · A (no scan key needed), and the client completes S = b_scan · T and runs the BIP-352 gap-limit match. '''[BIP-352]''' On a match it derives bk_k to unblind and b_spend + t_k to spend. -The Liquid-specific divergences are below. - -===Matching on Liquid omits compact filters '''[Liquid] [Choice]'''=== - -In the Bitcoin index-server model the client cannot recompute an output's -scriptPubKey from chain data without first guessing the tweak, so it uses -[https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki BIP-158] compact -block filters to test candidate scripts and then downloads only matching blocks. - -On Liquid this intermediate filtering step is unnecessary: Confidential Transactions -blind the asset, amount, and nonce of an output but '''not its scriptPubKey''', so -output scripts are available in the clear from ordinary block or transaction data. -A client that has derived a candidate script can therefore match it directly against -the transaction outputs it already retrieves. Accordingly this draft does not require -BIP-158 filters; an implementation MAY still use a filter or other index as an -optimization, but it is not part of the protocol. This is the one substantive -deviation from the Bitcoin index-server model, and it is forced by — and made safe -by — Liquid's public output scripts. - -===Server interface '''[Choice]'''=== - -The protocol requires only that, for each block height, a client can obtain the list -of partial tweaks T for that block's silent-payment-eligible -transactions. An abstract client interface is therefore minimal: +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]'''
 tweaks(block_height) -> [ serP(T_1), serP(T_2), ... ]
 
-A concrete wire format is intentionally left to a companion specification or to -existing Liquid indexing infrastructure; for example a REST endpoint returning an -array of compressed points per height, or an extension of an existing -descriptor-oriented indexing service. The cryptographic protocol in this document is -independent of that choice. - ==Spending a received output '''[Liquid]'''== A received silent-payment output is controlled by the scalar From 5f2a62cbdf317ed94fd410ec1026dd6a34bad8a3 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Tue, 2 Jun 2026 16:19:49 -0600 Subject: [PATCH 04/10] Make Taproot the output representation, matching BIP-352 exactly Switch silent-payment outputs from confidential P2WPKH to confidential Taproot (OP_1 ), 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). --- elip-silent-payments-liquid.mediawiki | 117 ++++++++------------------ 1 file changed, 34 insertions(+), 83 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index 27d55b3..8580678 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -72,7 +72,7 @@ To pay the address, a sender: # aggregates the private keys of its eligible transaction inputs into a single scalar a and forms A = a·G '''[BIP-352]'''; # computes a transaction-bound input_hash and an ECDH shared secret S with the receiver's scan key '''[BIP-352]'''; # derives, for output index k, a spend public key P_k that only the receiver can later re-derive '''[BIP-352]'''; -# places P_k in a Liquid output of the deployed type '''[Liquid]''', and blinds that output's asset and amount to a blinding key that is itself derived from S '''[Liquid]'''. +# places P_k in a Taproot (P2TR) output '''[BIP-352]''', and blinds that output's asset and amount to a blinding key that is itself derived from S '''[Liquid]'''. To receive, the receiver re-derives the candidate spend keys and output scripts from input_hash·A and its scan key, matches them against the transaction's @@ -104,73 +104,28 @@ A distinct, network-specific HRP (rather than Bitcoin's sp/ts ensures a Liquid silent-payment address can never be confused with a Bitcoin one, nor a mainnet address with a testnet one. -===Input aggregation and the input hash=== +===Reused from BIP-352 unchanged '''[BIP-352]'''=== -Input aggregation (a, A) and the input_hash are -computed exactly as in BIP-352, over the '''eligible''' inputs defined below. '''[BIP-352]''' -The only Liquid-specific note on encoding is that outpoint_L — the -lexicographically smallest input outpoint used in the input hash — is encoded as in a -transaction (32-byte txid in internal byte order followed by the 4-byte little-endian -index), which is identical to the Elements consensus encoding of an outpoint. - -====Eligible inputs '''[Liquid] [Choice]'''==== - -An input is eligible to contribute to a (sender) and A -(scanner) if and only if it is spent by a single public key whose value is -unambiguously recoverable from the input, and it is not an issuance, reissuance, or -peg-in input. Concretely this draft includes: - -* P2WPKH (elwpkh) — the dominant Liquid output type — taking the public key from the witness; -* P2SH-wrapped P2WPKH — likewise; -* P2PKH — taking the public key from the scriptSig. - -and excludes issuance/reissuance inputs, peg-in inputs, the fee output (which is -never an input), and any input whose signing public key is not uniquely determined. - -This mirrors BIP-352's eligible-input set, with two Liquid-specific notes. First, the -signing public key used here is the one revealed in the witness/scriptSig and is -independent of CT blinding, so aggregation proceeds exactly as on Bitcoin. Second, -BIP-352's even-Y negation of Taproot input keys does '''not''' apply, since none of -the eligible types above are Taproot key-path spends; keys are summed directly. The -rule would apply unchanged should Liquid Taproot key-path spends become eligible in a -future version. - -===Shared secret and output spend key '''[BIP-352]'''=== - -The ECDH shared secret S, the per-output tweak t_k, and the -output spend key P_k = B_spend + t_k·G are computed exactly as in BIP-352; -the receiver spends a recognized output at index k with the private key -b_spend + t_k (mod n). The symbols S and k are -restated here only because the Liquid-specific blinding key below is derived from them: +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 S and k:
+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           (P_k used directly; no script tree, no taptweak)
 
-===Output representation '''[Liquid] [Choice]'''=== - -In BIP-352 the output is a P2TR output whose key is P_k, visible in the -clear. Liquid does not deploy Taproot in general wallet usage, and the overwhelmingly -common Liquid output type is elwpkh (version-0 witness public-key hash). -This draft therefore defines a silent-payment output as a '''confidential -version-0 P2WPKH output''' whose witness program is HASH160(serP(P_k)): - -
-scriptPubKey = OP_0 
-
- -with the asset and amount blinded as in any CT output (see the next section). - -A consequence is that the value an index server publishes per transaction is a -'''compressed''' point (or equivalently the candidate scripts derived from it), -rather than the x-only key used in the Bitcoin index-server model. - -Emulating Taproot via Liquid's tapscript support was considered and is deferred: -it adds deployment surface for negligible benefit given current Liquid usage. A -future version MAY define a Taproot output type, at which point the BIP-352 x-only -conventions would apply to it directly. +The eligible-input set (P2TR key-path, P2WPKH, P2SH-P2WPKH, P2PKH), the even-Y rule for +Taproot keys, gap-limited scanning, 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: outpoint_L 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]'''=== @@ -243,19 +198,14 @@ existing Liquid indexing infrastructure. '''[Choice]''' tweaks(block_height) -> [ serP(T_1), serP(T_2), ... ] -==Spending a received output '''[Liquid]'''== - -A received silent-payment output is controlled by the scalar -d = b_spend + t_k (+ label_tweak_m), which is generally '''not''' a -BIP-32-derivable key. Signing such an input therefore requires a signer that accepts -a base key and an additive tweak (or equivalently the tweaked key directly), rather -than only keys located at a derivation path. +==Spending a received output '''[BIP-352]'''== -This is a software-signer capability: deriving d and producing an -ordinary signature for the corresponding P2WPKH input is straightforward and uses no -new cryptography. It is the only signer-side change Silent Payments require on -Liquid, and it is sufficient for a wallet to send to, receive from, and spend -silent-payment outputs. +Spending is an ordinary BIP-340 Taproot key-path spend with +d = b_spend + t_k (+ label_tweak_m) (even-Y normalized), exactly as in +BIP-352. Since d 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 @@ -295,7 +245,7 @@ A derived silent-payment output (for index k): SilentPaymentOutput { spend_pubkey: P_k = B_spend + t_k·G blinding_pubkey: BK_k = bk_k·G - // scriptPubKey = OP_0 , asset/amount blinded to BK_k + // scriptPubKey = OP_1 , asset/amount blinded to BK_k } @@ -318,7 +268,7 @@ b_scan = 1111111111111111111111111111111111111111111111111111111111111111 b_spend = 2222222222222222222222222222222222222222222222222222222222222222 -Two eligible inputs, each a P2WPKH spend, with private keys and outpoints: +Two eligible inputs (any BIP-352-eligible type), with private keys and outpoints:
 input 0: priv = 3131...31 (0x31 x32), outpoint txid = 1010...10 (0x10 x32), vout = 0
@@ -340,14 +290,14 @@ k = 0:
   BK_k (blinding pub) = 0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc
   bk_k (blinding priv) = 70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0
   spend priv (b_spend + t_k) = f03c436d2cd67ae1fecf7d88a38aa3a03c0abea43feaf6da8eb71e2e3a866bda
-  scriptPubKey = 0014aad1065758c02efbf32e797f108007741068eb25
+  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 = 001481bc88e345e0a585da4ee2157eef9e18bff416cf
+  scriptPubKey = 512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
 
The unlabeled mainnet (HRP lq) address for these keys: @@ -369,8 +319,8 @@ unblinds correctly under bk_k, and fails to unblind under any other 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 P2WPKH output on the chain and is -spent by an ordinary signature, so existing relay and validation rules apply +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 @@ -380,11 +330,12 @@ outputs are usable with software signing. ==Reference Implementation== -A reference implementation demonstrating address encoding, input aggregation, -sender output derivation, the shared-secret-derived blinding key, confidential output -construction and recovery, the tweak-server scan flow, labels, and tweak-aware -signing, together with the test vectors and adversarial tests referenced above, is -expected to accompany this proposal prior to a status of Final. +A reference implementation against the Liquid Wallet Kit (LWK) covers address +encoding, input aggregation, sender output derivation, the shared-secret-derived +blinding key, confidential Taproot output construction and recovery, the tweak-server +scan flow, labels, and tweak-aware BIP-340 key-path signing (including even-Y +normalization), together with the test vectors and the adversarial property tests +referenced above. It reproduces the test vectors in this document byte-for-byte. ==Acknowledgements== From 0124bbb78954ea60bc8b5e055dcc1d49edf96fae Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Tue, 2 Jun 2026 16:31:30 -0600 Subject: [PATCH 05/10] Fix SP address HRP collision with native Liquid; drop standalone Labels section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- elip-silent-payments-liquid.mediawiki | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index 8580678..8062ab0 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -91,18 +91,24 @@ network-specific human-readable part. '''[Liquid] [Choice]''' This draft uses: {| class="wikitable" ! Network !! HRP |- -| Liquid (mainnet) || lq +| Liquid (mainnet) || lqsp |- -| Liquid testnet / regtest || tlq +| Liquid testnet / regtest || tlqsp |} and version symbol q (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 (rather than Bitcoin's sp/tsp) -ensures a Liquid silent-payment address can never be confused with a Bitcoin one, -nor a mainnet address with a testnet one. +A distinct, network-specific HRP is required: it must differ both from Bitcoin's +silent-payment HRP (sp/tsp) '''and''' from the HRPs of +ordinary Liquid addresses — in particular Liquid's '''confidential''' addresses use +lq/tlq (blech32), so a silent-payment HRP of lq +would collide with them. The lqsp/tlqsp HRPs are distinct +from every existing Liquid address HRP (ex/tex for +unconfidential, lq/tlq 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]'''=== @@ -120,8 +126,9 @@ scriptPubKey = OP_1 (P_k used directly; no script tree, n The eligible-input set (P2TR key-path, P2WPKH, P2SH-P2WPKH, P2PKH), the even-Y rule for -Taproot keys, gap-limited scanning, 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. +Taproot keys, gap-limited scanning, labels (including the change label m = 0), +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: outpoint_L 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 @@ -160,7 +167,9 @@ Because bk_k and t_k are outputs of a random oracle (a tagged hash) evaluated on '''disjoint domains''' over the same secret S, they are independent: knowledge of one does not assist in recovering the other or S. The domain tag LiquidSilentPayments/Blind MUST differ -from the BIP-352 spend domain BIP0352/SharedSecret. +from the BIP-352 spend domain BIP0352/SharedSecret. The blinding key is +also unaffected by BIP-352 labels: it depends only on S and k, +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, @@ -168,15 +177,6 @@ negating the unlinkability Silent Payments provides. Exchanging a per-output bli key out of band would reintroduce the interaction Silent Payments is designed to eliminate. -===Labels '''[BIP-352]'''=== - -Labels are supported exactly as in BIP-352 and need no Liquid adaptation. The only -Liquid-specific note is that the output blinding key (below) is unaffected by labels: -it depends only on S and k, not on the labeled spend key. - -Implementations MAY support only the change label (m = 0) in a first -version. '''[Choice]''' - ==Light-client receive: the tweak server model== The light-client receive flow follows the tweak-server model of the @@ -300,10 +300,10 @@ k = 1: scriptPubKey = 512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92 -The unlabeled mainnet (HRP lq) address for these keys: +The unlabeled mainnet (HRP lqsp) address for these keys:
-lq1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyu7836wf
+lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe
 
A conforming implementation MUST reproduce A, input_hash, From 8d634b1e65ff8478db9630340ecc40c5bed4e206 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Tue, 2 Jun 2026 19:41:34 -0600 Subject: [PATCH 06/10] Scope down Reference Implementation 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. --- elip-silent-payments-liquid.mediawiki | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index 8062ab0..18b9cfe 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -330,12 +330,11 @@ outputs are usable with software signing. ==Reference Implementation== -A reference implementation against the Liquid Wallet Kit (LWK) covers address -encoding, input aggregation, sender output derivation, the shared-secret-derived -blinding key, confidential Taproot output construction and recovery, the tweak-server -scan flow, labels, and tweak-aware BIP-340 key-path signing (including even-Y -normalization), together with the test vectors and the adversarial property tests -referenced above. It reproduces the test vectors in this document byte-for-byte. +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== From c40601e1e53d066eae59eb72bc13a20a213ed807 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Mon, 29 Jun 2026 09:20:10 -0600 Subject: [PATCH 07/10] Silent Payments for Liquid: pin hash tags, fix Privacy rationale 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_() 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. --- elip-silent-payments-liquid.mediawiki | 467 ++++++++++++-------------- 1 file changed, 210 insertions(+), 257 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index 18b9cfe..4584800 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -1,92 +1,81 @@
-  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
+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
 
-==Introduction== - -===Abstract=== +==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== +[https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki BIP-352]. +Only the Liquid-specific adaptations are described here; the BIP-352 key-derivation +core is reused unchanged. -Throughout this document, every normative rule is tagged to make its origin explicit: +Three things differ from Bitcoin: +* the output representation is a confidential Taproot output (asset and amount blinded), +* a per-output blinding key is derived from the silent-payment shared secret (so the + receiver can unblind non-interactively), and +* the light-client receive flow skips BIP-158 compact filters because Liquid's + Confidential Transactions blind asset and amount but not the scriptPubKey. -* '''[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. -* '''[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. +==Motivation== -Notation follows BIP-352: +Confidential Transactions hide a Liquid output's asset and amount, but its script is +public. A receiver accepting many payments to a single published address must either +reuse that address — linking all payments — or run an interactive protocol to hand out +fresh ones. -* G is the secp256k1 generator; n the curve order. -* Lowercase letters denote scalars (private keys); uppercase letters the corresponding points, e.g. A = a·G. -* serP(P) is the 33-byte compressed encoding of a point P; ser32(i) the 4-byte big-endian encoding of an integer i. -* · is scalar–point multiplication and + is point addition or scalar addition mod n as appropriate. -* hash_tag(m) is the BIP-340 tagged hash SHA256(SHA256(tag) || SHA256(tag) || m) with ASCII tag tag. +Silent Payments eliminate this trade-off: 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 retain +the confidentiality of CT. -==Design== +==Conventions== -===Overview=== +Notation follows BIP-352: G is the secp256k1 generator, lowercase letters +are scalars, uppercase letters are points, serP(P) is 33-byte compressed +encoding, ser32(i) is 4-byte big-endian. -A receiver holds two key pairs: a '''scan''' key pair (b_scan, B_scan) and -a '''spend''' key pair (b_spend, B_spend). The static silent-payment -address encodes the two public keys. +The BIP-340 tagged hash is, for an ASCII tag string tag and message +m: -To pay the address, a sender: +
+hash_tag(m) = SHA256( SHA256(tag) || SHA256(tag) || m )
+
-# aggregates the private keys of its eligible transaction inputs into a single scalar a and forms A = a·G '''[BIP-352]'''; -# computes a transaction-bound input_hash and an ECDH shared secret S with the receiver's scan key '''[BIP-352]'''; -# derives, for output index k, a spend public key P_k that only the receiver can later re-derive '''[BIP-352]'''; -# places P_k in a Taproot (P2TR) output '''[BIP-352]''', and blinds that output's asset and amount to a blinding key that is itself derived from S '''[Liquid]'''. +When a tag is written in the subscript, the subscript text is the exact ASCII byte +string that is hashed — for example hashLiquidSilentPayments/Blind(m) +means tag = "LiquidSilentPayments/Blind" (26 bytes, no trailing +NUL or newline). Interoperability depends on every implementation hashing these exact +bytes; a one-character difference silently breaks unblinding across wallets. This +specification uses these tags: -To receive, the receiver re-derives the candidate spend keys and output scripts from -input_hash·A 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. +{| class="wikitable" +! Named form !! Exact tag string (ASCII) +|- +| hashBIP0352/Inputs || BIP0352/Inputs +|- +| hashBIP0352/SharedSecret || BIP0352/SharedSecret +|- +| hashLiquidSilentPayments/Blind || LiquidSilentPayments/Blind +|} -===Receiver keys and address=== +The first two are reused verbatim from BIP-352; the third is defined by this +specification. -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]''' +==Address Format== -The address is the Bech32m '''[BIP-352]''' encoding of a version symbol followed by -the payload serP(B_scan) || serP(B_spend) (66 bytes), with a -network-specific human-readable part. '''[Liquid] [Choice]''' This draft uses: +A silent-payment address encodes two public keys: the scan key B_scan +and the spend key B_spend. It is the Bech32m encoding of a version symbol +followed by the 33-byte compressed encoding of B_scan concatenated with +the 33-byte compressed encoding of B_spend (66 bytes total): {| class="wikitable" ! Network !! HRP @@ -96,211 +85,183 @@ network-specific human-readable part. '''[Liquid] [Choice]''' This draft uses: | Liquid testnet / regtest || tlqsp |} -and version symbol q (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 (sp/tsp) '''and''' from the HRPs of -ordinary Liquid addresses — in particular Liquid's '''confidential''' addresses use -lq/tlq (blech32), so a silent-payment HRP of lq -would collide with them. The lqsp/tlqsp HRPs are distinct -from every existing Liquid address HRP (ex/tex for -unconfidential, lq/tlq 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 S and k: - -
-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           (P_k used directly; no script tree, no taptweak)
-
- -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 m = 0), -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: outpoint_L 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: - -
-bk_k = hashLiquidSilentPayments/Blind( serP(S) || ser32(k) )   (a 32-byte scalar)
-BK_k = bk_k·G
-
- -The sender blinds the output to BK_k (i.e. uses BK_k 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 S, derives bk_k 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 bk_k and t_k are outputs of a random oracle (a -tagged hash) evaluated on '''disjoint domains''' over the same secret S, -they are independent: knowledge of one does not assist in recovering the other or -S. The domain tag LiquidSilentPayments/Blind MUST differ -from the BIP-352 spend domain BIP0352/SharedSecret. The blinding key is -also unaffected by BIP-352 labels: it depends only on S and k, -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''' -T = input_hash · A (no scan key needed), and the client completes -S = b_scan · T and runs the BIP-352 gap-limit match. '''[BIP-352]''' On a -match it derives bk_k to unblind and b_spend + t_k 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]''' - -
-tweaks(block_height) -> [ serP(T_1), serP(T_2), ... ]
-
- -==Spending a received output '''[BIP-352]'''== - -Spending is an ordinary BIP-340 Taproot key-path spend with -d = b_spend + t_k (+ label_tweak_m) (even-Y normalized), exactly as in -BIP-352. Since d 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: +Version symbol is q (Bech32 character for value 0). The 90-character +Bech32 length limit does not apply (per BIP-352). + +The HRP lqsp is distinct from all existing Liquid address HRPs — +ex/tex (unconfidential), lq/tlq +(confidential, blech32) — and from Bitcoin's sp/tsp, so a +silent-payment address cannot be confused with any other address type or network. + +Seed derivation is wallet-defined (a BIP-32 scheme analogous to BIP-352 is recommended). + +==Derivation== + +===Sender=== + +Given the receiver's address (B_scan, B_spend) and a set of eligible +transaction inputs (per BIP-352): + +# Aggregate input private keys: a = Σ a_i, A = a·G. +# Compute input_hash: +#: input_hash = int( hashBIP0352/Inputs(outpoint_L || A_33) ) mod n, +#: where outpoint_L is the Elements consensus encoding (32-byte txid in internal + byte order, then 4-byte little-endian vout), and A_33 is the + 33-byte compressed encoding of A. +# ECDH shared secret: S = input_hash · a · B_scan. +# Per output index k: +#: t_k = int( hashBIP0352/SharedSecret(S_33 || k_4) ) mod n, +#: where S_33 is the 33-byte compressed encoding of S and + k_4 is the 4-byte big-endian encoding of k. +#: P_k = B_spend + t_k·G +#: scriptPubKey = OP_1 (P2TR, no taptweak — per BIP-352) +#: bk_k = int( hashLiquidSilentPayments/Blind(S_33 || k_4) ) mod n, +#: BK_k = bk_k·G +#: Blind the output's asset and amount to BK_k as with any confidential + Liquid output. + +Steps 1–3 are BIP-352 except step 2 (outpoint encoding). Step 4 (blinding key) +is Liquid-specific. The eligible-input set, even-Y rule, gap-limit scanning, and labels +are as in BIP-352. + +===Receiver=== + +Given scan key b_scan, spend key b_spend, and the transaction's +tweak T = input_hash · A (from the tweak server): + +# S = b_scan · T +# For each output index k: recompute P_k and + scriptPubKey as above; match against the transaction's outputs. +# On a match: derive bk_k to unblind the output, and + d = b_spend + t_k to spend it. + +===Why a dedicated hash domain for the blinding key?=== + +bk_k and t_k are outputs of a tagged hash over the same secret +S but on disjoint domains (LiquidSilentPayments/Blind vs. +BIP0352/SharedSecret). They are therefore independent: knowledge of one +does not reveal the other or S. + +Two alternatives were rejected: +* A single fixed blinding key in the address would link all of a receiver's outputs + through a common blinding key, negating unlinkability. +* Out-of-band per-output blinding key exchange would reintroduce the interaction Silent + Payments is designed to eliminate. + +===Privacy=== + +The transaction publicly reveals A (computable from the inputs) and every +output scriptPubKey. Deciding whether an output is a silent payment to a +given address requires the shared secret +S = input_hash · a · B_scan = b_scan · (input_hash · A). Computing +S therefore requires either the sender's aggregated input secret +a or the receiver's secret scan key b_scan. + +A third party has neither. The address publishes only the public scan key +B_scan; recovering S from the public A and public +B_scan is the computational Diffie–Hellman problem. Knowing the address is +not sufficient, and the output index k is not secret (it is the sequential +0, 1, 2, …) and confers no privacy on its own. Privacy rests entirely on +the secrecy of b_scan: only the sender and the receiver can compute +S, derive P_k / BK_k, and so recognize or unblind +the output. Publishing the tweak T = input_hash · A does not weaken this, +since matching still requires b_scan · T. + +This is identical to BIP-352's privacy model on Bitcoin: detection requires the secret +scan key, which the transaction never reveals. The Liquid adaptation adds a further +benefit: even a party that holds b_scan and so can derive BK_k +learns only that an output is a silent payment (by matching the Taproot scriptPubKey) — +it cannot read the amount or asset, which CT hides from everyone, including the holder +of the scan key. + +A silent-payment output is indistinguishable from any other confidential Taproot output +on-chain. It carries no marker, no special nonce, and no correlated blinding key that +could be used to identify it without the receiver's secret scan key. + +==Light-Client Receive== + +Follow the BIP-352 index server / tweak-server model: the server publishes +T = the 33-byte compressed encoding of (input_hash · A) per transaction. +The client computes S = b_scan · T and runs the BIP-352 gap-limit match. + +The BIP-158 compact-filter step is unnecessary: CT blinds asset, amount, and nonce but +not the scriptPubKey, so a client matches derived candidate scripts +directly against the public output scripts it already retrieves. Filters MAY be used +as an optimization but are not part of the protocol. + +The protocol requires only that, per block height, a client can obtain that block's +tweaks. The concrete wire format is left to a companion specification. + +==Spending== + +An ordinary BIP-340 Taproot key-path spend with +d = b_spend + t_k (+ label_tweak_m) (even-Y normalized), as in BIP-352. +The only signer requirement is a signer that accepts a base key plus an additive tweak +rather than only keys at a fixed derivation path — a software-signer capability. +Hardware-signer support requires firmware-level key-tweak support and is out of scope +for this version. + +==Data Structures==
 SilentPaymentAddress {
-    scan_pubkey:  serP(B_scan)     // 33 bytes
-    spend_pubkey: serP(B_spend)    // 33 bytes; B_spend,m for a labeled address
+    scan_pubkey:  the 33-byte compressed encoding of B_scan
+    spend_pubkey: the 33-byte compressed encoding of B_spend
 }
-// wire: Bech32m( hrp, version=0, scan_pubkey || spend_pubkey )
-
+// wire: Bech32m( hrp, version=q, scan_pubkey || spend_pubkey ) -Aggregated input data computed by a sender: - -
-AggregatedInputs {
-    a:          scalar             // sender only
-    A:          point              // = a·G
-    input_hash: scalar             // = H_Inputs(outpoint_L || serP(A))
-}
-
- -A derived silent-payment output (for index k): - -
 SilentPaymentOutput {
-    spend_pubkey:  P_k = B_spend + t_k·G
+    spend_pubkey:    P_k = B_spend + t_k·G
     blinding_pubkey: BK_k = bk_k·G
-    // scriptPubKey = OP_1 , asset/amount blinded to BK_k
+    scriptPubKey = OP_1 
+    // asset/amount blinded to BK_k
 }
-
- -The light-client view per eligible transaction: -
-PartialTweak = serP( input_hash · A )      // published by the index/tweak server
+Tweak = the 33-byte compressed encoding of (input_hash · A)      // published by tweak server, per BIP-352
 
==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): +All byte strings are hex. Receiver keys:
 b_scan  = 1111111111111111111111111111111111111111111111111111111111111111
 b_spend = 2222222222222222222222222222222222222222222222222222222222222222
 
-Two eligible inputs (any BIP-352-eligible type), with private keys and outpoints: +Two eligible inputs:
 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
 
-Aggregated input values (outpoint_L is input 0, the lexicographically smaller): +Aggregated values (outpoint_L = input 0, the lexicographically smaller):
 A          = 031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99
 input_hash = d392922c00280a7e8d282182f5026f2fddbc74c1e1de18b4822128b2b77ec641
 
-Per-output derived values: +Per-output values:
 k = 0:
-  P_k (spend pubkey) = 02a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931
-  BK_k (blinding pub) = 0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc
-  bk_k (blinding priv) = 70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0
-  spend priv (b_spend + t_k) = f03c436d2cd67ae1fecf7d88a38aa3a03c0abea43feaf6da8eb71e2e3a866bda
+  P_k = 02a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931
+  BK_k = 0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc
+  bk_k = 70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0
+  spend priv = 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
+  P_k = 0229d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
+  BK_k = 03efdeda770ccdbe8bf466fba48bfd2b2c436ab0c04658fc6d6c277de5078129fa
+  bk_k = 945ba73a9804f62089c7d2ffdc079031031f0aebab372cec17ef9c110ebceb10
+  spend priv = 9eff3472230fc83ef5ea8f8c80401c4eecd595a048bd2482a107d3a49baa5a58
   scriptPubKey = 512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
 
-The unlabeled mainnet (HRP lqsp) address for these keys: +Mainnet address (HRP lqsp):
 lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe
@@ -308,33 +269,25 @@ lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su
 
 A conforming implementation MUST reproduce A, input_hash,
 and for each k the values P_k, BK_k,
-bk_k, 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 BK_k by the construction above
-unblinds correctly under bk_k, and fails to unblind under any other key.
+bk_k, the spend private key, and the scriptPubKey, and
+MUST produce the address above. Because the blinding factors are randomized per output,
+the full blinded output is not byte-reproducible; the recovery property is: an output
+blinded to BK_k as above unblinds correctly under bk_k
+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.
+No consensus change. A silent-payment output is an ordinary confidential Taproot output
+on the chain and is spent by an ordinary key-path signature; existing relay and
+validation rules apply unchanged. Wallets that do not implement this are unaffected.
 
 ==Reference Implementation==
 
-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.
+The reference implementation at elip-sp-reference/ reproduces the test
+vectors byte-for-byte and demonstrates the confidential-output round-trip: a sender
+blinds to BK_k, the receiver unblinds with independently computed
+bk_k. Wallet integration (scanning, signing,
+Wollet/TxBuilder wiring) is left to implementations.
 
 ==Acknowledgements==
 

From 520320e8f9c142e2e884c6275f32e66c87f6013e Mon Sep 17 00:00:00 2001
From: 42pupusas 
Date: Mon, 29 Jun 2026 10:51:49 -0600
Subject: [PATCH 08/10] Condense spec to BIP-352 style; fix Privacy, refs, and
 prose

---
 elip-silent-payments-liquid.mediawiki | 303 +++++++-------------------
 1 file changed, 84 insertions(+), 219 deletions(-)

diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki
index 4584800..ad3ddeb 100644
--- a/elip-silent-payments-liquid.mediawiki
+++ b/elip-silent-payments-liquid.mediawiki
@@ -2,7 +2,7 @@
 ELIP: ?
 Layer: Applications
 Title: Silent Payments for the Liquid Network
-Author: 42pupusas
+Author: 42pupusas 
 Comments-Summary: No comments yet.
 Comments-URI: https://github.com/ElementsProject/elips/wiki/Comments:ELIP-????
 Status: Draft
@@ -13,212 +13,80 @@ License: BSD-3-Clause
 
 ==Abstract==
 
-This document specifies Silent Payments for the Liquid Network, building on
-[https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki BIP-352].
-Only the Liquid-specific adaptations are described here; the BIP-352 key-derivation
-core is reused unchanged.
-
-Three things differ from Bitcoin:
-* the output representation is a confidential Taproot output (asset and amount blinded),
-* a per-output blinding key is derived from the silent-payment shared secret (so the
-  receiver can unblind non-interactively), and
-* the light-client receive flow skips BIP-158 compact filters because Liquid's
-  Confidential Transactions blind asset and amount but not the scriptPubKey.
+This document specifies Silent Payments for the Liquid Network. The key derivation,
+address format, scanning, and spending follow BIP-352BIP-352: Silent Payments. https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki
+exactly. This specification only adds Liquid-specific differences.
 
 ==Motivation==
 
-Confidential Transactions hide a Liquid output's asset and amount, but its script is
-public. A receiver accepting many payments to a single published address must either
-reuse that address — linking all payments — or run an interactive protocol to hand out
-fresh ones.
-
-Silent Payments eliminate this trade-off: 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 retain
-the confidentiality of CT.
-
-==Conventions==
-
-Notation follows BIP-352: G is the secp256k1 generator, lowercase letters
-are scalars, uppercase letters are points, serP(P) is 33-byte compressed
-encoding, ser32(i) is 4-byte big-endian.
-
-The BIP-340 tagged hash is, for an ASCII tag string tag and message
-m:
-
-
-hash_tag(m) = SHA256( SHA256(tag) || SHA256(tag) || m )
-
- -When a tag is written in the subscript, the subscript text is the exact ASCII byte -string that is hashed — for example hashLiquidSilentPayments/Blind(m) -means tag = "LiquidSilentPayments/Blind" (26 bytes, no trailing -NUL or newline). Interoperability depends on every implementation hashing these exact -bytes; a one-character difference silently breaks unblinding across wallets. This -specification uses these tags: - -{| class="wikitable" -! Named form !! Exact tag string (ASCII) -|- -| hashBIP0352/Inputs || BIP0352/Inputs -|- -| hashBIP0352/SharedSecret || BIP0352/SharedSecret -|- -| hashLiquidSilentPayments/Blind || LiquidSilentPayments/Blind -|} - -The first two are reused verbatim from BIP-352; the third is defined by this -specification. - -==Address Format== - -A silent-payment address encodes two public keys: the scan key B_scan -and the spend key B_spend. It is the Bech32m encoding of a version symbol -followed by the 33-byte compressed encoding of B_scan concatenated with -the 33-byte compressed encoding of B_spend (66 bytes total): - -{| class="wikitable" -! Network !! HRP -|- -| Liquid (mainnet) || lqsp -|- -| Liquid testnet / regtest || tlqsp -|} - -Version symbol is q (Bech32 character for value 0). The 90-character -Bech32 length limit does not apply (per BIP-352). - -The HRP lqsp is distinct from all existing Liquid address HRPs — -ex/tex (unconfidential), lq/tlq -(confidential, blech32) — and from Bitcoin's sp/tsp, so a -silent-payment address cannot be confused with any other address type or network. - -Seed derivation is wallet-defined (a BIP-32 scheme analogous to BIP-352 is recommended). - -==Derivation== - -===Sender=== - -Given the receiver's address (B_scan, B_spend) and a set of eligible -transaction inputs (per BIP-352): - -# Aggregate input private keys: a = Σ a_i, A = a·G. -# Compute input_hash: -#: input_hash = int( hashBIP0352/Inputs(outpoint_L || A_33) ) mod n, -#: where outpoint_L is the Elements consensus encoding (32-byte txid in internal - byte order, then 4-byte little-endian vout), and A_33 is the - 33-byte compressed encoding of A. -# ECDH shared secret: S = input_hash · a · B_scan. -# Per output index k: -#: t_k = int( hashBIP0352/SharedSecret(S_33 || k_4) ) mod n, -#: where S_33 is the 33-byte compressed encoding of S and - k_4 is the 4-byte big-endian encoding of k. -#: P_k = B_spend + t_k·G -#: scriptPubKey = OP_1 (P2TR, no taptweak — per BIP-352) -#: bk_k = int( hashLiquidSilentPayments/Blind(S_33 || k_4) ) mod n, -#: BK_k = bk_k·G -#: Blind the output's asset and amount to BK_k as with any confidential - Liquid output. - -Steps 1–3 are BIP-352 except step 2 (outpoint encoding). Step 4 (blinding key) -is Liquid-specific. The eligible-input set, even-Y rule, gap-limit scanning, and labels -are as in BIP-352. - -===Receiver=== - -Given scan key b_scan, spend key b_spend, and the transaction's -tweak T = input_hash · A (from the tweak server): - -# S = b_scan · T -# For each output index k: recompute P_k and - scriptPubKey as above; match against the transaction's outputs. -# On a match: derive bk_k to unblind the output, and - d = b_spend + t_k to spend it. - -===Why a dedicated hash domain for the blinding key?=== - -bk_k and t_k are outputs of a tagged hash over the same secret -S but on disjoint domains (LiquidSilentPayments/Blind vs. -BIP0352/SharedSecret). They are therefore independent: knowledge of one -does not reveal the other or S. - -Two alternatives were rejected: -* A single fixed blinding key in the address would link all of a receiver's outputs - through a common blinding key, negating unlinkability. -* Out-of-band per-output blinding key exchange would reintroduce the interaction Silent - Payments is designed to eliminate. +Confidential Transactions (CT)Confidential Transactions. https://elementsproject.org/features/confidential-transactions +hide a Liquid output's asset and amount but not its +script. So a receiver with one published address has to either reuse it, (linking +all their payments) or run an interactive protocol to hand out fresh ones. Silent +Payments remove that trade-off: one static address, a distinct unlinkable output per payment. + +==Specification== + +We reuse the notation, functions, and conventions of BIP-352. In particular, +serP(P) is the SEC1 compressed encoding of a point, ser32(i) +serializes a 32-bit unsigned integer most-significant-byte first, n is the +secp256k1 curve order, and hashtag(x) denotes +SHA256(SHA256(tag) || SHA256(tag) || x) as defined in +BIP-340BIP-340: Schnorr Signatures for secp256k1. https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki. + +We additionally define: + +* Let blind_tag be the exact 26-byte ASCII string LiquidSilentPayments/Blind. + +A mismatch in these bytes breaks unblinding across implementations. The tag is disjoint +from BIP-352's BIP0352/SharedSecret, so the blinding key and the BIP-352 +output tweak, though both derived from S, do not reveal each other. + +A silent payment on Liquid is a BIP-352 output that is additionally made a confidential +Liquid output, blinded to a per-output blinding key derived from the same shared secret. +The sender, for each output index k: + +* Let S be the shared secret and P_k the output public key, derived as in BIP-352 +* Let scriptPubKey = OP_1 , a BIP-341BIP-341: Taproot: SegWit version 1 spending rules. https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki Taproot output +* Let bk_k = int(hashblind_tag(serP(S) || ser32(k))) mod n +** If bk_k = 0, increment k and continue +* Let BK_k = bk_k·G +* Blind the output's asset and amount to BK_k, as for any confidential Liquid output + +The receiver, having recomputed S as in BIP-352, recomputes +bk_k and unblinds the output with no out-of-band exchange. Deriving the +blinding key from S avoids both a fixed address-level blinding key (which +would link a receiver's outputs) and an interactive per-output exchange. + +===Differences from BIP-352=== + +* '''Output:''' a confidential Liquid Taproot + output (asset and amount blinded to + BK_k), rather than a bare Taproot output. +* '''outpoint_L:''' the Elements consensus encoding (32-byte txid in + internal byte order, then 4-byte little-endian vout) where BIP-352 uses + the Bitcoin encoding. +* '''Address HRP:''' lqsp (mainnet) / tlqsp (testnet, regtest), + distinct from ex/tex, lq/tlq, and + Bitcoin's sp/tsp. Address payload, version symbol + (q), and the relaxed Bech32 length limit are as in BIP-352. +* '''Light client:''' the BIP-158BIP-158: Compact Block Filters for Light Clients. https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki + compact-filter step is unnecessary — CT does not blind + the scriptPubKey, so a client matches derived candidate scripts directly + against the public output scripts. The BIP-352 tweak serverBIP-352 Silent Payments index server. https://github.com/bitcoin/bips/blob/master/bip-0352/index-server.mediawiki + (publishing T = input_hash·A per transaction) is otherwise used unchanged. + +All other inputs like input eligibility, the even-Y rule, input_hash, +S = input_hash·a·B_scan, the output tweak t_k, labels, +gap-limit scanning, and the Taproot key-path spend with d = b_spend + t_k follow BIP-352 verbatim. ===Privacy=== -The transaction publicly reveals A (computable from the inputs) and every -output scriptPubKey. Deciding whether an output is a silent payment to a -given address requires the shared secret -S = input_hash · a · B_scan = b_scan · (input_hash · A). Computing -S therefore requires either the sender's aggregated input secret -a or the receiver's secret scan key b_scan. - -A third party has neither. The address publishes only the public scan key -B_scan; recovering S from the public A and public -B_scan is the computational Diffie–Hellman problem. Knowing the address is -not sufficient, and the output index k is not secret (it is the sequential -0, 1, 2, …) and confers no privacy on its own. Privacy rests entirely on -the secrecy of b_scan: only the sender and the receiver can compute -S, derive P_k / BK_k, and so recognize or unblind -the output. Publishing the tweak T = input_hash · A does not weaken this, -since matching still requires b_scan · T. - -This is identical to BIP-352's privacy model on Bitcoin: detection requires the secret -scan key, which the transaction never reveals. The Liquid adaptation adds a further -benefit: even a party that holds b_scan and so can derive BK_k -learns only that an output is a silent payment (by matching the Taproot scriptPubKey) — -it cannot read the amount or asset, which CT hides from everyone, including the holder -of the scan key. - -A silent-payment output is indistinguishable from any other confidential Taproot output -on-chain. It carries no marker, no special nonce, and no correlated blinding key that -could be used to identify it without the receiver's secret scan key. - -==Light-Client Receive== - -Follow the BIP-352 index server / tweak-server model: the server publishes -T = the 33-byte compressed encoding of (input_hash · A) per transaction. -The client computes S = b_scan · T and runs the BIP-352 gap-limit match. - -The BIP-158 compact-filter step is unnecessary: CT blinds asset, amount, and nonce but -not the scriptPubKey, so a client matches derived candidate scripts -directly against the public output scripts it already retrieves. Filters MAY be used -as an optimization but are not part of the protocol. - -The protocol requires only that, per block height, a client can obtain that block's -tweaks. The concrete wire format is left to a companion specification. - -==Spending== - -An ordinary BIP-340 Taproot key-path spend with -d = b_spend + t_k (+ label_tweak_m) (even-Y normalized), as in BIP-352. -The only signer requirement is a signer that accepts a base key plus an additive tweak -rather than only keys at a fixed derivation path — a software-signer capability. -Hardware-signer support requires firmware-level key-tweak support and is out of scope -for this version. - -==Data Structures== - -
-SilentPaymentAddress {
-    scan_pubkey:  the 33-byte compressed encoding of B_scan
-    spend_pubkey: the 33-byte compressed encoding of B_spend
-}
-// wire: Bech32m( hrp, version=q, scan_pubkey || spend_pubkey )
-
-SilentPaymentOutput {
-    spend_pubkey:    P_k = B_spend + t_k·G
-    blinding_pubkey: BK_k = bk_k·G
-    scriptPubKey = OP_1 
-    // asset/amount blinded to BK_k
-}
-
-Tweak = the 33-byte compressed encoding of (input_hash · A)      // published by tweak server, per BIP-352
-
+Detecting or unblinding a silent payment requires the Diffie-Hellman shared secret +S = input_hash·a·B_scan = b_scan·(input_hash·A). Computing it needs either the +sender's input secret a or the receiver's secret scan key b_scan, +so this inherits the BIP-352 privacy model. CT adds one thing on top: even someone who +holds b_scan learns only that an output is a silent payment, not its amount or asset. ==Test Vectors== @@ -267,30 +135,27 @@ Mainnet address (HRP lqsp): lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe
-A conforming implementation MUST reproduce A, input_hash, -and for each k the values P_k, BK_k, -bk_k, the spend private key, and the scriptPubKey, and -MUST produce the address above. Because the blinding factors are randomized per output, -the full blinded output is not byte-reproducible; the recovery property is: an output -blinded to BK_k as above unblinds correctly under bk_k -and fails to unblind under any other key. +Because blinding factors are randomized per output, the blinded output is not +byte-reproducible. What matters is that an output blinded to BK_k +unblinds under bk_k and under no other key. ==Backwards Compatibility== -No consensus change. A silent-payment output is an ordinary confidential Taproot output -on the chain and is spent by an ordinary key-path signature; existing relay and -validation rules apply unchanged. Wallets that do not implement this are unaffected. +No consensus change. A silent-payment output is an ordinary confidential Taproot output, +spent by an ordinary key-path signature, so existing relay and validation rules apply. +Wallets that don't implement this are unaffected. ==Reference Implementation== -The reference implementation at elip-sp-reference/ reproduces the test -vectors byte-for-byte and demonstrates the confidential-output round-trip: a sender -blinds to BK_k, the receiver unblinds with independently computed -bk_k. Wallet integration (scanning, signing, -Wollet/TxBuilder wiring) is left to implementations. +https://github.com/42Pupusas/elip-sp-reference/blob/main/src/lib.rs ==Acknowledgements== -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. +This specification builds on BIP-352, the BIP-352 index server specification, +and the Confidential Transactions work of the Elements Project. + +The authors thank JAN3 for sponsoring this work. + +==References== + + From 237599c3ae6f7233a1d80fbd5cc547c489ac5fb3 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Mon, 29 Jun 2026 13:23:41 -0600 Subject: [PATCH 09/10] Link both reference implementations (Rust and Python) --- elip-silent-payments-liquid.mediawiki | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index ad3ddeb..25f71b0 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -147,7 +147,10 @@ Wallets that don't implement this are unaffected. ==Reference Implementation== -https://github.com/42Pupusas/elip-sp-reference/blob/main/src/lib.rs +Two independent implementations that derive the test vectors above byte-for-byte: + +* Rust: https://github.com/42Pupusas/elip-sp-reference/blob/main/src/lib.rs +* Python: https://github.com/42Pupusas/elip-sp-reference/blob/main/python/elip_sp_reference/core.py ==Acknowledgements== From 28d6c187fc32561a59aab83e4604d13382a297c2 Mon Sep 17 00:00:00 2001 From: 42pupusas Date: Mon, 29 Jun 2026 13:30:33 -0600 Subject: [PATCH 10/10] Tighten Differences-from-BIP-352 section and surrounding prose --- elip-silent-payments-liquid.mediawiki | 36 +++++++++------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki index 25f71b0..9b3db8b 100644 --- a/elip-silent-payments-liquid.mediawiki +++ b/elip-silent-payments-liquid.mediawiki @@ -21,7 +21,7 @@ exactly. This specification only adds Liquid-specific differences. Confidential Transactions (CT)Confidential Transactions. https://elementsproject.org/features/confidential-transactions hide a Liquid output's asset and amount but not its -script. So a receiver with one published address has to either reuse it, (linking +script. A receiver with one published address has to either reuse it, (linking all their payments) or run an interactive protocol to hand out fresh ones. Silent Payments remove that trade-off: one static address, a distinct unlinkable output per payment. @@ -60,25 +60,15 @@ would link a receiver's outputs) and an interactive per-output exchange. ===Differences from BIP-352=== -* '''Output:''' a confidential Liquid Taproot - output (asset and amount blinded to - BK_k), rather than a bare Taproot output. -* '''outpoint_L:''' the Elements consensus encoding (32-byte txid in - internal byte order, then 4-byte little-endian vout) where BIP-352 uses - the Bitcoin encoding. -* '''Address HRP:''' lqsp (mainnet) / tlqsp (testnet, regtest), - distinct from ex/tex, lq/tlq, and - Bitcoin's sp/tsp. Address payload, version symbol - (q), and the relaxed Bech32 length limit are as in BIP-352. -* '''Light client:''' the BIP-158BIP-158: Compact Block Filters for Light Clients. https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki - compact-filter step is unnecessary — CT does not blind - the scriptPubKey, so a client matches derived candidate scripts directly - against the public output scripts. The BIP-352 tweak serverBIP-352 Silent Payments index server. https://github.com/bitcoin/bips/blob/master/bip-0352/index-server.mediawiki - (publishing T = input_hash·A per transaction) is otherwise used unchanged. - -All other inputs like input eligibility, the even-Y rule, input_hash, -S = input_hash·a·B_scan, the output tweak t_k, labels, -gap-limit scanning, and the Taproot key-path spend with d = b_spend + t_k follow BIP-352 verbatim. +* '''Output format.''' The output is a ''confidential'' Liquid Taproot output, with its asset and amount blinded to BK_k, rather than a bare Taproot output. +* '''outpoint_L encoding.''' Outpoints use the Elements consensus encoding (32-byte txid in internal byte order followed by 4-byte little-endian vout) in place of BIP-352's Bitcoin encoding. +* '''Address HRP.''' The human-readable part is lqsp (mainnet) or tlqsp (testnet and regtest) — distinct from ex/tex, lq/tlq, and Bitcoin's sp/tsp. The address payload, version symbol (q), and relaxed Bech32 length limit are unchanged from BIP-352. +* '''Light-client scanning.''' The BIP-158BIP-158: Compact Block Filters for Light Clients. https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki compact-filter step is unnecessary: CT does not blind the scriptPubKey, so a client matches its derived candidate scripts directly against the public output scripts. The BIP-352 tweak serverBIP-352 Silent Payments index server. https://github.com/bitcoin/bips/blob/master/bip-0352/index-server.mediawiki (publishing T = input_hash·A per transaction) is used unchanged. + +Everything else follows BIP-352 exactly: input eligibility, the even-Y rule, +input_hash, S = input_hash·a·B_scan, the output tweak +t_k, labels, gap-limit scanning, and the Taproot key-path spend with +d = b_spend + t_k. ===Privacy=== @@ -145,9 +135,7 @@ No consensus change. A silent-payment output is an ordinary confidential Taproot spent by an ordinary key-path signature, so existing relay and validation rules apply. Wallets that don't implement this are unaffected. -==Reference Implementation== - -Two independent implementations that derive the test vectors above byte-for-byte: +==Reference Implementations== * Rust: https://github.com/42Pupusas/elip-sp-reference/blob/main/src/lib.rs * Python: https://github.com/42Pupusas/elip-sp-reference/blob/main/python/elip_sp_reference/core.py @@ -157,7 +145,7 @@ Two independent implementations that derive the test vectors above byte-for-byte This specification builds on BIP-352, the BIP-352 index server specification, and the Confidential Transactions work of the Elements Project. -The authors thank JAN3 for sponsoring this work. +The authors thank JAN3 for sponsoring and supporting this work. ==References==