use k256::elliptic_curve::{ops::Reduce, sec1::ToEncodedPoint, Group};
use k256::{NonZeroScalar, ProjectivePoint, PublicKey, Scalar, U256};
use rand::rngs::OsRng;
use sha2::{Digest, Sha256};
use subtle::{ConditionallySelectable, ConstantTimeEq};
use crate::types::{hex_to_scalar, BlsagSignature, BlsagSignatureBinary, Error, KeyImage};
use crate::utils::{hex_to_point, random_non_zero_scalar};
const GENERATOR: ProjectivePoint = ProjectivePoint::GENERATOR;
pub fn sign_blsag_hex(
message: &[u8],
private_key_hex: &str,
ring_pubkeys_hex: &[String],
) -> Result<(BlsagSignature, String), Error> {
let private_key = hex_to_scalar(private_key_hex)?;
let ring_pubkeys: Vec<ProjectivePoint> = ring_pubkeys_hex
.iter()
.map(|s| hex_to_point(s))
.collect::<Result<Vec<_>, _>>()?;
let (binary_sig, key_image) = sign_blsag_binary(message, &private_key, &ring_pubkeys)?;
Ok((BlsagSignature::from(&binary_sig), key_image.to_hex()))
}
pub fn verify_blsag_hex(
signature_hex: &BlsagSignature,
key_image_hex: &str,
message: &[u8],
ring_pubkeys_hex: &[String],
) -> Result<bool, Error> {
let binary_sig = BlsagSignatureBinary::try_from(signature_hex)?;
let key_image = KeyImage::from_hex(key_image_hex)?;
let ring_pubkeys: Vec<ProjectivePoint> = ring_pubkeys_hex
.iter()
.map(|s| hex_to_point(s))
.collect::<Result<Vec<_>, _>>()?;
verify_blsag_binary(&binary_sig, &key_image, message, &ring_pubkeys)
}
pub fn sign_blsag_binary(
message: &[u8],
private_key: &Scalar,
ring_pubkeys: &[ProjectivePoint],
) -> Result<(BlsagSignatureBinary, KeyImage), Error> {
let ring_size = ring_pubkeys.len();
if ring_size < 2 {
return Err(Error::RingTooSmall(ring_size));
}
if *private_key == Scalar::ZERO {
return Err(Error::PrivateKeyFormat(
"Private key scalar cannot be zero".into(),
));
}
let d = *private_key;
let _d_nonzero = NonZeroScalar::new(d).expect("d checked non-zero");
let my_point = GENERATOR * d;
let flipped_d = d.negate();
let flipped_point = GENERATOR * flipped_d;
let mut signer_index: Option<usize> = None;
let mut used_d = d;
for (i, p) in ring_pubkeys.iter().enumerate() {
if p == &my_point {
signer_index = Some(i);
used_d = d;
break;
}
if p == &flipped_point {
signer_index = Some(i);
used_d = flipped_d;
break;
}
}
let signer_index = signer_index.ok_or(Error::SignerNotInRing)?;
let p_signer = &ring_pubkeys[signer_index];
let hp_p_signer = hash_to_point(p_signer)?;
let key_image_point = hp_p_signer * used_d; let key_image = KeyImage(key_image_point);
let mut r_scalars = vec![Scalar::ZERO; ring_size]; let mut c_scalars = vec![Scalar::ZERO; ring_size]; let os_rng = OsRng;
let alpha_nonzero = random_non_zero_scalar(os_rng);
let alpha = *alpha_nonzero.as_ref();
let l0_start = GENERATOR * alpha;
let l1_start = hp_p_signer * alpha;
let start_index = (signer_index + 1) % ring_size;
c_scalars[start_index] = hash_for_blsag_challenge(message, &l0_start, &l1_start)?;
let mut current_index = start_index;
while current_index != signer_index {
let r_nonzero = random_non_zero_scalar(os_rng);
r_scalars[current_index] = *r_nonzero.as_ref();
let p_i = &ring_pubkeys[current_index];
let hp_p_i = hash_to_point(p_i)?;
let li0 = (GENERATOR * r_scalars[current_index]) + (*p_i * c_scalars[current_index]);
let li1 =
(hp_p_i * r_scalars[current_index]) + (key_image_point * c_scalars[current_index]);
let next_index = (current_index + 1) % ring_size;
c_scalars[next_index] = hash_for_blsag_challenge(message, &li0, &li1)?;
current_index = next_index;
}
r_scalars[signer_index] = alpha - (c_scalars[signer_index] * used_d);
let signature = BlsagSignatureBinary {
c0: c_scalars[0],
s: r_scalars,
};
Ok((signature, key_image))
}
pub fn verify_blsag_binary(
signature: &BlsagSignatureBinary,
key_image: &KeyImage,
message: &[u8],
ring_pubkeys: &[ProjectivePoint],
) -> Result<bool, Error> {
let ring_size = ring_pubkeys.len();
if ring_size == 0 {
return Ok(false);
} if signature.s.len() != ring_size {
return Err(Error::InvalidSignatureFormat);
}
let key_image_point = key_image.as_point();
if key_image_point.is_identity().into() {
return Ok(false);
}
let c0_scalar = signature.c0;
let r_scalars = &signature.s;
let mut c_recalculated = vec![Scalar::ZERO; ring_size];
let mut current_c = c0_scalar;
for i in 0..ring_size {
let p_i = &ring_pubkeys[i];
let hp_p_i = hash_to_point(p_i)?;
let li0 = (GENERATOR * r_scalars[i]) + (*p_i * current_c);
let li1 = (hp_p_i * r_scalars[i]) + (*key_image_point * current_c);
let next_c = hash_for_blsag_challenge(message, &li0, &li1)?;
let next_index = (i + 1) % ring_size;
if next_index == 0 {
c_recalculated[0] = next_c;
} else {
}
current_c = next_c; }
let is_valid = current_c.ct_eq(&c0_scalar);
Ok(is_valid.into())
}
pub fn key_images_match(image1: &KeyImage, image2: &KeyImage) -> bool {
image1 == image2 }
fn hash_to_point(pubkey: &ProjectivePoint) -> Result<ProjectivePoint, Error> {
if pubkey.is_identity().into() {
return Err(Error::PublicKeyFormat("Cannot hash identity point".into()));
}
let compressed_pubkey = pubkey.to_encoded_point(true); let mut hasher = Sha256::new();
hasher.update(compressed_pubkey.as_bytes());
hasher.update(b"NostringerHp"); let mut hash = hasher.finalize();
let mut counter: u32 = 0;
const MAX_TRIES: u32 = 1000;
loop {
if counter >= MAX_TRIES {
return Err(Error::HashingError(format!(
"Failed to hash to point after {} tries",
MAX_TRIES
)));
}
let mut potential_point_bytes = [0u8; 33];
potential_point_bytes[1..].copy_from_slice(&hash);
potential_point_bytes[0] = 0x02;
if let Ok(pk) = PublicKey::from_sec1_bytes(&potential_point_bytes) {
let point = pk.to_projective();
if !bool::from(point.is_identity()) {
return Ok(point);
}
}
potential_point_bytes[0] = 0x03;
if let Ok(pk) = PublicKey::from_sec1_bytes(&potential_point_bytes) {
let point = pk.to_projective();
if !bool::from(point.is_identity()) {
return Ok(point);
}
}
let mut rehasher = Sha256::new();
rehasher.update(hash);
rehasher.update(counter.to_be_bytes());
hash = rehasher.finalize();
counter += 1;
}
}
fn hash_for_blsag_challenge(
message: &[u8],
p1: &ProjectivePoint,
p2: &ProjectivePoint,
) -> Result<Scalar, Error> {
let mut hasher = Sha256::new();
hasher.update(b"NostringerBlsagChallenge");
hasher.update(message);
hasher.update(p1.to_encoded_point(true).as_bytes());
hasher.update(p2.to_encoded_point(true).as_bytes());
let hash_result = hasher.finalize();
let hash_uint = U256::from_be_slice(&hash_result);
let scalar = Scalar::reduce(hash_uint);
let is_zero = scalar.ct_eq(&Scalar::ZERO);
Ok(Scalar::conditional_select(&scalar, &Scalar::ONE, is_zero))
}