Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 92 additions & 7 deletions keep-desktop/src/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,16 @@ pub(crate) struct FrostChannels {
}

fn push_frost_event(queue: &Mutex<VecDeque<FrostNodeMsg>>, event: FrostNodeMsg) {
if let Ok(mut q) = queue.lock() {
if q.len() >= MAX_FROST_EVENT_QUEUE {
q.pop_front();
match queue.lock() {
Ok(mut q) => {
if q.len() >= MAX_FROST_EVENT_QUEUE {
q.pop_front();
}
q.push_back(event);
}
Err(e) => {
tracing::warn!("frost event queue mutex poisoned, dropping event: {e}");
}
q.push_back(event);
}
}

Expand Down Expand Up @@ -512,6 +517,22 @@ pub(crate) async fn frost_event_listener(
},
);
}
Ok(KfpNodeEvent::DescriptorAcked {
session_id,
share_index,
ack_count,
expected_acks,
}) => {
push_frost_event(
&frost_events,
FrostNodeMsg::DescriptorAcked {
session_id,
share_index,
ack_count,
expected_acks,
},
);
}
Ok(KfpNodeEvent::DescriptorNacked {
session_id,
share_index,
Expand All @@ -532,6 +553,18 @@ pub(crate) async fn frost_event_listener(
FrostNodeMsg::DescriptorFailed { session_id, error },
);
}
Ok(KfpNodeEvent::XpubAnnounced {
share_index,
recovery_xpubs,
}) => {
push_frost_event(
&frost_events,
FrostNodeMsg::XpubAnnounced {
share_index,
recovery_xpubs,
},
);
}
Ok(_) => {}
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
Expand Down Expand Up @@ -623,9 +656,7 @@ impl App {
) {
if let Screen::Wallet(ws) = &mut self.screen {
if let Some(setup) = &mut ws.setup {
let matches =
setup.session_id.as_ref() == Some(session_id) || setup.session_id.is_none();
if matches {
if setup.session_id.as_ref() == Some(session_id) {
f(setup);
}
}
Expand Down Expand Up @@ -677,6 +708,31 @@ impl App {
self.update_wallet_setup(&session_id, |setup| {
setup.phase = SetupPhase::Coordinating(DescriptorProgress::Finalizing);
});
if self.active_coordinations.contains_key(&session_id) {
let Some(node) = self.get_frost_node() else {
return iced::Task::none();
};
return iced::Task::perform(
async move {
node.build_and_finalize_descriptor(session_id)
.await
.map_err(|e| format!("{e}"))
},
move |result| match result {
Ok(expected_acks) => Message::WalletDescriptorProgress(
DescriptorProgress::WaitingAcks {
received: 0,
expected: expected_acks,
},
Some(session_id),
),
Err(e) => Message::WalletDescriptorProgress(
DescriptorProgress::Failed(e),
Some(session_id),
),
},
);
}
}
FrostNodeMsg::DescriptorContributed { session_id, .. } => {
self.update_wallet_setup(&session_id, |setup| {
Expand All @@ -689,6 +745,19 @@ impl App {
}
});
}
FrostNodeMsg::DescriptorAcked {
session_id,
ack_count,
expected_acks,
..
} => {
self.update_wallet_setup(&session_id, |setup| {
setup.phase = SetupPhase::Coordinating(DescriptorProgress::WaitingAcks {
received: ack_count,
expected: expected_acks,
});
});
}
FrostNodeMsg::DescriptorComplete {
session_id,
external_descriptor,
Expand Down Expand Up @@ -717,6 +786,16 @@ impl App {
setup.phase = SetupPhase::Coordinating(DescriptorProgress::Failed(error));
});
}
FrostNodeMsg::XpubAnnounced {
share_index,
recovery_xpubs,
} => {
tracing::info!(
share_index,
count = recovery_xpubs.len(),
"Received recovery xpub announcement"
);
}
}
iced::Task::none()
}
Expand All @@ -736,6 +815,7 @@ impl App {
};

if !keep_frost_net::VALID_NETWORKS.contains(&network.as_str()) {
tracing::warn!(network = %network, "Ignoring descriptor contribution for invalid network");
return iced::Task::none();
}

Expand Down Expand Up @@ -856,6 +936,7 @@ impl App {
let _ = entry.response_tx.try_send(false);
}
}
self.active_coordinations.clear();
if let Some(s) = self.relay_screen_mut() {
s.status = ConnectionStatus::Disconnected;
s.peers.clear();
Expand All @@ -882,8 +963,12 @@ impl App {
let _ = tx.try_send(());
}
}
if let Ok(mut guard) = self.frost_node.lock() {
*guard = None;
}
self.frost_peers.clear();
self.pending_sign_display.clear();
self.active_coordinations.clear();
if let Ok(mut guard) = self.pending_sign_requests.lock() {
for entry in guard.drain(..) {
let _ = entry.response_tx.try_send(false);
Expand Down
30 changes: 30 additions & 0 deletions keep-desktop/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ pub enum FrostNodeMsg {
external_descriptor: String,
internal_descriptor: String,
},
DescriptorAcked {
session_id: [u8; 32],
share_index: u16,
ack_count: usize,
expected_acks: usize,
},
DescriptorNacked {
session_id: [u8; 32],
share_index: u16,
Expand All @@ -312,6 +318,10 @@ pub enum FrostNodeMsg {
session_id: [u8; 32],
error: String,
},
XpubAnnounced {
share_index: u16,
recovery_xpubs: Vec<keep_frost_net::AnnouncedXpub>,
},
}

impl fmt::Debug for FrostNodeMsg {
Expand Down Expand Up @@ -343,6 +353,18 @@ impl fmt::Debug for FrostNodeMsg {
.field("external_descriptor", &"***")
.field("internal_descriptor", &"***")
.finish(),
Self::DescriptorAcked {
session_id,
share_index,
ack_count,
expected_acks,
} => f
.debug_struct("DescriptorAcked")
.field("session_id", &hex::encode(session_id))
.field("share_index", share_index)
.field("ack_count", ack_count)
.field("expected_acks", expected_acks)
.finish(),
Self::DescriptorNacked {
session_id,
share_index,
Expand All @@ -358,6 +380,14 @@ impl fmt::Debug for FrostNodeMsg {
.field("session_id", &hex::encode(session_id))
.field("error", error)
.finish(),
Self::XpubAnnounced {
share_index,
recovery_xpubs,
} => f
.debug_struct("XpubAnnounced")
.field("share_index", share_index)
.field("xpub_count", &recovery_xpubs.len())
.finish(),
}
}
}
Expand Down
11 changes: 2 additions & 9 deletions keep-desktop/src/screen/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,10 @@ impl Default for TierConfig {

#[derive(Debug, Clone)]
pub enum DescriptorProgress {
WaitingContributions {
received: usize,
expected: usize,
},
WaitingContributions { received: usize, expected: usize },
Contributed,
Finalizing,
#[allow(dead_code)]
WaitingAcks {
received: usize,
expected: usize,
},
WaitingAcks { received: usize, expected: usize },
Complete,
Failed(String),
}
Expand Down
8 changes: 8 additions & 0 deletions keep-frost-net/src/descriptor_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,14 @@ impl DescriptorSession {
self.expected_acks.iter().all(|idx| self.acks.contains(idx))
}

pub fn ack_count(&self) -> usize {
self.acks.len()
}

pub fn expected_ack_count(&self) -> usize {
self.expected_acks.len()
}

pub fn descriptor(&self) -> Option<&FinalizedDescriptor> {
self.descriptor.as_ref()
}
Expand Down
34 changes: 26 additions & 8 deletions keep-frost-net/src/node/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ impl KfpNode {
peer.share_index
};

self.verify_peer_share_index(sender, sender_share_index)?;
self.check_proposer_authorized(sender_share_index)?;

info!(
Expand Down Expand Up @@ -789,7 +790,7 @@ impl KfpNode {
.ok_or_else(|| FrostNetError::UntrustedPeer(sender.to_string()))?
};

let is_complete = {
let (is_complete, ack_count, expected_acks) = {
let mut sessions = self.descriptor_sessions.write();
let session = sessions
.get_session_mut(&payload.session_id)
Expand All @@ -800,16 +801,29 @@ impl KfpNode {
payload.descriptor_hash,
&payload.key_proof_psbt,
)?;
session.is_complete()
(
session.is_complete(),
session.ack_count(),
session.expected_ack_count(),
)
};

info!(
session_id = %hex::encode(payload.session_id),
share_index,
ack_count,
expected_acks,
complete = is_complete,
"Received descriptor ACK"
);

let _ = self.event_tx.send(KfpNodeEvent::DescriptorAcked {
session_id: payload.session_id,
share_index,
ack_count,
expected_acks,
});

if is_complete {
let sessions = self.descriptor_sessions.read();
if let Some(session) = sessions.get_session(&payload.session_id) {
Expand All @@ -827,25 +841,27 @@ impl KfpNode {
Ok(())
}

pub async fn build_and_finalize_descriptor(&self, session_id: [u8; 32]) -> Result<()> {
let (external, internal, policy_hash) = {
pub async fn build_and_finalize_descriptor(&self, session_id: [u8; 32]) -> Result<usize> {
let (external, internal, policy_hash, expected_acks) = {
let sessions = self.descriptor_sessions.read();
let session = sessions
.get_session(&session_id)
.ok_or_else(|| FrostNetError::Session("unknown descriptor session".into()))?;

let policy_hash = derive_policy_hash(session.policy());
let expected_acks = session.expected_ack_count();
let (external, internal) = reconstruct_descriptor(
session.group_pubkey(),
session.policy(),
session.contributions(),
session.network(),
)?;
(external, internal, policy_hash)
(external, internal, policy_hash, expected_acks)
};

self.finalize_descriptor(session_id, &external, &internal, policy_hash)
.await
.await?;
Ok(expected_acks)
}

pub fn cancel_descriptor_session(&self, session_id: &[u8; 32]) {
Expand Down Expand Up @@ -885,7 +901,8 @@ impl KfpNode {
return Ok(());
}
if seen.len() >= 10_000 {
if let Some(&oldest) = seen.iter().min_by_key(|(_, ts, _)| *ts) {
tracing::warn!("seen_xpub_announces at capacity, evicting oldest entry");
if let Some(&oldest) = seen.iter().min_by_key(|&(_, ts, _)| ts) {
seen.remove(&oldest);
}
}
Expand Down Expand Up @@ -948,7 +965,8 @@ impl KfpNode {
.key_package()
.map_err(|e| FrostNetError::Crypto(format!("key package: {e}")))?;
let signing_share = key_package.signing_share();
let bytes = <[u8; 32]>::try_from(signing_share.serialize().as_slice())
let serialized = Zeroizing::new(signing_share.serialize());
let bytes = <[u8; 32]>::try_from(serialized.as_slice())
.map_err(|_| FrostNetError::Crypto("Invalid signing share length".into()))?;
Ok(Zeroizing::new(bytes))
}
Expand Down
18 changes: 18 additions & 0 deletions keep-frost-net/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ pub enum KfpNodeEvent {
internal_descriptor: String,
network: String,
},
DescriptorAcked {
session_id: [u8; 32],
share_index: u16,
ack_count: usize,
expected_acks: usize,
},
DescriptorNacked {
session_id: [u8; 32],
share_index: u16,
Expand Down Expand Up @@ -242,6 +248,18 @@ impl std::fmt::Debug for KfpNodeEvent {
.debug_struct("DescriptorComplete")
.field("session_id", &hex::encode(session_id))
.finish(),
Self::DescriptorAcked {
session_id,
share_index,
ack_count,
expected_acks,
} => f
.debug_struct("DescriptorAcked")
.field("session_id", &hex::encode(session_id))
.field("share_index", share_index)
.field("ack_count", ack_count)
.field("expected_acks", expected_acks)
.finish(),
Self::DescriptorNacked {
session_id,
share_index,
Expand Down