use crate::{Result, VeilCryptoError};
use k256::{
AffinePoint, ProjectivePoint, Scalar,
elliptic_curve::{
group::prime::PrimeCurveAffine,
ops::MulByGenerator,
sec1::{FromEncodedPoint, ToEncodedPoint},
Group, PrimeField,
},
};
use sha2::{Sha256, Digest};
pub fn borromean_hash(
e: &[u8],
message: &[u8],
ring_idx: usize,
element_idx: usize,
) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(e);
hasher.update(message);
hasher.update((ring_idx as u32).to_be_bytes());
hasher.update((element_idx as u32).to_be_bytes());
let result = hasher.finalize();
result.into()
}
pub fn borromean_verify(
e0: &[u8; 32],
s: &[[u8; 32]],
pubs: &[[u8; 33]],
rsizes: &[usize],
message: &[u8],
) -> Result<bool> {
let nrings = rsizes.len();
if nrings == 0 {
return Err(VeilCryptoError::Other("No rings provided".into()));
}
let total_pubs: usize = rsizes.iter().sum();
if s.len() != total_pubs || pubs.len() != total_pubs {
return Err(VeilCryptoError::Other(
format!("Signature length mismatch: s={}, pubs={}, expected={}",
s.len(), pubs.len(), total_pubs)
));
}
let mut count = 0;
let mut sha256_e0 = Sha256::new();
for (i, &ring_size) in rsizes.iter().enumerate() {
let hash = borromean_hash(e0, message, i, 0);
let mut en = Scalar::from_repr(hash.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".into()))?;
for j in 0..ring_size {
let s_scalar = Scalar::from_repr(s[count].into())
.into_option()
.ok_or_else(|| VeilCryptoError::InvalidSignature)?;
if s_scalar.is_zero().into() || en.is_zero().into() {
return Ok(false);
}
let pub_point = AffinePoint::from_encoded_point(
&k256::EncodedPoint::from_bytes(&pubs[count])
.map_err(|_| VeilCryptoError::InvalidPublicKey)?
)
.into_option()
.ok_or(VeilCryptoError::InvalidPublicKey)?;
if bool::from(pub_point.is_identity()) {
return Ok(false);
}
let s_times_g = ProjectivePoint::mul_by_generator(&s_scalar);
let en_times_pub = ProjectivePoint::from(pub_point) * en;
let r = s_times_g + en_times_pub;
if bool::from(r.is_identity()) {
return Ok(false);
}
let r_affine = r.to_affine();
let r_compressed = r_affine.to_encoded_point(true);
let r_bytes = r_compressed.as_bytes();
if j != ring_size - 1 {
let next_hash = borromean_hash(r_bytes, message, i, j + 1);
en = Scalar::from_repr(next_hash.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".into()))?;
} else {
sha256_e0.update(r_bytes);
}
count += 1;
}
}
sha256_e0.update(message);
let final_hash: [u8; 32] = sha256_e0.finalize().into();
Ok(final_hash == *e0)
}
pub fn borromean_sign(
pubs: &[[u8; 33]],
k: &[[u8; 32]],
sec: &[[u8; 32]],
s_random: &[[u8; 32]], rsizes: &[usize],
secidx: &[usize],
message: &[u8],
) -> Result<([u8; 32], Vec<[u8; 32]>)> {
let nrings = rsizes.len();
if nrings == 0 {
return Err(VeilCryptoError::Other("No rings provided".into()));
}
if k.len() != nrings || sec.len() != nrings || secidx.len() != nrings {
return Err(VeilCryptoError::Other("Invalid input lengths".into()));
}
let total_pubs: usize = rsizes.iter().sum();
if pubs.len() != total_pubs || s_random.len() != total_pubs {
return Err(VeilCryptoError::Other(
format!("Pubkey/signature length mismatch: pubs={}, s_random={}, expected={}",
pubs.len(), s_random.len(), total_pubs)
));
}
for (i, &idx) in secidx.iter().enumerate() {
if idx >= rsizes[i] {
return Err(VeilCryptoError::Other(
format!("Invalid secret index {} for ring {} of size {}", idx, i, rsizes[i])
));
}
}
let mut s: Vec<Scalar> = Vec::with_capacity(total_pubs);
for s_rand in s_random {
let scalar = Scalar::from_repr((*s_rand).into())
.into_option()
.ok_or(VeilCryptoError::InvalidSecretKey)?;
s.push(scalar);
}
let mut k_scalars = Vec::with_capacity(nrings);
for k_bytes in k {
let scalar = Scalar::from_repr((*k_bytes).into())
.into_option()
.ok_or(VeilCryptoError::InvalidSecretKey)?;
k_scalars.push(scalar);
}
let mut sec_scalars = Vec::with_capacity(nrings);
for sec_bytes in sec {
let scalar = Scalar::from_repr((*sec_bytes).into())
.into_option()
.ok_or(VeilCryptoError::InvalidSecretKey)?;
sec_scalars.push(scalar);
}
let mut sha256_e0 = Sha256::new();
let mut count = 0;
for (i, &ring_size) in rsizes.iter().enumerate() {
let r_point = ProjectivePoint::mul_by_generator(&k_scalars[i]);
if bool::from(r_point.is_identity()) {
return Err(VeilCryptoError::Other("Nonce generated point at infinity".into()));
}
let r_affine = r_point.to_affine();
let mut tmp = r_affine.to_encoded_point(true);
for j in (secidx[i] + 1)..ring_size {
let hash = borromean_hash(tmp.as_bytes(), message, i, j);
let en = Scalar::from_repr(hash.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".into()))?;
if bool::from(en.is_zero()) {
return Err(VeilCryptoError::Other("Zero challenge in signing".into()));
}
let pub_point = AffinePoint::from_encoded_point(
&k256::EncodedPoint::from_bytes(&pubs[count + j])
.map_err(|_| VeilCryptoError::InvalidPublicKey)?
)
.into_option()
.ok_or(VeilCryptoError::InvalidPublicKey)?;
let en_times_pub = ProjectivePoint::from(pub_point) * en;
let s_times_g = ProjectivePoint::mul_by_generator(&s[count + j]);
let r = en_times_pub + s_times_g;
if bool::from(r.is_identity()) {
return Err(VeilCryptoError::Other("Forgery generated point at infinity".into()));
}
tmp = r.to_affine().to_encoded_point(true);
}
sha256_e0.update(tmp.as_bytes());
count += ring_size;
}
sha256_e0.update(message);
let e0: [u8; 32] = sha256_e0.finalize().into();
count = 0;
for (i, &ring_size) in rsizes.iter().enumerate() {
let hash = borromean_hash(&e0, message, i, 0);
let mut en = Scalar::from_repr(hash.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".into()))?;
if bool::from(en.is_zero()) {
return Err(VeilCryptoError::Other("Zero challenge in phase 2".into()));
}
for j in 0..secidx[i] {
let pub_point = AffinePoint::from_encoded_point(
&k256::EncodedPoint::from_bytes(&pubs[count + j])
.map_err(|_| VeilCryptoError::InvalidPublicKey)?
)
.into_option()
.ok_or(VeilCryptoError::InvalidPublicKey)?;
let en_times_pub = ProjectivePoint::from(pub_point) * en;
let s_times_g = ProjectivePoint::mul_by_generator(&s[count + j]);
let r = en_times_pub + s_times_g;
if bool::from(r.is_identity()) {
return Err(VeilCryptoError::Other("Forgery generated point at infinity".into()));
}
let r_compressed = r.to_affine().to_encoded_point(true);
let next_hash = borromean_hash(r_compressed.as_bytes(), message, i, j + 1);
en = Scalar::from_repr(next_hash.into())
.into_option()
.ok_or_else(|| VeilCryptoError::Other("Invalid scalar from hash".into()))?;
if bool::from(en.is_zero()) {
return Err(VeilCryptoError::Other("Zero challenge in forgery loop".into()));
}
}
let en_times_sec = en * sec_scalars[i];
let real_sig = k_scalars[i] - en_times_sec;
if bool::from(real_sig.is_zero()) {
return Err(VeilCryptoError::Other("Real signature is zero - invalid nonce".into()));
}
s[count + secidx[i]] = real_sig;
count += ring_size;
}
let s_bytes: Vec<[u8; 32]> = s.iter()
.map(|scalar| {
let bytes = scalar.to_bytes();
let mut array = [0u8; 32];
array.copy_from_slice(&bytes);
array
})
.collect();
Ok((e0, s_bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_borromean_hash_basic() {
let e = [0u8; 32];
let message = b"test message";
let hash = borromean_hash(&e, message, 0, 0);
let hash2 = borromean_hash(&e, message, 0, 0);
assert_eq!(hash, hash2);
}
#[test]
fn test_borromean_hash_different_indices() {
let e = [0u8; 32];
let message = b"test";
let hash1 = borromean_hash(&e, message, 0, 0);
let hash2 = borromean_hash(&e, message, 0, 1); let hash3 = borromean_hash(&e, message, 1, 0);
assert_ne!(hash1, hash2);
assert_ne!(hash1, hash3);
assert_ne!(hash2, hash3);
}
#[test]
fn test_borromean_hash_matches_expected_format() {
let e = [0x42; 32];
let message = b"hello world";
let hash = borromean_hash(&e, message, 5, 10);
assert_eq!(hash.len(), 32);
assert_ne!(hash, [0u8; 32]);
}
#[test]
fn test_borromean_verify_input_validation() {
let e0 = [0u8; 32];
let s = vec![];
let pubs = vec![];
let rsizes = vec![];
let message = b"test";
let result = borromean_verify(&e0, &s, &pubs, &rsizes, message);
assert!(result.is_err());
}
#[test]
fn test_borromean_verify_length_mismatch() {
let e0 = [0u8; 32];
let s = vec![[1u8; 32]]; let pubs = vec![[2u8; 33], [3u8; 33]]; let rsizes = vec![2]; let message = b"test";
let result = borromean_verify(&e0, &s, &pubs, &rsizes, message);
assert!(result.is_err());
}
#[test]
fn test_borromean_verify_zero_scalar_rejection() {
use k256::elliptic_curve::ops::MulByGenerator;
let scalar = Scalar::from(5u64);
let point = ProjectivePoint::mul_by_generator(&scalar);
let pub_bytes = point.to_affine().to_encoded_point(true);
let mut pub_array = [0u8; 33];
pub_array.copy_from_slice(pub_bytes.as_bytes());
let e0 = [0u8; 32];
let s = vec![[0u8; 32]]; let pubs = vec![pub_array];
let rsizes = vec![1];
let message = b"test";
let result = borromean_verify(&e0, &s, &pubs, &rsizes, message);
assert_eq!(result.unwrap(), false);
}
#[test]
fn test_borromean_verify_invalid_pubkey() {
let e0 = [0u8; 32];
let s = vec![[1u8; 32]];
let invalid_pub = [0xFFu8; 33]; let pubs = vec![invalid_pub];
let rsizes = vec![1];
let message = b"test";
let result = borromean_verify(&e0, &s, &pubs, &rsizes, message);
assert!(result.is_err());
}
#[test]
fn test_borromean_sign_single_ring_roundtrip() {
use k256::elliptic_curve::ops::MulByGenerator;
let secret = Scalar::from(12345u64);
let pubkey = ProjectivePoint::mul_by_generator(&secret);
let other1 = ProjectivePoint::mul_by_generator(&Scalar::from(11111u64));
let other2 = ProjectivePoint::mul_by_generator(&Scalar::from(22222u64));
let mut pubs = Vec::new();
for point in [other1, pubkey, other2] {
let bytes = point.to_affine().to_encoded_point(true);
let mut array = [0u8; 33];
array.copy_from_slice(bytes.as_bytes());
pubs.push(array);
}
let nonce = Scalar::from(99999u64);
let nonce_bytes = nonce.to_bytes();
let mut nonce_array = [0u8; 32];
nonce_array.copy_from_slice(&nonce_bytes);
let secret_bytes = secret.to_bytes();
let mut secret_array = [0u8; 32];
secret_array.copy_from_slice(&secret_bytes);
let s_random = vec![
[0x11u8; 32], [0x00u8; 32], [0x22u8; 32], ];
let message = b"test message";
let rsizes = vec![3];
let secidx = vec![1];
let (e0, s) = borromean_sign(
&pubs,
&[nonce_array],
&[secret_array],
&s_random,
&rsizes,
&secidx,
message,
).unwrap();
let verify_result = borromean_verify(&e0, &s, &pubs, &rsizes, message).unwrap();
assert!(verify_result, "Signature should verify successfully");
}
#[test]
fn test_borromean_sign_multiple_rings_roundtrip() {
use k256::elliptic_curve::ops::MulByGenerator;
let secret1 = Scalar::from(11111u64);
let pub1_0 = ProjectivePoint::mul_by_generator(&secret1);
let pub1_1 = ProjectivePoint::mul_by_generator(&Scalar::from(11112u64));
let secret2 = Scalar::from(22222u64);
let pub2_0 = ProjectivePoint::mul_by_generator(&Scalar::from(22220u64));
let pub2_1 = ProjectivePoint::mul_by_generator(&Scalar::from(22221u64));
let pub2_2 = ProjectivePoint::mul_by_generator(&secret2);
let mut pubs = Vec::new();
for point in [pub1_0, pub1_1, pub2_0, pub2_1, pub2_2] {
let bytes = point.to_affine().to_encoded_point(true);
let mut array = [0u8; 33];
array.copy_from_slice(bytes.as_bytes());
pubs.push(array);
}
let nonce1_bytes = Scalar::from(99991u64).to_bytes();
let mut nonce1 = [0u8; 32];
nonce1.copy_from_slice(&nonce1_bytes);
let nonce2_bytes = Scalar::from(99992u64).to_bytes();
let mut nonce2 = [0u8; 32];
nonce2.copy_from_slice(&nonce2_bytes);
let secret1_bytes = secret1.to_bytes();
let mut secret1_array = [0u8; 32];
secret1_array.copy_from_slice(&secret1_bytes);
let secret2_bytes = secret2.to_bytes();
let mut secret2_array = [0u8; 32];
secret2_array.copy_from_slice(&secret2_bytes);
let s_random = vec![
[0x00u8; 32], [0x11u8; 32], [0x20u8; 32], [0x21u8; 32], [0x00u8; 32], ];
let message = b"multi-ring test";
let rsizes = vec![2, 3];
let secidx = vec![0, 2];
let (e0, s) = borromean_sign(
&pubs,
&[nonce1, nonce2],
&[secret1_array, secret2_array],
&s_random,
&rsizes,
&secidx,
message,
).unwrap();
let verify_result = borromean_verify(&e0, &s, &pubs, &rsizes, message).unwrap();
assert!(verify_result, "Multi-ring signature should verify");
}
#[test]
fn test_borromean_sign_invalid_secret_index() {
use k256::elliptic_curve::ops::MulByGenerator;
let pub1 = ProjectivePoint::mul_by_generator(&Scalar::from(1u64));
let mut pub_array = [0u8; 33];
pub_array.copy_from_slice(pub1.to_affine().to_encoded_point(true).as_bytes());
let pubs = vec![pub_array];
let nonce = [1u8; 32];
let secret = [2u8; 32];
let s_random = vec![[3u8; 32]];
let rsizes = vec![1];
let secidx = vec![5]; let message = b"test";
let result = borromean_sign(&pubs, &[nonce], &[secret], &s_random, &rsizes, &secidx, message);
assert!(result.is_err());
}
#[test]
fn test_borromean_sign_different_messages_different_signatures() {
use k256::elliptic_curve::ops::MulByGenerator;
let secret = Scalar::from(12345u64);
let pubkey = ProjectivePoint::mul_by_generator(&secret);
let mut pub_array = [0u8; 33];
pub_array.copy_from_slice(pubkey.to_affine().to_encoded_point(true).as_bytes());
let pubs = vec![pub_array];
let nonce = Scalar::from(99999u64).to_bytes();
let mut nonce_array = [0u8; 32];
nonce_array.copy_from_slice(&nonce);
let secret_bytes = secret.to_bytes();
let mut secret_array = [0u8; 32];
secret_array.copy_from_slice(&secret_bytes);
let s_random = vec![[0u8; 32]];
let rsizes = vec![1];
let secidx = vec![0];
let (e0_1, s_1) = borromean_sign(
&pubs, &[nonce_array], &[secret_array], &s_random, &rsizes, &secidx, b"message1"
).unwrap();
let (e0_2, s_2) = borromean_sign(
&pubs, &[nonce_array], &[secret_array], &s_random, &rsizes, &secidx, b"message2"
).unwrap();
assert_ne!(e0_1, e0_2);
assert_ne!(s_1[0], s_2[0]);
}
}