Skip to main content

tbs/
lib.rs

1//! # Threshold Blind Signatures
2//!
3//! This library implements an ad-hoc threshold blind signature scheme based on
4//! BLS signatures using the (unrelated) BLS12-381 curve.
5
6use std::collections::BTreeMap;
7use std::io::Write;
8
9use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar, pairing};
10use fedimint_core::bitcoin::hashes::sha256;
11use fedimint_core::encoding::{Decodable, Encodable};
12use fedimint_core::{BitcoinHash, bls12_381_serde};
13use group::ff::Field;
14use group::{Curve, Group};
15use hex::encode;
16use rand::SeedableRng;
17use rand::rngs::OsRng;
18use rand_chacha::ChaChaRng;
19use serde::{Deserialize, Serialize};
20use sha3::Digest;
21
22const HASH_TAG: &[u8] = b"TBS_BLS12-381_";
23const FINGERPRINT_TAG: &[u8] = b"TBS_KFP24_";
24
25fn hash_bytes_to_g1(data: &[u8]) -> G1Projective {
26    let mut hash_engine = sha3::Sha3_256::new();
27
28    hash_engine.update(HASH_TAG);
29    hash_engine.update(data);
30
31    let mut prng = ChaChaRng::from_seed(hash_engine.finalize().into());
32
33    G1Projective::random(&mut prng)
34}
35
36#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
37pub struct SecretKeyShare(#[serde(with = "bls12_381_serde::scalar")] pub Scalar);
38
39#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
40pub struct PublicKeyShare(#[serde(with = "bls12_381_serde::g2")] pub G2Affine);
41
42#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
43pub struct AggregatePublicKey(#[serde(with = "bls12_381_serde::g2")] pub G2Affine);
44
45#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
46pub struct Message(#[serde(with = "bls12_381_serde::g1")] pub G1Affine);
47
48#[derive(Copy, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
49pub struct BlindingKey(#[serde(with = "bls12_381_serde::scalar")] pub Scalar);
50
51#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
52pub struct BlindedMessage(#[serde(with = "bls12_381_serde::g1")] pub G1Affine);
53
54#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
55pub struct BlindedSignatureShare(#[serde(with = "bls12_381_serde::g1")] pub G1Affine);
56
57#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
58pub struct BlindedSignature(#[serde(with = "bls12_381_serde::g1")] pub G1Affine);
59
60#[derive(Copy, Clone, Debug, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
61pub struct Signature(#[serde(with = "bls12_381_serde::g1")] pub G1Affine);
62
63macro_rules! point_hash_impl {
64    ($type:ty) => {
65        impl std::hash::Hash for $type {
66            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
67                self.0.to_compressed().hash(state);
68            }
69        }
70    };
71}
72
73point_hash_impl!(PublicKeyShare);
74point_hash_impl!(AggregatePublicKey);
75point_hash_impl!(Message);
76point_hash_impl!(BlindedMessage);
77point_hash_impl!(BlindedSignatureShare);
78point_hash_impl!(BlindedSignature);
79point_hash_impl!(Signature);
80
81pub fn derive_pk_share(sk: &SecretKeyShare) -> PublicKeyShare {
82    PublicKeyShare((G2Projective::generator() * sk.0).to_affine())
83}
84
85impl std::hash::Hash for BlindingKey {
86    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
87        self.0.to_bytes().hash(state);
88    }
89}
90
91impl BlindingKey {
92    pub fn random() -> BlindingKey {
93        // TODO: fix rand incompatibities
94        BlindingKey(Scalar::random(OsRng))
95    }
96
97    fn fingerprint(&self) -> [u8; 32] {
98        let mut hash_engine = sha3::Sha3_256::new();
99        hash_engine.update(FINGERPRINT_TAG);
100        hash_engine.update(self.0.to_bytes());
101        let result = hash_engine.finalize();
102        result.into()
103    }
104}
105
106impl ::core::fmt::Debug for BlindingKey {
107    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
108        let fingerprint = self.fingerprint();
109        let fingerprint_hex = encode(&fingerprint[..]);
110        write!(f, "BlindingKey({fingerprint_hex})")
111    }
112}
113
114impl ::core::fmt::Display for BlindingKey {
115    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
116        let fingerprint = self.fingerprint();
117        let fingerprint_hex = encode(&fingerprint[..]);
118        write!(f, "{fingerprint_hex}")
119    }
120}
121
122impl Message {
123    pub fn from_bytes(msg: &[u8]) -> Message {
124        Message(hash_bytes_to_g1(msg).to_affine())
125    }
126
127    /// Creates a [`Message`] by hashing the input bytes with SHA-256 using
128    /// the domain separator `FEDIMINT_TBS_BLS12_381_MESSAGE`, then mapping
129    /// the hash to a BLS12-381 G1 curve point via a seeded [`ChaChaRng`].
130    pub fn from_bytes_sha256(bytes: &[u8]) -> Message {
131        let mut engine = sha256::HashEngine::default();
132
133        engine
134            .write_all("FEDIMINT_TBS_BLS12_381_MESSAGE".as_bytes())
135            .expect("Writing to a hash engine cannot fail");
136
137        engine
138            .write_all(bytes)
139            .expect("Writing to a hash engine cannot fail");
140
141        let seed = sha256::Hash::from_engine(engine).to_byte_array();
142
143        Message(G1Projective::random(&mut ChaChaRng::from_seed(seed)).to_affine())
144    }
145}
146
147pub fn blind_message(msg: Message, blinding_key: BlindingKey) -> BlindedMessage {
148    let blinded_msg = msg.0 * blinding_key.0;
149
150    BlindedMessage(blinded_msg.to_affine())
151}
152
153pub fn sign_message(msg: BlindedMessage, sks: SecretKeyShare) -> BlindedSignatureShare {
154    let sig = msg.0 * sks.0;
155    BlindedSignatureShare(sig.to_affine())
156}
157
158pub fn verify_signature_share(
159    msg: BlindedMessage,
160    sig: BlindedSignatureShare,
161    pk: PublicKeyShare,
162) -> bool {
163    pairing(&msg.0, &pk.0) == pairing(&sig.0, &G2Affine::generator())
164}
165
166/// Combines the exact threshold of valid blinded signature shares to a blinded
167/// signature. The responsibility of verifying the shares and supplying
168/// exactly the necessary threshold of shares lies with the caller.
169/// # Panics
170/// If shares is empty
171pub fn aggregate_signature_shares(
172    shares: &BTreeMap<u64, BlindedSignatureShare>,
173) -> BlindedSignature {
174    // this is a special case for one-of-one federations
175    if shares.len() == 1 {
176        return BlindedSignature(
177            shares
178                .values()
179                .next()
180                .expect("We have at least one value")
181                .0,
182        );
183    }
184
185    BlindedSignature(
186        lagrange_multipliers(
187            shares
188                .keys()
189                .cloned()
190                .map(|peer| Scalar::from(peer + 1))
191                .collect(),
192        )
193        .into_iter()
194        .zip(shares.values())
195        .map(|(lagrange_multiplier, share)| lagrange_multiplier * share.0)
196        .reduce(|a, b| a + b)
197        .expect("We have at least one share")
198        .to_affine(),
199    )
200}
201
202// TODO: aggregating public key shares is hacky since we can obtain the
203// aggregated public by evaluating the dkg polynomial at zero - this function
204// should be removed, however it is currently needed in the mint module to
205// until we add the aggregated public key to the mint config.
206pub fn aggregate_public_key_shares(shares: &BTreeMap<u64, PublicKeyShare>) -> AggregatePublicKey {
207    // this is a special case for one-of-one federations
208    if shares.len() == 1 {
209        return AggregatePublicKey(
210            shares
211                .values()
212                .next()
213                .expect("We have at least one value")
214                .0,
215        );
216    }
217
218    AggregatePublicKey(
219        lagrange_multipliers(
220            shares
221                .keys()
222                .cloned()
223                .map(|peer| Scalar::from(peer + 1))
224                .collect(),
225        )
226        .into_iter()
227        .zip(shares.values())
228        .map(|(lagrange_multiplier, share)| lagrange_multiplier * share.0)
229        .reduce(|a, b| a + b)
230        .expect("We have at least one share")
231        .to_affine(),
232    )
233}
234
235fn lagrange_multipliers(scalars: Vec<Scalar>) -> Vec<Scalar> {
236    scalars
237        .iter()
238        .map(|i| {
239            scalars
240                .iter()
241                .filter(|j| *j != i)
242                .map(|j| j * (j - i).invert().expect("We filtered the case j == i"))
243                .reduce(|a, b| a * b)
244                .expect("We have at least one share")
245        })
246        .collect()
247}
248
249pub fn verify_blinded_signature(
250    msg: BlindedMessage,
251    sig: BlindedSignature,
252    pk: AggregatePublicKey,
253) -> bool {
254    pairing(&msg.0, &pk.0) == pairing(&sig.0, &G2Affine::generator())
255}
256
257pub fn unblind_signature(blinding_key: BlindingKey, blinded_sig: BlindedSignature) -> Signature {
258    let sig = blinded_sig.0 * blinding_key.0.invert().unwrap();
259    Signature(sig.to_affine())
260}
261
262pub fn verify(msg: Message, sig: Signature, pk: AggregatePublicKey) -> bool {
263    pairing(&msg.0, &pk.0) == pairing(&sig.0, &G2Affine::generator())
264}
265
266#[cfg(test)]
267mod tests;