From e3e7a0d77c8fe581c243de9296e6766814476ac7 Mon Sep 17 00:00:00 2001 From: jCabala Date: Tue, 12 May 2026 22:16:19 +0200 Subject: [PATCH 01/13] fix: Added a fallback in comms/http for when the participant accidentally rejoins --- .gitignore | 6 +++- .../src/coordinator/comms/http.rs | 35 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ccb959f..4d555e3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,9 @@ mina-frost-client/tests/assets/ # GraphQL schemas *.graphql -# AI generated folders +# AI generated folders and files .claude/ +.codex + +# Playground +playground/ \ No newline at end of file diff --git a/mina-frost-client/src/coordinator/comms/http.rs b/mina-frost-client/src/coordinator/comms/http.rs index 6149629..6d00096 100644 --- a/mina-frost-client/src/coordinator/comms/http.rs +++ b/mina-frost-client/src/coordinator/comms/http.rs @@ -1,7 +1,7 @@ //! HTTP implementation of the Comms trait. use std::{ - collections::{BTreeMap, HashMap}, + collections::{BTreeMap, HashMap, HashSet}, error::Error, io::{BufRead, Write}, marker::PhantomData, @@ -35,6 +35,12 @@ pub struct HTTPComms { state: CoordinatorSessionState, pubkeys: HashMap>, cipher: Option, + /// Pubkeys that have already submitted a commitment in round 1. Used in + /// round 2 to detect and skip duplicate Noise handshakes from participants + /// that re-joined the session (e.g. from a second process), which would + /// otherwise cause SnowError(Decrypt) because the transport-mode Noise + /// state cannot process a new handshake message. + commitment_senders: HashSet, _phantom: PhantomData, } @@ -51,6 +57,7 @@ impl HTTPComms { ), pubkeys: Default::default(), cipher: None, + commitment_senders: Default::default(), _phantom: Default::default(), }) } @@ -128,8 +135,13 @@ impl Comms for HTTPComms { }) .await?; for msg in r.msgs { + if self.commitment_senders.contains(&msg.sender) { + continue; + } + let sender = msg.sender.clone(); let msg = cipher.decrypt(msg)?; self.state.recv(msg)?; + self.commitment_senders.insert(sender); } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); @@ -185,6 +197,7 @@ impl Comms for HTTPComms { eprintln!("Waiting for participants to send their SignatureShares..."); + let mut seen_share_senders: HashSet = HashSet::new(); loop { let r = self .client @@ -194,8 +207,24 @@ impl Comms for HTTPComms { }) .await?; for msg in r.msgs { - let msg = cipher.decrypt(msg)?; - self.state.recv(msg)?; + if seen_share_senders.contains(&msg.sender) { + continue; + } + let sender = msg.sender.clone(); + match cipher.decrypt(msg) { + Ok(msg) => { + self.state.recv(msg)?; + seen_share_senders.insert(sender); + } + Err(_) if self.commitment_senders.contains(&sender) => { + // A duplicate participant process re-joined this session + // and sent a fresh Noise handshake. The Noise state for + // this sender is already in transport mode so decryption + // fails. Ignore — the real signature share will arrive + // from the original process. + } + Err(e) => return Err(e.into()), + } } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); From a9d56ec8b84e23cfab1860a5b40ffec1695fb818 Mon Sep 17 00:00:00 2001 From: jCabala Date: Tue, 12 May 2026 22:19:50 +0200 Subject: [PATCH 02/13] feat: Removed the ability to join without specifying session id --- mina-frost-client/src/cli/args.rs | 5 ++--- mina-frost-client/src/cli/participant.rs | 6 +++--- mina-frost-client/src/coordinator/comms/http.rs | 10 ++++------ mina-frost-client/src/participant/comms/http.rs | 16 +++------------- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/mina-frost-client/src/cli/args.rs b/mina-frost-client/src/cli/args.rs index ce92bc0..34fee89 100644 --- a/mina-frost-client/src/cli/args.rs +++ b/mina-frost-client/src/cli/args.rs @@ -204,10 +204,9 @@ pub enum Command { /// to list) #[arg(short, long)] group: String, - /// The session ID to use (use `sessions` to list). Can be omitted in - /// case there is a single active session. + /// The session ID to use (use `sessions` to list). #[arg(short = 'S', long)] - session: Option, + session: String, /// Automatically answer yes to signing any package. #[arg(short = 'y', long, default_value_t = false)] yes: bool, diff --git a/mina-frost-client/src/cli/participant.rs b/mina-frost-client/src/cli/participant.rs index 1950dae..aa8b27e 100644 --- a/mina-frost-client/src/cli/participant.rs +++ b/mina-frost-client/src/cli/participant.rs @@ -41,7 +41,7 @@ pub async fn run_bluepallas(args: &Command) -> Result<(), Box> { &group_config, key_package, server_url, - session, + &session, )?; // Execute signing @@ -84,7 +84,7 @@ fn setup_participant_config( group_config: &Group, key_package: KeyPackage, server_url: Option, - session: Option, + session: &str, ) -> Result, Box> { // Determine server URL let server_url = if let Some(server_url) = server_url { @@ -113,7 +113,7 @@ fn setup_participant_config( port: server_url_parsed .port_or_known_default() .expect("always works for https"), - session_id: session.unwrap_or_default(), + session_id: session.to_owned(), comm_privkey: Some( user_config .communication_key diff --git a/mina-frost-client/src/coordinator/comms/http.rs b/mina-frost-client/src/coordinator/comms/http.rs index 6d00096..be94bfb 100644 --- a/mina-frost-client/src/coordinator/comms/http.rs +++ b/mina-frost-client/src/coordinator/comms/http.rs @@ -105,12 +105,10 @@ impl Comms for HTTPComms { }) .await?; - if self.config.signers.is_empty() { - eprintln!( - "Send the following session ID to participants: {}", - r.session_id - ); - } + eprintln!( + "Send the following session ID to participants: {}", + r.session_id + ); self.session_id = Some(r.session_id); let Some(comm_privkey) = &self.config.comm_privkey else { diff --git a/mina-frost-client/src/participant/comms/http.rs b/mina-frost-client/src/participant/comms/http.rs index cf5c834..c56a15b 100644 --- a/mina-frost-client/src/participant/comms/http.rs +++ b/mina-frost-client/src/participant/comms/http.rs @@ -168,19 +168,9 @@ where ); eprintln!("Joining signing session..."); - let session_id = match self.session_id { - Some(s) => s, - None => { - // Get session ID from server - let r = self.client.list_sessions().await?; - if r.session_ids.len() > 1 { - return Err(eyre!("user has more than one FROST session active; use `mina-frost-client sessions` to list them and specify the session ID with `-S`").into()); - } else if r.session_ids.is_empty() { - return Err(eyre!("User has no current sessions active. The Coordinator should either specify your username, or manually share the session ID which you can specify with --session_id").into()); - } - r.session_ids[0] - } - }; + let session_id = self + .session_id + .ok_or_else(|| eyre!("session ID is required; use `-S` to specify it"))?; self.session_id = Some(session_id); let (Some(comm_privkey), Some(comm_coordinator_pubkey_getter)) = ( From 40c2f7f43657565c8386c3226ab67c58bc8e6d04 Mon Sep 17 00:00:00 2001 From: jCabala Date: Tue, 12 May 2026 22:31:29 +0200 Subject: [PATCH 03/13] feat: Updated comms/http error handling in case of participant re-joining --- .../src/coordinator/comms/http.rs | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/mina-frost-client/src/coordinator/comms/http.rs b/mina-frost-client/src/coordinator/comms/http.rs index be94bfb..17720db 100644 --- a/mina-frost-client/src/coordinator/comms/http.rs +++ b/mina-frost-client/src/coordinator/comms/http.rs @@ -35,12 +35,6 @@ pub struct HTTPComms { state: CoordinatorSessionState, pubkeys: HashMap>, cipher: Option, - /// Pubkeys that have already submitted a commitment in round 1. Used in - /// round 2 to detect and skip duplicate Noise handshakes from participants - /// that re-joined the session (e.g. from a second process), which would - /// otherwise cause SnowError(Decrypt) because the transport-mode Noise - /// state cannot process a new handshake message. - commitment_senders: HashSet, _phantom: PhantomData, } @@ -57,7 +51,6 @@ impl HTTPComms { ), pubkeys: Default::default(), cipher: None, - commitment_senders: Default::default(), _phantom: Default::default(), }) } @@ -124,6 +117,7 @@ impl Comms for HTTPComms { eprint!("Waiting for participants to send their commitments..."); + let mut commitment_senders: HashSet = HashSet::new(); loop { let r = self .client @@ -133,13 +127,17 @@ impl Comms for HTTPComms { }) .await?; for msg in r.msgs { - if self.commitment_senders.contains(&msg.sender) { + if commitment_senders.contains(&msg.sender) { + eprintln!( + "Warning: participant {:?} attempted to rejoin the session; ignoring", + msg.sender + ); continue; } let sender = msg.sender.clone(); let msg = cipher.decrypt(msg)?; self.state.recv(msg)?; - self.commitment_senders.insert(sender); + commitment_senders.insert(sender); } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); @@ -209,20 +207,18 @@ impl Comms for HTTPComms { continue; } let sender = msg.sender.clone(); - match cipher.decrypt(msg) { - Ok(msg) => { - self.state.recv(msg)?; - seen_share_senders.insert(sender); + let msg = match cipher.decrypt(msg) { + Ok(msg) => msg, + Err(_) => { + eprintln!( + "Warning: failed to decrypt message from {:?}; ignoring", + sender + ); + continue; } - Err(_) if self.commitment_senders.contains(&sender) => { - // A duplicate participant process re-joined this session - // and sent a fresh Noise handshake. The Noise state for - // this sender is already in transport mode so decryption - // fails. Ignore — the real signature share will arrive - // from the original process. - } - Err(e) => return Err(e.into()), - } + }; + self.state.recv(msg)?; + seen_share_senders.insert(sender); } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); From d3d0059837bbce732fc47eca4b29e6a5ed8d9330 Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 13 May 2026 10:35:12 +0200 Subject: [PATCH 04/13] test: Added a test triggering participant warinings --- .../tests/assets-duplicate/0.toml | 31 +++ .../tests/assets-duplicate/1.toml | 31 +++ .../tests/assets-duplicate/2.toml | 31 +++ .../assets-duplicate/localhost+2-key.pem | 28 +++ .../tests/assets-duplicate/localhost+2.pem | 25 +++ .../tests/assets-duplicate/signature.json | 30 +++ .../tests/duplicate_participant.rs | 210 ++++++++++++++++++ mina-frost-client/tests/helpers.rs | 89 ++++++-- 8 files changed, 454 insertions(+), 21 deletions(-) create mode 100644 mina-frost-client/tests/assets-duplicate/0.toml create mode 100644 mina-frost-client/tests/assets-duplicate/1.toml create mode 100644 mina-frost-client/tests/assets-duplicate/2.toml create mode 100644 mina-frost-client/tests/assets-duplicate/localhost+2-key.pem create mode 100644 mina-frost-client/tests/assets-duplicate/localhost+2.pem create mode 100644 mina-frost-client/tests/assets-duplicate/signature.json create mode 100644 mina-frost-client/tests/duplicate_participant.rs diff --git a/mina-frost-client/tests/assets-duplicate/0.toml b/mina-frost-client/tests/assets-duplicate/0.toml new file mode 100644 index 0000000..ef46e26 --- /dev/null +++ b/mina-frost-client/tests/assets-duplicate/0.toml @@ -0,0 +1,31 @@ +version = 0 + +[communication_key] +privkey = "1d9ffe8c35772755d735c20860bbfc929d5d6b2a6a7f7a5ec6a991aa290d94f2" +pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" + +[contact.1] +name = "1" +pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" + +[contact.2] +name = "2" +pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80] +description = "Duplicate Test Group" +public_key_package = "009b56619e03be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab30914227dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de71090071c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c800102" +key_package = "009b56619edabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422fdda4a7aa6d5b845169d9712025e50327a13955517ae81ac9142c93f320a16097dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de710900de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c8002" +server_url = "localhost:2744" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25] +identifier = "71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25" +pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621] +identifier = "be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621" +pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422] +identifier = "dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422" +pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" diff --git a/mina-frost-client/tests/assets-duplicate/1.toml b/mina-frost-client/tests/assets-duplicate/1.toml new file mode 100644 index 0000000..c88d9c1 --- /dev/null +++ b/mina-frost-client/tests/assets-duplicate/1.toml @@ -0,0 +1,31 @@ +version = 0 + +[communication_key] +privkey = "0f679ef84f970808b3296451528dc1155719d42c92e4a8e703039dcac2b6a593" +pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" + +[contact.0] +name = "0" +pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" + +[contact.2] +name = "2" +pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80] +description = "Duplicate Test Group" +public_key_package = "009b56619e03be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab30914227dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de71090071c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c800102" +key_package = "009b56619ebe27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621152153e6f1e287a65ca63793b1edb497e9eeb0d93bb03ae61c688908fb10e827dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c8002" +server_url = "localhost:2744" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25] +identifier = "71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25" +pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621] +identifier = "be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621" +pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422] +identifier = "dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422" +pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" diff --git a/mina-frost-client/tests/assets-duplicate/2.toml b/mina-frost-client/tests/assets-duplicate/2.toml new file mode 100644 index 0000000..de2ff79 --- /dev/null +++ b/mina-frost-client/tests/assets-duplicate/2.toml @@ -0,0 +1,31 @@ +version = 0 + +[communication_key] +privkey = "d8b8df0097f47d6ba0862f346ccbd8bffdfc928a035a8d85bd796c450d670220" +pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" + +[contact.0] +name = "0" +pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" + +[contact.1] +name = "1" +pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80] +description = "Duplicate Test Group" +public_key_package = "009b56619e03be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab30914227dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de71090071c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c800102" +key_package = "009b56619e71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e254f4012186a57ff3b9e9510ab65534749eb7732008793cf15712cb8857ecb0739d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c8002" +server_url = "localhost:2744" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25] +identifier = "71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25" +pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621] +identifier = "be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621" +pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" + +[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422] +identifier = "dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422" +pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" diff --git a/mina-frost-client/tests/assets-duplicate/localhost+2-key.pem b/mina-frost-client/tests/assets-duplicate/localhost+2-key.pem new file mode 100644 index 0000000..509ef75 --- /dev/null +++ b/mina-frost-client/tests/assets-duplicate/localhost+2-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4FMCfeQifMjOk +bvR4dZvs2eLYn1+X8clXlpb47a7Ez1jkCbKHttv/qZKB8FjXoL0yrfvXJ9swbSu8 +sraBeJ18AjJWKsLzdPQDQv8k/MjvxENV9vKyQJ5rsr0Cxg5dtXjKs+Va6PuT/UMW +RZ79HN4qWhLBif/muhRmLCT4VLlSCZrEhY9/ZFlQBE3/QQJEp8hpQS52mvPDCnPM +L7sHPB65lzKfSJmzEGRHE4568h/YUT+Vr1x+DqaDd1rt/IMPU/NihkNyR0nFV9J5 +c1BVf9kDTv6esxO2oP7ieZsfFuacd8xYr+KjiyfMXMfSKz+NedKzyp9a0lQtLKeP +XTTg5hxXAgMBAAECggEBAIUjPU4aFApQHNnJBjg5l9Tuogta+aDD14Pp/rd78POX +pAsKmH1f3C5FTxl75EXQlAabn5I12WplC0UnXKi3u+99BzsbInVDGVlik3cE/a+s +m6cJPO9/nRT36YygArP0FLFG+5OM/qhCfMsW/67Buk2XaAli7hrKawaa1wJlBMQ3 +xATaNN+ROtv3tQEi+Rt4T/NDd/ihbn58hQz/+2253c8F4SWANBPx00n/Kc4qh/lW +VJ3DC2sWfKZTOJj2lfxuak1fPaS0V1vM2Mdhw/MHLOXrhtusEse/uMCEJpoHCn27 +4PEkF4nHEWGVoHBWiJNpUwLA/121DfMeQFjBCsMgYEECgYEA3l5nZxjvIXkER1rL +TjUeYRENgxqw4oCFuZOo4a4Z5bNThvl8jiEWzvlynDNtNvpZmgsNmUtW6XHGgnAd +PDt/xryW875ORyso9cYoRsD/4+BqNFOVsYBzmw+8/rD5hlnmQ+bEJSRQcYb2pITb +3T9JGI1Ra1PBjqmMNywmizNzZ6UCgYEA0+vwTl6PsNV5WGKR0Z9dRWht2fT8/yga +VVMoe7A1u61IHrBgGzBLuS/5wkqFLc3cQ74XzAW6rZM5r/DvqF87tGIilNsQaAH8 +dVO0vxDU/SUubpHfIY2+bqqzaawK8g6ZfXyQ1F1n7rIRvoL6elFgm9cfzAkztL+/ +cVft1ybXk0sCgYAaoqAsHzZ1gfNbQKrdgUtUQSCnNYk+eDqMUf2Gmr4LX4F/x159 +/8SlogdyYk6QqUgWRYCpffa5G6G1egxmbJIPkgNays7Bg4/ycHvejbuY5gaSofhq +PIKcbjQrJbOdviLwuzF/aWwhTRM5/ZgAVlZBFJxOCMhEeFaNPUKYpd8K6QKBgQC0 +2zQ4DadfaK+oaGa9mN3GsDqfud53+0eN7ewsstMImfdkiW1bhrn5DyJ9V4+U6YzD +G2W/rlwEahLfPiWpcazIYr/Ufafgu2Ey1/722GyMpcCciEz+m7MbSv7WfgbRUPhZ +CYeoVRavtLRD/A+7d9uU4+C1Bl1kMgg1uK634OQ1RQKBgQDPkGxLWLoUKORtJHPy +4+Z5soKs8ChgmTUCeNQ4SRq27rprmDUoGLswOfWsroZq6eLWKvkcKRxSCbm1cl+w +W5dzBsdv1nmGbxhjknFa3fogU0o6sLy+ifPmQdTF4GccOrVlAHcJE/3gjHlTjL7p +V9e7faojkR6/xJ/b70IFEXLG9w== +-----END PRIVATE KEY----- diff --git a/mina-frost-client/tests/assets-duplicate/localhost+2.pem b/mina-frost-client/tests/assets-duplicate/localhost+2.pem new file mode 100644 index 0000000..1165564 --- /dev/null +++ b/mina-frost-client/tests/assets-duplicate/localhost+2.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEHjCCAoagAwIBAgIQDsZghV1VTzBzuGrEUs2tozANBgkqhkiG9w0BAQsFADBb +MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExGDAWBgNVBAsMD2pjYWJh +bGFAamNhYmFsYTEfMB0GA1UEAwwWbWtjZXJ0IGpjYWJhbGFAamNhYmFsYTAeFw0y +NjA1MTMwODMwNDBaFw0yODA4MTMwODMwNDBaMEMxJzAlBgNVBAoTHm1rY2VydCBk +ZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEYMBYGA1UECwwPamNhYmFsYUBqY2FiYWxh +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuBTAn3kInzIzpG70eHWb +7Nni2J9fl/HJV5aW+O2uxM9Y5Amyh7bb/6mSgfBY16C9Mq371yfbMG0rvLK2gXid +fAIyVirC83T0A0L/JPzI78RDVfbyskCea7K9AsYOXbV4yrPlWuj7k/1DFkWe/Rze +KloSwYn/5roUZiwk+FS5UgmaxIWPf2RZUARN/0ECRKfIaUEudprzwwpzzC+7Bzwe +uZcyn0iZsxBkRxOOevIf2FE/la9cfg6mg3da7fyDD1PzYoZDckdJxVfSeXNQVX/Z +A07+nrMTtqD+4nmbHxbmnHfMWK/io4snzFzH0is/jXnSs8qfWtJULSynj1004OYc +VwIDAQABo3YwdDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw +HwYDVR0jBBgwFoAUNlUPrs9TEKJga6jrQcSeBDiCVkIwLAYDVR0RBCUwI4IJbG9j +YWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IB +gQA8wi2xE0nO2l2kJTU/piOWK25ApdMKIt/bGpQCveuiaJmUUUr3dmfH36D4NQY4 +AR28l6k/D7xhv7X9YCJVSTTazxXGiC1aB8mpjjJPnyg2LhNkXyKkfmhMpyTyUYIP +/X54aF28eQwOSYNMt3KZIedaL8vg/kJefdnnufcolhpOxwZcj90nAwNLvPlRbfUI +OvnabgyeoNf9yV59+hxWR6jYMqcROd8sIMYsTecnyy1x/Fxyz3r/Xotwyqh4YVH6 +OqG2uGUbTLfXg8NBLUdePzc9FbiH6lrM1v6+m5WZv8LvSWJFZxnDH2/iCTDkIgw9 +n7bOtxXrdMJg3J8LI1Wxnhh87BBIgQzQi+TO43ftL2yFcDuvLW28zoZtzHXD5ETK +6KIJVUESz5XMz3NzzSuMYOW690WQteWsVCAUUDVxiNUyFAIMUcuolFrxJVU7JSWr +RgLrR2xgsNhvo4TFZzDvGmWLLaX/5xolrSVDTO/7CmDQsTU5XzI7m61Qov/3P7nY +zzI= +-----END CERTIFICATE----- diff --git a/mina-frost-client/tests/assets-duplicate/signature.json b/mina-frost-client/tests/assets-duplicate/signature.json new file mode 100644 index 0000000..e850381 --- /dev/null +++ b/mina-frost-client/tests/assets-duplicate/signature.json @@ -0,0 +1,30 @@ +{ + "publicKey": { + "address": "B62qqv4t1oUzJep9zQsKrzTTcMnqFDZmyND3VHMaSSMamhFLxrpNhDA" + }, + "signature": { + "field": "20383297309219312205605853359266608882627991642556524872147742446783289839449", + "scalar": "17509109384997598891982644786121795253567372265334850085803366484295595123976", + "base58": "7mXAHRrXJ2evk7Lo9vnTmdfyibL3FcmM9GJJBr1sq8U4Y9mvvCzhPyiKDdBiS2YdsNwi3v8w1B6rFdW8PCc2cNM77Pmwu88E" + }, + "payload": { + "networkId": "testnet", + "kind": { + "tag": "Legacy", + "transaction": { + "to": "B62qkcvM4DZE7k23ZHMLt1uaMVcixuxxuyz1XNJNCLkFbitDdUHxWs1", + "from": "B62qkcvM4DZE7k23ZHMLt1uaMVcixuxxuyz1XNJNCLkFbitDdUHxWs1", + "fee": "1000000000", + "amount": "1000000000", + "nonce": "1", + "memo": "Hello Mina x FROST from the Rasp", + "valid_until": "4294967295", + "tag": [ + false, + false, + false + ] + } + } + } +} \ No newline at end of file diff --git a/mina-frost-client/tests/duplicate_participant.rs b/mina-frost-client/tests/duplicate_participant.rs new file mode 100644 index 0000000..cc256c5 --- /dev/null +++ b/mina-frost-client/tests/duplicate_participant.rs @@ -0,0 +1,210 @@ +mod helpers; + +use helpers::{ + binary_name, build_client_binary, form_group_with_dkg, get_session_id, greet_participants, + group_keys_from_config, introduce_participant, run_cli_spawn_piped, start_frostd, + CliParticipant, SigningParticipant, +}; +use lazy_static::lazy_static; +use std::fs; +use std::io::Result; +use std::path::{Path, PathBuf}; +use std::process::{Child, Output}; +use std::thread; +use std::time::Duration; + +lazy_static! { + static ref binary_path: PathBuf = PathBuf::from(format!( + "{}/../target/release/{}", + env!("CARGO_MANIFEST_DIR"), + binary_name() + )); + static ref working_dir: PathBuf = PathBuf::from(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/assets-duplicate" + )); + static ref message_path: PathBuf = PathBuf::from(concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/signing_example/message.json" + )); +} + +const SIG_FILE: &str = "signature.json"; +const NETWORK_ID: &str = "testnet"; + +fn setup() -> Result { + if working_dir.exists() { + fs::remove_dir_all(working_dir.clone())?; + } + fs::create_dir_all(working_dir.clone())?; + + let built_binary = build_client_binary( + env!("CARGO_MANIFEST_DIR"), + None, + mina_tx::zkapp_tx::IS_MESA_HARDFORK, + ); + assert!( + built_binary.exists(), + "release client binary does not exist at {}", + built_binary.display() + ); + + start_frostd(&working_dir) +} + +fn participant_args( + participant: &SigningParticipant, + server_url: &str, + group_pk_hex: &str, + session_id: &str, +) -> Vec { + vec![ + "participant".to_string(), + "-c".to_string(), + participant.config_path.clone(), + "-s".to_string(), + server_url.to_string(), + "--group".to_string(), + group_pk_hex.to_string(), + "-S".to_string(), + session_id.to_string(), + "-y".to_string(), + ] +} + +fn sign_with_duplicate_participant( + binary: &Path, + cwd: &Path, + group_pk_hex: &str, + msg_path: &Path, + sig_file: &str, + network_id: &str, + server_url: &str, + threshold: usize, + participants: &[SigningParticipant], +) -> Output { + assert!(participants.len() >= threshold && threshold > 0); + + let mut coord_args = vec![ + "coordinator".to_string(), + "-c".to_string(), + participants[0].config_path.clone(), + "-s".to_string(), + server_url.to_string(), + "--group".to_string(), + group_pk_hex.to_string(), + "-m".to_string(), + msg_path.to_string_lossy().to_string(), + "-o".to_string(), + sig_file.to_string(), + "-n".to_string(), + network_id.to_string(), + ]; + for participant in participants.iter().take(threshold) { + coord_args.push("-S".to_string()); + coord_args.push(participant.pubkey_hex.clone()); + } + + let coordinator = run_cli_spawn_piped(binary, cwd, &coord_args); + + let session_id = get_session_id(binary, cwd, &participants[0].config_path, server_url, group_pk_hex) + .expect("no signing session appeared after coordinator started"); + + let participant_children: Vec = participants + .iter() + .take(threshold) + .map(|p| { + let args = participant_args(p, server_url, group_pk_hex, &session_id); + run_cli_spawn_piped(binary, cwd, &args) + }) + .collect(); + + // Give them time to complete the Noise handshake and send their commitments + thread::sleep(Duration::from_millis(1500)); + + // Spawn a duplicate of the first participant (fresh Noise context, same session) + let dupe_args = participant_args(&participants[0], server_url, group_pk_hex, &session_id); + let mut dupe = run_cli_spawn_piped(binary, cwd, &dupe_args); + thread::sleep(Duration::from_secs(2)); + let _ = dupe.kill(); + let _ = dupe.wait(); + + for child in participant_children { + let _ = child.wait_with_output(); + } + + coordinator + .wait_with_output() + .expect("coordinator subprocess did not exit") +} + +/// Verifies that when a participant accidentally re-joins a session with a fresh +/// Noise context, the coordinator warns and continues rather than crashing, and +/// the signing session completes successfully. +#[test] +fn duplicate_participant_handled_gracefully() -> Result<()> { + let mut server_process = setup()?; + + let participants = (0..3) + .map(|x| introduce_participant(&binary_path, &working_dir, &x.to_string())) + .collect::>(); + + greet_participants(&binary_path, &working_dir, &participants); + + form_group_with_dkg( + &binary_path, + &working_dir, + &participants, + 2, + "localhost:2744", + "Duplicate Test Group", + )?; + + let (group_pk_hex, _) = + group_keys_from_config(&binary_path, &working_dir, &participants[0].toml); + + let signing_participants: Vec = participants + .iter() + .map(|p| SigningParticipant { + config_path: p.toml.clone(), + pubkey_hex: p.pubkey_hex.clone(), + }) + .collect(); + + let output = sign_with_duplicate_participant( + &binary_path, + &working_dir, + &group_pk_hex, + &message_path, + SIG_FILE, + NETWORK_ID, + "localhost:2744", + 2, + &signing_participants, + ); + + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + + assert!( + output.status.success(), + "coordinator failed unexpectedly\nstdout={stdout}\nstderr={stderr}" + ); + assert!( + stdout.contains("Signature saved"), + "coordinator did not save a signature\nstdout={stdout}\nstderr={stderr}" + ); + let warned = stderr.contains("attempted to rejoin the session; ignoring") + || stderr.contains("failed to decrypt message from"); + assert!( + warned, + "coordinator did not warn about the duplicate participant\nstderr={stderr}" + ); + assert!( + !stderr.contains("SnowError"), + "coordinator crashed with SnowError (regression)\nstderr={stderr}" + ); + + server_process.kill()?; + Ok(()) +} diff --git a/mina-frost-client/tests/helpers.rs b/mina-frost-client/tests/helpers.rs index af3ac3c..abc12af 100644 --- a/mina-frost-client/tests/helpers.rs +++ b/mina-frost-client/tests/helpers.rs @@ -269,6 +269,59 @@ pub fn run_cli_spawn_piped(binary: &Path, cwd: &Path, args: &[String]) -> Child .expect("failed to spawn CLI command") } +/// Poll the server until a signing session appears, then return its ID. +pub fn get_session_id( + binary: &Path, + cwd: &Path, + config: &str, + server_url: &str, + group_pk_hex: &str, +) -> Option { + let re = Regex::new(r"Session with ID ([0-9a-f-]{36})").unwrap(); + for _ in 0..20 { + if let Ok(output) = Command::new(binary) + .args([ + "sessions", + "-c", + config, + "-s", + server_url, + "--group", + group_pk_hex, + ]) + .current_dir(cwd) + .output() + { + let stderr = String::from_utf8_lossy(&output.stderr); + if let Some(caps) = re.captures(&stderr) { + return Some(caps[1].to_string()); + } + } + thread::sleep(Duration::from_millis(500)); + } + None +} + +fn participant_args( + participant: &SigningParticipant, + server_url: &str, + group_pk_hex: &str, + session_id: &str, +) -> Vec { + vec![ + "participant".to_string(), + "-c".to_string(), + participant.config_path.clone(), + "-s".to_string(), + server_url.to_string(), + "--group".to_string(), + group_pk_hex.to_string(), + "-S".to_string(), + session_id.to_string(), + "-y".to_string(), + ] +} + pub fn sign_with_binary( binary: &Path, cwd: &Path, @@ -292,7 +345,7 @@ pub fn sign_with_binary( ); assert!(threshold > 0, "threshold must be > 0"); - let mut args = vec![ + let mut coord_args = vec![ "coordinator".to_string(), "-c".to_string(), participants[0].config_path.clone(), @@ -307,33 +360,26 @@ pub fn sign_with_binary( "-n".to_string(), network_id.to_string(), ]; - for participant in participants.iter().take(threshold) { - args.push("-S".to_string()); - args.push(participant.pubkey_hex.clone()); + coord_args.push("-S".to_string()); + coord_args.push(participant.pubkey_hex.clone()); } let mut children: Vec<(Vec, Child)> = Vec::new(); - children.push((args.clone(), run_cli_spawn_piped(binary, cwd, &args))); + children.push((coord_args.clone(), run_cli_spawn_piped(binary, cwd, &coord_args))); - thread::sleep(Duration::from_secs(1)); + let session_id = get_session_id( + binary, + cwd, + &participants[0].config_path, + server_url, + group_pk_hex, + ) + .expect("no signing session appeared after coordinator started"); for participant in participants.iter().take(threshold) { - let participant_args = vec![ - "participant".to_string(), - "-c".to_string(), - participant.config_path.clone(), - "-s".to_string(), - server_url.to_string(), - "--group".to_string(), - group_pk_hex.to_string(), - "-y".to_string(), - ]; - - children.push(( - participant_args.clone(), - run_cli_spawn_piped(binary, cwd, &participant_args), - )); + let args = participant_args(participant, server_url, group_pk_hex, &session_id); + children.push((args.clone(), run_cli_spawn_piped(binary, cwd, &args))); } for (child_args, child) in children { @@ -350,6 +396,7 @@ pub fn sign_with_binary( } } + pub fn run_cli_spawn_quiet(binary: &Path, cwd: &Path, args: &[String]) -> io::Result { Command::new(binary) .args(args) From 55564dd6b6ea99d662eab414f66f0c5b7aee32a1 Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 13 May 2026 10:40:44 +0200 Subject: [PATCH 05/13] chore: Run pre-commit --- .gitignore | 2 +- mina-frost-client/tests/assets-duplicate/signature.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4d555e3..63ad03f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,4 @@ mina-frost-client/tests/assets/ .codex # Playground -playground/ \ No newline at end of file +playground/ diff --git a/mina-frost-client/tests/assets-duplicate/signature.json b/mina-frost-client/tests/assets-duplicate/signature.json index e850381..479bacc 100644 --- a/mina-frost-client/tests/assets-duplicate/signature.json +++ b/mina-frost-client/tests/assets-duplicate/signature.json @@ -27,4 +27,4 @@ } } } -} \ No newline at end of file +} From ff3a2445046ed894f5b34afdb3bca9c4e5840b8f Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 13 May 2026 10:50:50 +0200 Subject: [PATCH 06/13] chore: Bumped deps to fix cargo audit error --- Cargo.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f1a1ed..0a3d4d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,7 +275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", "rayon", ] @@ -1785,7 +1785,7 @@ dependencies = [ "mina-signer", "mina-tx", "postcard", - "rand 0.8.5", + "rand 0.8.6", "rand_core 0.6.4", "regex", "reqwest", @@ -1828,7 +1828,7 @@ dependencies = [ "mina-curves", "o1-utils", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "rayon", "serde", "serde_with", @@ -1849,7 +1849,7 @@ dependencies = [ "mina-hasher", "num-bigint", "o1-utils", - "rand 0.8.5", + "rand 0.8.6", "serde", "sha2", "thiserror 2.0.18", @@ -1910,7 +1910,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", ] @@ -1951,7 +1951,7 @@ dependencies = [ "hex", "num-bigint", "num-integer", - "rand 0.8.5", + "rand 0.8.6", "rand_core 0.6.4", "rayon", "rmp-serde", @@ -2182,7 +2182,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -2226,7 +2226,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -2281,9 +2281,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2292,9 +2292,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -2302,9 +2302,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20 0.10.0", "getrandom 0.4.2", @@ -2599,9 +2599,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -2996,7 +2996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", @@ -3327,7 +3327,7 @@ checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", - "rand 0.10.0", + "rand 0.10.1", "serde_core", "wasm-bindgen", ] @@ -3887,7 +3887,7 @@ dependencies = [ "derive_more", "ed25519", "ed25519-dalek", - "rand 0.8.5", + "rand 0.8.6", "sha2", "x25519-dalek", "zeroize", From 510be5f6899d24b50daf7b2629fa44acbe402040 Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 13 May 2026 10:56:30 +0200 Subject: [PATCH 07/13] fix: Fixed formatting --- mina-frost-client/tests/duplicate_participant.rs | 10 ++++++++-- mina-frost-client/tests/helpers.rs | 6 ++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mina-frost-client/tests/duplicate_participant.rs b/mina-frost-client/tests/duplicate_participant.rs index cc256c5..c79b31a 100644 --- a/mina-frost-client/tests/duplicate_participant.rs +++ b/mina-frost-client/tests/duplicate_participant.rs @@ -107,8 +107,14 @@ fn sign_with_duplicate_participant( let coordinator = run_cli_spawn_piped(binary, cwd, &coord_args); - let session_id = get_session_id(binary, cwd, &participants[0].config_path, server_url, group_pk_hex) - .expect("no signing session appeared after coordinator started"); + let session_id = get_session_id( + binary, + cwd, + &participants[0].config_path, + server_url, + group_pk_hex, + ) + .expect("no signing session appeared after coordinator started"); let participant_children: Vec = participants .iter() diff --git a/mina-frost-client/tests/helpers.rs b/mina-frost-client/tests/helpers.rs index abc12af..b4733dc 100644 --- a/mina-frost-client/tests/helpers.rs +++ b/mina-frost-client/tests/helpers.rs @@ -366,7 +366,10 @@ pub fn sign_with_binary( } let mut children: Vec<(Vec, Child)> = Vec::new(); - children.push((coord_args.clone(), run_cli_spawn_piped(binary, cwd, &coord_args))); + children.push(( + coord_args.clone(), + run_cli_spawn_piped(binary, cwd, &coord_args), + )); let session_id = get_session_id( binary, @@ -396,7 +399,6 @@ pub fn sign_with_binary( } } - pub fn run_cli_spawn_quiet(binary: &Path, cwd: &Path, args: &[String]) -> io::Result { Command::new(binary) .args(args) From 485ef1230b3dd162fedf60f06ed9cba117812158 Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 13 May 2026 11:38:24 +0200 Subject: [PATCH 08/13] fix: Fixed examples to use flags --- .../signing_example/signing_example.sh | 31 +++++++++++++++---- .../tests/duplicate_participant.rs | 10 +++--- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/mina-frost-client/examples/signing_example/signing_example.sh b/mina-frost-client/examples/signing_example/signing_example.sh index a5a2843..ea0d98e 100755 --- a/mina-frost-client/examples/signing_example/signing_example.sh +++ b/mina-frost-client/examples/signing_example/signing_example.sh @@ -102,22 +102,41 @@ use_frost_client coordinator \ -o "$GENERATED_DIR/signature.json" & COORDINATOR_PID=$! -# Give coordinator time to create the signing session +# Wait for the coordinator to create the signing session and get its ID echo "Waiting for coordinator to create signing session..." -sleep 5 +SESSION_ID="" +for _i in $(seq 1 20); do + SESSION_ID=$(use_frost_client sessions \ + -c "$GENERATED_DIR/alice.toml" \ + --server-url "$SERVER_URL" \ + --group "$GROUP_PUBLIC_KEY" 2>&1 \ + | sed -n 's/^Session with ID //p' | head -n1) + [ -n "$SESSION_ID" ] && break + sleep 0.5 +done + +if [ -z "$SESSION_ID" ]; then + echo "ERROR: Could not get session ID from coordinator" + exit 1 +fi +echo "Session ID: $SESSION_ID" echo "Starting participant (Bob)..." -echo "y" | use_frost_client participant \ +use_frost_client participant \ -c "$GENERATED_DIR/bob.toml" \ --server-url "$SERVER_URL" \ - --group "$GROUP_PUBLIC_KEY" & + --group "$GROUP_PUBLIC_KEY" \ + --session "$SESSION_ID" \ + -y & BOB_PID=$! echo "Starting participant (Eve)..." -echo "y" | use_frost_client participant \ +use_frost_client participant \ -c "$GENERATED_DIR/eve.toml" \ --server-url "$SERVER_URL" \ - --group "$GROUP_PUBLIC_KEY" & + --group "$GROUP_PUBLIC_KEY" \ + --session "$SESSION_ID" \ + -y & EVE_PID=$! # Wait for completion diff --git a/mina-frost-client/tests/duplicate_participant.rs b/mina-frost-client/tests/duplicate_participant.rs index c79b31a..e118ef2 100644 --- a/mina-frost-client/tests/duplicate_participant.rs +++ b/mina-frost-client/tests/duplicate_participant.rs @@ -2,7 +2,7 @@ mod helpers; use helpers::{ binary_name, build_client_binary, form_group_with_dkg, get_session_id, greet_participants, - group_keys_from_config, introduce_participant, run_cli_spawn_piped, start_frostd, + group_keys_from_config, introduce_participant, run_cli_spawn_piped, start_frostd, ChildGuard, CliParticipant, SigningParticipant, }; use lazy_static::lazy_static; @@ -32,7 +32,7 @@ lazy_static! { const SIG_FILE: &str = "signature.json"; const NETWORK_ID: &str = "testnet"; -fn setup() -> Result { +fn setup() -> Result { if working_dir.exists() { fs::remove_dir_all(working_dir.clone())?; } @@ -49,7 +49,7 @@ fn setup() -> Result { built_binary.display() ); - start_frostd(&working_dir) + start_frostd(&working_dir).map(ChildGuard) } fn participant_args( @@ -149,7 +149,7 @@ fn sign_with_duplicate_participant( /// the signing session completes successfully. #[test] fn duplicate_participant_handled_gracefully() -> Result<()> { - let mut server_process = setup()?; + let server_process = setup()?; let participants = (0..3) .map(|x| introduce_participant(&binary_path, &working_dir, &x.to_string())) @@ -211,6 +211,6 @@ fn duplicate_participant_handled_gracefully() -> Result<()> { "coordinator crashed with SnowError (regression)\nstderr={stderr}" ); - server_process.kill()?; + drop(server_process); Ok(()) } From 208fc9bfdd37d8f25b44b0012fa38544cf6cb68d Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 13 May 2026 11:42:01 +0200 Subject: [PATCH 09/13] chore: Updated docs with new session flag --- SIGNING-WORKFLOW.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SIGNING-WORKFLOW.md b/SIGNING-WORKFLOW.md index b7bd6be..6762358 100644 --- a/SIGNING-WORKFLOW.md +++ b/SIGNING-WORKFLOW.md @@ -19,7 +19,7 @@ This guide walks you through signing Mina transactions using FROST threshold sig | Join DKG (participant) | `mina-frost-client dkg -d -s -t -c ` | | List groups | `mina-frost-client groups -c ` | | Coordinate signing | `mina-frost-client coordinator -g -S -m -o -n -c ` | -| Join signing | `mina-frost-client participant -g -c ` | +| Join signing | `mina-frost-client participant -g -S -c ` | | Build GraphQL | `mina-frost-client graphql-build -i -o ` | | Broadcast | `mina-frost-client graphql-broadcast -g -e ` | @@ -601,7 +601,7 @@ mina-frost-client participant \ | `-c` | Path to participant's config file | | `-s` | Server URL (optional if stored in group config) | | `-g` | Group public key | -| `-S` | Session ID (optional if only one active session) | +| `-S` | Session ID (use `sessions` command to list if coordinator output was lost) | | `-y` | Auto-approve signing (skip confirmation prompt) | **Example:** @@ -611,6 +611,7 @@ mina-frost-client participant \ -c ~/.frost/bob.toml \ -s localhost:2744 \ -g \ + -S \ -y # Eve joins @@ -618,6 +619,7 @@ mina-frost-client participant \ -c ~/.frost/eve.toml \ -s localhost:2744 \ -g \ + -S \ -y ``` From 6258493d2cbde27df1c0b3aff9451af037c65fc8 Mon Sep 17 00:00:00 2001 From: jCabala Date: Fri, 15 May 2026 19:47:43 +0200 Subject: [PATCH 10/13] fix: Ignored the assets from the duplicate participant test --- .gitignore | 1 + .../tests/assets-duplicate/0.toml | 31 ------------------- .../tests/assets-duplicate/1.toml | 31 ------------------- .../tests/assets-duplicate/2.toml | 31 ------------------- .../assets-duplicate/localhost+2-key.pem | 28 ----------------- .../tests/assets-duplicate/localhost+2.pem | 25 --------------- .../tests/assets-duplicate/signature.json | 30 ------------------ 7 files changed, 1 insertion(+), 176 deletions(-) delete mode 100644 mina-frost-client/tests/assets-duplicate/0.toml delete mode 100644 mina-frost-client/tests/assets-duplicate/1.toml delete mode 100644 mina-frost-client/tests/assets-duplicate/2.toml delete mode 100644 mina-frost-client/tests/assets-duplicate/localhost+2-key.pem delete mode 100644 mina-frost-client/tests/assets-duplicate/localhost+2.pem delete mode 100644 mina-frost-client/tests/assets-duplicate/signature.json diff --git a/.gitignore b/.gitignore index 63ad03f..3267c9b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ target/ .vscode/ mina-frost-client/tests/assets/ +mina-frost-client/tests/assets-duplicate/ # GraphQL schemas *.graphql diff --git a/mina-frost-client/tests/assets-duplicate/0.toml b/mina-frost-client/tests/assets-duplicate/0.toml deleted file mode 100644 index ef46e26..0000000 --- a/mina-frost-client/tests/assets-duplicate/0.toml +++ /dev/null @@ -1,31 +0,0 @@ -version = 0 - -[communication_key] -privkey = "1d9ffe8c35772755d735c20860bbfc929d5d6b2a6a7f7a5ec6a991aa290d94f2" -pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" - -[contact.1] -name = "1" -pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" - -[contact.2] -name = "2" -pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80] -description = "Duplicate Test Group" -public_key_package = "009b56619e03be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab30914227dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de71090071c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c800102" -key_package = "009b56619edabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422fdda4a7aa6d5b845169d9712025e50327a13955517ae81ac9142c93f320a16097dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de710900de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c8002" -server_url = "localhost:2744" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25] -identifier = "71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25" -pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621] -identifier = "be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621" -pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422] -identifier = "dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422" -pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" diff --git a/mina-frost-client/tests/assets-duplicate/1.toml b/mina-frost-client/tests/assets-duplicate/1.toml deleted file mode 100644 index c88d9c1..0000000 --- a/mina-frost-client/tests/assets-duplicate/1.toml +++ /dev/null @@ -1,31 +0,0 @@ -version = 0 - -[communication_key] -privkey = "0f679ef84f970808b3296451528dc1155719d42c92e4a8e703039dcac2b6a593" -pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" - -[contact.0] -name = "0" -pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" - -[contact.2] -name = "2" -pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80] -description = "Duplicate Test Group" -public_key_package = "009b56619e03be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab30914227dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de71090071c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c800102" -key_package = "009b56619ebe27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621152153e6f1e287a65ca63793b1edb497e9eeb0d93bb03ae61c688908fb10e827dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c8002" -server_url = "localhost:2744" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25] -identifier = "71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25" -pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621] -identifier = "be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621" -pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422] -identifier = "dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422" -pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" diff --git a/mina-frost-client/tests/assets-duplicate/2.toml b/mina-frost-client/tests/assets-duplicate/2.toml deleted file mode 100644 index de2ff79..0000000 --- a/mina-frost-client/tests/assets-duplicate/2.toml +++ /dev/null @@ -1,31 +0,0 @@ -version = 0 - -[communication_key] -privkey = "d8b8df0097f47d6ba0862f346ccbd8bffdfc928a035a8d85bd796c450d670220" -pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" - -[contact.0] -name = "0" -pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" - -[contact.1] -name = "1" -pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80] -description = "Duplicate Test Group" -public_key_package = "009b56619e03be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621dbb860b8b51085b6036186c491313b1c68af7a061ca2f4c220aef0447fc86c1680dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab30914227dc4bdaeb06b3c2cb7e04fb1805ed90696fdaf8b2a31ee74fbcd7d80d8de71090071c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c800102" -key_package = "009b56619e71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e254f4012186a57ff3b9e9510ab65534749eb7732008793cf15712cb8857ecb0739d582c97944446d4953461adcc7a667cc29c7d75b6652e8faac78da34b9a4432880de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c8002" -server_url = "localhost:2744" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25] -identifier = "71c4c9886a979a4e42a7e2b7717f50b85058a41e8d8b81ea822dffe9eded2e25" -pubkey = "bc0a77414b6d5106baaa1ccb14166bd034a6a7fb0784d5c6a4f193a1321d4342" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621] -identifier = "be27d1ddd1fa93611f769b8abb4dbc983497361398366f488a84e6e63585f621" -pubkey = "ac88ffe06b7c243950347719b730846c1b804d10c9f6b6b71bd468903630ff46" - -[group.de9e7f7f1cc5e692d68729fa32dd2a51a49fa28a26b11021fb335029fb95923c80.participant.dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422] -identifier = "dabde6979f55068961e7f02b6585d4a7e827496ba419354066d0be1ab3091422" -pubkey = "90f0364a0301811dd32d9056c457c8b0510aed8db299eb68afe81d4133e3dd05" diff --git a/mina-frost-client/tests/assets-duplicate/localhost+2-key.pem b/mina-frost-client/tests/assets-duplicate/localhost+2-key.pem deleted file mode 100644 index 509ef75..0000000 --- a/mina-frost-client/tests/assets-duplicate/localhost+2-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4FMCfeQifMjOk -bvR4dZvs2eLYn1+X8clXlpb47a7Ez1jkCbKHttv/qZKB8FjXoL0yrfvXJ9swbSu8 -sraBeJ18AjJWKsLzdPQDQv8k/MjvxENV9vKyQJ5rsr0Cxg5dtXjKs+Va6PuT/UMW -RZ79HN4qWhLBif/muhRmLCT4VLlSCZrEhY9/ZFlQBE3/QQJEp8hpQS52mvPDCnPM -L7sHPB65lzKfSJmzEGRHE4568h/YUT+Vr1x+DqaDd1rt/IMPU/NihkNyR0nFV9J5 -c1BVf9kDTv6esxO2oP7ieZsfFuacd8xYr+KjiyfMXMfSKz+NedKzyp9a0lQtLKeP -XTTg5hxXAgMBAAECggEBAIUjPU4aFApQHNnJBjg5l9Tuogta+aDD14Pp/rd78POX -pAsKmH1f3C5FTxl75EXQlAabn5I12WplC0UnXKi3u+99BzsbInVDGVlik3cE/a+s -m6cJPO9/nRT36YygArP0FLFG+5OM/qhCfMsW/67Buk2XaAli7hrKawaa1wJlBMQ3 -xATaNN+ROtv3tQEi+Rt4T/NDd/ihbn58hQz/+2253c8F4SWANBPx00n/Kc4qh/lW -VJ3DC2sWfKZTOJj2lfxuak1fPaS0V1vM2Mdhw/MHLOXrhtusEse/uMCEJpoHCn27 -4PEkF4nHEWGVoHBWiJNpUwLA/121DfMeQFjBCsMgYEECgYEA3l5nZxjvIXkER1rL -TjUeYRENgxqw4oCFuZOo4a4Z5bNThvl8jiEWzvlynDNtNvpZmgsNmUtW6XHGgnAd -PDt/xryW875ORyso9cYoRsD/4+BqNFOVsYBzmw+8/rD5hlnmQ+bEJSRQcYb2pITb -3T9JGI1Ra1PBjqmMNywmizNzZ6UCgYEA0+vwTl6PsNV5WGKR0Z9dRWht2fT8/yga -VVMoe7A1u61IHrBgGzBLuS/5wkqFLc3cQ74XzAW6rZM5r/DvqF87tGIilNsQaAH8 -dVO0vxDU/SUubpHfIY2+bqqzaawK8g6ZfXyQ1F1n7rIRvoL6elFgm9cfzAkztL+/ -cVft1ybXk0sCgYAaoqAsHzZ1gfNbQKrdgUtUQSCnNYk+eDqMUf2Gmr4LX4F/x159 -/8SlogdyYk6QqUgWRYCpffa5G6G1egxmbJIPkgNays7Bg4/ycHvejbuY5gaSofhq -PIKcbjQrJbOdviLwuzF/aWwhTRM5/ZgAVlZBFJxOCMhEeFaNPUKYpd8K6QKBgQC0 -2zQ4DadfaK+oaGa9mN3GsDqfud53+0eN7ewsstMImfdkiW1bhrn5DyJ9V4+U6YzD -G2W/rlwEahLfPiWpcazIYr/Ufafgu2Ey1/722GyMpcCciEz+m7MbSv7WfgbRUPhZ -CYeoVRavtLRD/A+7d9uU4+C1Bl1kMgg1uK634OQ1RQKBgQDPkGxLWLoUKORtJHPy -4+Z5soKs8ChgmTUCeNQ4SRq27rprmDUoGLswOfWsroZq6eLWKvkcKRxSCbm1cl+w -W5dzBsdv1nmGbxhjknFa3fogU0o6sLy+ifPmQdTF4GccOrVlAHcJE/3gjHlTjL7p -V9e7faojkR6/xJ/b70IFEXLG9w== ------END PRIVATE KEY----- diff --git a/mina-frost-client/tests/assets-duplicate/localhost+2.pem b/mina-frost-client/tests/assets-duplicate/localhost+2.pem deleted file mode 100644 index 1165564..0000000 --- a/mina-frost-client/tests/assets-duplicate/localhost+2.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEHjCCAoagAwIBAgIQDsZghV1VTzBzuGrEUs2tozANBgkqhkiG9w0BAQsFADBb -MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExGDAWBgNVBAsMD2pjYWJh -bGFAamNhYmFsYTEfMB0GA1UEAwwWbWtjZXJ0IGpjYWJhbGFAamNhYmFsYTAeFw0y -NjA1MTMwODMwNDBaFw0yODA4MTMwODMwNDBaMEMxJzAlBgNVBAoTHm1rY2VydCBk -ZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEYMBYGA1UECwwPamNhYmFsYUBqY2FiYWxh -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuBTAn3kInzIzpG70eHWb -7Nni2J9fl/HJV5aW+O2uxM9Y5Amyh7bb/6mSgfBY16C9Mq371yfbMG0rvLK2gXid -fAIyVirC83T0A0L/JPzI78RDVfbyskCea7K9AsYOXbV4yrPlWuj7k/1DFkWe/Rze -KloSwYn/5roUZiwk+FS5UgmaxIWPf2RZUARN/0ECRKfIaUEudprzwwpzzC+7Bzwe -uZcyn0iZsxBkRxOOevIf2FE/la9cfg6mg3da7fyDD1PzYoZDckdJxVfSeXNQVX/Z -A07+nrMTtqD+4nmbHxbmnHfMWK/io4snzFzH0is/jXnSs8qfWtJULSynj1004OYc -VwIDAQABo3YwdDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw -HwYDVR0jBBgwFoAUNlUPrs9TEKJga6jrQcSeBDiCVkIwLAYDVR0RBCUwI4IJbG9j -YWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IB -gQA8wi2xE0nO2l2kJTU/piOWK25ApdMKIt/bGpQCveuiaJmUUUr3dmfH36D4NQY4 -AR28l6k/D7xhv7X9YCJVSTTazxXGiC1aB8mpjjJPnyg2LhNkXyKkfmhMpyTyUYIP -/X54aF28eQwOSYNMt3KZIedaL8vg/kJefdnnufcolhpOxwZcj90nAwNLvPlRbfUI -OvnabgyeoNf9yV59+hxWR6jYMqcROd8sIMYsTecnyy1x/Fxyz3r/Xotwyqh4YVH6 -OqG2uGUbTLfXg8NBLUdePzc9FbiH6lrM1v6+m5WZv8LvSWJFZxnDH2/iCTDkIgw9 -n7bOtxXrdMJg3J8LI1Wxnhh87BBIgQzQi+TO43ftL2yFcDuvLW28zoZtzHXD5ETK -6KIJVUESz5XMz3NzzSuMYOW690WQteWsVCAUUDVxiNUyFAIMUcuolFrxJVU7JSWr -RgLrR2xgsNhvo4TFZzDvGmWLLaX/5xolrSVDTO/7CmDQsTU5XzI7m61Qov/3P7nY -zzI= ------END CERTIFICATE----- diff --git a/mina-frost-client/tests/assets-duplicate/signature.json b/mina-frost-client/tests/assets-duplicate/signature.json deleted file mode 100644 index 479bacc..0000000 --- a/mina-frost-client/tests/assets-duplicate/signature.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "publicKey": { - "address": "B62qqv4t1oUzJep9zQsKrzTTcMnqFDZmyND3VHMaSSMamhFLxrpNhDA" - }, - "signature": { - "field": "20383297309219312205605853359266608882627991642556524872147742446783289839449", - "scalar": "17509109384997598891982644786121795253567372265334850085803366484295595123976", - "base58": "7mXAHRrXJ2evk7Lo9vnTmdfyibL3FcmM9GJJBr1sq8U4Y9mvvCzhPyiKDdBiS2YdsNwi3v8w1B6rFdW8PCc2cNM77Pmwu88E" - }, - "payload": { - "networkId": "testnet", - "kind": { - "tag": "Legacy", - "transaction": { - "to": "B62qkcvM4DZE7k23ZHMLt1uaMVcixuxxuyz1XNJNCLkFbitDdUHxWs1", - "from": "B62qkcvM4DZE7k23ZHMLt1uaMVcixuxxuyz1XNJNCLkFbitDdUHxWs1", - "fee": "1000000000", - "amount": "1000000000", - "nonce": "1", - "memo": "Hello Mina x FROST from the Rasp", - "valid_until": "4294967295", - "tag": [ - false, - false, - false - ] - } - } - } -} From aa73edd71d2e98620db580001f7f1960427cb1be Mon Sep 17 00:00:00 2001 From: jCabala Date: Fri, 15 May 2026 20:06:14 +0200 Subject: [PATCH 11/13] fix: Fixed duplicate code and added Display trait to PubKey --- mina-frost-client/src/api.rs | 6 +++++ .../src/coordinator/comms/http.rs | 6 ++--- .../tests/duplicate_participant.rs | 24 ++----------------- mina-frost-client/tests/helpers.rs | 2 +- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/mina-frost-client/src/api.rs b/mina-frost-client/src/api.rs index b414d94..479c915 100644 --- a/mina-frost-client/src/api.rs +++ b/mina-frost-client/src/api.rs @@ -76,6 +76,12 @@ impl std::fmt::Debug for PublicKey { } } +impl std::fmt::Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(&self.0)) + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SendArgs { pub session_id: Uuid, diff --git a/mina-frost-client/src/coordinator/comms/http.rs b/mina-frost-client/src/coordinator/comms/http.rs index 17720db..f05cac4 100644 --- a/mina-frost-client/src/coordinator/comms/http.rs +++ b/mina-frost-client/src/coordinator/comms/http.rs @@ -129,7 +129,7 @@ impl Comms for HTTPComms { for msg in r.msgs { if commitment_senders.contains(&msg.sender) { eprintln!( - "Warning: participant {:?} attempted to rejoin the session; ignoring", + "Warning: participant {} attempted to rejoin the session; ignoring", msg.sender ); continue; @@ -193,7 +193,7 @@ impl Comms for HTTPComms { eprintln!("Waiting for participants to send their SignatureShares..."); - let mut seen_share_senders: HashSet = HashSet::new(); + let mut seen_share_senders: HashSet = HashSet::new(); loop { let r = self .client @@ -211,7 +211,7 @@ impl Comms for HTTPComms { Ok(msg) => msg, Err(_) => { eprintln!( - "Warning: failed to decrypt message from {:?}; ignoring", + "Warning: failed to decrypt message from {}; ignoring", sender ); continue; diff --git a/mina-frost-client/tests/duplicate_participant.rs b/mina-frost-client/tests/duplicate_participant.rs index e118ef2..cc8ffbc 100644 --- a/mina-frost-client/tests/duplicate_participant.rs +++ b/mina-frost-client/tests/duplicate_participant.rs @@ -2,8 +2,8 @@ mod helpers; use helpers::{ binary_name, build_client_binary, form_group_with_dkg, get_session_id, greet_participants, - group_keys_from_config, introduce_participant, run_cli_spawn_piped, start_frostd, ChildGuard, - CliParticipant, SigningParticipant, + group_keys_from_config, introduce_participant, participant_args, run_cli_spawn_piped, + start_frostd, ChildGuard, CliParticipant, SigningParticipant, }; use lazy_static::lazy_static; use std::fs; @@ -52,26 +52,6 @@ fn setup() -> Result { start_frostd(&working_dir).map(ChildGuard) } -fn participant_args( - participant: &SigningParticipant, - server_url: &str, - group_pk_hex: &str, - session_id: &str, -) -> Vec { - vec![ - "participant".to_string(), - "-c".to_string(), - participant.config_path.clone(), - "-s".to_string(), - server_url.to_string(), - "--group".to_string(), - group_pk_hex.to_string(), - "-S".to_string(), - session_id.to_string(), - "-y".to_string(), - ] -} - fn sign_with_duplicate_participant( binary: &Path, cwd: &Path, diff --git a/mina-frost-client/tests/helpers.rs b/mina-frost-client/tests/helpers.rs index b4733dc..d2e9379 100644 --- a/mina-frost-client/tests/helpers.rs +++ b/mina-frost-client/tests/helpers.rs @@ -302,7 +302,7 @@ pub fn get_session_id( None } -fn participant_args( +pub fn participant_args( participant: &SigningParticipant, server_url: &str, group_pk_hex: &str, From 5970231b24ab313ea6017002226d72adade78e3a Mon Sep 17 00:00:00 2001 From: jCabala Date: Sat, 16 May 2026 19:27:36 +0200 Subject: [PATCH 12/13] feat: Added comments for the snow error cases --- .../src/coordinator/comms/http.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/mina-frost-client/src/coordinator/comms/http.rs b/mina-frost-client/src/coordinator/comms/http.rs index f05cac4..66cbe99 100644 --- a/mina-frost-client/src/coordinator/comms/http.rs +++ b/mina-frost-client/src/coordinator/comms/http.rs @@ -127,6 +127,7 @@ impl Comms for HTTPComms { }) .await?; for msg in r.msgs { + // A participant may rejoin with a fresh Noise context; warn and skip to avoid DoS. if commitment_senders.contains(&msg.sender) { eprintln!( "Warning: participant {} attempted to rejoin the session; ignoring", @@ -135,7 +136,17 @@ impl Comms for HTTPComms { continue; } let sender = msg.sender.clone(); - let msg = cipher.decrypt(msg)?; + // A malicious or broken participant must not be able to kill the coordinator. + let msg = match cipher.decrypt(msg) { + Ok(msg) => msg, + Err(_) => { + eprintln!( + "Warning: failed to decrypt message from {}; ignoring", + sender + ); + continue; + } + }; self.state.recv(msg)?; commitment_senders.insert(sender); } @@ -203,10 +214,16 @@ impl Comms for HTTPComms { }) .await?; for msg in r.msgs { + // A participant may rejoin with a fresh Noise context; warn and skip to avoid DoS. if seen_share_senders.contains(&msg.sender) { + eprintln!( + "Warning: participant {} attempted to rejoin the session; ignoring", + msg.sender + ); continue; } let sender = msg.sender.clone(); + // A malicious or broken participant must not be able to kill the coordinator. let msg = match cipher.decrypt(msg) { Ok(msg) => msg, Err(_) => { From 44465822baa520ada6495e23fb5b5ee0ed57a927 Mon Sep 17 00:00:00 2001 From: jCabala Date: Wed, 20 May 2026 23:33:00 +0200 Subject: [PATCH 13/13] feat: Added ignoring invalid participant on all rcv msgs --- .../src/coordinator/comms/http.rs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/mina-frost-client/src/coordinator/comms/http.rs b/mina-frost-client/src/coordinator/comms/http.rs index 66cbe99..98b89b6 100644 --- a/mina-frost-client/src/coordinator/comms/http.rs +++ b/mina-frost-client/src/coordinator/comms/http.rs @@ -147,8 +147,17 @@ impl Comms for HTTPComms { continue; } }; - self.state.recv(msg)?; - commitment_senders.insert(sender); + match self.state.recv(msg) { + Ok(()) => { + commitment_senders.insert(sender); + } + Err(e) => { + eprintln!( + "Warning: ignoring invalid commitment from {}: {}", + sender, e + ); + } + } } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); @@ -234,8 +243,17 @@ impl Comms for HTTPComms { continue; } }; - self.state.recv(msg)?; - seen_share_senders.insert(sender); + match self.state.recv(msg) { + Ok(()) => { + seen_share_senders.insert(sender); + } + Err(e) => { + eprintln!( + "Warning: ignoring invalid signature share from {}: {}", + sender, e + ); + } + } } tokio::time::sleep(Duration::from_secs(2)).await; eprint!(".");