ct.h raw

   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