fedimint_core/
session_outcome.rs1use std::collections::BTreeMap;
2use std::io::Write as _;
3
4use bitcoin::hashes::{Hash, sha256};
5use parity_scale_codec::{Decode, Encode};
6use secp256k1::{Message, PublicKey, SECP256K1, schnorr};
7
8use crate::encoding::{Decodable, Encodable};
9use crate::epoch::ConsensusItem;
10use crate::{NumPeersExt as _, PeerId};
11
12#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable)]
19pub struct AcceptedItem {
20 pub item: ConsensusItem,
21 pub peer: PeerId,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable)]
35pub struct SessionOutcome {
36 pub items: Vec<AcceptedItem>,
37}
38
39impl SessionOutcome {
40 pub fn header(&self, index: u64) -> [u8; 40] {
46 let mut header = [0; 40];
47
48 header[..8].copy_from_slice(&index.to_be_bytes());
49
50 let leaf_hashes = self
51 .items
52 .iter()
53 .map(Encodable::consensus_hash::<sha256::Hash>);
54
55 if let Some(root) = bitcoin::merkle_tree::calculate_root(leaf_hashes) {
56 header[8..].copy_from_slice(&root.to_byte_array());
57 } else {
58 assert!(self.items.is_empty());
59 }
60
61 header
62 }
63}
64
65#[derive(Clone, Debug, Encodable, Decodable, Encode, Decode, PartialEq, Eq, Hash)]
66pub struct SchnorrSignature(pub [u8; 64]);
67
68#[derive(Clone, Debug, Encodable, Decodable, Eq, PartialEq)]
75pub struct SignedSessionOutcome {
76 pub session_outcome: SessionOutcome,
77 pub signatures: std::collections::BTreeMap<PeerId, SchnorrSignature>,
78}
79
80impl SignedSessionOutcome {
81 pub fn verify(
82 &self,
83 broadcast_public_keys: &BTreeMap<PeerId, PublicKey>,
84 block_index: u64,
85 ) -> bool {
86 let message = {
87 let 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 };
96
97 let threshold = broadcast_public_keys.to_num_peers().threshold();
98 if self.signatures.len() < threshold {
99 return false;
100 }
101
102 self.signatures.iter().all(|(peer_id, signature)| {
103 let Some(pub_key) = broadcast_public_keys.get(peer_id) else {
104 return false;
105 };
106 let Ok(signature) = schnorr::Signature::from_slice(&signature.0) else {
107 return false;
108 };
109 SECP256K1
110 .verify_schnorr(&signature, &message, &pub_key.x_only_public_key().0)
111 .is_ok()
112 })
113 }
114}
115
116#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
117pub enum SessionStatus {
118 Initial,
119 Pending(Vec<AcceptedItem>),
120 Complete(SessionOutcome),
121}
122
123#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
124pub enum SessionStatusV2 {
125 Initial,
126 Pending(Vec<AcceptedItem>),
127 Complete(SignedSessionOutcome),
128}
129
130impl From<SessionStatusV2> for SessionStatus {
131 fn from(value: SessionStatusV2) -> Self {
132 match value {
133 SessionStatusV2::Initial => Self::Initial,
134 SessionStatusV2::Pending(items) => Self::Pending(items),
135 SessionStatusV2::Complete(signed_session_outcome) => {
136 Self::Complete(signed_session_outcome.session_outcome)
137 }
138 }
139 }
140}