Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


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

==Abstract==

This document specifies Silent Payments for the Liquid Network. The key derivation,
address format, scanning, and spending follow BIP-352<ref name="bip352">BIP-352: Silent Payments. https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki</ref>
exactly. This specification only adds Liquid-specific differences.

==Motivation==

Confidential Transactions (CT)<ref name="ct">Confidential Transactions. https://elementsproject.org/features/confidential-transactions</ref>
hide a Liquid output's asset and amount but not its
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.

==Specification==

We reuse the notation, functions, and conventions of BIP-352. In particular,
<code>serP(P)</code> is the SEC1 compressed encoding of a point, <code>ser32(i)</code>
serializes a 32-bit unsigned integer most-significant-byte first, <code>n</code> is the
secp256k1 curve order, and <code>hash<sub>tag</sub>(x)</code> denotes
<code>SHA256(SHA256(tag) || SHA256(tag) || x)</code> as defined in
BIP-340<ref name="bip340">BIP-340: Schnorr Signatures for secp256k1. https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki</ref>.

We additionally define:

* Let <code>blind_tag</code> be the exact 26-byte ASCII string <code>LiquidSilentPayments/Blind</code>.

A mismatch in these bytes breaks unblinding across implementations. The tag is disjoint
from BIP-352's <code>BIP0352/SharedSecret</code>, so the blinding key and the BIP-352
output tweak, though both derived from <code>S</code>, 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 <code>k</code>:

* Let <code>S</code> be the shared secret and <code>P_k</code> the output public key, derived as in BIP-352
* Let <code>scriptPubKey = OP_1 <x_only(P_k)></code>, a BIP-341<ref name="bip341">BIP-341: Taproot: SegWit version 1 spending rules. https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki</ref> Taproot output
* Let <code>bk_k = int(hash<sub>blind_tag</sub>(serP(S) || ser32(k))) mod n</code>
** If <code>bk_k = 0</code>, increment <code>k</code> and continue
* Let <code>BK_k = bk_k·G</code>
* Blind the output's asset and amount to <code>BK_k</code>, as for any confidential Liquid output

The receiver, having recomputed <code>S</code> as in BIP-352, recomputes
<code>bk_k</code> and unblinds the output with no out-of-band exchange. Deriving the
blinding key from <code>S</code> 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 format.''' The output is a ''confidential'' Liquid Taproot<ref name="bip341" /> output, with its asset and amount blinded to <code>BK_k</code>, rather than a bare Taproot output.
* '''<code>outpoint_L</code> encoding.''' Outpoints use the Elements consensus encoding (32-byte txid in internal byte order followed by 4-byte little-endian <code>vout</code>) in place of BIP-352's Bitcoin encoding.
* '''Address HRP.''' The human-readable part is <code>lqsp</code> (mainnet) or <code>tlqsp</code> (testnet and regtest) — distinct from <code>ex</code>/<code>tex</code>, <code>lq</code>/<code>tlq</code>, and Bitcoin's <code>sp</code>/<code>tsp</code>. The address payload, version symbol (<code>q</code>), and relaxed Bech32 length limit are unchanged from BIP-352.
* '''Light-client scanning.''' The BIP-158<ref name="bip158">BIP-158: Compact Block Filters for Light Clients. https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki</ref> compact-filter step is unnecessary: CT does not blind the <code>scriptPubKey</code>, so a client matches its derived candidate scripts directly against the public output scripts. The BIP-352 tweak server<ref name="indexserver">BIP-352 Silent Payments index server. https://github.com/bitcoin/bips/blob/master/bip-0352/index-server.mediawiki</ref> (publishing <code>T = input_hash·A</code> per transaction) is used unchanged.

Everything else follows BIP-352 exactly: input eligibility, the even-Y rule,
<code>input_hash</code>, <code>S = input_hash·a·B_scan</code>, the output tweak
<code>t_k</code>, labels, gap-limit scanning, and the Taproot key-path spend with
<code>d = b_spend + t_k</code>.

===Privacy===

Detecting or unblinding a silent payment requires the Diffie-Hellman shared secret
<code>S = input_hash·a·B_scan = b_scan·(input_hash·A)</code>. Computing it needs either the
sender's input secret <code>a</code> or the receiver's secret scan key <code>b_scan</code>,
so this inherits the BIP-352 privacy model. CT adds one thing on top: even someone who
holds <code>b_scan</code> learns only that an output is a silent payment, not its amount or asset.

==Test Vectors==

All byte strings are hex. Receiver keys:

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

Two eligible inputs:

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

Aggregated values (<code>outpoint_L</code> = input 0, the lexicographically smaller):

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

Per-output values:

<pre>
k = 0:
P_k = 02a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931
BK_k = 0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc
bk_k = 70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0
spend priv = f03c436d2cd67ae1fecf7d88a38aa3a03c0abea43feaf6da8eb71e2e3a866bda
scriptPubKey = 5120a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931

k = 1:
P_k = 0229d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
BK_k = 03efdeda770ccdbe8bf466fba48bfd2b2c436ab0c04658fc6d6c277de5078129fa
bk_k = 945ba73a9804f62089c7d2ffdc079031031f0aebab372cec17ef9c110ebceb10
spend priv = 9eff3472230fc83ef5ea8f8c80401c4eecd595a048bd2482a107d3a49baa5a58
scriptPubKey = 512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92
</pre>

Mainnet address (HRP <code>lqsp</code>):

<pre>
lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe
</pre>

Because blinding factors are randomized per output, the blinded output is not
byte-reproducible. What matters is that an output blinded to <code>BK_k</code>
unblinds under <code>bk_k</code> and under no other key.

==Backwards Compatibility==

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 Implementations==

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This should be moved to this repo. Follow the convention in https://github.com/bitcoin/bips/blob/master/bip-0352/reference.py and use MIT license

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Okay ignore my comment about MIT license as other ELIPS are BSD license as well

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This should be moved to this repo

Do you mean the reference implementations? I noticed all the other ELIPs had external reference implementations so did it that way.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think they are almost always in https://github.com/ElementsProject/elements and thats why. Having them in a personal repo seems wrong or atleast different from the conventions in the bips repo


* Rust: [[elip-silent-payments-liquid/rust/src/lib.rs|elip-silent-payments-liquid/rust/src/lib.rs]]
* Python: [[elip-silent-payments-liquid/python/elip_sp_reference/core.py|elip-silent-payments-liquid/python/elip_sp_reference/core.py]]

==Acknowledgements==

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JAN3 should be in the acknowledgements if this BIP is awarded the bounty


This specification builds on BIP-352, the BIP-352 index server specification<ref name="indexserver" />,
and the Confidential Transactions work of the Elements Project<ref name="ct" />.

The authors thank JAN3 for sponsoring and supporting this work.

==References==

<references />
26 changes: 26 additions & 0 deletions elip-silent-payments-liquid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# elip-silent-payments-liquid — reference implementations

Reference implementations for [`elip-silent-payments-liquid.mediawiki`](../elip-silent-payments-liquid.mediawiki),
following the layout convention of [bitcoin/bips](https://github.com/bitcoin/bips)
(e.g. `bip-0352/`): supporting code lives in a folder named after the ELIP.

Two independent implementations derive the same test vectors byte-for-byte:

- [`rust/`](rust) — built on `lwk_wollet`. `cargo test` to run.
- [`python/`](python) — in the canonical BIP-352 `reference.py` style, using the
pure-Python `secp256k1lab` for the curve algebra and `wallycore` only for the
Liquid Confidential Transactions plumbing. See [`python/README.md`](python/README.md).

Both cover:

- **Address encoding** — Bech32m, HRP `lqsp`/`tlqsp`, version `q`
- **Sender derivation** — input aggregation, ECDH shared secret, `P_k`, `BK_k`, `bk_k`
- **Tweak server** — `T = input_hash · A`, publish, and client-side `S = b_scan · T`
- **Receiver scanning** — recompute `P_k`, match against outputs, derive spend secret
- **Confidential output blinding and unblinding** — the ELIP's novel claim: `bk_k`
derived from the shared secret unblinds the output non-interactively
- **Test vectors** — deterministic known-answer values matching the ELIP specification

## License

BSD-3-Clause.
13 changes: 13 additions & 0 deletions elip-silent-payments-liquid/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Virtual environments
.venv/
venv/

# Build / packaging artifacts
*.egg-info/
build/
dist/

# Caches
__pycache__/
*.py[cod]
.pytest_cache/
57 changes: 57 additions & 0 deletions elip-silent-payments-liquid/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Python reference implementation

Written in the style of the canonical
[BIP-352 reference](https://github.com/bitcoin/bips/blob/master/bip-0352/reference.py):
the elliptic-curve algebra is done with the pure-Python
[`secp256k1lab`](https://github.com/secp256k1lab/secp256k1lab), so the spec math
reads literally —

```python
P_k = B_spend + t_k * G # output public key
S = input_hash * a * B_scan # sender's shared secret
a = -a if is_taproot and odd_y else a # BIP-341 even-Y normalization
```

It re-derives the ELIP's test vectors byte-for-byte (the same constants as the
sibling Rust [`reference`](../rust) implementation).

> **INSECURE.** `secp256k1lab` is slow and not constant-time, intended for
> prototyping, test vectors, and education only. Do not use in production.

## What's BIP-352 and what's new

Everything up to the output public key `P_k` is plain BIP-352. The two
Liquid-specific additions are:

1. **The output blinding key** `bk_k = tagged_hash("LiquidSilentPayments/Blind",
serP(S) || ser32(k))`. Both parties derive it from the shared secret `S`, so
the receiver can unblind the Confidential Transactions output with no
out-of-band data.
2. **The CT plumbing** (`build_confidential_sp_txout` / `unblind_output`) — the
one place needing Liquid primitives, so it (and only it) uses `wallycore`.
Everything else is pure `secp256k1lab`.

## Layout

```
elip_sp_reference/
__init__.py public API re-exports
core.py the protocol — addresses, inputs, shared secret, derivation,
tweak server, and CT blinding/unblinding
bech32m.py Bech32m, verbatim from BIP-350 / the BIP-352 reference
tests/
test_vectors.py byte-pinned vectors + taproot/tweak-server/address/CT round-trips
```

## Running

```sh
python3 -m venv .venv
source .venv/bin/activate # fish: source .venv/bin/activate.fish
pip install -e '.[test]'
pytest -v
```

`secp256k1lab` is fetched from git (it is not published on PyPI). `wallycore`'s
PyPI wheels are built with Elements support, which the CT test needs; if it is
not installed, that one test is skipped automatically.
56 changes: 56 additions & 0 deletions elip-silent-payments-liquid/python/elip_sp_reference/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Silent Payments for the Liquid Network — Python reference implementation.

Written in the style of the canonical BIP-352 reference, using the pure-Python
``secp256k1lab`` for the curve algebra (so the spec math reads literally) and
``wallycore`` only for the Liquid-specific Confidential Transactions plumbing.

INSECURE — prototyping and test vectors only. See :mod:`elip_sp_reference.core`.
"""

from .core import (
TAG_INPUTS,
TAG_SHARED_SECRET,
TAG_BLIND,
SP_ADDRESS_VERSION,
ser_uint32,
hrp_for,
encode_silent_payment_address,
decode_silent_payment_address,
sum_input_privkeys,
get_input_hash,
sender_shared_secret,
receiver_shared_secret,
output_tweak,
output_pubkey,
output_spend_privkey,
blinding_privkey,
script_pubkey,
compute_tweak,
shared_secret_from_tweak,
build_confidential_sp_txout,
unblind_output,
)

__all__ = [
"TAG_INPUTS",
"TAG_SHARED_SECRET",
"TAG_BLIND",
"SP_ADDRESS_VERSION",
"ser_uint32",
"hrp_for",
"encode_silent_payment_address",
"decode_silent_payment_address",
"sum_input_privkeys",
"get_input_hash",
"sender_shared_secret",
"receiver_shared_secret",
"output_tweak",
"output_pubkey",
"output_spend_privkey",
"blinding_privkey",
"script_pubkey",
"compute_tweak",
"shared_secret_from_tweak",
"build_confidential_sp_txout",
"unblind_output",
]
Loading