pub mod types;
pub mod utils;
use k256::elliptic_curve::{ops::Reduce, point::AffineCoordinates, sec1::ToEncodedPoint, Group};
use k256::{NonZeroScalar, ProjectivePoint, PublicKey, Scalar, SecretKey, U256};
use rand::rngs::OsRng;
use sha2::{Digest, Sha256};
use subtle::{ConditionallySelectable, ConstantTimeEq};
pub use types::{Error, KeyPair, KeyPairHex, RingSignature, RingSignatureBinary};
use types::{
hex_to_scalar, normalize_hex, scalar_to_hex, BlsagSignature, BlsagSignatureBinary, KeyImage,
};
use utils::{hex_to_point, random_non_zero_scalar};
const GENERATOR: ProjectivePoint = ProjectivePoint::GENERATOR;
pub fn sign(
message: &[u8],
private_key_hex: &str,
ring_pubkeys_hex: &[String],
) -> Result<RingSignature, Error> {
sign_with_hex(message, private_key_hex, ring_pubkeys_hex)
}
pub fn sign_with_hex(
message: &[u8],
private_key_hex: &str,
ring_pubkeys_hex: &[String],
) -> Result<RingSignature, Error> {
let private_key = hex_to_scalar(private_key_hex)?;
let ring_pubkeys: Vec<ProjectivePoint> = ring_pubkeys_hex
.iter()
.map(|pubkey_str| hex_to_point(pubkey_str))
.collect::<Result<_, _>>()?;
let binary_signature = sign_binary(message, &private_key, &ring_pubkeys)?;
Ok(RingSignature::from(&binary_signature))
}
pub fn verify(
signature: &RingSignature,
message: &[u8],
ring_pubkeys_hex: &[String],
) -> Result<bool, Error> {
verify_with_hex(signature, message, ring_pubkeys_hex)
}
pub fn verify_with_hex(
signature: &RingSignature,
message: &[u8],
ring_pubkeys_hex: &[String],
) -> Result<bool, Error> {
let binary_signature = RingSignatureBinary::try_from(signature)?;
let ring_pubkeys: Vec<ProjectivePoint> = ring_pubkeys_hex
.iter()
.map(|pubkey_str| hex_to_point(pubkey_str))
.collect::<Result<_, _>>()?;
verify_binary(&binary_signature, message, &ring_pubkeys)
}
fn hash_to_scalar(
message: &[u8],
ring_pubkeys_hex: &[String],
ephemeral_point: &ProjectivePoint,
) -> Result<Scalar, Error> {
let mut hasher = Sha256::new();
hasher.update(message);
for pk_hex in ring_pubkeys_hex {
let norm_hex = normalize_hex(pk_hex)?;
let pk_bytes = hex::decode(&norm_hex)?;
hasher.update(&pk_bytes);
}
let ephemeral_compressed = ephemeral_point.to_encoded_point(true);
hasher.update(ephemeral_compressed.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))
}
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 {
c_recalculated[next_index] = next_c; }
current_c = next_c; }
let is_valid = current_c.ct_eq(&c0_scalar);
Ok(is_valid.into())
}
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))
}
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 key_images_match(image1: &KeyImage, image2: &KeyImage) -> bool {
image1 == image2 }
pub fn generate_keypair_hex(format: &str) -> KeyPairHex {
let os_rng = OsRng;
let secret_scalar_nonzero = random_non_zero_scalar(os_rng);
let secret_key = SecretKey::from(secret_scalar_nonzero);
let secret_scalar = *secret_scalar_nonzero.as_ref();
let private_key_hex = scalar_to_hex(&secret_scalar);
let public_key = secret_key.public_key();
let mut point = public_key.to_projective();
let public_key_hex = match format {
"xonly" => {
let affine = point.to_affine();
let y_is_odd = affine.y_is_odd();
if y_is_odd.into() {
let flipped_scalar = secret_scalar.negate();
point = GENERATOR * flipped_scalar;
}
let final_affine = point.to_affine();
hex::encode(final_affine.x().as_slice())
}
"uncompressed" => hex::encode(point.to_encoded_point(false).as_bytes()),
"compressed" => hex::encode(point.to_encoded_point(true).as_bytes()),
_ => hex::encode(point.to_encoded_point(true).as_bytes()),
};
KeyPairHex {
private_key_hex,
public_key_hex,
}
}
pub fn generate_keypairs(count: usize, format: &str) -> Vec<KeyPairHex> {
(0..count).map(|_| generate_keypair_hex(format)).collect()
}
pub fn get_public_keys(keypairs: &[KeyPairHex]) -> Vec<String> {
keypairs
.iter()
.map(|kp| kp.public_key_hex.clone())
.collect()
}
pub fn sign_binary(
message: &[u8],
private_key: &Scalar,
ring_pubkeys: &[ProjectivePoint],
) -> Result<RingSignatureBinary, 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 was checked non-zero, NonZeroScalar::new must succeed");
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 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 alpha_g = GENERATOR * alpha;
let start_index = (signer_index + 1) % ring_size;
let ring_pubkeys_hex: Vec<String> = ring_pubkeys
.iter()
.map(|point| hex::encode(point.to_encoded_point(true).as_bytes()))
.collect();
c_scalars[start_index] = hash_to_scalar(message, &ring_pubkeys_hex, &alpha_g)?;
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 xi = (GENERATOR * r_scalars[current_index])
+ (ring_pubkeys[current_index] * c_scalars[current_index]);
let next_index = (current_index + 1) % ring_size;
c_scalars[next_index] = hash_to_scalar(message, &ring_pubkeys_hex, &xi)?;
current_index = next_index;
}
r_scalars[signer_index] = alpha - (c_scalars[signer_index] * used_d);
Ok(RingSignatureBinary {
c0: c_scalars[0],
s: r_scalars,
})
}
pub fn verify_binary(
signature: &RingSignatureBinary,
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 c0_scalar = signature.c0;
let r_scalars = &signature.s;
let ring_pubkeys_hex: Vec<String> = ring_pubkeys
.iter()
.map(|point| hex::encode(point.to_encoded_point(true).as_bytes()))
.collect();
let mut current_c = c0_scalar;
for i in 0..ring_size {
let xi = (GENERATOR * r_scalars[i]) + (ring_pubkeys[i] * current_c);
current_c = hash_to_scalar(message, &ring_pubkeys_hex, &xi)?;
}
let is_valid = current_c.ct_eq(&c0_scalar);
Ok(is_valid.into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_api() {
let keypair1 = generate_keypair_hex("xonly");
let keypair2 = generate_keypair_hex("xonly");
let keypair3 = generate_keypair_hex("xonly");
let _private_key1 = hex_to_scalar(&keypair1.private_key_hex).unwrap();
let private_key2 = hex_to_scalar(&keypair2.private_key_hex).unwrap();
let _private_key3 = hex_to_scalar(&keypair3.private_key_hex).unwrap();
let pubkey1 = hex_to_point(&keypair1.public_key_hex).unwrap();
let pubkey2 = hex_to_point(&keypair2.public_key_hex).unwrap();
let pubkey3 = hex_to_point(&keypair3.public_key_hex).unwrap();
let ring_binary = vec![pubkey1, pubkey2, pubkey3];
let ring_hex = vec![
keypair1.public_key_hex.clone(),
keypair2.public_key_hex.clone(),
keypair3.public_key_hex.clone(),
];
let message = b"Test message for binary API";
let binary_sig = sign_binary(message, &private_key2, &ring_binary).unwrap();
let binary_verify = verify_binary(&binary_sig, message, &ring_binary).unwrap();
assert!(binary_verify, "Binary verification should succeed");
let tampered = b"Tampered message";
let tampered_verify = verify_binary(&binary_sig, tampered, &ring_binary).unwrap();
assert!(
!tampered_verify,
"Verification with tampered message should fail"
);
let hex_sig = RingSignature::from(&binary_sig);
let binary_sig2 = RingSignatureBinary::try_from(&hex_sig).unwrap();
let verify_after_conversion = verify_binary(&binary_sig2, message, &ring_binary).unwrap();
assert!(
verify_after_conversion,
"Verification after conversion should succeed"
);
let hex_sig_direct = sign(message, &keypair2.private_key_hex, &ring_hex).unwrap();
let hex_verify = verify(&hex_sig_direct, message, &ring_hex).unwrap();
assert!(hex_verify, "Hex verification should succeed");
}
}