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+ +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(P_k used directly; no script tree, no taptweak) +
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.