bulletproofs.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_CRYPTO_BULLETPROOFS_H
   6  #define LIMENKA_CRYPTO_BULLETPROOFS_H
   7  
   8  #include <cstdint>
   9  #include <cstddef>
  10  #include <vector>
  11  #include <span>
  12  
  13  // ---------------------------------------------------------------------------
  14  // Bulletproofs for confidential transactions (P2BPCT)
  15  //
  16  // Range proof proving that a Pedersen-committed amount lies in [0, 2^128)
  17  // without revealing the amount.  Uses the secp256k1 curve with NUMS
  18  // generator points derived via try-and-increment hash-to-curve.
  19  //
  20  // Follows Bunz et al. "Bulletproofs: Short Proofs for Confidential
  21  // Transactions and More" (IEEE S&P 2018), single-party (m=1) range proof,
  22  // n = 128 bits.  The verification equations match the dalek-cryptography
  23  // bulletproofs construction.
  24  //
  25  // Value unit is a wallet convention; consensus only sees a 128-bit scalar
  26  // in [0, 2^128).  The limenka wallet commits attosats: satoshis * 10^18
  27  // plus the sub-satoshi fraction.
  28  // ---------------------------------------------------------------------------
  29  
  30  // Number of bits in the range proof (128 bits = CAmount range).
  31  static constexpr size_t BP_BITS = 128;
  32  
  33  // Number of inner-product rounds: log2(BP_BITS).
  34  static constexpr size_t BP_ROUNDS = 7;
  35  
  36  // Size of a compressed secp256k1 public key (33 bytes).
  37  static constexpr size_t BP_POINT_SIZE = 33;
  38  
  39  // Size of a scalar (32 bytes).
  40  static constexpr size_t BP_SCALAR_SIZE = 32;
  41  
  42  // A Pedersen commitment C = v*G + r*H is a compressed EC point (33 bytes).
  43  using BPCommitment = std::vector<uint8_t>;
  44  
  45  // A scalar (32 bytes, big-endian, mod secp256k1 order).
  46  using BPScalar = std::vector<uint8_t>;
  47  
  48  // A bulletproof includes the range proof and the inner product argument.
  49  // Serialized format (matches dalek-cryptography bulletproofs):
  50  //   A     (33 bytes) — commitment to a_L, a_R
  51  //   S     (33 bytes) — commitment to s_L, s_R
  52  //   T1    (33 bytes) — first t polynomial commitment
  53  //   T2    (33 bytes) — second t polynomial commitment
  54  //   t_hat (32 bytes) — t(x) inner product value
  55  //   taux  (32 bytes) — t(x) blinding factor
  56  //   mu    (32 bytes) — alpha + rho*x combined blinding
  57  //   L[7]  (33 bytes each) — IPA left points (rounds 7..1)
  58  //   R[7]  (33 bytes each) — IPA right points (rounds 7..1)
  59  //   a     (32 bytes) — final IPA scalar
  60  //   b     (32 bytes) — final IPA scalar
  61  //
  62  // Total: 4*33 + 3*32 + 14*33 + 2*32 = 754 bytes
  63  struct Bulletproof {
  64      std::vector<uint8_t> A;     // 33 bytes
  65      std::vector<uint8_t> S;     // 33 bytes
  66      std::vector<uint8_t> T1;    // 33 bytes
  67      std::vector<uint8_t> T2;    // 33 bytes
  68      std::vector<uint8_t> t_hat; // 32 bytes
  69      std::vector<uint8_t> taux;  // 32 bytes
  70      std::vector<uint8_t> mu;    // 32 bytes
  71      std::vector<uint8_t> L[BP_ROUNDS]; // 7 x 33 bytes
  72      std::vector<uint8_t> R[BP_ROUNDS]; // 7 x 33 bytes
  73      std::vector<uint8_t> a;     // 32 bytes
  74      std::vector<uint8_t> b;     // 32 bytes
  75  };
  76  
  77  // Initialize the NUMS generator points.  Must be called once before any
  78  // verification or commitment operation.  Thread-safe after first call.
  79  void InitBulletproofGenerators();
  80  
  81  // Verify that all generators are valid compressed points, pairwise distinct,
  82  // and distinct from the secp256k1 base point G (the NUMS property).  Returns
  83  // false if any generator is malformed or collides with another.
  84  bool VerifyBulletproofGenerators();
  85  
  86  // Compute a Pedersen commitment C = r*G + v*H, where r is the blinding
  87  // (private key, in the secp256k1 base point G) and v is the amount (in a
  88  // NUMS value generator H).  Returns false on failure.
  89  bool CommitAmount(__int128 amount, const BPScalar& blinding, BPCommitment& out);
  90  
  91  // Verify a bulletproof range proof.
  92  // commitment: Pedersen commitment C = v*G + r*H (33 bytes)
  93  // proof:      the bulletproof proving that v in [0, 2^128)
  94  // Returns true if the proof is valid.
  95  bool VerifyBulletproof(const BPCommitment& commitment, const Bulletproof& proof);
  96  
  97  // Prove a range proof for a committed amount (wallet-side, not consensus).
  98  // Derives all blinding randomness deterministically from a 32-byte seed.
  99  // WALLET REQUIREMENT: `blinding` and `seed` MUST be fresh 32-byte values from
 100  // a CSPRNG for every output.  Reusing a seed or blinding leaks the amount
 101  // (Pedersen commitments are only hiding under fresh uniform blinding).
 102  // Returns false if `blinding` or `seed` is not 32 bytes.
 103  bool ProveBulletproof(__int128 amount, const BPScalar& blinding, const BPScalar& seed,
 104                        BPCommitment& commitment, Bulletproof& proof);
 105  
 106  // Verify the confidential transaction balance and kernel signature.
 107  // Checks sum(input) - sum(output) == fee*H and that `kernel_sig` (64-byte
 108  // BIP340 Schnorr) is valid over `kernel_msg` (32 bytes) with public key
 109  // equal to the kernel excess point.
 110  // 33-byte points absorbing blinding contributions the signer could not
 111  // know (stealth outputs); each offset is added to the excess key.
 112  bool VerifyCTBalance(const std::vector<std::vector<uint8_t>>& input_commitments,
 113                       const std::vector<std::vector<uint8_t>>& output_commitments,
 114                       __int128 fee,
 115                       const std::vector<uint8_t>& kernel_msg,
 116                       const std::vector<uint8_t>& kernel_sig,
 117                       const std::vector<__int128>& transparent_inputs = {});
 118  
 119  // Sign a confidential transaction kernel with the excess blinding factor
 120  // (wallet-side, not consensus).  Returns a 64-byte BIP340 signature, or
 121  // false if `excess` is zero (the kernel key would be the identity - the
 122  // wallet must re-blind one of its outputs in that case).
 123  bool CreateCTKernelSig(const BPScalar& excess, const std::vector<uint8_t>& kernel_msg,
 124                         std::vector<uint8_t>& sig);
 125  
 126  // Deserialize a bulletproof from a byte range.  Returns false on malformed input.
 127  bool ParseBulletproof(std::span<const uint8_t> data, Bulletproof& proof);
 128  
 129  // Serialize a bulletproof into the canonical byte format (754 bytes).
 130  std::vector<uint8_t> SerializeBulletproof(const Bulletproof& proof);
 131  
 132  #endif // LIMENKA_CRYPTO_BULLETPROOFS_H
 133