1use std::collections::BTreeMap;
2use std::io::Write as _;
34use bitcoin::hashes::{Hash, sha256};
5use parity_scale_codec::{Decode, Encode};
6use secp256k1::{Message, PublicKey, SECP256K1, schnorr};
78use crate::encoding::{Decodable, Encodable};
9use crate::epoch::ConsensusItem;
10use crate::{NumPeersExt as _, PeerId};
1112/// A consensus item accepted in the consensus
13///
14/// If two correct nodes obtain two ordered items from the broadcast they
15/// are guaranteed to be in the same order. However, an ordered items is
16/// only guaranteed to be seen by all correct nodes if a correct node decides to
17/// accept it.
18#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable)]
19pub struct AcceptedItem {
20pub item: ConsensusItem,
21pub peer: PeerId,
22}
2324/// Items ordered in a single session that have been accepted by Fedimint
25/// consensus.
26///
27/// A running Federation produces a [`SessionOutcome`] every couple of minutes.
28/// Therefore, just like in Bitcoin, a [`SessionOutcome`] might be empty if no
29/// items are ordered in that time or all ordered items are discarded by
30/// Fedimint Consensus.
31///
32/// When session is closed it is signed over by the peers and produces a
33/// [`SignedSessionOutcome`].
34#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable)]
35pub struct SessionOutcome {
36pub items: Vec<AcceptedItem>,
37}
3839impl SessionOutcome {
40/// A blocks header consists of 40 bytes formed by its index in big endian
41 /// bytes concatenated with the merkle root build from the consensus
42 /// hashes of its [`AcceptedItem`]s or 32 zero bytes if the block is
43 /// empty. The use of a merkle tree allows for efficient inclusion
44 /// proofs of accepted consensus items for clients.
45pub fn header(&self, index: u64) -> [u8; 40] {
46let mut header = [0; 40];
4748 header[..8].copy_from_slice(&index.to_be_bytes());
4950let leaf_hashes = self
51.items
52 .iter()
53 .map(Encodable::consensus_hash::<sha256::Hash>);
5455if let Some(root) = bitcoin::merkle_tree::calculate_root(leaf_hashes) {
56 header[8..].copy_from_slice(&root.to_byte_array());
57 } else {
58assert!(self.items.is_empty());
59 }
6061 header
62 }
63}
6465#[derive(Clone, Debug, Encodable, Decodable, Encode, Decode, PartialEq, Eq, Hash)]
66pub struct SchnorrSignature(pub [u8; 64]);
6768/// A [`SessionOutcome`], signed by the Federation.
69///
70/// A signed block combines a block with the naive threshold secp schnorr
71/// signature for its header created by the federation. The signed blocks allow
72/// clients and recovering guardians to verify the federations consensus
73/// history. After a signed block has been created it is stored in the database.
74#[derive(Clone, Debug, Encodable, Decodable, Eq, PartialEq)]
75pub struct SignedSessionOutcome {
76pub session_outcome: SessionOutcome,
77pub signatures: std::collections::BTreeMap<PeerId, SchnorrSignature>,
78}
7980impl SignedSessionOutcome {
81pub fn verify(
82&self,
83 broadcast_public_keys: &BTreeMap<PeerId, PublicKey>,
84 block_index: u64,
85 ) -> bool {
86let message = {
87let mut engine = sha256::HashEngine::default();
88 engine
89 .write_all(broadcast_public_keys.consensus_hash_sha256().as_ref())
90 .expect("Writing to a hash engine can not fail");
91 engine
92 .write_all(&self.session_outcome.header(block_index))
93 .expect("Writing to a hash engine can not fail");
94 Message::from_digest(sha256::Hash::from_engine(engine).to_byte_array())
95 };
9697let threshold = broadcast_public_keys.to_num_peers().threshold();
98if self.signatures.len() < threshold {
99return false;
100 }
101102self.signatures.iter().all(|(peer_id, signature)| {
103let Some(pub_key) = broadcast_public_keys.get(peer_id) else {
104return false;
105 };
106let Ok(signature) = schnorr::Signature::from_slice(&signature.0) else {
107return false;
108 };
109 SECP256K1
110 .verify_schnorr(&signature, &message, &pub_key.x_only_public_key().0)
111 .is_ok()
112 })
113 }
114}
115116#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
117pub enum SessionStatus {
118 Initial,
119 Pending(Vec<AcceptedItem>),
120 Complete(SessionOutcome),
121}
122123#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
124pub enum SessionStatusV2 {
125 Initial,
126 Pending(Vec<AcceptedItem>),
127 Complete(SignedSessionOutcome),
128}
129130impl From<SessionStatusV2> for SessionStatus {
131fn from(value: SessionStatusV2) -> Self {
132match value {
133 SessionStatusV2::Initial => Self::Initial,
134 SessionStatusV2::Pending(items) => Self::Pending(items),
135 SessionStatusV2::Complete(signed_session_outcome) => {
136Self::Complete(signed_session_outcome.session_outcome)
137 }
138 }
139 }
140}