use getrandom::SysRng;
use p256::Scalar;
use p256::ecdsa::{Signature, SigningKey, signature::Signer};
use p256::elliptic_curve::{Field, PrimeField, rand_core::UnwrapErr};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlindedMessage {
pub blinded_hash_hex: String,
}
#[derive(Debug, Clone)]
pub struct BlindFactor {
pub t: Scalar,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RawSignature {
pub r_hex: String,
pub s_hex: String,
}
pub fn blind(message_hash: &[u8; 32]) -> (BlindedMessage, BlindFactor) {
let t = Scalar::random(&mut UnwrapErr(SysRng));
let e = bytes_to_scalar(message_hash);
let blinded = e * t;
let blinded_bytes: [u8; 32] = blinded.to_repr().into();
(
BlindedMessage {
blinded_hash_hex: hex::encode(blinded_bytes),
},
BlindFactor { t },
)
}
pub fn blind_sign(signing_key: &SigningKey, blinded: &BlindedMessage) -> RawSignature {
let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
let arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
let sig: Signature = signing_key.sign(&arr);
let (r, s) = sig.split_scalars();
let r_bytes: [u8; 32] = r.to_repr().into();
let s_bytes: [u8; 32] = s.to_repr().into();
RawSignature {
r_hex: hex::encode(r_bytes),
s_hex: hex::encode(s_bytes),
}
}
pub fn unblind(raw: &RawSignature, factor: &BlindFactor) -> Signature {
let r_bytes = hex::decode(&raw.r_hex).unwrap();
let s_bytes = hex::decode(&raw.s_hex).unwrap();
let r_arr: [u8; 32] = r_bytes.as_slice().try_into().unwrap();
let s_arr: [u8; 32] = s_bytes.as_slice().try_into().unwrap();
let r = Scalar::from_repr(r_arr.into()).unwrap_or(Scalar::ZERO);
let s = Scalar::from_repr(s_arr.into()).unwrap_or(Scalar::ZERO);
let t_inv = factor.t.invert().unwrap_or(Scalar::ZERO);
let s_unblinded = s * t_inv;
let r_bytes: [u8; 32] = r.to_repr().into();
let s_bytes: [u8; 32] = s_unblinded.to_repr().into();
Signature::from_scalars(r_bytes, s_bytes).unwrap()
}
fn bytes_to_scalar(bytes: &[u8; 32]) -> Scalar {
let fb = p256::FieldBytes::from(*bytes);
Option::<Scalar>::from(Scalar::from_repr(fb)).unwrap_or(Scalar::ZERO)
}
#[cfg(test)]
mod tests {
use super::*;
use p256::ecdsa::signature::Verifier;
use p256::elliptic_curve::Generate;
#[test]
fn blind_sign_produces_valid_sig_on_blinded_hash() {
let signing = SigningKey::generate();
let vk = signing.verifying_key();
let msg_hash = [0x42u8; 32];
let (blinded, _factor) = blind(&msg_hash);
let raw_sig = blind_sign(&signing, &blinded);
let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
let blinded_arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
let r_bytes = hex::decode(&raw_sig.r_hex).unwrap();
let s_bytes = hex::decode(&raw_sig.s_hex).unwrap();
let r_arr: [u8; 32] = r_bytes.as_slice().try_into().unwrap();
let s_arr: [u8; 32] = s_bytes.as_slice().try_into().unwrap();
let sig = Signature::from_scalars(r_arr, s_arr).unwrap();
assert!(vk.verify(&blinded_arr, &sig).is_ok());
}
#[test]
fn blinding_hides_message() {
let msg1 = [0x11u8; 32];
let msg2 = [0x22u8; 32];
let (b1, _) = blind(&msg1);
let (b2, _) = blind(&msg2);
assert_ne!(b1.blinded_hash_hex, b2.blinded_hash_hex);
}
#[test]
fn different_blind_factors_produce_different_blinds() {
let msg = [0x42u8; 32];
let (b1, _) = blind(&msg);
let (b2, _) = blind(&msg);
assert_ne!(b1.blinded_hash_hex, b2.blinded_hash_hex);
}
#[test]
fn signer_cannot_recover_message() {
let msg = [0x99u8; 32];
let (blinded, _) = blind(&msg);
let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
let blinded_arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
assert_ne!(blinded_arr, msg);
}
#[test]
fn multiple_messages_each_produce_valid_sigs() {
let signing = SigningKey::generate();
let vk = signing.verifying_key();
for i in 0u8..5 {
let msg_hash = [i; 32];
let (blinded, _factor) = blind(&msg_hash);
let raw = blind_sign(&signing, &blinded);
let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
let blinded_arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
let r_bytes = hex::decode(&raw.r_hex).unwrap();
let s_bytes = hex::decode(&raw.s_hex).unwrap();
let r_arr: [u8; 32] = r_bytes.as_slice().try_into().unwrap();
let s_arr: [u8; 32] = s_bytes.as_slice().try_into().unwrap();
let sig = Signature::from_scalars(r_arr, s_arr).unwrap();
assert!(vk.verify(&blinded_arr, &sig).is_ok(), "message {i}");
}
}
}