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