use crate::{Result, VeilCryptoError};
use k256::{
elliptic_curve::{
ops::MulByGenerator, sec1::{ToEncodedPoint, FromEncodedPoint}, Group,
PrimeField,
},
AffinePoint, EncodedPoint, ProjectivePoint, Scalar,
};
const GENERATOR_H_BYTES: [u8; 33] = [
0x11, 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a,
0x5e, 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a,
0xc0,
];
fn get_generator_h() -> Result<ProjectivePoint> {
use k256::FieldElement;
let x_bytes: [u8; 32] = GENERATOR_H_BYTES[1..33]
.try_into()
.map_err(|_| VeilCryptoError::Other("Invalid x-coordinate slice".into()))?;
let x = FieldElement::from_bytes(&x_bytes.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid x-coordinate field element".into()))?;
let x_cubed = x * x * x;
let b = FieldElement::from(7u64);
let y_squared = x_cubed + b;
let y_canonical = y_squared.sqrt()
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Point not on curve".into()))?;
let y_bytes = y_canonical.to_bytes();
let mut point_bytes = [0u8; 65];
point_bytes[0] = 0x04; point_bytes[1..33].copy_from_slice(&x_bytes);
point_bytes[33..65].copy_from_slice(&y_bytes);
let encoded = EncodedPoint::from_bytes(&point_bytes)
.map_err(|_| VeilCryptoError::Other("Invalid point encoding".into()))?;
let point = AffinePoint::from_encoded_point(&encoded)
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid point".into()))?;
let mut result: ProjectivePoint = point.into();
if GENERATOR_H_BYTES[0] & 1 != 0 {
result = -result;
}
Ok(result)
}
pub fn pedersen_ecmult_point(
blind_scalar: &Scalar,
value: u64,
h_point: &ProjectivePoint,
) -> Result<ProjectivePoint> {
let mut value_bytes = [0u8; 32];
value_bytes[24..32].copy_from_slice(&value.to_be_bytes());
let value_scalar = Scalar::from_repr(value_bytes.into())
.into_option()
.ok_or(VeilCryptoError::Other("Invalid value scalar".into()))?;
let blind_g = ProjectivePoint::mul_by_generator(blind_scalar);
let value_h = *h_point * value_scalar;
let commitment = blind_g + value_h;
Ok(commitment)
}
pub fn get_generator_h_point() -> Result<ProjectivePoint> {
get_generator_h()
}
pub fn pedersen_commit(value: u64, blind: &[u8]) -> Result<Vec<u8>> {
if blind.len() != 32 {
return Err(VeilCryptoError::InvalidSecretKey);
}
let blind_array: [u8; 32] = blind
.try_into()
.map_err(|_| VeilCryptoError::InvalidSecretKey)?;
let blind_scalar = Scalar::from_repr(blind_array.into())
.into_option()
.ok_or(VeilCryptoError::InvalidSecretKey)?;
let mut value_bytes = [0u8; 32];
value_bytes[24..32].copy_from_slice(&value.to_be_bytes());
let value_scalar = Scalar::from_repr(value_bytes.into())
.into_option()
.ok_or(VeilCryptoError::Other("Invalid value scalar".into()))?;
let h_point = get_generator_h()?;
let blind_g = ProjectivePoint::mul_by_generator(&blind_scalar);
let value_h = h_point * value_scalar;
let commitment = blind_g + value_h;
if commitment.is_identity().into() {
return Err(VeilCryptoError::Other(
"Commitment is point at infinity".into(),
));
}
let affine = commitment.to_affine();
let encoded_uncompressed = affine.to_encoded_point(false); let encoded_compressed = affine.to_encoded_point(true);
let y_bytes = encoded_uncompressed.y()
.ok_or_else(|| VeilCryptoError::Other("Missing y coordinate".into()))?;
let y = k256::FieldElement::from_bytes(y_bytes.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid y coordinate".into()))?;
let y_has_sqrt = y.sqrt().is_some();
let y_is_qr: bool = y_has_sqrt.into();
let mut result = vec![0u8; 33];
result[0] = if y_is_qr { 0x08 } else { 0x09 };
result[1..33].copy_from_slice(&encoded_compressed.as_bytes()[1..33]);
Ok(result)
}
pub fn pedersen_blind_sum(blinds: &[Vec<u8>], n_positive: usize) -> Result<Vec<u8>> {
if blinds.is_empty() {
return Err(VeilCryptoError::Other("No blinds provided".into()));
}
if n_positive > blinds.len() {
return Err(VeilCryptoError::Other(
"n_positive cannot exceed number of blinds".into(),
));
}
for (i, blind) in blinds.iter().enumerate() {
if blind.len() != 32 {
return Err(VeilCryptoError::Other(
format!("Blind {} has invalid length: expected 32, got {}", i, blind.len()),
));
}
}
let mut acc = Scalar::ZERO;
for (i, blind) in blinds.iter().enumerate() {
let blind_array: [u8; 32] = blind
.as_slice()
.try_into()
.map_err(|_| VeilCryptoError::InvalidSecretKey)?;
let mut scalar = Scalar::from_repr(blind_array.into())
.into_option()
.ok_or(VeilCryptoError::InvalidSecretKey)?;
if i >= n_positive {
scalar = scalar.negate();
}
acc = acc.add(&scalar);
}
let result_bytes = acc.to_bytes();
Ok(result_bytes.to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pedersen_commit_invalid_blind() {
let blind = vec![0x01; 31]; let value = 100u64;
let result = pedersen_commit(value, &blind);
assert!(result.is_err(), "Should fail with invalid blind length");
}
#[test]
fn test_h_generator_loading() {
let h_gen = get_generator_h().unwrap();
let affine = h_gen.to_affine();
let encoded = affine.to_encoded_point(true);
println!("H generator (compressed): {}", hex::encode(encoded.as_bytes()));
println!("H generator x-coordinate: {}", hex::encode(&encoded.as_bytes()[1..33]));
println!("Expected x-coordinate: 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0");
assert_eq!(&encoded.as_bytes()[1..33], &GENERATOR_H_BYTES[1..33]);
}
#[test]
fn test_1_times_h() {
let blind = vec![0u8; 32]; let value = 1u64;
let result = pedersen_commit(value, &blind).unwrap();
println!("1*H = {}", hex::encode(&result));
println!("Expected from C for value=1 with all-zero blind would be 1*H");
}
#[test]
fn test_1_times_g() {
use k256::elliptic_curve::{ops::MulByGenerator, sec1::ToEncodedPoint};
let one = Scalar::ONE;
let point = ProjectivePoint::mul_by_generator(&one);
let affine = point.to_affine();
let encoded = affine.to_encoded_point(true);
println!("1*G from k256:");
println!(" Point: {}", hex::encode(encoded.as_bytes()));
println!(" Parity: 0x{:02x} (0x02=even, 0x03=odd)", encoded.as_bytes()[0]);
println!("\nC implementation gives:");
println!(" 0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798");
println!(" Prefix: 0x08");
}
#[test]
fn test_debug_value_zero_parity() {
use k256::elliptic_curve::{ops::MulByGenerator, PrimeField, sec1::ToEncodedPoint};
let blind = vec![0x42; 32];
let blind_array: [u8; 32] = blind.clone().try_into().unwrap();
let blind_scalar = Scalar::from_repr(blind_array.into()).unwrap();
let point = ProjectivePoint::mul_by_generator(&blind_scalar);
let affine = point.to_affine();
let encoded = affine.to_encoded_point(true);
println!("Direct blind*G computation:");
println!(" Point: {}", hex::encode(encoded.as_bytes()));
println!(" Parity byte: 0x{:02x} (0x02=even y, 0x03=odd y)", encoded.as_bytes()[0]);
println!(" X-coordinate: {}", hex::encode(&encoded.as_bytes()[1..33]));
let result = pedersen_commit(0, &blind).unwrap();
println!("\nOur pedersen_commit(value=0, blind=0x42...):");
println!(" Result: {}", hex::encode(&result));
println!(" Prefix: 0x{:02x}", result[0]);
println!("\nC implementation expects:");
println!(" Result: 0824653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c");
println!(" Prefix: 0x08");
assert_eq!(hex::encode(&result[1..33]), "24653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c");
}
#[test]
fn test_vector2_point_parity() {
let blind = vec![0x42; 32];
let value = 0u64;
let result = pedersen_commit(value, &blind).unwrap();
println!("Vector 2 result: {}", hex::encode(&result));
println!("Expected: 0824653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c");
println!("Parity byte: got 0x{:02x}, expected 0x08", result[0]);
use k256::{ProjectivePoint, Scalar};
use k256::elliptic_curve::ops::MulByGenerator;
let blind_array: [u8; 32] = blind.try_into().unwrap();
let blind_scalar = Scalar::from_repr(blind_array.into()).unwrap();
let point = ProjectivePoint::mul_by_generator(&blind_scalar);
let affine = point.to_affine();
let encoded = affine.to_encoded_point(true);
println!("blind*G compressed: {}", hex::encode(encoded.as_bytes()));
println!("Y parity: 0x{:02x} (0x02=even, 0x03=odd)", encoded.as_bytes()[0]);
}
#[test]
fn test_pedersen_blind_sum_vector_1() {
let blind1 = hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap();
let blind2 = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap();
let expected = "0303030303030303030303030303030303030303030303030303030303030303";
let blinds = vec![blind1, blind2];
let result = pedersen_blind_sum(&blinds, 2).unwrap();
assert_eq!(
hex::encode(&result),
expected,
"Vector 1: Sum of two positive blinds should match C implementation"
);
}
#[test]
fn test_pedersen_blind_sum_vector_2() {
let blind1 = hex::decode("0505050505050505050505050505050505050505050505050505050505050505").unwrap();
let blind2 = hex::decode("0303030303030303030303030303030303030303030303030303030303030303").unwrap();
let expected = "0202020202020202020202020202020202020202020202020202020202020202";
let blinds = vec![blind1, blind2];
let result = pedersen_blind_sum(&blinds, 1).unwrap();
assert_eq!(
hex::encode(&result),
expected,
"Vector 2: Positive minus negative should match C implementation"
);
}
#[test]
fn test_pedersen_blind_sum_vector_3() {
let blind1 = hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap();
let blind2 = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap();
let blind3 = hex::decode("0303030303030303030303030303030303030303030303030303030303030303").unwrap();
let blind4 = hex::decode("0404040404040404040404040404040404040404040404040404040404040404").unwrap();
let expected = "0202020202020202020202020202020202020202020202020202020202020202";
let blinds = vec![blind1, blind2, blind3, blind4];
let result = pedersen_blind_sum(&blinds, 3).unwrap();
assert_eq!(
hex::encode(&result),
expected,
"Vector 3: Multiple blind sum should match C implementation"
);
}
#[test]
fn test_pedersen_blind_sum_empty() {
let blinds: Vec<Vec<u8>> = vec![];
let result = pedersen_blind_sum(&blinds, 0);
assert!(result.is_err(), "Should fail with empty blinds");
}
#[test]
fn test_pedersen_blind_sum_invalid_n_positive() {
let blind1 = vec![0x01; 32];
let blinds = vec![blind1];
let result = pedersen_blind_sum(&blinds, 2);
assert!(result.is_err(), "Should fail when n_positive exceeds number of blinds");
}
#[test]
fn test_pedersen_blind_sum_invalid_length() {
let blind1 = vec![0x01; 31]; let blind2 = vec![0x02; 32];
let blinds = vec![blind1, blind2];
let result = pedersen_blind_sum(&blinds, 2);
assert!(result.is_err(), "Should fail with invalid blind length");
}
#[test]
fn test_pedersen_commit_blockchain_vector_1() {
let value = 1100000000u64;
let blind_hex = "7e05e476e3159797fe8f0578ad37bfd5775a70e0674ad928bdec74ab7e430880";
let expected_commitment = "09e2fc408916f7813179b6540c91d7d39e779ccc42a522bdfb154004cae85409d2";
let blind = hex::decode(blind_hex).unwrap();
let result = pedersen_commit(value, &blind).unwrap();
println!("\nBlockchain Vector 1 (value={}):", value);
println!(" Our result: {}", hex::encode(&result));
println!(" Blockchain: {}", expected_commitment);
println!(" Our prefix: 0x{:02x}", result[0]);
println!(" Expected prefix: 0x09");
let our_x = hex::encode(&result[1..33]);
let expected_x = &expected_commitment[2..]; println!(" Our x: {}", our_x);
println!(" Expected x: {}", expected_x);
if our_x == expected_x {
println!(" ✅ X-coordinates MATCH!");
if result[0] == 0x09 {
println!(" ✅ Prefix MATCHES! Full commitment correct!");
} else {
println!(" ⚠️ Prefix differs: got 0x{:02x}, expected 0x09", result[0]);
println!(" This suggests QR/NOT-QR logic difference between k256 and C++");
}
} else {
println!(" ❌ X-coordinates DIFFER - commitment calculation is wrong!");
}
assert_eq!(our_x, expected_x, "X-coordinates should match blockchain commitment");
}
#[test]
fn test_pedersen_commit_blockchain_vector_2() {
let value = 600000000u64;
let blind_hex = "c5bb492b50cd862cdea0f411b0ee3ec464670f3f6e1a93909da06575475c6648";
let expected_commitment = "08744532b82ac2131ee55f9c1c12fb6859d3bbbbd505a952ddcca142e097eb4409";
let blind = hex::decode(blind_hex).unwrap();
let result = pedersen_commit(value, &blind).unwrap();
println!("\nBlockchain Vector 2 (value={}):", value);
println!(" Our result: {}", hex::encode(&result));
println!(" Blockchain: {}", expected_commitment);
println!(" Our prefix: 0x{:02x}", result[0]);
println!(" Expected prefix: 0x08");
let our_x = hex::encode(&result[1..33]);
let expected_x = &expected_commitment[2..]; println!(" Our x: {}", our_x);
println!(" Expected x: {}", expected_x);
if our_x == expected_x {
println!(" ✅ X-coordinates MATCH!");
if result[0] == 0x08 {
println!(" ✅ Prefix MATCHES! Full commitment correct!");
} else {
println!(" ⚠️ Prefix differs: got 0x{:02x}, expected 0x08", result[0]);
println!(" This suggests QR/NOT-QR logic difference between k256 and C++");
}
} else {
println!(" ❌ X-coordinates DIFFER - commitment calculation is wrong!");
}
assert_eq!(our_x, expected_x, "X-coordinates should match blockchain commitment");
}
}