1 // Copyright (c) 2025 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #ifndef LIMENKA_WALLET_CT_H
6 #define LIMENKA_WALLET_CT_H
7
8 #include <consensus/ct.h>
9 #include <psbt.h>
10 #include <crypto/bulletproofs.h>
11 #include <key.h>
12 #include <pubkey.h>
13 #include <primitives/transaction.h>
14 #include <random.h>
15 #include <uint256.h>
16 #include <util/result.h>
17
18 #include <cstdint>
19 #include <utility>
20 #include <vector>
21
22 namespace wallet {
23
24 class CWallet;
25
26 // One lambda (whole unit) in attosats: 10^26 = satoshis (10^8) * attosats
27 // per satoshi (10^18).
28 static constexpr CAmount LAMBDA_SCALE = CAmount{100000000} * ATTOSATS_PER_SATOSHI;
29
30 // Format an attosat amount as lambda with up to 26 decimals (trailing
31 // zeros trimmed). Parsing accepts fixed-point strings of the same shape.
32 std::string AttosatsToString(CAmount attosats);
33 bool ParseAttosatsString(const std::string& s, CAmount& out);
34
35 // HD derivation paths for the wallet's stealth keypair (dedicated purpose).
36 // Both hardened so the master key suffices to recover them.
37 static constexpr uint32_t STEALTH_VIEW_PATH = 0x800001F4; // m/500'
38 static constexpr uint32_t STEALTH_SPEND_PATH = 0x800001F5; // m/501'
39
40 // A stealth CT address: the receiver publishes (view, spend) once and the
41 // sender pays to it non-interactively. The view key recovers the blinding
42 // via ECDH with the kernel ephemeral; the spend key separates scanning
43 // capability (watchtowers, lightning monitors) from spending capability.
44 struct StealthCTAddress {
45 CPubKey view; //!< V_scan (33 bytes compressed)
46 CPubKey spend; //!< V_spend (33 bytes compressed)
47
48 SERIALIZE_METHODS(StealthCTAddress, obj) { READWRITE(obj.view, obj.spend); }
49 };
50
51 // Sender-side result of deriving a stealth output.
52 struct StealthPayment {
53 BPCommitment commitment; //!< C = blind*G + v*H (33 bytes)
54 BPScalar blinding; //!< sender-chosen r (sender signs the excess)
55 std::vector<uint8_t> E; //!< ephemeral t*G (33 bytes)
56 std::vector<uint8_t> enc_amount;//!< v XOR H(shared_v) (32 bytes)
57 std::vector<uint8_t> enc_blind; //!< r XOR H(shared_s) (32 bytes)
58 CAmount amount{0}; //!< attosats
59 };
60
61 // Everything the wallet must retain to spend a confidential output it
62 // created. The bulletproof rides in the spend witness; the blinding is
63 // needed to compute the kernel excess of the spending transaction.
64 struct CTReceipt {
65 uint64_t amount_lo{0}; //!< attosats, little-endian halves (16 bytes total)
66 uint64_t amount_hi{0};
67 BPScalar blinding; //!< 32 bytes, fresh per output
68 BPScalar seed; //!< 32 bytes, fresh per output
69 uint32_t vout_index{0}; //!< position of the output in its creating tx
70 Bulletproof proof; //!< 754 bytes serialized
71
72 SERIALIZE_METHODS(CTReceipt, obj) {
73 READWRITE(obj.amount_lo, obj.amount_hi, obj.blinding, obj.seed,
74 obj.vout_index,
75 obj.proof.A, obj.proof.S, obj.proof.T1, obj.proof.T2,
76 obj.proof.t_hat, obj.proof.taux, obj.proof.mu, obj.proof.a,
77 obj.proof.b);
78 for (size_t i = 0; i < BP_ROUNDS; ++i) {
79 READWRITE(obj.proof.L[i], obj.proof.R[i]);
80 }
81 }
82
83 CAmount Amount() const
84 {
85 return static_cast<CAmount>(amount_lo) |
86 (static_cast<CAmount>(amount_hi) << 64);
87 }
88 void SetAmount(CAmount v)
89 {
90 amount_lo = static_cast<uint64_t>(v);
91 amount_hi = static_cast<uint64_t>(static_cast<__uint128_t>(v) >> 64);
92 }
93 };
94
95 // Result of building a confidential transaction.
96 struct CTCreationResult {
97 CMutableTransaction tx;
98 std::vector<CTReceipt> new_receipts; // one per output, same order
99 int kernel_index{-1};
100 // Present when this is a stealth payment: the sender-chosen blinding
101 // and encrypted fields (the blinding must fold into the PSBT excess).
102 std::optional<StealthPayment> stealth_payment;
103 };
104
105 // The witness v4 output script for a committed amount: OP_4 <commitment>.
106 CScript GetConfidentialScript(const BPCommitment& commitment);
107
108 // Create a confidential output committing amount_attosats. Derives a
109 // fresh blinding and seed from the CSPRNG, proves the range [0, 2^128),
110 // and fills the receipt the wallet must keep.
111 bool CreateConfidentialOutput(CAmount amount_attosats, FastRandomContext& rng,
112 BPCommitment& commitment, CTReceipt& receipt);
113
114 // excess = sum(in_blindings) - sum(out_blindings) mod n. Returns false if
115 // the excess is zero (the kernel key would be the identity - re-blind an
116 // output and try again).
117 bool ComputeCTExcess(const std::vector<BPScalar>& in_blindings,
118 const std::vector<BPScalar>& out_blindings, BPScalar& excess);
119
120 // Build the kernel output script:
121 // OP_RETURN <"BK"> <fee:16 LE> <sig:64> [<"S"> <E> <enc_amount> <enc_blind>]
122 // The message binds the fee, input prevouts, output scripts, and the
123 // stealth fields (substitution protection).
124 bool BuildCTKernelOutput(const BPScalar& excess, CAmount fee, const CTransaction& tx,
125 int kernel_index,
126 const std::vector<uint8_t>& E,
127 const std::vector<uint8_t>& enc_amount,
128 const std::vector<uint8_t>& enc_blind,
129 CScript& kernel_script,
130 const std::vector<CAmount>& transparent_input_values = {});
131
132 // Derive the stealth address from the receiver's view and spend secrets.
133 StealthCTAddress CreateStealthCTAddress(const CKey& view_secret, const CKey& spend_secret);
134
135 // Sender side: derive a stealth output for the receiver's static address.
136 // The sender chooses the blinding r itself, so its excess signature covers
137 // the output without any receiver interaction (no kernel offset points).
138 // C = r*G + v*H; E = t*G; enc_amount = v XOR H(shared_v) with
139 // shared_v = t*V_view; enc_blind = r XOR H(shared_s) with
140 // shared_s = t*V_spend. The view key can scan (decrypt v) but only the
141 // spend key can recover the blinding.
142 bool CreateStealthOutput(CAmount amount_attosats, const StealthCTAddress& address,
143 FastRandomContext& rng, StealthPayment& out);
144
145 // Receiver side: recover (amount, blinding) from a kernel's stealth
146 // fields. Decrypts v with the view key and r with the spend key, then
147 // verifies the commitment C == r*G + v*H. Returns nullopt if the output
148 // does not belong to this receiver.
149 std::optional<std::pair<CAmount, BPScalar>> RecoverStealthOutput(
150 const CKey& view_secret, const CKey& spend_secret,
151 const std::vector<uint8_t>& E, const std::vector<uint8_t>& enc_amount,
152 const std::vector<uint8_t>& enc_blind, const BPCommitment& commitment);
153
154 // Build a confidential transaction paying a stealth address. Inputs are
155 // CT outputs the wallet holds receipts for; the kernel carries the
156 // ephemeral and the encrypted amount/blinding fields.
157 util::Result<CTCreationResult> CreateStealthTransaction(
158 const std::vector<std::pair<COutPoint, CTReceipt>>& ct_inputs,
159 const StealthCTAddress& address, CAmount amount, CAmount fee, FastRandomContext& rng,
160 int change_outputs = 1);
161
162 // Split a total attosat value into n random, widely-varying parts (for
163 // privacy: temporal rejoining is harder when the sizes differ). The
164 // last part carries the remainder. n is clamped to [1, 16].
165 std::vector<CAmount> SplitAttosats(CAmount total, int n, FastRandomContext& rng);
166
167 // PSBT global-proprietary payload for confidential transactions: the
168 // kernel metadata (signature empty until finalize) plus the blindings and
169 // proofs needed to sign the kernel. The blindings are secret - a CT PSBT
170 // must be treated as sensitive, like a PSBT carrying private keys.
171 struct CTPSBTData {
172 CTKernelData kernel; //!< fee/stealth fields; sig empty
173 std::vector<CTReceipt> in_receipts; //!< one per CT input (blinding+proof)
174 std::vector<CTReceipt> out_receipts; //!< one per CT output (blinding+proof)
175 //! Blindings of outputs the wallet built but does not own (stealth
176 //! payments to lm2): they fold into the kernel excess but carry no
177 //! receipt (the receiver generates its own).
178 std::vector<BPScalar> out_blindings_extra;
179
180 template <typename Stream>
181 void Serialize(Stream& s) const
182 {
183 const uint64_t fee_lo = static_cast<uint64_t>(kernel.fee);
184 const uint64_t fee_hi = static_cast<uint64_t>(static_cast<__uint128_t>(kernel.fee) >> 64);
185 ::Serialize(s, fee_lo);
186 ::Serialize(s, fee_hi);
187 ::Serialize(s, kernel.has_stealth);
188 ::Serialize(s, kernel.E);
189 ::Serialize(s, kernel.enc_amount);
190 ::Serialize(s, kernel.enc_blind);
191 ::Serialize(s, kernel.sig);
192 ::Serialize(s, in_receipts);
193 ::Serialize(s, out_receipts);
194 ::Serialize(s, out_blindings_extra);
195 }
196
197 template <typename Stream>
198 void Unserialize(Stream& s)
199 {
200 uint64_t fee_lo, fee_hi;
201 ::Unserialize(s, fee_lo);
202 ::Unserialize(s, fee_hi);
203 kernel.fee = static_cast<CAmount>(fee_lo) | (static_cast<CAmount>(fee_hi) << 64);
204 ::Unserialize(s, kernel.has_stealth);
205 ::Unserialize(s, kernel.E);
206 ::Unserialize(s, kernel.enc_amount);
207 ::Unserialize(s, kernel.enc_blind);
208 ::Unserialize(s, kernel.sig);
209 ::Unserialize(s, in_receipts);
210 ::Unserialize(s, out_receipts);
211 ::Unserialize(s, out_blindings_extra);
212 }
213 };
214
215 // Store/extract the CT payload in a PSBT global proprietary field.
216 bool SetCTPSBTData(PartiallySignedTransaction& psbt, const CTPSBTData& data);
217 bool GetCTPSBTData(const PartiallySignedTransaction& psbt, CTPSBTData& data);
218
219 // Assemble the kernel output script from fully-populated kernel data
220 // (including the signature).
221 bool BuildCTKernelScript(const CTKernelData& kernel, CScript& script);
222
223 // Enumerate the wallet's unspent confidential outputs (receipts that are
224 // not yet spent on-chain). Returns (outpoint, receipt) pairs.
225 bool ListUnspentCTOutputs(const CWallet& wallet, const std::vector<COutPoint>& outpoints,
226 std::vector<std::pair<COutPoint, CTReceipt>>& out);
227
228 // Result of building a mint transaction (transparent value into CT).
229 struct MintCreationResult {
230 CMutableTransaction tx;
231 std::vector<CTReceipt> new_receipts; // one per output, same order
232 int kernel_index{-1};
233 };
234
235 // Build a mint transaction: spends transparent inputs (satoshis) and
236 // creates confidential outputs of the given attosat amounts. The input
237 // values enter the kernel balance with zero blinding, so the kernel
238 // signature only covers the output blindings; the transparent inputs are
239 // signed normally afterwards. The explicit fee is in attosats.
240 util::Result<MintCreationResult> CreateMintTransaction(
241 const std::vector<std::pair<COutPoint, CAmount>>& transparent_inputs,
242 const std::vector<CAmount>& output_amounts, CAmount fee, FastRandomContext& rng);
243
244 // Build a fully confidential transaction. Inputs are CT outputs the wallet
245 // already holds receipts for; outputs are new confidential outputs of the
246 // given attosat amounts. The fee is in attosats. Input witnesses carry
247 // the stored range proofs; the kernel carries the 128-bit fee and the
248 // excess signature. Callers persist the returned receipts (keyed by the
249 // transaction id) before broadcasting.
250 util::Result<CTCreationResult> CreateConfidentialTransaction(
251 const std::vector<std::pair<COutPoint, CTReceipt>>& ct_inputs,
252 const std::vector<CAmount>& output_amounts, CAmount fee, FastRandomContext& rng);
253
254 } // namespace wallet
255
256 #endif // LIMENKA_WALLET_CT_H
257