diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki new file mode 100644 index 0000000..18b9cfe --- /dev/null +++ b/elip-silent-payments-liquid.mediawiki @@ -0,0 +1,343 @@ +
+  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, building on +[https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki BIP-352]. It assumes +familiarity with BIP-352 and describes only what Liquid requires to differ: the +BIP-352 key-derivation core is reused unchanged, while three things are adapted to +Confidential Transactions (CT) and Liquid's deployed output types — the output +representation, a per-output blinding key derived from the silent-payment shared +secret (so a confidential output can be discovered and unblinded non-interactively), +and a light-client receive flow following the "tweak server" model of the +[https://github.com/silent-payments/BIP0352-index-server-specification BIP-352 index server specification]. + +===Copyright=== + +This document is licensed under the 3-clause BSD license. + +===Motivation=== + +Confidential Transactions hide a Liquid output's asset and amount, but its script — +and so any reused address — is public. A receiver accepting many payments to one +published address must today either reuse an address, linking those payments, or run +an interactive protocol to hand out fresh ones. + +Silent Payments remove this trade-off, and compose naturally with CT: the receiver +publishes one static address, each sender independently derives a distinct output +only the receiver can recognize, and the payment graph gains the unlinkability of +Silent Payments while amounts keep the confidentiality of CT. This document defines +how to construct and recognize such outputs on Liquid so that independent +implementations interoperate. + +==Conventions and Provenance of Each Rule== + +Throughout this document, every normative rule is tagged to make its origin explicit: + +* '''[BIP-352]''' — the rule is taken unchanged from BIP-352. Implementations SHOULD reuse existing, reviewed BIP-352 logic for these parts. +* '''[Liquid]''' — the rule is an adaptation made necessary by a structural difference between Liquid and Bitcoin (most importantly, Confidential Transactions and Liquid's deployed output types). These are the substantive technical contributions of this document. +* '''[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: + +* 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 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 +outputs, and on a match derives the blinding key to unblind and the spend key to spend. + +===Receiver keys and address=== + +The scan and spend key pairs are independent secp256k1 key pairs as in BIP-352. '''[BIP-352]''' +Seed derivation is left to the wallet (a BIP-32 scheme analogous to BIP-352's is +RECOMMENDED), since only the public keys are transmitted. '''[Choice]''' + +The address is the Bech32m '''[BIP-352]''' encoding of a version symbol followed by +the payload 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) || lqsp +|- +| 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: + +
+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_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
+
+ +==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 (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
+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 = 5120a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931
+
+k = 1:
+  P_k (spend pubkey) = 0229d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
+  BK_k (blinding pub) = 03efdeda770ccdbe8bf466fba48bfd2b2c436ab0c04658fc6d6c277de5078129fa
+  bk_k (blinding priv) = 945ba73a9804f62089c7d2ffdc079031031f0aebab372cec17ef9c110ebceb10
+  spend priv (b_spend + t_k) = 9eff3472230fc83ef5ea8f8c80401c4eecd595a048bd2482a107d3a49baa5a58
+  scriptPubKey = 512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
+
+ +The unlabeled mainnet (HRP lqsp) address for these keys: + +
+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 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. + +==Backwards Compatibility== + +This document defines a new, opt-in address type and output convention. It introduces +no consensus change and does not affect existing addresses, descriptors, or +transactions. Wallets that do not implement it are unaffected; a silent-payment +output, once created, is an ordinary confidential Taproot output on the chain and is +spent by an ordinary key-path signature, so existing relay and validation rules apply +unchanged. + +Discovering and spending silent-payment outputs requires wallet support (scanning and +tweak-aware signing). Hardware signers require firmware support for additive key +tweaks, which does not exist in common protocols today; until then, silent-payment +outputs are usable with software signing. + +==Reference Implementation== + +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== + +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.