use blake3;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::RngExt;
use thiserror::Error;
use zeroize::Zeroize;
#[derive(Debug, Error)]
pub enum BlindError {
#[error("Invalid commitment")]
InvalidCommitment,
#[error("Invalid token signature")]
InvalidSignature,
#[error("Signature verification failed")]
VerificationFailed,
#[error("Invalid public key")]
InvalidPublicKey,
#[error("Token already spent")]
TokenAlreadySpent,
}
pub type BlindResult<T> = Result<T, BlindError>;
#[derive(Clone, Debug)]
pub struct UnlinkableToken {
pub serial: [u8; 32],
pub value: u64,
pub expiry: u64,
}
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct BlindingFactor {
factor: [u8; 32],
}
#[derive(Clone, Debug)]
pub struct TokenCommitment {
commitment: [u8; 32],
}
#[derive(Clone, Debug)]
pub struct SignedCommitment {
commitment: [u8; 32],
signature: [u8; 64],
}
#[derive(Clone, Debug)]
pub struct RedeemableToken {
pub token: UnlinkableToken,
pub blinding_factor: [u8; 32],
pub signature: [u8; 64],
}
impl UnlinkableToken {
pub fn new(value: u64, expiry: u64) -> Self {
let mut rng = rand::rng();
let mut serial = [0u8; 32];
rng.fill(&mut serial);
Self {
serial,
value,
expiry,
}
}
pub fn with_serial(serial: [u8; 32], value: u64, expiry: u64) -> Self {
Self {
serial,
value,
expiry,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.serial);
bytes.extend_from_slice(&self.value.to_le_bytes());
bytes.extend_from_slice(&self.expiry.to_le_bytes());
bytes
}
}
impl BlindingFactor {
pub fn generate() -> Self {
let mut rng = rand::rng();
let mut factor = [0u8; 32];
rng.fill(&mut factor);
Self { factor }
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self { factor: bytes }
}
pub fn to_bytes(&self) -> [u8; 32] {
self.factor
}
pub fn commit(&self, token: &UnlinkableToken) -> TokenCommitment {
let mut hasher = blake3::Hasher::new();
hasher.update(b"TOKEN_COMMITMENT:");
hasher.update(&token.to_bytes());
hasher.update(&self.factor);
TokenCommitment {
commitment: *hasher.finalize().as_bytes(),
}
}
pub fn verify_commitment(&self, token: &UnlinkableToken, commitment: &TokenCommitment) -> bool {
let recomputed = self.commit(token);
recomputed.commitment == commitment.commitment
}
}
impl TokenCommitment {
pub fn as_bytes(&self) -> &[u8; 32] {
&self.commitment
}
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self { commitment: bytes }
}
}
pub struct BlindSigner {
signing_key: SigningKey,
}
impl BlindSigner {
pub fn generate() -> Self {
use rand_core06::OsRng;
Self {
signing_key: SigningKey::generate(&mut OsRng),
}
}
pub fn from_signing_key(signing_key: SigningKey) -> Self {
Self { signing_key }
}
pub fn from_bytes(bytes: &[u8; 32]) -> Self {
Self {
signing_key: SigningKey::from_bytes(bytes),
}
}
pub fn public_key(&self) -> BlindPublicKey {
BlindPublicKey {
verifying_key: self.signing_key.verifying_key(),
}
}
pub fn sign_commitment(&self, commitment: &TokenCommitment) -> SignedCommitment {
let signature = self.signing_key.sign(&commitment.commitment);
SignedCommitment {
commitment: commitment.commitment,
signature: signature.to_bytes(),
}
}
pub fn to_bytes(&self) -> [u8; 32] {
self.signing_key.to_bytes()
}
}
#[derive(Clone, Debug)]
pub struct BlindPublicKey {
verifying_key: VerifyingKey,
}
impl BlindPublicKey {
pub fn from_bytes(bytes: &[u8; 32]) -> BlindResult<Self> {
let verifying_key =
VerifyingKey::from_bytes(bytes).map_err(|_| BlindError::InvalidPublicKey)?;
Ok(Self { verifying_key })
}
pub fn to_bytes(&self) -> [u8; 32] {
self.verifying_key.to_bytes()
}
pub fn verify_token(&self, redeemable: &RedeemableToken) -> BlindResult<()> {
let blinding = BlindingFactor::from_bytes(redeemable.blinding_factor);
let commitment = blinding.commit(&redeemable.token);
let signature = Signature::from_bytes(&redeemable.signature);
self.verifying_key
.verify(&commitment.commitment, &signature)
.map_err(|_| BlindError::VerificationFailed)?;
Ok(())
}
pub fn verify_commitment(&self, signed: &SignedCommitment) -> BlindResult<()> {
let signature = Signature::from_bytes(&signed.signature);
self.verifying_key
.verify(&signed.commitment, &signature)
.map_err(|_| BlindError::VerificationFailed)?;
Ok(())
}
}
pub struct BlindSignatureProtocol;
impl BlindSignatureProtocol {
pub fn create_token(
value: u64,
expiry: u64,
) -> (TokenCommitment, UnlinkableToken, BlindingFactor) {
let token = UnlinkableToken::new(value, expiry);
let blinding = BlindingFactor::generate();
let commitment = blinding.commit(&token);
(commitment, token, blinding)
}
pub fn issue_token(issuer: &BlindSigner, commitment: &TokenCommitment) -> SignedCommitment {
issuer.sign_commitment(commitment)
}
pub fn prepare_redemption(
token: UnlinkableToken,
blinding: BlindingFactor,
signed: SignedCommitment,
) -> RedeemableToken {
RedeemableToken {
token,
blinding_factor: blinding.to_bytes(),
signature: signed.signature,
}
}
pub fn verify_and_redeem(
public_key: &BlindPublicKey,
redeemable: &RedeemableToken,
current_time: u64,
) -> BlindResult<()> {
if current_time > redeemable.token.expiry {
return Err(BlindError::VerificationFailed);
}
public_key.verify_token(redeemable)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unlinkable_token_flow() {
let issuer = BlindSigner::generate();
let public_key = issuer.public_key();
let (commitment, token, blinding) = BlindSignatureProtocol::create_token(100, u64::MAX);
let signed = BlindSignatureProtocol::issue_token(&issuer, &commitment);
let redeemable = BlindSignatureProtocol::prepare_redemption(token, blinding, signed);
BlindSignatureProtocol::verify_and_redeem(&public_key, &redeemable, 0).unwrap();
}
#[test]
fn test_commitment_verification() {
let token = UnlinkableToken::new(50, u64::MAX);
let blinding = BlindingFactor::generate();
let commitment = blinding.commit(&token);
assert!(blinding.verify_commitment(&token, &commitment));
let wrong_blinding = BlindingFactor::generate();
assert!(!wrong_blinding.verify_commitment(&token, &commitment));
}
#[test]
fn test_different_tokens() {
let issuer = BlindSigner::generate();
let public_key = issuer.public_key();
let (comm1, tok1, blind1) = BlindSignatureProtocol::create_token(100, u64::MAX);
let (comm2, tok2, blind2) = BlindSignatureProtocol::create_token(200, u64::MAX);
assert_ne!(tok1.serial, tok2.serial);
assert_ne!(comm1.as_bytes(), comm2.as_bytes());
let signed1 = issuer.sign_commitment(&comm1);
let signed2 = issuer.sign_commitment(&comm2);
let redeem1 = BlindSignatureProtocol::prepare_redemption(tok1, blind1, signed1);
let redeem2 = BlindSignatureProtocol::prepare_redemption(tok2, blind2, signed2);
public_key.verify_token(&redeem1).unwrap();
public_key.verify_token(&redeem2).unwrap();
}
#[test]
fn test_wrong_blinding() {
let issuer = BlindSigner::generate();
let public_key = issuer.public_key();
let (commitment, token, _correct_blinding) =
BlindSignatureProtocol::create_token(100, u64::MAX);
let signed = issuer.sign_commitment(&commitment);
let wrong_blinding = BlindingFactor::generate();
let wrong_redeemable = RedeemableToken {
token,
blinding_factor: wrong_blinding.to_bytes(),
signature: signed.signature,
};
assert!(public_key.verify_token(&wrong_redeemable).is_err());
}
#[test]
fn test_expired_token() {
let issuer = BlindSigner::generate();
let public_key = issuer.public_key();
let (commitment, token, blinding) = BlindSignatureProtocol::create_token(100, 1000);
let signed = issuer.sign_commitment(&commitment);
let redeemable = BlindSignatureProtocol::prepare_redemption(token, blinding, signed);
BlindSignatureProtocol::verify_and_redeem(&public_key, &redeemable, 999).unwrap();
assert!(BlindSignatureProtocol::verify_and_redeem(&public_key, &redeemable, 1001).is_err());
}
#[test]
fn test_unlinkability() {
let issuer = BlindSigner::generate();
let (comm1, tok1, _) = BlindSignatureProtocol::create_token(100, u64::MAX);
let (comm2, tok2, _) = BlindSignatureProtocol::create_token(100, u64::MAX);
assert_ne!(comm1.as_bytes(), comm2.as_bytes());
assert_ne!(tok1.serial, tok2.serial);
let sig1 = issuer.sign_commitment(&comm1);
let sig2 = issuer.sign_commitment(&comm2);
assert_ne!(sig1.signature, sig2.signature);
}
#[test]
fn test_serialization() {
let token = UnlinkableToken::new(123, 456);
let bytes = token.to_bytes();
assert_eq!(bytes.len(), 32 + 8 + 8);
assert_eq!(&bytes[..32], &token.serial);
}
#[test]
fn test_key_serialization() {
let issuer = BlindSigner::generate();
let public_key = issuer.public_key();
let issuer_bytes = issuer.to_bytes();
let issuer2 = BlindSigner::from_bytes(&issuer_bytes);
let pk_bytes = public_key.to_bytes();
let pk2 = BlindPublicKey::from_bytes(&pk_bytes).unwrap();
let (commitment, token, blinding) = BlindSignatureProtocol::create_token(100, u64::MAX);
let signed = issuer2.sign_commitment(&commitment);
let redeemable = BlindSignatureProtocol::prepare_redemption(token, blinding, signed);
pk2.verify_token(&redeemable).unwrap();
}
#[test]
fn test_blinding_factor_zeroize() {
let factor = BlindingFactor::generate();
let _bytes = factor.to_bytes();
drop(factor);
}
}