confium_privacy/
blind_ecdsa.rs1use getrandom::SysRng;
10use p256::FieldBytes;
11use p256::Scalar;
12use p256::ecdsa::{Signature, SigningKey, signature::Signer};
13use p256::elliptic_curve::{Field, PrimeField, rand_core::UnwrapErr};
14use serde::{Deserialize, Serialize};
15use sha2::{Digest as _, Sha256};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct BlindedMessage {
20 pub blinded_hash_hex: String,
22}
23
24#[derive(Debug, Clone)]
26pub struct BlindFactor {
27 pub t: Scalar,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct RawSignature {
34 pub r_hex: String,
35 pub s_hex: String,
36}
37
38pub fn blind(message_hash: &[u8; 32]) -> (BlindedMessage, BlindFactor) {
40 let t = Scalar::random(&mut UnwrapErr(SysRng));
41 let e = bytes_to_scalar(message_hash);
42 let blinded = e * t;
44 let blinded_bytes: [u8; 32] = blinded.to_repr().into();
45 (
46 BlindedMessage {
47 blinded_hash_hex: hex::encode(blinded_bytes),
48 },
49 BlindFactor { t },
50 )
51}
52
53pub fn blind_sign(signing_key: &SigningKey, blinded: &BlindedMessage) -> RawSignature {
55 let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
56 let arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
57 let sig: Signature = signing_key.sign(&arr);
59 let (r, s) = sig.split_scalars();
60 let r_bytes: [u8; 32] = r.to_repr().into();
61 let s_bytes: [u8; 32] = s.to_repr().into();
62 RawSignature {
63 r_hex: hex::encode(r_bytes),
64 s_hex: hex::encode(s_bytes),
65 }
66}
67
68pub fn unblind(raw: &RawSignature, factor: &BlindFactor) -> Signature {
70 let r_bytes = hex::decode(&raw.r_hex).unwrap();
71 let s_bytes = hex::decode(&raw.s_hex).unwrap();
72 let r_arr: [u8; 32] = r_bytes.as_slice().try_into().unwrap();
73 let s_arr: [u8; 32] = s_bytes.as_slice().try_into().unwrap();
74 let r = reduce_to_scalar(r_arr);
75 let s = reduce_to_scalar(s_arr);
76 let t_inv = factor.t.invert().unwrap_or(Scalar::ZERO);
79 let s_unblinded = s * t_inv;
80 let r_bytes: [u8; 32] = r.to_repr().into();
81 let s_bytes: [u8; 32] = s_unblinded.to_repr().into();
82 Signature::from_scalars(r_bytes, s_bytes).unwrap()
83}
84
85fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
89 loop {
90 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
91 return s;
92 }
93 let mut h = Sha256::new();
94 h.update(b"confium-scalar-reduce-v1");
95 h.update(bytes);
96 bytes = h.finalize().into();
97 }
98}
99
100fn bytes_to_scalar(bytes: &[u8; 32]) -> Scalar {
101 reduce_to_scalar(*bytes)
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use p256::ecdsa::signature::Verifier;
108 use p256::elliptic_curve::Generate;
109
110 #[test]
111 fn blind_sign_produces_valid_sig_on_blinded_hash() {
112 let signing = SigningKey::generate();
113 let vk = signing.verifying_key();
114
115 let msg_hash = [0x42u8; 32];
116 let (blinded, _factor) = blind(&msg_hash);
117 let raw_sig = blind_sign(&signing, &blinded);
118
119 let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
121 let blinded_arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
122 let r_bytes = hex::decode(&raw_sig.r_hex).unwrap();
123 let s_bytes = hex::decode(&raw_sig.s_hex).unwrap();
124 let r_arr: [u8; 32] = r_bytes.as_slice().try_into().unwrap();
125 let s_arr: [u8; 32] = s_bytes.as_slice().try_into().unwrap();
126 let sig = Signature::from_scalars(r_arr, s_arr).unwrap();
127 assert!(vk.verify(&blinded_arr, &sig).is_ok());
128 }
129
130 #[test]
131 fn blinding_hides_message() {
132 let msg1 = [0x11u8; 32];
133 let msg2 = [0x22u8; 32];
134 let (b1, _) = blind(&msg1);
135 let (b2, _) = blind(&msg2);
136 assert_ne!(b1.blinded_hash_hex, b2.blinded_hash_hex);
137 }
138
139 #[test]
140 fn different_blind_factors_produce_different_blinds() {
141 let msg = [0x42u8; 32];
142 let (b1, _) = blind(&msg);
143 let (b2, _) = blind(&msg);
144 assert_ne!(b1.blinded_hash_hex, b2.blinded_hash_hex);
145 }
146
147 #[test]
148 fn signer_cannot_recover_message() {
149 let msg = [0x99u8; 32];
150 let (blinded, _) = blind(&msg);
151 let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
152 let blinded_arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
153 assert_ne!(blinded_arr, msg);
154 }
155
156 #[test]
157 fn multiple_messages_each_produce_valid_sigs() {
158 let signing = SigningKey::generate();
159 let vk = signing.verifying_key();
160
161 for i in 0u8..5 {
162 let msg_hash = [i; 32];
163 let (blinded, _factor) = blind(&msg_hash);
164 let raw = blind_sign(&signing, &blinded);
165
166 let blinded_bytes = hex::decode(&blinded.blinded_hash_hex).unwrap();
168 let blinded_arr: [u8; 32] = blinded_bytes.as_slice().try_into().unwrap();
169 let r_bytes = hex::decode(&raw.r_hex).unwrap();
170 let s_bytes = hex::decode(&raw.s_hex).unwrap();
171 let r_arr: [u8; 32] = r_bytes.as_slice().try_into().unwrap();
172 let s_arr: [u8; 32] = s_bytes.as_slice().try_into().unwrap();
173 let sig = Signature::from_scalars(r_arr, s_arr).unwrap();
174 assert!(vk.verify(&blinded_arr, &sig).is_ok(), "message {i}");
175 }
176 }
177}