fedimint_server/consensus/aleph_bft/
keychain.rs1use std::collections::BTreeMap;
2use std::io::Write;
3
4use aleph_bft::Keychain as KeychainTrait;
5use bitcoin::hashes::Hash;
6use fedimint_core::encoding::Encodable;
7use fedimint_core::{NumPeersExt, PeerId, secp256k1};
8use secp256k1::hashes::sha256;
9use secp256k1::{Keypair, Message, PublicKey, schnorr};
10
11use crate::config::ServerConfig;
12
13#[derive(Clone, Debug)]
14pub struct Keychain {
15 identity: PeerId,
16 pks: BTreeMap<PeerId, PublicKey>,
17 message_tag: sha256::Hash,
18 keypair: Keypair,
19}
20
21impl Keychain {
22 pub fn new(cfg: &ServerConfig) -> Self {
23 Keychain {
24 identity: cfg.local.identity,
25 pks: cfg.consensus.broadcast_public_keys.clone(),
26 message_tag: cfg.consensus.broadcast_public_keys.consensus_hash(),
27 keypair: cfg
28 .private
29 .broadcast_secret_key
30 .keypair(secp256k1::SECP256K1),
31 }
32 }
33
34 fn tagged_message(&self, message: &[u8]) -> Message {
38 let mut engine = sha256::HashEngine::default();
39
40 engine
41 .write_all(self.message_tag.as_ref())
42 .expect("Writing to a hash engine can not fail");
43
44 engine
45 .write_all(message)
46 .expect("Writing to a hash engine can not fail");
47
48 let hash = sha256::Hash::from_engine(engine);
49
50 Message::from_digest(*hash.as_ref())
51 }
52
53 pub fn sign_schnorr(&self, message: &[u8]) -> schnorr::Signature {
54 self.keypair.sign_schnorr(self.tagged_message(message))
55 }
56
57 pub fn verify_schnorr(
58 &self,
59 message: &[u8],
60 signature: &schnorr::Signature,
61 peer_id: PeerId,
62 ) -> bool {
63 match self.pks.get(&peer_id) {
64 Some(public_key) => secp256k1::SECP256K1
65 .verify_schnorr(
66 signature,
67 &self.tagged_message(message),
68 &public_key.x_only_public_key().0,
69 )
70 .is_ok(),
71 None => false,
72 }
73 }
74}
75
76impl aleph_bft::Index for Keychain {
77 fn index(&self) -> aleph_bft::NodeIndex {
78 self.identity.to_usize().into()
79 }
80}
81
82#[async_trait::async_trait]
83impl aleph_bft::Keychain for Keychain {
84 type Signature = [u8; 64];
85
86 fn node_count(&self) -> aleph_bft::NodeCount {
87 self.pks.len().into()
88 }
89
90 fn sign(&self, message: &[u8]) -> Self::Signature {
91 self.sign_schnorr(message).serialize()
92 }
93
94 fn verify(
95 &self,
96 message: &[u8],
97 signature: &Self::Signature,
98 node_index: aleph_bft::NodeIndex,
99 ) -> bool {
100 let Some(peer_id) = super::to_peer_id(node_index) else {
104 return false;
105 };
106
107 match schnorr::Signature::from_slice(signature) {
108 Ok(sig) => self.verify_schnorr(message, &sig, peer_id),
109 Err(_) => false,
110 }
111 }
112}
113
114impl aleph_bft::MultiKeychain for Keychain {
115 type PartialMultisignature = aleph_bft::NodeMap<[u8; 64]>;
116
117 fn bootstrap_multi(
118 &self,
119 signature: &Self::Signature,
120 index: aleph_bft::NodeIndex,
121 ) -> Self::PartialMultisignature {
122 let mut partial = aleph_bft::NodeMap::with_size(self.pks.len().into());
123
124 partial.insert(index, *signature);
125
126 partial
127 }
128
129 fn is_complete(&self, msg: &[u8], partial: &Self::PartialMultisignature) -> bool {
130 if partial.iter().count() < self.pks.to_num_peers().threshold() {
131 return false;
132 }
133
134 partial.iter().all(|(i, sgn)| self.verify(msg, sgn, i))
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use std::collections::BTreeMap;
141
142 use aleph_bft::{Keychain as _, NodeIndex};
143 use fedimint_core::encoding::Encodable;
144 use fedimint_core::{PeerId, secp256k1};
145 use secp256k1::{Keypair, SecretKey};
146
147 use super::Keychain;
148
149 fn keychain() -> Keychain {
150 let keypair = Keypair::from_secret_key(
151 secp256k1::SECP256K1,
152 &SecretKey::from_slice(&[1; 32]).expect("Valid secret key"),
153 );
154
155 let pks = BTreeMap::from([(PeerId::from(0), keypair.public_key())]);
156
157 Keychain {
158 identity: PeerId::from(0),
159 message_tag: pks.consensus_hash(),
160 pks,
161 keypair,
162 }
163 }
164
165 #[test]
166 fn verify_rejects_out_of_range_node_index() {
167 let keychain = keychain();
168 let signature = keychain.sign(b"message");
169
170 assert!(keychain.verify(b"message", &signature, NodeIndex(0)));
171
172 assert!(!keychain.verify(b"message", &signature, NodeIndex(usize::from(u16::MAX) + 1)));
176 assert!(!keychain.verify(b"message", &signature, NodeIndex(usize::MAX)));
177 }
178}