pub mod blsag;
pub mod keys;
pub mod sag;
pub mod serialization;
pub mod types;
pub mod utils;
#[cfg(feature = "wasm")]
pub mod wasm;
pub use types::{Error, KeyPair, KeyPairHex, RingSignature, RingSignatureBinary, SignatureVariant};
pub use serialization::{CompactSignature, SerializationError};
pub use types::hex_to_scalar;
pub use keys::{generate_keypair_hex, generate_keypairs, get_public_keys};
impl From<SerializationError> for Error {
fn from(err: SerializationError) -> Self {
Error::Serialization(err.to_string())
}
}
pub fn sign(
message: &[u8],
private_key_input: &str,
ring_pubkeys_input: &[String],
variant: SignatureVariant,
) -> Result<String, Error> {
match variant {
SignatureVariant::Sag => sign_compact_sag(message, private_key_input, ring_pubkeys_input),
SignatureVariant::Blsag => {
sign_compact_blsag(message, private_key_input, ring_pubkeys_input)
}
}
}
pub fn verify(
compact_signature: &str,
message: &[u8],
ring_pubkeys_input: &[String],
) -> Result<bool, Error> {
let compact_sig = CompactSignature::deserialize(compact_signature)
.map_err(|e| Error::Serialization(e.to_string()))?;
let ring_pubkeys: Vec<k256::ProjectivePoint> = ring_pubkeys_input
.iter()
.map(|pubkey_str| utils::parse_public_key(pubkey_str))
.collect::<Result<_, _>>()?;
match compact_sig {
CompactSignature::Sag(binary_sig) => {
sag::verify_binary(&binary_sig, message, &ring_pubkeys)
}
CompactSignature::Blsag(binary_sig, key_image) => {
blsag::verify_blsag_binary(&binary_sig, &key_image, message, &ring_pubkeys)
}
}
}
pub fn sign_compact_sag(
message: &[u8],
private_key_input: &str,
ring_pubkeys_input: &[String],
) -> Result<String, Error> {
let private_key = utils::parse_secret_key(private_key_input)?;
let ring_pubkeys: Vec<k256::ProjectivePoint> = ring_pubkeys_input
.iter()
.map(|pubkey_str| utils::parse_public_key(pubkey_str))
.collect::<Result<_, _>>()?;
let binary_signature =
sag::sign_binary(message, &private_key, &ring_pubkeys, rand::rngs::OsRng)?;
let compact_sig = CompactSignature::Sag(binary_signature);
compact_sig
.serialize()
.map_err(|e| Error::Serialization(e.to_string()))
}
pub fn sign_compact_blsag(
message: &[u8],
private_key_input: &str,
ring_pubkeys_input: &[String],
) -> Result<String, Error> {
let private_key = utils::parse_secret_key(private_key_input)?;
let ring_pubkeys: Vec<k256::ProjectivePoint> = ring_pubkeys_input
.iter()
.map(|pubkey_str| utils::parse_public_key(pubkey_str))
.collect::<Result<_, _>>()?;
let (binary_sig, key_image) = blsag::sign_blsag_binary(message, &private_key, &ring_pubkeys)?;
let compact_sig = CompactSignature::Blsag(binary_sig, key_image);
compact_sig
.serialize()
.map_err(|e| Error::Serialization(e.to_string()))
}
pub fn verify_compact(
compact_signature: &str,
message: &[u8],
ring_pubkeys_input: &[String],
) -> Result<bool, Error> {
verify(compact_signature, message, ring_pubkeys_input)
}
#[cfg(test)]
mod bech32_tests {
use super::*; use nostr::prelude::{Keys, ToBech32};
#[test]
fn test_sign_verify_sag_with_bech32() -> Result<(), Error> {
let keypair1 = Keys::generate();
let keypair2 = Keys::generate();
let keypair3 = Keys::generate();
let nsec2 = keypair2
.secret_key()
.to_bech32()
.expect("SecretKey to_bech32 should not fail");
let npub1 = keypair1
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let npub2 = keypair2
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let npub3 = keypair3
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let ring = vec![npub1, npub2.clone(), npub3];
let message = b"test message sag bech32";
let signature = sign(message, &nsec2, &ring, SignatureVariant::Sag)?;
let is_valid = verify(&signature, message, &ring)?;
assert!(is_valid, "SAG signature should be valid with npub keys");
let ring_mixed = vec![
keypair1.public_key().to_hex(),
npub2,
keypair3.public_key().to_hex(),
];
let is_valid_mixed = verify(&signature, message, &ring_mixed)?;
assert!(
is_valid_mixed,
"SAG signature should be valid with mixed hex/npub keys"
);
Ok(())
}
#[test]
fn test_sign_verify_blsag_with_bech32() -> Result<(), Error> {
let keypair1 = Keys::generate();
let keypair2 = Keys::generate();
let nsec1 = keypair1
.secret_key()
.to_bech32()
.expect("SecretKey to_bech32 should not fail");
let npub1 = keypair1
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let npub2 = keypair2
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let ring = vec![npub1.clone(), npub2];
let message = b"test message blsag bech32";
let signature = sign(message, &nsec1, &ring, SignatureVariant::Blsag)?;
let is_valid = verify(&signature, message, &ring)?;
assert!(is_valid, "BLSAG signature should be valid with npub keys");
let ring_hex = vec![
keypair1.public_key().to_hex(),
keypair2.public_key().to_hex(),
];
let is_valid_hex = verify(&signature, message, &ring_hex)?;
assert!(
is_valid_hex,
"BLSAG signature should be valid when verifying with hex keys"
);
Ok(())
}
#[test]
fn test_sign_with_hex_verify_with_bech32() -> Result<(), Error> {
let keypair1 = Keys::generate();
let keypair2 = Keys::generate();
let sec1_hex = keypair1.secret_key().to_secret_hex();
let pub1_hex = keypair1.public_key().to_hex();
let pub2_hex = keypair2.public_key().to_hex();
let pub1_npub = keypair1
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let pub2_npub = keypair2
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let ring_hex = vec![pub1_hex, pub2_hex];
let ring_npub = vec![pub1_npub, pub2_npub];
let message = b"sign hex verify bech32";
let signature = sign(message, &sec1_hex, &ring_hex, SignatureVariant::Sag)?;
let is_valid = verify(&signature, message, &ring_npub)?;
assert!(
is_valid,
"Should be able to verify with npub keys a signature made with hex keys"
);
Ok(())
}
#[test]
fn test_invalid_nsec_input() -> Result<(), Error> {
let keypair1 = Keys::generate();
let keypair2 = Keys::generate();
let npub1 = keypair1
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let npub2 = keypair2
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let ring = vec![npub1, npub2];
let message = b"invalid nsec test";
let invalid_nsec = "nsec1invalid";
let result = sign(message, invalid_nsec, &ring, SignatureVariant::Sag);
assert!(
matches!(result, Err(Error::SecretKeyFormat(_))),
"Should fail with SecretKeyFormat error"
);
let invalid_hex_nsec = "deadbeef"; let result_hex = sign(message, invalid_hex_nsec, &ring, SignatureVariant::Sag);
assert!(result_hex.is_err(), "Should fail with invalid hex format");
Ok(())
}
#[test]
fn test_invalid_npub_input() -> Result<(), Error> {
let keypair1 = Keys::generate();
let keypair2 = Keys::generate();
let valid_npub1 = keypair1
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let valid_npub2 = keypair2
.public_key()
.to_bech32()
.expect("PublicKey to_bech32 is infallible");
let nsec1 = keypair1
.secret_key()
.to_bech32()
.expect("SecretKey to_bech32 should not fail");
let message = b"invalid npub test";
let invalid_npub = "npub1invalid";
let ring_invalid = vec![valid_npub1.clone(), invalid_npub.to_string()];
let result_sign = sign(message, &nsec1, &ring_invalid, SignatureVariant::Sag);
assert!(
matches!(result_sign, Err(Error::PublicKeyFormat(_))),
"Signing should fail with invalid npub"
);
let invalid_hex_npub = "deadbeef"; let ring_invalid_hex = vec![valid_npub2.clone(), invalid_hex_npub.to_string()];
let valid_ring = vec![valid_npub1.clone(), valid_npub2.clone()];
let valid_sig = sign(b"temp", &nsec1, &valid_ring, SignatureVariant::Sag)?;
let result_verify_bech32 = verify(&valid_sig, b"temp", &ring_invalid);
assert!(
matches!(result_verify_bech32, Err(Error::PublicKeyFormat(_))),
"Verify should fail with invalid npub"
);
let result_verify_hex = verify(&valid_sig, b"temp", &ring_invalid_hex);
assert!(
matches!(result_verify_hex, Err(Error::PublicKeyFormat(_))),
"Verify should fail with invalid hex as npub"
);
Ok(())
}
}