use k256::elliptic_curve::{
ops::Reduce,
point::AffineCoordinates,
rand_core::{self},
sec1::ToEncodedPoint,
PrimeField,
};
use k256::{NonZeroScalar, ProjectivePoint, PublicKey, Scalar, SecretKey, U256};
use rand::rngs::OsRng;
use sha2::{Digest, Sha256};
use subtle::{ConditionallySelectable, ConstantTimeEq};
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
const GENERATOR: ProjectivePoint = ProjectivePoint::GENERATOR;
#[derive(Error, Debug)]
pub enum Error {
#[error("Hex decoding failed: {0}")]
HexDecode(#[from] hex::FromHexError),
#[error("Invalid private key format: {0}")]
PrivateKeyFormat(String),
#[error("Invalid public key format: {0}")]
PublicKeyFormat(String),
#[error("Invalid scalar encoding (>= curve order N)")]
InvalidScalarEncoding,
#[error("Secp256k1 curve error: {0}")]
Secp256k1(#[from] k256::elliptic_curve::Error),
#[error("Ring must have at least 2 members, got {0}")]
RingTooSmall(usize),
#[error("Signer's public key (or its negation) not found in the ring")]
SignerNotInRing,
#[error("Signature verification failed (internal calculation mismatch)")]
VerificationFailed,
#[error("Invalid signature format (e.g., incorrect number of 's' values)")]
InvalidSignatureFormat,
#[error("Hashing error: {0}")]
HashingError(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RingSignature {
pub c0: String,
pub s: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RingSignatureBinary {
pub c0: Scalar,
pub s: Vec<Scalar>,
}
#[derive(Debug, Clone)]
pub struct KeyPair {
pub private_key: Scalar,
pub public_key: ProjectivePoint,
}
impl From<&RingSignatureBinary> for RingSignature {
fn from(binary: &RingSignatureBinary) -> Self {
RingSignature {
c0: scalar_to_hex(&binary.c0),
s: binary.s.iter().map(scalar_to_hex).collect(),
}
}
}
impl TryFrom<&RingSignature> for RingSignatureBinary {
type Error = Error;
fn try_from(sig: &RingSignature) -> Result<Self, Self::Error> {
let c0 = hex_to_scalar(&sig.c0)?;
let s = sig
.s
.iter()
.map(|s_hex| hex_to_scalar(s_hex))
.collect::<Result<Vec<Scalar>, Error>>()?;
Ok(RingSignatureBinary { c0, s })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct KeyPairHex {
pub private_key_hex: String,
pub public_key_hex: String,
}
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 normalize_hex(hex_str: &str) -> Result<String, Error> {
let lower = hex_str
.trim_start_matches("0x")
.trim_start_matches("0X")
.to_lowercase();
if lower.chars().any(|c| !c.is_ascii_hexdigit()) {
return Err(Error::PublicKeyFormat(format!(
"Non-hex characters found: {}",
hex_str
)));
}
Ok(lower)
}
fn scalar_to_hex(scalar: &Scalar) -> String {
hex::encode(scalar.to_bytes().as_slice())
}
fn hex_to_scalar(hex_str: &str) -> Result<Scalar, Error> {
let padded_hex = if hex_str.len() < 64 {
format!("{:0>64}", hex_str)
} else {
hex_str.to_string()
};
if padded_hex.len() != 64 {
return Err(Error::PrivateKeyFormat(format!(
"Hex len {} != 64",
padded_hex.len()
)));
}
let bytes = hex::decode(&padded_hex)?;
let field_bytes = k256::FieldBytes::from_slice(&bytes);
let maybe_scalar = Scalar::from_repr(*field_bytes);
if maybe_scalar.is_some().into() {
Ok(maybe_scalar.unwrap())
} else {
Err(Error::InvalidScalarEncoding)
}
}
fn hex_to_point(pubkey_hex: &str) -> Result<ProjectivePoint, Error> {
let hex_norm = normalize_hex(pubkey_hex)?;
let point_bytes = match hex_norm.len() {
64 => hex::decode(format!("02{}", hex_norm))?,
66 => {
if !hex_norm.starts_with("02") && !hex_norm.starts_with("03") {
return Err(Error::PublicKeyFormat(format!(
"Invalid prefix: {}",
&hex_norm[..2]
)));
}
hex::decode(&hex_norm)?
}
130 => {
if !hex_norm.starts_with("04") {
return Err(Error::PublicKeyFormat(format!(
"Invalid prefix: {}",
&hex_norm[..2]
)));
}
hex::decode(&hex_norm)?
}
_ => {
return Err(Error::PublicKeyFormat(format!(
"Invalid length: {}",
hex_norm.len()
)));
}
};
let public_key = PublicKey::from_sec1_bytes(&point_bytes)
.map_err(|e| Error::PublicKeyFormat(format!("SEC1 parse error: {}", e)))?;
Ok(public_key.to_projective())
}
fn random_non_zero_scalar(
mut rng: impl rand_core::RngCore + rand_core::CryptoRng,
) -> NonZeroScalar {
NonZeroScalar::random(&mut rng)
}
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 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");
}
}