Skip to main content

cougr_core/zk/
commitment.rs

1//! Pedersen commitment scheme using BN254 G1 curve points.
2//!
3//! A Pedersen commitment `C = v*G + r*H` allows committing to a value `v`
4//! with blinding factor `r`, such that:
5//! - The commitment hides `v` (information-theoretically secure with random `r`)
6//! - The commitment is binding (computationally secure under discrete log assumption)
7
8use soroban_sdk::{contracttype, Env};
9
10use super::crypto::{bn254_g1_add, bn254_g1_mul};
11use super::error::ZKError;
12use super::types::{G1Point, Scalar};
13
14/// Pedersen commitment parameters: two independent G1 generator points.
15///
16/// `g` is the "value generator" and `h` is the "blinding generator".
17/// These must be chosen such that the discrete log of `h` with respect to `g`
18/// is unknown (nothing-up-my-sleeve construction recommended).
19#[contracttype]
20#[derive(Clone, Debug)]
21pub struct PedersenParams {
22    /// Value generator point (G1).
23    pub g: G1Point,
24    /// Blinding generator point (G1).
25    pub h: G1Point,
26}
27
28/// A Pedersen commitment point on BN254 G1.
29#[contracttype]
30#[derive(Clone, Debug)]
31pub struct PedersenCommitment {
32    /// The commitment point C = v*G + r*H.
33    pub point: G1Point,
34}
35
36/// Create a Pedersen commitment: `C = value * G + blinding * H`.
37///
38/// # Arguments
39/// - `params`: Generator points (G, H)
40/// - `value`: The value to commit to (scalar field element)
41/// - `blinding`: Random blinding factor (scalar field element)
42pub fn pedersen_commit(
43    env: &Env,
44    params: &PedersenParams,
45    value: &Scalar,
46    blinding: &Scalar,
47) -> Result<PedersenCommitment, ZKError> {
48    let vg = bn254_g1_mul(env, &params.g, value)?;
49    let rh = bn254_g1_mul(env, &params.h, blinding)?;
50    let point = bn254_g1_add(env, &vg, &rh)?;
51    Ok(PedersenCommitment { point })
52}
53
54/// Verify a Pedersen commitment opening.
55///
56/// Checks that `commitment == value * G + blinding * H`.
57pub fn pedersen_verify(
58    env: &Env,
59    params: &PedersenParams,
60    commitment: &PedersenCommitment,
61    value: &Scalar,
62    blinding: &Scalar,
63) -> Result<bool, ZKError> {
64    let expected = pedersen_commit(env, params, value, blinding)?;
65    Ok(commitment.point.bytes == expected.point.bytes)
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use soroban_sdk::{BytesN, Env};
72
73    #[test]
74    fn test_pedersen_params_creation() {
75        let env = Env::default();
76        let params = PedersenParams {
77            g: G1Point {
78                bytes: BytesN::from_array(&env, &[0u8; 64]),
79            },
80            h: G1Point {
81                bytes: BytesN::from_array(&env, &[0u8; 64]),
82            },
83        };
84        assert_eq!(params.g.bytes.len(), 64);
85        assert_eq!(params.h.bytes.len(), 64);
86    }
87
88    #[test]
89    fn test_pedersen_commitment_type_creation() {
90        let env = Env::default();
91        let commitment = PedersenCommitment {
92            point: G1Point {
93                bytes: BytesN::from_array(&env, &[0u8; 64]),
94            },
95        };
96        assert_eq!(commitment.point.bytes.len(), 64);
97    }
98
99    #[test]
100    fn test_pedersen_same_commitment_equals() {
101        let env = Env::default();
102        let c1 = PedersenCommitment {
103            point: G1Point {
104                bytes: BytesN::from_array(&env, &[1u8; 64]),
105            },
106        };
107        let c2 = PedersenCommitment {
108            point: G1Point {
109                bytes: BytesN::from_array(&env, &[1u8; 64]),
110            },
111        };
112        assert_eq!(c1.point.bytes, c2.point.bytes);
113    }
114
115    #[test]
116    fn test_pedersen_different_commitments_differ() {
117        let env = Env::default();
118        let c1 = PedersenCommitment {
119            point: G1Point {
120                bytes: BytesN::from_array(&env, &[1u8; 64]),
121            },
122        };
123        let c2 = PedersenCommitment {
124            point: G1Point {
125                bytes: BytesN::from_array(&env, &[2u8; 64]),
126            },
127        };
128        assert_ne!(c1.point.bytes, c2.point.bytes);
129    }
130}