fedimint_core/
session_outcome.rs1use std::collections::BTreeMap;
2use std::io::Write as _;
3
4use bitcoin::hashes::{Hash, sha256};
5use secp256k1::{Message, PublicKey, SECP256K1};
6
7use crate::encoding::{Decodable, Encodable};
8use crate::epoch::ConsensusItem;
9use crate::{NumPeersExt as _, PeerId, secp256k1};
10
11#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable)]
18pub struct AcceptedItem {
19 pub item: ConsensusItem,
20 pub peer: PeerId,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Encodable, Decodable)]
34pub struct SessionOutcome {
35 pub items: Vec<AcceptedItem>,
36}
37
38impl SessionOutcome {
39 pub fn header(&self, index: u64) -> [u8; 40] {
45 let mut header = [0; 40];
46
47 header[..8].copy_from_slice(&index.to_be_bytes());
48
49 let leaf_hashes = self
50 .items
51 .iter()
52 .map(Encodable::consensus_hash::<sha256::Hash>);
53
54 if let Some(root) = bitcoin::merkle_tree::calculate_root(leaf_hashes) {
55 header[8..].copy_from_slice(&root.to_byte_array());
56 } else {
57 assert!(self.items.is_empty());
58 }
59
60 header
61 }
62}
63
64#[derive(Clone, Debug, Encodable, Decodable, Eq, PartialEq)]
71pub struct SignedSessionOutcome {
72 pub session_outcome: SessionOutcome,
73 pub signatures: std::collections::BTreeMap<PeerId, secp256k1::schnorr::Signature>,
74}
75
76impl SignedSessionOutcome {
77 pub fn verify(
78 &self,
79 broadcast_public_keys: &BTreeMap<PeerId, PublicKey>,
80 block_index: u64,
81 ) -> bool {
82 let message = {
83 let mut engine = sha256::HashEngine::default();
84 engine
85 .write_all(broadcast_public_keys.consensus_hash_sha256().as_ref())
86 .expect("Writing to a hash engine can not fail");
87 engine
88 .write_all(&self.session_outcome.header(block_index))
89 .expect("Writing to a hash engine can not fail");
90 Message::from_digest(sha256::Hash::from_engine(engine).to_byte_array())
91 };
92
93 let threshold = broadcast_public_keys.to_num_peers().threshold();
94 if self.signatures.len() < threshold {
95 return false;
96 }
97
98 self.signatures.iter().all(|(peer_id, signature)| {
99 let Some(pub_key) = broadcast_public_keys.get(peer_id) else {
100 return false;
101 };
102
103 SECP256K1
104 .verify_schnorr(signature, &message, &pub_key.x_only_public_key().0)
105 .is_ok()
106 })
107 }
108}
109
110#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
111pub enum SessionStatus {
112 Initial,
113 Pending(Vec<AcceptedItem>),
114 Complete(SessionOutcome),
115}
116
117#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
118pub enum SessionStatusV2 {
119 Initial,
120 Pending(Vec<AcceptedItem>),
121 Complete(SignedSessionOutcome),
122}
123
124impl From<SessionStatusV2> for SessionStatus {
125 fn from(value: SessionStatusV2) -> Self {
126 match value {
127 SessionStatusV2::Initial => Self::Initial,
128 SessionStatusV2::Pending(items) => Self::Pending(items),
129 SessionStatusV2::Complete(signed_session_outcome) => {
130 Self::Complete(signed_session_outcome.session_outcome)
131 }
132 }
133 }
134}