diff --git a/elip-silent-payments-liquid.mediawiki b/elip-silent-payments-liquid.mediawiki
new file mode 100644
index 0000000..577b06c
--- /dev/null
+++ b/elip-silent-payments-liquid.mediawiki
@@ -0,0 +1,152 @@
+
+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
+
+
+==Abstract==
+
+This document specifies Silent Payments for the Liquid Network. The key derivation,
+address format, scanning, and spending follow BIP-352[BIP-352: Silent Payments. https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki]
+exactly. This specification only adds Liquid-specific differences.
+
+==Motivation==
+
+Confidential Transactions (CT)[Confidential Transactions. https://elementsproject.org/features/confidential-transactions]
+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,
+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-340[BIP-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-341[BIP-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 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-158[BIP-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 server[BIP-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===
+
+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==
+
+All byte strings are hex. Receiver keys:
+
+
+b_scan = 1111111111111111111111111111111111111111111111111111111111111111
+b_spend = 2222222222222222222222222222222222222222222222222222222222222222
+
+
+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 values (outpoint_L = input 0, the lexicographically smaller):
+
+
+A = 031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99
+input_hash = d392922c00280a7e8d282182f5026f2fddbc74c1e1de18b4822128b2b77ec641
+
+
+Per-output values:
+
+
+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
+
+
+Mainnet address (HRP lqsp):
+
+
+lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe
+
+
+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,
+spent by an ordinary key-path signature, so existing relay and validation rules apply.
+Wallets that don't implement this are unaffected.
+
+==Reference Implementations==
+
+* 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==
+
+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 and supporting this work.
+
+==References==
+
+
diff --git a/elip-silent-payments-liquid/README.md b/elip-silent-payments-liquid/README.md
new file mode 100644
index 0000000..e7e988d
--- /dev/null
+++ b/elip-silent-payments-liquid/README.md
@@ -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.
diff --git a/elip-silent-payments-liquid/python/.gitignore b/elip-silent-payments-liquid/python/.gitignore
new file mode 100644
index 0000000..fa492bf
--- /dev/null
+++ b/elip-silent-payments-liquid/python/.gitignore
@@ -0,0 +1,13 @@
+# Virtual environments
+.venv/
+venv/
+
+# Build / packaging artifacts
+*.egg-info/
+build/
+dist/
+
+# Caches
+__pycache__/
+*.py[cod]
+.pytest_cache/
diff --git a/elip-silent-payments-liquid/python/README.md b/elip-silent-payments-liquid/python/README.md
new file mode 100644
index 0000000..e05038e
--- /dev/null
+++ b/elip-silent-payments-liquid/python/README.md
@@ -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.
diff --git a/elip-silent-payments-liquid/python/elip_sp_reference/__init__.py b/elip-silent-payments-liquid/python/elip_sp_reference/__init__.py
new file mode 100644
index 0000000..4d4834a
--- /dev/null
+++ b/elip-silent-payments-liquid/python/elip_sp_reference/__init__.py
@@ -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",
+]
diff --git a/elip-silent-payments-liquid/python/elip_sp_reference/bech32m.py b/elip-silent-payments-liquid/python/elip_sp_reference/bech32m.py
new file mode 100644
index 0000000..5f87cdb
--- /dev/null
+++ b/elip-silent-payments-liquid/python/elip_sp_reference/bech32m.py
@@ -0,0 +1,139 @@
+# Copyright (c) 2017, 2020 Pieter Wuille
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+"""Reference implementation for Bech32/Bech32m and segwit addresses.
+
+Verbatim from BIP-350 / the BIP-352 reference, so silent-payment address
+encoding matches the canonical implementation exactly.
+"""
+
+
+from enum import Enum
+
+class Encoding(Enum):
+ """Enumeration type to list the various supported encodings."""
+ BECH32 = 1
+ BECH32M = 2
+
+CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+BECH32M_CONST = 0x2bc830a3
+
+def bech32_polymod(values):
+ """Internal function that computes the Bech32 checksum."""
+ generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
+ chk = 1
+ for value in values:
+ top = chk >> 25
+ chk = (chk & 0x1ffffff) << 5 ^ value
+ for i in range(5):
+ chk ^= generator[i] if ((top >> i) & 1) else 0
+ return chk
+
+
+def bech32_hrp_expand(hrp):
+ """Expand the HRP into values for checksum computation."""
+ return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
+
+
+def bech32_verify_checksum(hrp, data):
+ """Verify a checksum given HRP and converted data characters."""
+ const = bech32_polymod(bech32_hrp_expand(hrp) + data)
+ if const == 1:
+ return Encoding.BECH32
+ if const == BECH32M_CONST:
+ return Encoding.BECH32M
+ return None
+
+def bech32_create_checksum(hrp, data, spec):
+ """Compute the checksum values given HRP and data."""
+ values = bech32_hrp_expand(hrp) + data
+ const = BECH32M_CONST if spec == Encoding.BECH32M else 1
+ polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
+ return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
+
+
+def bech32_encode(hrp, data, spec):
+ """Compute a Bech32 string given HRP and data values."""
+ combined = data + bech32_create_checksum(hrp, data, spec)
+ return hrp + '1' + ''.join([CHARSET[d] for d in combined])
+
+def bech32_decode(bech):
+ """Validate a Bech32/Bech32m string, and determine HRP and data."""
+ if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
+ (bech.lower() != bech and bech.upper() != bech)):
+ return (None, None, None)
+ bech = bech.lower()
+ pos = bech.rfind('1')
+
+ # remove the requirement that bech32m be less than 90 chars
+ if pos < 1 or pos + 7 > len(bech):
+ return (None, None, None)
+ if not all(x in CHARSET for x in bech[pos+1:]):
+ return (None, None, None)
+ hrp = bech[:pos]
+ data = [CHARSET.find(x) for x in bech[pos+1:]]
+ spec = bech32_verify_checksum(hrp, data)
+ if spec is None:
+ return (None, None, None)
+ return (hrp, data[:-6], spec)
+
+def convertbits(data, frombits, tobits, pad=True):
+ """General power-of-2 base conversion."""
+ acc = 0
+ bits = 0
+ ret = []
+ maxv = (1 << tobits) - 1
+ max_acc = (1 << (frombits + tobits - 1)) - 1
+ for value in data:
+ if value < 0 or (value >> frombits):
+ return None
+ acc = ((acc << frombits) | value) & max_acc
+ bits += frombits
+ while bits >= tobits:
+ bits -= tobits
+ ret.append((acc >> bits) & maxv)
+ if pad:
+ if bits:
+ ret.append((acc << (tobits - bits)) & maxv)
+ elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
+ return None
+ return ret
+
+
+def decode(hrp, addr):
+ """Decode a segwit address."""
+ hrpgot, data, spec = bech32_decode(addr)
+ if hrpgot != hrp:
+ return (None, None)
+ decoded = convertbits(data[1:], 5, 8, False)
+ if decoded is None or len(decoded) < 2:
+ return (None, None)
+ if data[0] > 16:
+ return (None, None)
+ return (data[0], decoded)
+
+
+def encode(hrp, witver, witprog):
+ """Encode a segwit address."""
+ spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
+ ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
+ if decode(hrp, ret) == (None, None):
+ return None
+ return ret
diff --git a/elip-silent-payments-liquid/python/elip_sp_reference/core.py b/elip-silent-payments-liquid/python/elip_sp_reference/core.py
new file mode 100644
index 0000000..1f5f5ff
--- /dev/null
+++ b/elip-silent-payments-liquid/python/elip_sp_reference/core.py
@@ -0,0 +1,256 @@
+"""Silent Payments for the Liquid Network — 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`` library, so the spec
+math reads literally — ``B_spend + t_k * G``, ``input_hash * a * B_scan``, ``-k``.
+
+INSECURE. ``secp256k1lab`` is for prototyping and test vectors only; it is slow
+and not constant-time. Do not use in production.
+
+Everything up to and including the output public key ``P_k`` is BIP-352. The two
+Liquid-specific additions are:
+
+* the per-output **blinding key** ``bk_k`` (tag ``LiquidSilentPayments/Blind``),
+ which lets the sender blind a Confidential Transactions output so that only the
+ receiver — who can recompute ``bk_k`` — can unblind it, and
+* the Confidential Transactions plumbing (``build_confidential_sp_txout`` /
+ ``unblind_output``), which is the one place that needs Liquid primitives and so
+ uses ``wallycore`` rather than ``secp256k1lab``.
+"""
+
+from typing import Dict, List, Tuple
+
+from secp256k1lab.secp256k1 import G, GE, Scalar
+from secp256k1lab.util import tagged_hash
+
+from . import bech32m
+
+
+# ═══════════════════════════════════════════════════════════════════════════════
+# Tagged-hash domains
+# ═══════════════════════════════════════════════════════════════════════════════
+
+TAG_INPUTS = "BIP0352/Inputs"
+TAG_SHARED_SECRET = "BIP0352/SharedSecret"
+# Disjoint from BIP0352/SharedSecret, so bk_k and t_k are independent even though
+# both are derived from the same shared secret S.
+TAG_BLIND = "LiquidSilentPayments/Blind"
+
+# Address version 0 (Bech32 character `q`).
+SP_ADDRESS_VERSION = 0
+
+
+def ser_uint32(n: int) -> bytes:
+ """Serialize a 32-bit counter, most-significant byte first."""
+ return n.to_bytes(4, "big")
+
+
+# ═══════════════════════════════════════════════════════════════════════════════
+# Address encoding / decoding
+# ═══════════════════════════════════════════════════════════════════════════════
+
+# HRP per network. Distinct from Liquid's ex/lq (and testnet tex/tlq) prefixes.
+_HRP = {"liquid": "lqsp", "liquid-testnet": "tlqsp", "liquid-regtest": "tlqsp"}
+
+
+def hrp_for(network: str) -> str:
+ return _HRP["liquid"] if network == "liquid" else _HRP["liquid-testnet"]
+
+
+def encode_silent_payment_address(B_scan: GE, B_spend: GE, network: str = "liquid") -> str:
+ """Bech32m(HRP, version || serP(B_scan) || serP(B_spend))."""
+ data = bech32m.convertbits(
+ B_scan.to_bytes_compressed() + B_spend.to_bytes_compressed(), 8, 5
+ )
+ return bech32m.bech32_encode(
+ hrp_for(network), [SP_ADDRESS_VERSION] + data, bech32m.Encoding.BECH32M
+ )
+
+
+def decode_silent_payment_address(address: str, network: str = "liquid") -> Tuple[GE, GE]:
+ """Inverse of encode_silent_payment_address. Raises on a bad HRP or version."""
+ version, data = bech32m.decode(hrp_for(network), address)
+ if data is None:
+ raise ValueError("bad HRP or checksum")
+ if version != SP_ADDRESS_VERSION:
+ raise ValueError(f"unknown address version {version}")
+ if len(data) != 66:
+ raise ValueError(f"wrong payload length {len(data)}")
+ B_scan = GE.from_bytes_compressed(bytes(data[:33]))
+ B_spend = GE.from_bytes_compressed(bytes(data[33:]))
+ return B_scan, B_spend
+
+
+# ═══════════════════════════════════════════════════════════════════════════════
+# Inputs — aggregation and input hash
+# ═══════════════════════════════════════════════════════════════════════════════
+
+
+def sum_input_privkeys(input_priv_keys: List[Tuple[Scalar, bool]]) -> Scalar:
+ """Sum the eligible input private keys, applying BIP-352 even-Y normalization.
+
+ Each entry is ``(private_key, is_taproot)``. A taproot (BIP-341) prevout
+ commits only to the x-only key, so its implicit public key is even-Y: if
+ ``a * G`` has odd Y, negate ``a`` first. Non-taproot keys are summed as-is.
+ """
+ negated = []
+ for a, is_taproot in input_priv_keys:
+ if is_taproot and not (a * G).has_even_y():
+ a = -a
+ negated.append(a)
+ return Scalar.sum(*negated)
+
+
+def get_input_hash(outpoints: List[bytes], A: GE) -> Scalar:
+ """input_hash = tagged_hash("BIP0352/Inputs", lowest_outpoint || serP(A)).
+
+ Each outpoint is the 36 bytes ``txid (32) || vout (4, little-endian)``.
+ """
+ lowest = sorted(outpoints)[0]
+ h = tagged_hash(TAG_INPUTS, lowest + A.to_bytes_compressed())
+ return Scalar.from_bytes_wrapping(h)
+
+
+# ═══════════════════════════════════════════════════════════════════════════════
+# Shared secret
+# ═══════════════════════════════════════════════════════════════════════════════
+
+
+def sender_shared_secret(input_hash: Scalar, a_sum: Scalar, B_scan: GE) -> GE:
+ """Sender's ECDH shared secret S = input_hash * a * B_scan."""
+ return input_hash * a_sum * B_scan
+
+
+def receiver_shared_secret(input_hash: Scalar, b_scan: Scalar, A_sum: GE) -> GE:
+ """Receiver's ECDH shared secret S = input_hash * b_scan * A_sum."""
+ return input_hash * b_scan * A_sum
+
+
+# ═══════════════════════════════════════════════════════════════════════════════
+# Per-output derivation
+# ═══════════════════════════════════════════════════════════════════════════════
+
+
+def output_tweak(S: GE, k: int) -> Scalar:
+ """t_k = tagged_hash("BIP0352/SharedSecret", serP(S) || ser32(k))."""
+ return Scalar.from_bytes_wrapping(
+ tagged_hash(TAG_SHARED_SECRET, S.to_bytes_compressed() + ser_uint32(k))
+ )
+
+
+def output_pubkey(B_spend: GE, S: GE, k: int) -> GE:
+ """P_k = B_spend + t_k * G."""
+ return B_spend + output_tweak(S, k) * G
+
+
+def output_spend_privkey(b_spend: Scalar, S: GE, k: int) -> Scalar:
+ """The spend private key for P_k: b_spend + t_k (mod n)."""
+ return b_spend + output_tweak(S, k)
+
+
+def blinding_privkey(S: GE, k: int) -> Scalar:
+ """bk_k = tagged_hash("LiquidSilentPayments/Blind", serP(S) || ser32(k)).
+
+ The Liquid-specific output blinding key. Both sender and receiver can derive
+ it from S, so the receiver needs no out-of-band data to unblind the output.
+ """
+ return Scalar.from_bytes_wrapping(
+ tagged_hash(TAG_BLIND, S.to_bytes_compressed() + ser_uint32(k))
+ )
+
+
+def script_pubkey(P_k: GE) -> bytes:
+ """scriptPubKey = OP_1 (a P2TR output, no taptweak per BIP-352)."""
+ return bytes([0x51, 0x20]) + P_k.to_bytes_xonly()
+
+
+# ═══════════════════════════════════════════════════════════════════════════════════════════
+# Tweak server
+# ═══════════════════════════════════════════════════════════════════════════════════════════
+#
+# The server publishes T = input_hash * A per transaction; a client holding
+# b_scan computes S = b_scan * T, recovering the sender's shared secret without
+# learning any private key. On Liquid the scriptPubKey is public, so BIP-158
+# compact filters are unnecessary.
+
+
+def compute_tweak(input_pubkeys: List[GE], outpoints: List[bytes]) -> Tuple[GE, Scalar, GE]:
+ """Compute (T = input_hash * A, input_hash, A) from the eligible input pubkeys."""
+ A = GE.sum(*input_pubkeys)
+ input_hash = get_input_hash(outpoints, A)
+ T = input_hash * A
+ return T, input_hash, A
+
+
+def shared_secret_from_tweak(b_scan: Scalar, T: GE) -> GE:
+ """S = b_scan * T (from a published tweak)."""
+ return b_scan * T
+
+
+# ═══════════════════════════════════════════════════════════════════════════════
+# Confidential Transactions blinding / unblinding (Liquid-specific)
+# ═══════════════════════════════════════════════════════════════════════════════
+#
+# This is the only part that needs Liquid primitives, so it uses wallycore. The
+# sender blinds the output to BK_k = bk_k * G; the CT nonce is an ECDH between an
+# ephemeral key and BK_k. The receiver recomputes bk_k, derives the same nonce,
+# and unblinds — recovering the plaintext asset and value with no extra messages.
+
+
+def build_confidential_sp_txout(
+ BK_k: GE,
+ P_k: GE,
+ asset_id: bytes,
+ value: int,
+ abf: bytes,
+ vbf: bytes,
+ ephemeral_sk: Scalar,
+) -> Dict[str, bytes]:
+ """Build a confidential output blinded to BK_k.
+
+ ``asset_id`` is the 32-byte asset tag, ``value`` the amount in satoshi,
+ ``abf``/``vbf`` the 32-byte asset/value blinding factors, ``ephemeral_sk`` the
+ ephemeral scalar whose ECDH with BK_k forms the CT nonce. Returns the
+ commitments, nonce pubkey, rangeproof and scriptPubKey.
+ """
+ import wallycore as wally
+
+ script = script_pubkey(P_k)
+ nonce_hash = wally.ecdh_nonce_hash(BK_k.to_bytes_compressed(), ephemeral_sk.to_bytes())
+ nonce_pubkey = (ephemeral_sk * G).to_bytes_compressed()
+
+ asset_generator = wally.asset_generator_from_bytes(asset_id, abf)
+ value_commitment = wally.asset_value_commitment(value, vbf, asset_generator)
+ rangeproof = wally.asset_rangeproof_with_nonce(
+ value, nonce_hash, asset_id, abf, vbf,
+ value_commitment, script, asset_generator,
+ 1, 0, 52,
+ )
+ return {
+ "asset_generator": asset_generator,
+ "value_commitment": value_commitment,
+ "nonce_pubkey": nonce_pubkey,
+ "rangeproof": rangeproof,
+ "script_pubkey": script,
+ }
+
+
+def unblind_output(txout: Dict[str, bytes], bk_k: Scalar) -> Dict[str, object]:
+ """Unblind a confidential output with bk_k, recovering (asset, value, abf, vbf).
+
+ Raises if bk_k is wrong (the rangeproof will not validate against the nonce).
+ """
+ import wallycore as wally
+
+ nonce_hash = wally.ecdh_nonce_hash(txout["nonce_pubkey"], bk_k.to_bytes())
+ # The high-level wrapper allocates its own asset/abf/vbf buffers and returns
+ # (value, asset, abf, vbf).
+ value, asset, abf, vbf = wally.asset_unblind_with_nonce(
+ nonce_hash,
+ txout["rangeproof"],
+ txout["value_commitment"],
+ txout["script_pubkey"],
+ txout["asset_generator"],
+ )
+ return {"asset": bytes(asset), "value": value, "abf": bytes(abf), "vbf": bytes(vbf)}
diff --git a/elip-silent-payments-liquid/python/pyproject.toml b/elip-silent-payments-liquid/python/pyproject.toml
new file mode 100644
index 0000000..2817433
--- /dev/null
+++ b/elip-silent-payments-liquid/python/pyproject.toml
@@ -0,0 +1,26 @@
+[build-system]
+requires = ["setuptools>=61"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "elip-sp-reference"
+version = "0.1.0"
+description = "Python reference implementation for the Silent Payments for the Liquid Network ELIP. Written in the BIP-352 reference style with secp256k1lab; re-derives the spec's test vectors and uses wallycore for the Liquid Confidential Transactions plumbing."
+license = { text = "BSD-3-Clause" }
+requires-python = ">=3.11"
+dependencies = [
+ # Pure-Python secp256k1 (INSECURE; prototyping only) — same library the
+ # canonical BIP-352 reference uses. Not on PyPI; installed from git.
+ "secp256k1lab @ git+https://github.com/secp256k1lab/secp256k1lab.git",
+ # Liquid-specific Confidential Transactions primitives.
+ "wallycore>=1.3.0",
+]
+
+[project.optional-dependencies]
+test = ["pytest>=7"]
+
+[tool.setuptools]
+packages = ["elip_sp_reference"]
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
diff --git a/elip-silent-payments-liquid/python/tests/test_vectors.py b/elip-silent-payments-liquid/python/tests/test_vectors.py
new file mode 100644
index 0000000..e78d058
--- /dev/null
+++ b/elip-silent-payments-liquid/python/tests/test_vectors.py
@@ -0,0 +1,219 @@
+"""Reference test vectors, in the style of the BIP-352 reference tests.
+
+Same constants as the Rust ``reference`` implementation, asserted byte-for-byte.
+"""
+
+import os
+import sys
+
+import pytest
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from secp256k1lab.secp256k1 import G, GE, Scalar # noqa: E402
+
+from elip_sp_reference import ( # noqa: E402
+ blinding_privkey,
+ compute_tweak,
+ decode_silent_payment_address,
+ encode_silent_payment_address,
+ get_input_hash,
+ output_pubkey,
+ output_spend_privkey,
+ receiver_shared_secret,
+ script_pubkey,
+ sender_shared_secret,
+ shared_secret_from_tweak,
+ sum_input_privkeys,
+)
+
+
+def sk(b: int) -> Scalar:
+ return Scalar.from_bytes_checked(bytes([b] * 32))
+
+
+def outpoint(txid_byte: int, vout: int) -> bytes:
+ return bytes([txid_byte] * 32) + vout.to_bytes(4, "little")
+
+
+def test_vectors():
+ """b_scan = 0x11*32, b_spend = 0x22*32; inputs 0x31 @ 0x10:0, 0x32 @ 0x20:1."""
+ b_scan, b_spend = sk(0x11), sk(0x22)
+ B_scan, B_spend = b_scan * G, b_spend * G
+
+ input_priv_keys = [(sk(0x31), False), (sk(0x32), False)]
+ outpoints = [outpoint(0x10, 0), outpoint(0x20, 1)]
+
+ a_sum = sum_input_privkeys(input_priv_keys)
+ A = a_sum * G
+ input_hash = get_input_hash(outpoints, A)
+
+ assert (
+ A.to_bytes_compressed().hex()
+ == "031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99"
+ ), "A"
+ assert (
+ input_hash.to_bytes().hex()
+ == "d392922c00280a7e8d282182f5026f2fddbc74c1e1de18b4822128b2b77ec641"
+ ), "input_hash"
+
+ expected = [
+ (
+ 0,
+ "02a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931",
+ "0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc",
+ "70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0",
+ "f03c436d2cd67ae1fecf7d88a38aa3a03c0abea43feaf6da8eb71e2e3a866bda",
+ "5120a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931",
+ ),
+ (
+ 1,
+ "0229d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92",
+ "03efdeda770ccdbe8bf466fba48bfd2b2c436ab0c04658fc6d6c277de5078129fa",
+ "945ba73a9804f62089c7d2ffdc079031031f0aebab372cec17ef9c110ebceb10",
+ "9eff3472230fc83ef5ea8f8c80401c4eecd595a048bd2482a107d3a49baa5a58",
+ "512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92",
+ ),
+ ]
+
+ S_send = sender_shared_secret(input_hash, a_sum, B_scan)
+ S_recv = receiver_shared_secret(input_hash, b_scan, A)
+ assert S_send == S_recv, "sender and receiver shared secrets agree"
+
+ for k, pk, bk_pub, bk_sk, spk_sk, script in expected:
+ P_k = output_pubkey(B_spend, S_send, k)
+ bk = blinding_privkey(S_send, k)
+ BK_k = bk * G
+ spend_sk = output_spend_privkey(b_spend, S_send, k)
+
+ # The receiver recomputes the same P_k from b_spend and the shared secret.
+ assert output_pubkey(B_spend, S_recv, k) == P_k, f"P_k receiver agrees k={k}"
+ assert spend_sk * G == P_k, f"spend_sk * G == P_k k={k}"
+
+ assert P_k.to_bytes_compressed().hex() == pk, f"P_k k={k}"
+ assert BK_k.to_bytes_compressed().hex() == bk_pub, f"BK_k k={k}"
+ assert bk.to_bytes().hex() == bk_sk, f"bk_k k={k}"
+ assert spend_sk.to_bytes().hex() == spk_sk, f"spend_sk k={k}"
+ assert script_pubkey(P_k).hex() == script, f"scriptPubKey k={k}"
+
+ assert (
+ encode_silent_payment_address(B_scan, B_spend, "liquid")
+ == "lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe"
+ ), "mainnet address"
+
+
+def test_taproot_even_y_negation():
+ """BIP-352 even-Y normalization for taproot inputs."""
+ # Find an odd-Y private key.
+ odd = None
+ for b in range(1, 0x100):
+ if not (sk(b) * G).has_even_y():
+ odd = sk(b)
+ break
+ assert odd is not None
+
+ op = outpoint(0x10, 0)
+
+ # As a taproot input, an odd-Y key is negated; A differs from the non-taproot
+ # aggregate, but its x-only coordinate is unchanged and A is even-Y.
+ a_tr = sum_input_privkeys([(odd, True)])
+ a_legacy = sum_input_privkeys([(odd, False)])
+ assert a_tr != a_legacy, "odd-Y taproot key is negated"
+ assert a_tr == -a_legacy, "negation is exactly n - a"
+
+ A_tr = a_tr * G
+ assert A_tr.to_bytes_xonly() == (odd * G).to_bytes_xonly(), "x-only A unchanged"
+
+ # An even-Y key behaves identically whether or not it is taproot.
+ even = None
+ for b in range(1, 0x100):
+ if (sk(b) * G).has_even_y():
+ even = sk(b)
+ break
+ assert even is not None
+ assert sum_input_privkeys([(even, True)]) == sum_input_privkeys([(even, False)])
+
+
+def test_tweak_server_agreement():
+ """A tweak server publishes T = input_hash * A; client computes S = b_scan * T."""
+ b_scan, b_spend = sk(0x11), sk(0x22)
+ B_scan, B_spend = b_scan * G, b_spend * G
+
+ input_priv_keys = [(sk(0x55), False)]
+ op = outpoint(0xAA, 0)
+
+ a_sum = sum_input_privkeys(input_priv_keys)
+ A = a_sum * G
+ input_hash = get_input_hash([op], A)
+
+ S_sender = sender_shared_secret(input_hash, a_sum, B_scan)
+
+ # Server side: publish T = input_hash * A from public keys only.
+ T, ih_server, A_server = compute_tweak([sk(0x55) * G], [op])
+ assert A_server == A and ih_server == input_hash
+ # Client side: S = b_scan * T.
+ S_client = shared_secret_from_tweak(b_scan, T)
+
+ assert S_client == S_sender, "client shared secret matches sender"
+ assert output_pubkey(B_spend, S_client, 0) == output_pubkey(B_spend, S_sender, 0)
+
+
+def test_address_round_trip_and_network_separation():
+ b_scan, b_spend = sk(0x11), sk(0x22)
+ B_scan, B_spend = b_scan * G, b_spend * G
+
+ for network in ("liquid", "liquid-testnet"):
+ enc = encode_silent_payment_address(B_scan, B_spend, network)
+ d_scan, d_spend = decode_silent_payment_address(enc, network)
+ assert d_scan == B_scan and d_spend == B_spend
+
+ mainnet = encode_silent_payment_address(B_scan, B_spend, "liquid")
+ with pytest.raises(ValueError):
+ decode_silent_payment_address(mainnet, "liquid-testnet")
+
+
+def test_ct_round_trip_unblind_with_bk():
+ """CT round-trip: sender blinds to BK_k, receiver unblinds with bk_k."""
+ pytest.importorskip("wallycore")
+ from elip_sp_reference import build_confidential_sp_txout, unblind_output
+
+ b_scan, b_spend = sk(0x11), sk(0x22)
+ B_scan, B_spend = b_scan * G, b_spend * G
+
+ input_priv_keys = [(sk(0x33), False)]
+ op = outpoint(0xAB, 0)
+ a_sum = sum_input_privkeys(input_priv_keys)
+ A = a_sum * G
+ input_hash = get_input_hash([op], A)
+
+ k = 0
+ S = sender_shared_secret(input_hash, a_sum, B_scan)
+ P_k = output_pubkey(B_spend, S, k)
+ bk = blinding_privkey(S, k)
+ BK_k = bk * G
+
+ asset_id = bytes([0x42] * 32)
+ value = 123_456
+ abf = bytes([0x01] * 32)
+ vbf = bytes([0x02] * 32)
+ ephemeral_sk = sk(0x07)
+
+ txout = build_confidential_sp_txout(BK_k, P_k, asset_id, value, abf, vbf, ephemeral_sk)
+
+ # The receiver recomputes bk_k independently and unblinds.
+ S_recv = receiver_shared_secret(input_hash, b_scan, A)
+ bk_recv = blinding_privkey(S_recv, k)
+ assert bk_recv == bk
+
+ recovered = unblind_output(txout, bk_recv)
+ assert recovered["value"] == value
+ assert recovered["abf"] == abf
+ assert recovered["vbf"] == vbf
+
+ # A wrong scan key derives a different bk_k, so unblind fails.
+ wrong_bk = blinding_privkey(
+ receiver_shared_secret(input_hash, sk(0x99), A), k
+ )
+ assert wrong_bk != bk
+ with pytest.raises(Exception):
+ unblind_output(txout, wrong_bk)
diff --git a/elip-silent-payments-liquid/rust/.gitignore b/elip-silent-payments-liquid/rust/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/elip-silent-payments-liquid/rust/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/elip-silent-payments-liquid/rust/Cargo.lock b/elip-silent-payments-liquid/rust/Cargo.lock
new file mode 100644
index 0000000..b202266
--- /dev/null
+++ b/elip-silent-payments-liquid/rust/Cargo.lock
@@ -0,0 +1,3127 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "aes-gcm-siv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "polyval",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "age"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a07d86e4272c093c88caf7864a2d09af52a5159180848ca4832a3cdbd7d014d5"
+dependencies = [
+ "age-core",
+ "base64 0.21.7",
+ "bech32 0.9.1",
+ "chacha20poly1305",
+ "cookie-factory",
+ "hmac",
+ "i18n-embed",
+ "i18n-embed-fl",
+ "lazy_static",
+ "nom",
+ "pin-project",
+ "rand 0.8.6",
+ "rust-embed",
+ "scrypt",
+ "sha2",
+ "subtle",
+ "x25519-dalek",
+ "zeroize",
+]
+
+[[package]]
+name = "age-core"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99"
+dependencies = [
+ "base64 0.21.7",
+ "chacha20poly1305",
+ "cookie-factory",
+ "hkdf",
+ "io_tee",
+ "nom",
+ "rand 0.8.6",
+ "secrecy",
+ "sha2",
+]
+
+[[package]]
+name = "arc-swap"
+version = "1.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base58ck"
+version = "0.1.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0"
+dependencies = [
+ "bitcoin_hashes",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
+name = "basic-toml"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bech32"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
+
+[[package]]
+name = "bech32"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
+
+[[package]]
+name = "bip39"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc"
+dependencies = [
+ "bitcoin_hashes",
+ "serde",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "bitcoin"
+version = "0.32.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed8ccb78a9ff7a6fbb90e2fb9b8588b4a9928d49d8af3cb789108a84ea6b0ce"
+dependencies = [
+ "base58ck",
+ "base64 0.21.7",
+ "bech32 0.11.1",
+ "bitcoin-io",
+ "bitcoin-units",
+ "bitcoin_hashes",
+ "hex-conservative 0.2.2",
+ "hex_lit",
+ "secp256k1",
+ "serde",
+]
+
+[[package]]
+name = "bitcoin-consensus-encoding"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40"
+dependencies = [
+ "bitcoin-internals",
+]
+
+[[package]]
+name = "bitcoin-internals"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64"
+dependencies = [
+ "hex-conservative 0.3.2",
+]
+
+[[package]]
+name = "bitcoin-io"
+version = "0.1.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78"
+dependencies = [
+ "bitcoin-consensus-encoding",
+]
+
+[[package]]
+name = "bitcoin-private"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57"
+
+[[package]]
+name = "bitcoin-units"
+version = "0.1.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cb95693f371d089a4b5b6fc41c6f3ea6e01ee8c15388335dfac8ea685173b51"
+dependencies = [
+ "bitcoin-consensus-encoding",
+ "serde",
+]
+
+[[package]]
+name = "bitcoin_hashes"
+version = "0.14.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2"
+dependencies = [
+ "bitcoin-io",
+ "hex-conservative 0.2.2",
+ "serde",
+]
+
+[[package]]
+name = "bitcoincore-rpc"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee"
+dependencies = [
+ "bitcoincore-rpc-json",
+ "jsonrpc",
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "bitcoincore-rpc-json"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a"
+dependencies = [
+ "bitcoin",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bmp-monochrome"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829a082bd3761fde7476dc2ed85ca56c11628948460ece621e4f56fef5046567"
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
+
+[[package]]
+name = "cbc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chacha20"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "chacha20poly1305"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
+dependencies = [
+ "aead",
+ "chacha20",
+ "cipher",
+ "poly1305",
+ "zeroize",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+ "zeroize",
+]
+
+[[package]]
+name = "cookie-factory"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
+dependencies = [
+ "futures",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "electrum-client"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a0bd443023f9f5c4b7153053721939accc7113cbdf810a024434eed454b3db1"
+dependencies = [
+ "bitcoin",
+ "byteorder",
+ "libc",
+ "log",
+ "rustls",
+ "serde",
+ "serde_json",
+ "webpki-roots 0.25.4",
+ "winapi",
+]
+
+[[package]]
+name = "elements"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83c63a04238f4b7f3564fe9705921452c2900efd00982545e5c634fac9024fe2"
+dependencies = [
+ "bech32 0.11.1",
+ "bitcoin",
+ "secp256k1-zkp",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "elements"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1210978e2686f892318085f806866014d4788479a37f0a634db97fcb39454bde"
+dependencies = [
+ "bech32 0.11.1",
+ "bitcoin",
+ "secp256k1-zkp",
+ "serde_json",
+]
+
+[[package]]
+name = "elements-miniscript"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "571fa105690f83c7833df2109eb2e14ca0e62d633d2624ffcb166ff18a3da870"
+dependencies = [
+ "bitcoin",
+ "elements 0.25.3",
+ "miniscript",
+ "serde",
+]
+
+[[package]]
+name = "elip-sp-reference"
+version = "0.1.0"
+dependencies = [
+ "bech32 0.11.1",
+ "lwk_wollet",
+ "rand 0.8.6",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "find-crate"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fluent"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a"
+dependencies = [
+ "fluent-bundle",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-bundle"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493"
+dependencies = [
+ "fluent-langneg",
+ "fluent-syntax",
+ "intl-memoizer",
+ "intl_pluralrules",
+ "rustc-hash 1.1.0",
+ "self_cell 0.10.3",
+ "smallvec",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-langneg"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-syntax"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d"
+dependencies = [
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "hex-conservative"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "hex-conservative"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "hex_lit"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
+
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "http"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots 1.0.8",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
+[[package]]
+name = "i18n-config"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef"
+dependencies = [
+ "basic-toml",
+ "log",
+ "serde",
+ "serde_derive",
+ "thiserror 1.0.69",
+ "unic-langid",
+]
+
+[[package]]
+name = "i18n-embed"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "669ffc2c93f97e6ddf06ddbe999fcd6782e3342978bb85f7d3c087c7978404c4"
+dependencies = [
+ "arc-swap",
+ "fluent",
+ "fluent-langneg",
+ "fluent-syntax",
+ "i18n-embed-impl",
+ "intl-memoizer",
+ "log",
+ "parking_lot",
+ "rust-embed",
+ "thiserror 1.0.69",
+ "unic-langid",
+ "walkdir",
+]
+
+[[package]]
+name = "i18n-embed-fl"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04b2969d0b3fc6143776c535184c19722032b43e6a642d710fa3f88faec53c2d"
+dependencies = [
+ "find-crate",
+ "fluent",
+ "fluent-syntax",
+ "i18n-config",
+ "i18n-embed",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+ "unic-langid",
+]
+
+[[package]]
+name = "i18n-embed-impl"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2"
+dependencies = [
+ "find-crate",
+ "i18n-config",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
+
+[[package]]
+name = "icu_properties"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
+
+[[package]]
+name = "icu_provider"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
+[[package]]
+name = "intl-memoizer"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f"
+dependencies = [
+ "type-map",
+ "unic-langid",
+]
+
+[[package]]
+name = "intl_pluralrules"
+version = "7.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "io_tee"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304"
+
+[[package]]
+name = "ipnet"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "js-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonrpc"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf"
+dependencies = [
+ "base64 0.13.1",
+ "minreq",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "lwk_common"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2c72efef1d108bbfeb135d0b6617729165532533d362ae4c9c93b2699b5132b"
+dependencies = [
+ "aes-gcm-siv",
+ "base64 0.21.7",
+ "elements 0.25.3",
+ "elements-miniscript",
+ "getrandom 0.2.17",
+ "qr_code",
+ "rand 0.8.6",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror 2.0.18",
+]
+
+[[package]]
+name = "lwk_wollet"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b9fa3bfd61ee241a0deea4395dfa6392e52b79f9961e919eb35bd32137c53cf"
+dependencies = [
+ "aes",
+ "aes-gcm",
+ "aes-gcm-siv",
+ "age",
+ "base64 0.21.7",
+ "bip39",
+ "bitcoincore-rpc",
+ "cbc",
+ "electrum-client",
+ "elements 0.25.3",
+ "elements 0.26.2",
+ "elements-miniscript",
+ "flate2",
+ "futures",
+ "fxhash",
+ "hmac",
+ "idna",
+ "js-sys",
+ "log",
+ "lwk_common",
+ "once_cell",
+ "pbkdf2",
+ "rand 0.8.6",
+ "regex-lite",
+ "reqwest",
+ "rmp-serde",
+ "rmpv",
+ "scrypt",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-tungstenite",
+ "url",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniscript"
+version = "12.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8343cc1ef1408bd9bdbf69f7aef47017dfab7e6349ec26fddf62e0e9fb5a4cf"
+dependencies = [
+ "bech32 0.11.1",
+ "bitcoin",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "minreq"
+version = "2.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05015102dad0f7d61691ca347e9d9d9006685a64aefb3d79eecf62665de2153d"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "password-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pin-project"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "qr_code"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d2564aae5faaf3acb512b35b8bcb9a298d9d8c72d181c598691d800ee78a00"
+dependencies = [
+ "bmp-monochrome",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash 2.1.2",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
+dependencies = [
+ "bytes",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand 0.9.4",
+ "ring",
+ "rustc-hash 2.1.2",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex-lite"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
+
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots 1.0.8",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rmp"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "rmp-serde"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
+dependencies = [
+ "rmp",
+ "serde",
+]
+
+[[package]]
+name = "rmpv"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417"
+dependencies = [
+ "rmp",
+ "serde",
+ "serde_bytes",
+]
+
+[[package]]
+name = "rust-embed"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
+dependencies = [
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "password-hash",
+ "pbkdf2",
+ "salsa20",
+ "sha2",
+]
+
+[[package]]
+name = "secp256k1"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
+dependencies = [
+ "bitcoin_hashes",
+ "rand 0.8.6",
+ "secp256k1-sys",
+ "serde",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "secp256k1-zkp"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3"
+dependencies = [
+ "bitcoin-private",
+ "rand 0.8.6",
+ "secp256k1",
+ "secp256k1-zkp-sys",
+ "serde",
+]
+
+[[package]]
+name = "secp256k1-zkp-sys"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57f08b2d0b143a22e07f798ae4f0ab20d5590d7c68e0d090f2088a48a21d1654"
+dependencies = [
+ "cc",
+ "secp256k1-sys",
+]
+
+[[package]]
+name = "secrecy"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "self_cell"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d"
+dependencies = [
+ "self_cell 1.2.2",
+]
+
+[[package]]
+name = "self_cell"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.150"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "socket2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
+dependencies = [
+ "displaydoc",
+ "serde_core",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.52.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tungstenite",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tungstenite"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
+dependencies = [
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.9.4",
+ "rustls",
+ "rustls-pki-types",
+ "sha1",
+ "thiserror 2.0.18",
+ "utf-8",
+]
+
+[[package]]
+name = "type-map"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
+dependencies = [
+ "rustc-hash 2.1.2",
+]
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unic-langid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
+dependencies = [
+ "unic-langid-impl",
+]
+
+[[package]]
+name = "unic-langid-impl"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
+dependencies = [
+ "serde",
+ "tinystr",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.25.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.8",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "writeable"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+
+[[package]]
+name = "x25519-dalek"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core 0.6.4",
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
+dependencies = [
+ "serde",
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/elip-silent-payments-liquid/rust/Cargo.toml b/elip-silent-payments-liquid/rust/Cargo.toml
new file mode 100644
index 0000000..f8ddd20
--- /dev/null
+++ b/elip-silent-payments-liquid/rust/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "elip-sp-reference"
+version = "0.1.0"
+edition = "2021"
+description = "Minimal, standalone reference implementation for the Silent Payments for the Liquid Network ELIP. Re-derives the spec's test vectors using LWK only as a source of cryptographic primitives."
+license = "BSD-3-Clause"
+
+# This crate is the reference the ELIP points at. It deliberately depends on LWK
+# *only* for the underlying secp256k1 / elements primitives (the global `EC`
+# context, `PublicKey`/`SecretKey`/`Scalar`, the tagged-hash machinery, Taproot
+# script construction). Everything specific to Silent Payments is implemented here
+# from the ELIP, so the crate reproduces the construction byte-for-byte on its own.
+[dependencies]
+lwk_wollet = "0.17.0"
+bech32 = "0.11"
+rand = "0.8"
diff --git a/elip-silent-payments-liquid/rust/README.md b/elip-silent-payments-liquid/rust/README.md
new file mode 100644
index 0000000..798c8c3
--- /dev/null
+++ b/elip-silent-payments-liquid/rust/README.md
@@ -0,0 +1,30 @@
+# Rust reference implementation
+
+Reference implementation for the *Silent Payments for the Liquid Network* ELIP.
+Each function maps to a single derivation step from the specification. The only
+external dependency is [`lwk_wollet`](https://crates.io/crates/lwk_wollet), used
+for secp256k1 primitives, key and scalar types, tagged hashes, Taproot script
+construction, and Confidential Transactions primitives. All Silent Payments logic
+is implemented directly from the ELIP.
+
+## Covers
+
+- **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
+
+## Run
+
+```sh
+# 5 tests: test_vectors, taproot_even_y_negation, tweak_server_agreement,
+# ct_round_trip_unblind_with_bk, address_round_trip_and_network_separation
+cargo test
+```
+
+## License
+
+BSD-3-Clause.
diff --git a/elip-silent-payments-liquid/rust/src/lib.rs b/elip-silent-payments-liquid/rust/src/lib.rs
new file mode 100644
index 0000000..f63ce0f
--- /dev/null
+++ b/elip-silent-payments-liquid/rust/src/lib.rs
@@ -0,0 +1,770 @@
+//! # Silent Payments for the Liquid Network — Reference Implementation
+//!
+//! Reference implementation for the Liquid Silent Payments ELIP. Each function
+//! maps to a single derivation step from the specification. The only external
+//! dependency is [`lwk_wollet`], used for secp256k1 primitives, tagged hashes,
+//! Taproot script construction, and Confidential Transactions — all Silent
+//! Payments logic is implemented here directly.
+//!
+//! ## Protocol overview
+//!
+//! ### 1. Address encoding
+//!
+//! The receiver picks two random private keys `b_scan` and `b_spend`. Their
+//! public keys `B_scan = b_scan·G` and `B_spend = b_spend·G` are Bech32m-encoded
+//! with HRP `lqsp` (mainnet) or `tlqsp` (testnet), version `q`.
+//!
+//! ### 2. Sender derivation
+//!
+//! Given the receiver's address `(B_scan, B_spend)` and eligible inputs:
+//!
+//! a. `a = Σ a_i`, `A = a·G`
+//! b. `outpoint_L` = the lexicographically smallest input outpoint
+//! c. `input_hash = SHA256("BIP0352/Inputs"||"BIP0352/Inputs" || outpoint_L || serP(A))` mod n
+//! d. `S = input_hash · a · B_scan`
+//! e. For each output index `k`:
+//! - `t_k = SHA256("BIP0352/SharedSecret"||… || serP(S) || ser32(k))` mod n
+//! - `P_k = B_spend + t_k·G`
+//! - `bk_k = SHA256("LiquidSilentPayments/Blind"||… || serP(S) || ser32(k))` mod n
+//! - `BK_k = bk_k·G`
+//!
+//! The output is a confidential Taproot output: `OP_1 `, blinded to `BK_k`.
+//!
+//! ### 3. Tweak server
+//!
+//! The server publishes `T = input_hash · A` per transaction. The client computes
+//! `S = b_scan · T`, recovering the sender's shared secret without private keys.
+//!
+//! ### 4. Receiver
+//!
+//! `S = b_scan · (input_hash · A)`, then for each `k` recompute `P_k`, match
+//! against outputs, derive `bk_k` and `b_spend + t_k`.
+//!
+//! ### 5. Spending
+//!
+//! BIP-340 Schnorr with key `d = b_spend + t_k` (even-Y normalized). Standard
+//! key-path Taproot.
+
+use bech32::primitives::decode::CheckedHrpstring;
+use bech32::{Bech32m, Fe32, Hrp};
+
+use lwk_wollet::elements::schnorr::TweakedPublicKey;
+use lwk_wollet::elements::secp256k1_zkp::XOnlyPublicKey;
+use lwk_wollet::elements::Script;
+use lwk_wollet::hashes::{sha256t_hash_newtype, Hash, HashEngine};
+use lwk_wollet::secp256k1::{Parity, PublicKey, Scalar, SecretKey};
+use lwk_wollet::{ElementsNetwork, EC};
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// Section 1 — Tagged hash domains
+// ═══════════════════════════════════════════════════════════════════════════════
+
+sha256t_hash_newtype! {
+ /// `input_hash = SHA256("BIP0352/Inputs"||"BIP0352/Inputs" || outpoint_L || serP(A))` mod n.
+ struct InputsTag = hash_str("BIP0352/Inputs");
+ #[hash_newtype(forward)]
+ struct InputsHash(_);
+
+ /// `t_k = SHA256("BIP0352/SharedSecret"||"BIP0352/SharedSecret" || serP(S) || ser32(k))` mod n.
+ struct SharedSecretTag = hash_str("BIP0352/SharedSecret");
+ #[hash_newtype(forward)]
+ struct SharedSecretHash(_);
+
+ /// `bk_k = SHA256("LiquidSilentPayments/Blind"||"LiquidSilentPayments/Blind" || serP(S) || ser32(k))` mod n.
+ ///
+ /// The domain `LiquidSilentPayments/Blind` is disjoint from
+ /// `BIP0352/SharedSecret`, so `bk_k` and `t_k` are independent.
+ struct BlindTag = hash_str("LiquidSilentPayments/Blind");
+ #[hash_newtype(forward)]
+ struct BlindHash(_);
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// Section 2 — Address encoding / decoding
+// ═══════════════════════════════════════════════════════════════════════════════
+
+/// Address version 0 → Bech32 character `q`.
+const SP_ADDRESS_VERSION: Fe32 = Fe32::Q;
+
+/// HRP per network. Distinct from `ex`/`tex`, `lq`/`tlq`, and `sp`/`tsp`.
+fn hrp_for(network: ElementsNetwork) -> Hrp {
+ match network {
+ ElementsNetwork::Liquid => Hrp::parse_unchecked("lqsp"),
+ _ => Hrp::parse_unchecked("tlqsp"),
+ }
+}
+
+/// A silent-payment address: `B_scan` and `B_spend`.
+///
+/// Wire format: Bech32m(HRP, version=`q`, `serP(B_scan) || serP(B_spend)`).
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SilentPaymentAddress {
+ pub scan: PublicKey,
+ pub spend: PublicKey,
+}
+
+impl SilentPaymentAddress {
+ /// Encode: HRP + `q` + 66 bytes of key data, 5-bit groups, Bech32m checksum.
+ pub fn encode(&self, network: ElementsNetwork) -> String {
+ use bech32::primitives::iter::{ByteIterExt, Fe32IterExt};
+
+ let mut payload = Vec::with_capacity(66);
+ payload.extend_from_slice(&self.scan.serialize());
+ payload.extend_from_slice(&self.spend.serialize());
+
+ std::iter::once(SP_ADDRESS_VERSION)
+ .chain(payload.into_iter().bytes_to_fes())
+ .with_checksum::(&hrp_for(network))
+ .chars()
+ .collect()
+ }
+
+ /// Parse, validating the HRP and version byte.
+ pub fn parse(s: &str, network: ElementsNetwork) -> Result {
+ use bech32::primitives::iter::Fe32IterExt;
+
+ let checked =
+ CheckedHrpstring::new::(s).map_err(|_| AddressError::InvalidBech32m)?;
+ if checked.hrp() != hrp_for(network) {
+ return Err(AddressError::WrongNetwork);
+ }
+
+ let mut iter = checked.fe32_iter::>();
+ let version = iter.next().ok_or(AddressError::Truncated)?;
+ if version != SP_ADDRESS_VERSION {
+ return Err(AddressError::UnknownVersion);
+ }
+
+ let bytes: Vec = iter.fes_to_bytes().collect();
+ if bytes.len() != 66 {
+ return Err(AddressError::WrongPayloadLength(bytes.len()));
+ }
+
+ let scan =
+ PublicKey::from_slice(&bytes[..33]).map_err(|_| AddressError::InvalidPublicKey)?;
+ let spend =
+ PublicKey::from_slice(&bytes[33..]).map_err(|_| AddressError::InvalidPublicKey)?;
+
+ Ok(SilentPaymentAddress { scan, spend })
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum AddressError {
+ InvalidBech32m,
+ WrongNetwork,
+ Truncated,
+ UnknownVersion,
+ WrongPayloadLength(usize),
+ InvalidPublicKey,
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// Section 3 — Derivation core
+// ═══════════════════════════════════════════════════════════════════════════════
+
+/// The aggregated input state computed once per transaction by the sender.
+#[derive(Debug, Clone, Copy)]
+pub struct AggregatedInputs {
+ pub a_sum: SecretKey,
+ pub a_pubkey: PublicKey,
+ pub input_hash: Scalar,
+}
+
+/// One derived output: spend key, blinding key, blinding secret.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SilentPaymentOutput {
+ pub spend_pubkey: PublicKey,
+ pub blinding_pubkey: PublicKey,
+ pub blinding_seckey: SecretKey,
+}
+
+impl SilentPaymentOutput {
+ /// `scriptPubKey = OP_1 ` (no taptweak, per BIP-352).
+ pub fn script_pubkey(&self) -> Script {
+ let x_only: XOnlyPublicKey = self.spend_pubkey.x_only_public_key().0;
+ Script::new_v1_p2tr_tweaked(TweakedPublicKey::new(x_only))
+ }
+}
+
+// ── Helpers ──
+
+/// Convert a [`SecretKey`] to a [`Scalar`] for curve arithmetic.
+fn secret_key_to_scalar(sk: &SecretKey) -> Scalar {
+ Scalar::from_be_bytes(sk.secret_bytes()).expect("secret key is a valid scalar")
+}
+
+/// BIP-352 even-Y normalization for a Taproot (BIP-341) input key.
+///
+/// A taproot prevout commits only to the x-only key, i.e. the implicit public
+/// key has an even Y. The signing key for the silent-payment computation must
+/// therefore be the one whose public key is even-Y: if `sk·G` has odd Y, negate
+/// `sk` (which flips the parity) before aggregating. For non-taproot inputs the
+/// full public key is recoverable from the witness/scriptSig and no negation is
+/// applied.
+fn normalize_taproot_input_key(sk: SecretKey) -> SecretKey {
+ match sk.x_only_public_key(&EC).1 {
+ Parity::Even => sk,
+ Parity::Odd => sk.negate(),
+ }
+}
+
+/// The lexicographically smallest outpoint, `outpoint_L`.
+fn lowest_outpoint(outpoints: impl IntoIterator- >) -> Vec {
+ outpoints
+ .into_iter()
+ .map(|o| o.as_ref().to_vec())
+ .min()
+ .expect("at least one outpoint")
+}
+
+/// `input_hash = SHA256("BIP0352/Inputs"||"BIP0352/Inputs" || outpoint_L || serP(A))` mod n.
+///
+/// `outpoint_L` is the lexicographically smallest outpoint, selected internally.
+fn input_hash(outpoints: &[Vec], a_pubkey: &PublicKey) -> Scalar {
+ let mut eng = InputsHash::engine();
+ eng.input(&lowest_outpoint(outpoints));
+ eng.input(&a_pubkey.serialize());
+ Scalar::from_be_bytes(InputsHash::from_engine(eng).to_byte_array())
+ .expect("input_hash < curve order")
+}
+
+/// `t_k = SHA256("BIP0352/SharedSecret"||"BIP0352/SharedSecret" || serP(S) || ser32(k))` mod n.
+fn shared_secret_tweak(s: &PublicKey, k: u32) -> Scalar {
+ let mut eng = SharedSecretHash::engine();
+ eng.input(&s.serialize());
+ eng.input(&k.to_be_bytes());
+ Scalar::from_be_bytes(SharedSecretHash::from_engine(eng).to_byte_array())
+ .expect("t_k < curve order")
+}
+
+/// `bk_k = SHA256("LiquidSilentPayments/Blind"||"LiquidSilentPayments/Blind" || serP(S) || ser32(k))` mod n.
+fn blinding_secret(s: &PublicKey, k: u32) -> SecretKey {
+ let mut eng = BlindHash::engine();
+ eng.input(&s.serialize());
+ eng.input(&k.to_be_bytes());
+ SecretKey::from_slice(&BlindHash::from_engine(eng).to_byte_array()).expect("bk_k < curve order")
+}
+
+/// Sender's ECDH shared secret: `S = input_hash · a · B_scan`.
+fn sender_shared_secret(scan: &PublicKey, agg: &AggregatedInputs) -> PublicKey {
+ let a_ih = agg.a_sum.mul_tweak(&agg.input_hash).expect("scalar mul");
+ scan.mul_tweak(&EC, &secret_key_to_scalar(&a_ih))
+ .expect("ecdh")
+}
+
+/// Receiver's ECDH shared secret: `S = b_scan · (input_hash · A)`.
+fn receiver_shared_secret(
+ b_scan: &SecretKey,
+ a_pubkey: &PublicKey,
+ input_hash: &Scalar,
+) -> PublicKey {
+ let b_ih = b_scan.mul_tweak(input_hash).expect("scalar mul");
+ a_pubkey
+ .mul_tweak(&EC, &secret_key_to_scalar(&b_ih))
+ .expect("ecdh")
+}
+
+/// Derive `P_k`, `bk_k`, `BK_k` for output `k` from a shared secret and spend base.
+fn derive_output(spend_base: &PublicKey, s: &PublicKey, k: u32) -> SilentPaymentOutput {
+ let t_k = shared_secret_tweak(s, k);
+ let spend_pubkey = spend_base.add_exp_tweak(&EC, &t_k).expect("P_k");
+ let sk = blinding_secret(s, k);
+ SilentPaymentOutput {
+ spend_pubkey,
+ blinding_pubkey: sk.public_key(&EC),
+ blinding_seckey: sk,
+ }
+}
+
+// ── Public API ──
+
+/// Aggregate the sender's eligible inputs.
+///
+/// Keys are used as-is; this models a set of non-taproot inputs whose full public
+/// key is recoverable from the witness/scriptSig. For taproot (BIP-341) inputs,
+/// use [`aggregate_inputs_with_parity`] so the even-Y normalization is applied.
+pub fn aggregate_inputs(inputs: &[(Vec, SecretKey)]) -> AggregatedInputs {
+ let keys: Vec<(Vec, SecretKey, bool)> = inputs
+ .iter()
+ .map(|(o, sk)| (o.clone(), *sk, false))
+ .collect();
+ aggregate_inputs_with_parity(&keys)
+}
+
+/// Aggregate eligible inputs, applying BIP-352 even-Y normalization per input.
+///
+/// Each entry is `(outpoint, private_key, is_taproot)`. For taproot inputs the
+/// key is replaced by [`normalize_taproot_input_key`] before summation, exactly
+/// as BIP-352 requires ("for each private key `a_i` corresponding to a BIP341
+/// taproot output, check that the private key produces a point with an even Y
+/// coordinate and negate the private key if not"). The aggregate `a` and
+/// `A = a·G` — and therefore every derived output — differ from the un-normalized
+/// aggregate whenever any taproot input key had odd Y.
+pub fn aggregate_inputs_with_parity(inputs: &[(Vec, SecretKey, bool)]) -> AggregatedInputs {
+ let norm = |sk: SecretKey, is_taproot: bool| {
+ if is_taproot {
+ normalize_taproot_input_key(sk)
+ } else {
+ sk
+ }
+ };
+ let (first, rest) = inputs.split_first().expect("at least one eligible input");
+ let mut a_sum = norm(first.1, first.2);
+ for (_, sk, is_taproot) in rest {
+ a_sum = a_sum
+ .add_tweak(&secret_key_to_scalar(&norm(*sk, *is_taproot)))
+ .expect("non-zero aggregated key");
+ }
+ let a_pubkey = a_sum.public_key(&EC);
+ let outpoints: Vec> = inputs.iter().map(|(o, _, _)| o.clone()).collect();
+ let input_hash = input_hash(&outpoints, &a_pubkey);
+ AggregatedInputs {
+ a_sum,
+ a_pubkey,
+ input_hash,
+ }
+}
+
+/// Sender: derive output `k` for a given address.
+pub fn sender_derive_output(
+ address: &SilentPaymentAddress,
+ agg: &AggregatedInputs,
+ k: u32,
+) -> SilentPaymentOutput {
+ let s = sender_shared_secret(&address.scan, agg);
+ derive_output(&address.spend, &s, k)
+}
+
+/// Receiver: recompute output `k` and the spend private key `b_spend + t_k`.
+pub fn receiver_derive_output(
+ b_scan: &SecretKey,
+ b_spend: &SecretKey,
+ a_pubkey: &PublicKey,
+ input_hash: &Scalar,
+ k: u32,
+) -> (SilentPaymentOutput, SecretKey) {
+ let s = receiver_shared_secret(b_scan, a_pubkey, input_hash);
+ let out = derive_output(&b_spend.public_key(&EC), &s, k);
+ let t_k = shared_secret_tweak(&s, k);
+ let spend_sk = b_spend.add_tweak(&t_k).expect("spend sk");
+ (out, spend_sk)
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// Section 4 — Tweak server
+// ═══════════════════════════════════════════════════════════════════════════════
+//
+// The server publishes `T = input_hash · A`. The client computes `S = b_scan · T`.
+// On Liquid, BIP-158 compact filters are unnecessary: the scriptPubKey is public.
+
+/// Compute `(T = input_hash · A, input_hash, A)` from the eligible input public
+/// keys and the transaction's outpoints (the smallest is selected internally).
+pub fn compute_tweak(
+ input_pubkeys: &[PublicKey],
+ outpoints: &[Vec],
+) -> (PublicKey, Scalar, PublicKey) {
+ let a = PublicKey::combine_keys(&input_pubkeys.iter().collect::>())
+ .expect("at least one input pubkey");
+ let ih = input_hash(outpoints, &a);
+ let t = a.mul_tweak(&EC, &ih).expect("tweak point");
+ (t, ih, a)
+}
+
+/// A published tweak for one transaction.
+#[derive(Debug, Clone)]
+pub struct TweakEntry {
+ pub txid: [u8; 32],
+ pub tweak: PublicKey, // T = input_hash · A
+ pub a_pubkey: PublicKey, // A
+ pub input_hash: Scalar, // input_hash
+}
+
+/// Minimal tweak server.
+#[derive(Debug, Default)]
+pub struct TweakServer {
+ entries: Vec,
+}
+
+impl TweakServer {
+ pub fn publish(&mut self, txid: [u8; 32], input_pubkeys: &[PublicKey], outpoints: &[Vec]) {
+ let (tweak, input_hash, a_pubkey) = compute_tweak(input_pubkeys, outpoints);
+ self.entries.push(TweakEntry {
+ txid,
+ tweak,
+ a_pubkey,
+ input_hash,
+ });
+ }
+
+ pub fn get_by_txid(&self, txid: &[u8; 32]) -> Option<&TweakEntry> {
+ self.entries.iter().find(|e| &e.txid == txid)
+ }
+}
+
+/// `S = b_scan · T` (from a tweak server entry).
+pub fn shared_secret_from_tweak(b_scan: &SecretKey, entry: &TweakEntry) -> PublicKey {
+ let b = secret_key_to_scalar(b_scan);
+ entry.tweak.mul_tweak(&EC, &b).expect("ecdh")
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// Section 5 — Confidential output blinding / unblinding
+// ═══════════════════════════════════════════════════════════════════════════════
+//
+// The sender blinds to `BK_k = bk_k·G`. The receiver recomputes `bk_k` and
+// unblinds, recovering plaintext asset + value with no out-of-band exchange.
+
+/// Build a confidential `TxOut` blinded to `BK_k`.
+pub fn build_confidential_sp_txout(
+ out: &SilentPaymentOutput,
+ asset: lwk_wollet::elements::AssetId,
+ value: u64,
+ rng: &mut (impl rand::RngCore + rand::CryptoRng),
+) -> Result<
+ (
+ lwk_wollet::elements::TxOut,
+ lwk_wollet::elements::TxOutSecrets,
+ ),
+ lwk_wollet::elements::secp256k1_zkp::Error,
+> {
+ use lwk_wollet::elements::confidential::{
+ Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor,
+ };
+ use lwk_wollet::elements::secp256k1_zkp::{Generator, PedersenCommitment, RangeProof, Tag};
+ use lwk_wollet::elements::{TxOut, TxOutSecrets, TxOutWitness};
+
+ let script_pubkey = out.script_pubkey();
+
+ let abf = AssetBlindingFactor::new(&mut *rng);
+ let vbf = ValueBlindingFactor::new(&mut *rng);
+
+ // CT nonce derived from BK_k — this is what lets the receiver unblind.
+ let (nonce, ct_shared_secret) = Nonce::new_confidential(&mut *rng, &EC, &out.blinding_pubkey);
+
+ let asset_tag = Tag::from(asset.into_inner().to_byte_array());
+ let asset_gen = Generator::new_blinded(&EC, asset_tag, abf.into_inner());
+ let value_commitment = PedersenCommitment::new(&EC, value, vbf.into_inner(), asset_gen);
+
+ let mut message = [0u8; 64];
+ message[..32].copy_from_slice(&asset.into_inner().to_byte_array());
+ message[32..].copy_from_slice(abf.into_inner().as_ref());
+
+ let rangeproof = RangeProof::new(
+ &EC,
+ 1,
+ value_commitment,
+ value,
+ vbf.into_inner(),
+ &message,
+ script_pubkey.as_bytes(),
+ ct_shared_secret,
+ 0,
+ 52,
+ asset_gen,
+ )?;
+
+ let txout = TxOut {
+ asset: Asset::Confidential(asset_gen),
+ value: Value::Confidential(value_commitment),
+ nonce,
+ script_pubkey,
+ witness: TxOutWitness {
+ surjection_proof: None,
+ rangeproof: Some(Box::new(rangeproof)),
+ },
+ };
+ let secrets = TxOutSecrets {
+ asset,
+ asset_bf: abf,
+ value,
+ value_bf: vbf,
+ };
+
+ Ok((txout, secrets))
+}
+
+/// Unblind a confidential output with `bk_k`, recovering asset + value.
+pub fn unblind_output(
+ txout: &lwk_wollet::elements::TxOut,
+ bk_k: SecretKey,
+) -> Result {
+ txout.unblind(&EC, bk_k)
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// Section 6 — Tests
+// ═══════════════════════════════════════════════════════════════════════════════
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use lwk_wollet::elements::hashes::hex::DisplayHex;
+
+ fn sk_byte(b: u8) -> SecretKey {
+ SecretKey::from_slice(&[b; 32]).unwrap()
+ }
+
+ fn outpoint(txid_byte: u8, vout: u32) -> Vec {
+ let mut v = vec![txid_byte; 32];
+ v.extend_from_slice(&vout.to_le_bytes());
+ v
+ }
+
+ /// Test vectors from the ELIP.
+ ///
+ /// b_scan = 0x11×32, b_spend = 0x22×32.
+ /// Two inputs: 0x31×32 @ 0x10…:0, 0x32×32 @ 0x20…:1.
+ #[test]
+ fn test_vectors() {
+ let b_scan = sk_byte(0x11);
+ let b_spend = sk_byte(0x22);
+ let address = SilentPaymentAddress {
+ scan: b_scan.public_key(&EC),
+ spend: b_spend.public_key(&EC),
+ };
+
+ let agg = aggregate_inputs(&[
+ (outpoint(0x10, 0), sk_byte(0x31)),
+ (outpoint(0x20, 1), sk_byte(0x32)),
+ ]);
+
+ assert_eq!(
+ agg.a_pubkey.serialize().to_lower_hex_string(),
+ "031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99",
+ "A"
+ );
+ assert_eq!(
+ agg.input_hash.to_be_bytes().to_lower_hex_string(),
+ "d392922c00280a7e8d282182f5026f2fddbc74c1e1de18b4822128b2b77ec641",
+ "input_hash"
+ );
+
+ let expected: [(u32, &str, &str, &str, &str, &str); 2] = [
+ (
+ 0,
+ "02a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931",
+ "0344e1289497e6da66fde710d2f38de053fc07355e405524401d7d609df5a1a8cc",
+ "70ab8897b64bd21b427339ff4d014b883191ef6425862246c53bfc27a59aa3f0",
+ "f03c436d2cd67ae1fecf7d88a38aa3a03c0abea43feaf6da8eb71e2e3a866bda",
+ "5120a29d9716417c964ca9e477343e71ffe730a4991a3eaad668eabec84e9feb7931",
+ ),
+ (
+ 1,
+ "0229d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92",
+ "03efdeda770ccdbe8bf466fba48bfd2b2c436ab0c04658fc6d6c277de5078129fa",
+ "945ba73a9804f62089c7d2ffdc079031031f0aebab372cec17ef9c110ebceb10",
+ "9eff3472230fc83ef5ea8f8c80401c4eecd595a048bd2482a107d3a49baa5a58",
+ "512029d77654023af267dbe9cb7ff1956f947c816f203494381308387168fb010c92",
+ ),
+ ];
+
+ for (k, pk, bk, bk_sk, spk_sk, script) in expected {
+ let sender = sender_derive_output(&address, &agg, k);
+ let (recv, spend_sk) =
+ receiver_derive_output(&b_scan, &b_spend, &agg.a_pubkey, &agg.input_hash, k);
+
+ assert_eq!(sender, recv, "sender/receiver agree at k={k}");
+ assert_eq!(
+ sender.spend_pubkey.serialize().to_lower_hex_string(),
+ pk,
+ "P_k k={k}"
+ );
+ assert_eq!(
+ sender.blinding_pubkey.serialize().to_lower_hex_string(),
+ bk,
+ "BK_k k={k}"
+ );
+ assert_eq!(
+ sender.blinding_seckey.secret_bytes().to_lower_hex_string(),
+ bk_sk,
+ "bk_k k={k}"
+ );
+ assert_eq!(
+ spend_sk.secret_bytes().to_lower_hex_string(),
+ spk_sk,
+ "spend_sk k={k}"
+ );
+ assert_eq!(
+ sender.script_pubkey().as_bytes().to_lower_hex_string(),
+ script,
+ "scriptPubKey k={k}"
+ );
+ }
+
+ assert_eq!(
+ address.encode(ElementsNetwork::Liquid),
+ "lqsp1qqd8n2k7uklxq4aegau7vawtptkgxsja4kt99lpv6krctwpq8tpc65qjxd4lu4etruh9sngx3su9mtqp5fqzxz7re59y5nnez9p03ht3lyudcfhfe",
+ "mainnet address"
+ );
+ }
+
+ /// BIP-352 even-Y normalization for taproot inputs.
+ ///
+ /// `normalize_taproot_input_key` must return a key whose public point has
+ /// even Y, leaving even-Y keys untouched and negating odd-Y ones. We assert
+ /// the invariant directly, and that aggregating an odd-Y key *as taproot*
+ /// changes the aggregate (negation happened) while aggregating it *as
+ /// non-taproot* does not.
+ #[test]
+ fn taproot_even_y_negation() {
+ // Find one even-Y and one odd-Y sample key among simple byte fills.
+ let mut even_key = None;
+ let mut odd_key = None;
+ for b in 1u8..=0xff {
+ let sk = sk_byte(b);
+ match sk.x_only_public_key(&EC).1 {
+ Parity::Even if even_key.is_none() => even_key = Some(sk),
+ Parity::Odd if odd_key.is_none() => odd_key = Some(sk),
+ _ => {}
+ }
+ if even_key.is_some() && odd_key.is_some() {
+ break;
+ }
+ }
+ let even_key = even_key.expect("some even-Y key exists");
+ let odd_key = odd_key.expect("some odd-Y key exists");
+
+ // Invariant: output always has even-Y public key.
+ for sk in [even_key, odd_key] {
+ let norm = normalize_taproot_input_key(sk);
+ assert_eq!(
+ norm.x_only_public_key(&EC).1,
+ Parity::Even,
+ "normalized key must have even-Y public point"
+ );
+ }
+ // Even-Y key is untouched; odd-Y key is negated.
+ assert_eq!(
+ normalize_taproot_input_key(even_key),
+ even_key,
+ "even-Y unchanged"
+ );
+ assert_eq!(
+ normalize_taproot_input_key(odd_key),
+ odd_key.negate(),
+ "odd-Y negated"
+ );
+
+ // Aggregation: treating the odd-Y key as taproot must differ from
+ // treating it as non-taproot (the negation changes `a` and `A`).
+ let op = outpoint(0x10, 0);
+ let as_taproot = aggregate_inputs_with_parity(&[(op.clone(), odd_key, true)]);
+ let as_legacy = aggregate_inputs_with_parity(&[(op.clone(), odd_key, false)]);
+ assert_ne!(
+ as_taproot.a_pubkey.serialize(),
+ as_legacy.a_pubkey.serialize(),
+ "odd-Y taproot input must be negated, changing A"
+ );
+ // The taproot aggregate's A is the even-Y point of the odd-Y key.
+ assert_eq!(
+ as_taproot.a_pubkey.x_only_public_key().0,
+ odd_key.x_only_public_key(&EC).0,
+ "x-only A is unchanged by negation"
+ );
+ assert_eq!(as_taproot.a_pubkey.serialize()[0], 0x02, "A is even-Y");
+
+ // An even-Y taproot input behaves identically to a non-taproot input.
+ let t_even = aggregate_inputs_with_parity(&[(op.clone(), even_key, true)]);
+ let l_even = aggregate_inputs_with_parity(&[(op.clone(), even_key, false)]);
+ assert_eq!(
+ t_even.a_pubkey.serialize(),
+ l_even.a_pubkey.serialize(),
+ "even-Y key: taproot vs non-taproot agree"
+ );
+ }
+
+ /// Tweak server: client's `S = b_scan · T` must equal sender's `S`.
+ #[test]
+ fn tweak_server_agreement() {
+ let b_scan = sk_byte(0x11);
+ let b_spend = sk_byte(0x22);
+ let address = SilentPaymentAddress {
+ scan: b_scan.public_key(&EC),
+ spend: b_spend.public_key(&EC),
+ };
+
+ let agg = aggregate_inputs(&[(outpoint(0xAA, 0), sk_byte(0x55))]);
+ let sender_out = sender_derive_output(&address, &agg, 0);
+
+ // Tweak server publishes T from public keys only.
+ let (tweak, input_hash, a_pubkey) =
+ compute_tweak(&[sk_byte(0x55).public_key(&EC)], &[outpoint(0xAA, 0)]);
+ let client_s = shared_secret_from_tweak(
+ &b_scan,
+ &TweakEntry {
+ txid: [0u8; 32],
+ tweak,
+ a_pubkey,
+ input_hash,
+ },
+ );
+
+ let client_out = derive_output(&b_spend.public_key(&EC), &client_s, 0);
+ assert_eq!(client_out, sender_out, "client output must match sender");
+
+ let sender_s = sender_shared_secret(&address.scan, &agg);
+ assert_eq!(
+ client_s.serialize(),
+ sender_s.serialize(),
+ "shared secrets must match"
+ );
+ }
+
+ /// CT round-trip: sender blinds to BK_k, receiver unblinds with bk_k.
+ #[test]
+ fn ct_round_trip_unblind_with_bk() {
+ use lwk_wollet::elements::AssetId;
+
+ let b_scan = sk_byte(0x11);
+ let b_spend = sk_byte(0x22);
+ let address = SilentPaymentAddress {
+ scan: b_scan.public_key(&EC),
+ spend: b_spend.public_key(&EC),
+ };
+
+ let agg = aggregate_inputs(&[(outpoint(0xAB, 0), sk_byte(0x33))]);
+ let k = 0;
+ let asset = AssetId::from_slice(&[0x42; 32]).unwrap();
+ let value = 123_456u64;
+ let mut rng = rand::thread_rng();
+
+ let sender_out = sender_derive_output(&address, &agg, k);
+ let (txout, secrets) =
+ build_confidential_sp_txout(&sender_out, asset, value, &mut rng).unwrap();
+
+ let (recv_out, _) =
+ receiver_derive_output(&b_scan, &b_spend, &agg.a_pubkey, &agg.input_hash, k);
+ assert_eq!(recv_out.blinding_seckey, sender_out.blinding_seckey);
+
+ let recovered = txout.unblind(&EC, recv_out.blinding_seckey).unwrap();
+ assert_eq!(recovered.asset, asset);
+ assert_eq!(recovered.value, value);
+ assert_eq!(recovered.asset_bf, secrets.asset_bf);
+ assert_eq!(recovered.value_bf, secrets.value_bf);
+
+ // Wrong scan key → unblind fails.
+ let (wrong, _) =
+ receiver_derive_output(&sk_byte(0x99), &b_spend, &agg.a_pubkey, &agg.input_hash, k);
+ assert_ne!(wrong.blinding_seckey, sender_out.blinding_seckey);
+ assert!(txout.unblind(&EC, wrong.blinding_seckey).is_err());
+ }
+
+ /// Address round-trip and network separation.
+ #[test]
+ fn address_round_trip_and_network_separation() {
+ let address = SilentPaymentAddress {
+ scan: sk_byte(0x11).public_key(&EC),
+ spend: sk_byte(0x22).public_key(&EC),
+ };
+ for network in [ElementsNetwork::Liquid, ElementsNetwork::LiquidTestnet] {
+ let enc = address.encode(network);
+ assert_eq!(SilentPaymentAddress::parse(&enc, network).unwrap(), address);
+ }
+ let mainnet = address.encode(ElementsNetwork::Liquid);
+ assert_eq!(
+ SilentPaymentAddress::parse(&mainnet, ElementsNetwork::LiquidTestnet),
+ Err(AddressError::WrongNetwork),
+ );
+ }
+}