server: add multi-message support (#125)

This commit is contained in:
Conrado Gouvea 2024-02-22 10:30:17 -03:00 committed by GitHub
parent cb77d35f26
commit 601cc8a409
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 208 additions and 49 deletions

View File

@ -16,10 +16,17 @@ pub(crate) async fn create_new_session(
State(state): State<SharedState>,
Json(args): Json<CreateNewSessionArgs>,
) -> Result<Json<CreateNewSessionOutput>, AppError> {
if args.message_count == 0 {
return Err(AppError(
StatusCode::INTERNAL_SERVER_ERROR,
eyre!("invalid message_count"),
));
}
// Create new session object.
let id = Uuid::new_v4();
let session = Session {
identifiers: args.identifiers.iter().cloned().collect(),
message_count: args.message_count,
state: SessionState::WaitingForCommitments {
commitments: Default::default(),
},
@ -30,6 +37,24 @@ pub(crate) async fn create_new_session(
Ok(Json(user))
}
/// Implement the get_session_info API
pub(crate) async fn get_session_info(
State(state): State<SharedState>,
Json(args): Json<GetSessionInfoArgs>,
) -> Result<Json<GetSessionInfoOutput>, AppError> {
let state_lock = state.read().unwrap();
let session = state_lock.sessions.get(&args.session_id).ok_or(AppError(
StatusCode::NOT_FOUND,
eyre!("session ID not found"),
))?;
Ok(Json(GetSessionInfoOutput {
identifiers: session.identifiers.iter().copied().collect(),
message_count: session.message_count,
}))
}
/// Implement the send_commitments API
// TODO: get identifier from channel rather from arguments
pub(crate) async fn send_commitments(
@ -52,6 +77,12 @@ pub(crate) async fn send_commitments(
if !session.identifiers.contains(&args.identifier) {
return Err(AppError(StatusCode::NOT_FOUND, eyre!("invalid identifier")));
}
if args.commitments.len() != session.message_count as usize {
return Err(AppError(
StatusCode::INTERNAL_SERVER_ERROR,
eyre!("wrong number of commitments"),
));
}
// Add commitment to map.
// Currently ignores the possibility of overwriting previous values
// (it seems better to ignore overwrites, which could be caused by
@ -88,7 +119,17 @@ pub(crate) async fn get_commitments(
match &session.state {
SessionState::CommitmentsReady { commitments } => Ok(Json(GetCommitmentsOutput {
commitments: commitments.clone(),
// Convert the BTreeMap<Identifier, Vec<SigningCommitments>> map
// into a Vec<BTreeMap<Identifier, SigningCommitments>> map to make
// it easier for the coordinator to build the SigningPackages.
commitments: (0..session.message_count)
.map(|i| {
commitments
.iter()
.map(|(id, c)| (*id, c[i as usize]))
.collect()
})
.collect(),
})),
_ => Err(AppError(
StatusCode::INTERNAL_SERVER_ERROR,
@ -114,10 +155,19 @@ pub(crate) async fn send_signing_package(
match &mut session.state {
SessionState::CommitmentsReady { .. } => {
if args.signing_package.len() != session.message_count as usize
|| args.randomizer.len() != session.message_count as usize
{
return Err(AppError(
StatusCode::INTERNAL_SERVER_ERROR,
eyre!("wrong number of inputs"),
));
}
session.state = SessionState::WaitingForSignatureShares {
signing_package: args.signing_package,
signature_shares: Default::default(),
randomizer: args.randomizer,
aux_msg: args.aux_msg,
};
}
_ => {
@ -147,9 +197,11 @@ pub(crate) async fn get_signing_package(
signing_package,
signature_shares: _,
randomizer,
aux_msg,
} => Ok(Json(GetSigningPackageOutput {
signing_package: signing_package.clone(),
randomizer: *randomizer,
randomizer: randomizer.clone(),
aux_msg: aux_msg.clone(),
})),
_ => Err(AppError(
StatusCode::INTERNAL_SERVER_ERROR,
@ -179,10 +231,17 @@ pub(crate) async fn send_signature_share(
signing_package: _,
signature_shares,
randomizer: _,
aux_msg: _,
} => {
if !session.identifiers.contains(&args.identifier) {
return Err(AppError(StatusCode::NOT_FOUND, eyre!("invalid identifier")));
}
if args.signature_share.len() != session.message_count as usize {
return Err(AppError(
StatusCode::INTERNAL_SERVER_ERROR,
eyre!("wrong number of signature shares"),
));
}
// Currently ignoring the possibility of overwriting previous values
// (it seems better to ignore overwrites, which could be caused by
// poor networking connectivity leading to retries)
@ -219,7 +278,17 @@ pub(crate) async fn get_signature_shares(
match &session.state {
SessionState::SignatureSharesReady { signature_shares } => {
Ok(Json(GetSignatureSharesOutput {
signature_shares: signature_shares.clone(),
// Convert the BTreeMap<Identifier, Vec<SigningCommitments>> map
// into a Vec<BTreeMap<Identifier, SigningCommitments>> map to make
// it easier for the coordinator to build the SigningPackages.
signature_shares: (0..session.message_count)
.map(|i| {
signature_shares
.iter()
.map(|(id, s)| (*id, s[i as usize]))
.collect()
})
.collect(),
}))
}
_ => Err(AppError(

View File

@ -20,6 +20,7 @@ pub fn router() -> Router {
let shared_state = state::SharedState::default();
Router::new()
.route("/create_new_session", post(functions::create_new_session))
.route("/get_session_info", post(functions::get_session_info))
.route("/send_commitments", post(functions::send_commitments))
.route("/get_commitments", post(functions::get_commitments))
.route(

View File

@ -11,30 +11,38 @@ use reddsa::frost::redpallas as frost;
pub enum SessionState {
/// Waiting for participants to send their commitments.
WaitingForCommitments {
/// Commitments sent by participants so far.
commitments: BTreeMap<frost::Identifier, frost::round1::SigningCommitments>,
/// Commitments sent by participants so far, for each message being
/// signed.
commitments: BTreeMap<frost::Identifier, Vec<frost::round1::SigningCommitments>>,
},
/// Commitments have been sent by all participants; ready to be fetched by
/// the coordinator. Waiting for coordinator to send the SigningPackage.
CommitmentsReady {
/// All commitments sent by participants.
commitments: BTreeMap<frost::Identifier, frost::round1::SigningCommitments>,
/// All commitments sent by participants, for each message being signed.
commitments: BTreeMap<frost::Identifier, Vec<frost::round1::SigningCommitments>>,
},
/// SigningPackage ready to be fetched by participants. Waiting for
/// participants to send their signature shares.
WaitingForSignatureShares {
/// SigningPackage sent by the coordinator to be sent to participants.
signing_package: frost::SigningPackage,
/// Randomizer sent by coordinator to be sent to participants
/// SigningPackage sent by the coordinator to be sent to participants,
/// for each message being signed.
signing_package: Vec<frost::SigningPackage>,
/// Randomizer sent by coordinator to be sent to participants, for each
/// message being signed.
/// (Rerandomized FROST only. TODO: make it optional?)
randomizer: frost::round2::Randomizer,
/// Signature shares sent by participants so far.
signature_shares: BTreeMap<frost::Identifier, frost::round2::SignatureShare>,
randomizer: Vec<frost::round2::Randomizer>,
/// Auxiliary (optional) message. A context-specific data that is
/// supposed to be interpreted by the participants.
aux_msg: Vec<u8>,
/// Signature shares sent by participants so far, for each message being
/// signed.
signature_shares: BTreeMap<frost::Identifier, Vec<frost::round2::SignatureShare>>,
},
/// SignatureShares have been sent by all participants; ready to be fetched
/// by the coordinator.
SignatureSharesReady {
signature_shares: BTreeMap<frost::Identifier, frost::round2::SignatureShare>,
/// Signature shares sent by participants, for each message being signed.
signature_shares: BTreeMap<frost::Identifier, Vec<frost::round2::SignatureShare>>,
},
}
@ -50,6 +58,8 @@ impl Default for SessionState {
pub struct Session {
/// The set of identifiers for the session.
pub(crate) identifiers: BTreeSet<frost::Identifier>,
/// The number of messages being simultaneously signed.
pub(crate) message_count: u8,
/// The session state.
pub(crate) state: SessionState,
}

View File

@ -8,6 +8,7 @@ use reddsa::frost::redpallas as frost;
#[derive(Serialize, Deserialize)]
pub struct CreateNewSessionArgs {
pub identifiers: Vec<frost::Identifier>,
pub message_count: u8,
}
#[derive(Serialize, Deserialize)]
@ -15,11 +16,22 @@ pub struct CreateNewSessionOutput {
pub session_id: Uuid,
}
#[derive(Serialize, Deserialize)]
pub struct GetSessionInfoArgs {
pub session_id: Uuid,
}
#[derive(Serialize, Deserialize)]
pub struct GetSessionInfoOutput {
pub identifiers: Vec<frost::Identifier>,
pub message_count: u8,
}
#[derive(Serialize, Deserialize)]
pub struct SendCommitmentsArgs {
pub session_id: Uuid,
pub identifier: frost::Identifier,
pub commitments: frost::round1::SigningCommitments,
pub commitments: Vec<frost::round1::SigningCommitments>,
}
#[derive(Serialize, Deserialize)]
@ -29,14 +41,15 @@ pub struct GetCommitmentsArgs {
#[derive(Serialize, Deserialize)]
pub struct GetCommitmentsOutput {
pub commitments: BTreeMap<frost::Identifier, frost::round1::SigningCommitments>,
pub commitments: Vec<BTreeMap<frost::Identifier, frost::round1::SigningCommitments>>,
}
#[derive(Serialize, Deserialize)]
pub struct SendSigningPackageArgs {
pub session_id: Uuid,
pub signing_package: frost::SigningPackage,
pub randomizer: frost::round2::Randomizer,
pub signing_package: Vec<frost::SigningPackage>,
pub aux_msg: Vec<u8>,
pub randomizer: Vec<frost::round2::Randomizer>,
}
#[derive(Serialize, Deserialize)]
@ -46,15 +59,16 @@ pub struct GetSigningPackageArgs {
#[derive(Serialize, Deserialize)]
pub struct GetSigningPackageOutput {
pub signing_package: frost::SigningPackage,
pub randomizer: frost::round2::Randomizer,
pub signing_package: Vec<frost::SigningPackage>,
pub randomizer: Vec<frost::round2::Randomizer>,
pub aux_msg: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
pub struct SendSignatureShareArgs {
pub session_id: Uuid,
pub identifier: frost::Identifier,
pub signature_share: frost::round2::SignatureShare,
pub signature_share: Vec<frost::round2::SignatureShare>,
}
#[derive(Serialize, Deserialize)]
@ -64,7 +78,7 @@ pub struct GetSignatureSharesArgs {
#[derive(Serialize, Deserialize)]
pub struct GetSignatureSharesOutput {
pub signature_shares: BTreeMap<frost::Identifier, frost::round2::SignatureShare>,
pub signature_shares: Vec<BTreeMap<frost::Identifier, frost::round2::SignatureShare>>,
}
#[derive(Serialize, Deserialize)]

View File

@ -14,6 +14,7 @@ use reddsa::frost::redpallas as frost;
/// each client will run independently.
#[tokio::test]
async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
// Create key shares
let mut rng = thread_rng();
let (shares, pubkeys) =
frost::keys::generate_with_dealer(3, 2, frost::keys::IdentifierList::Default, &mut rng)
@ -28,33 +29,66 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
})
.collect();
// Instantiate test server using axum_test
let router = router();
let server = TestServer::new(router)?;
// As the coordinator, create a new signing session with all participants,
// for 2 messages
let res = server
.post("/create_new_session")
.json(&server::CreateNewSessionArgs {
identifiers: key_packages.keys().copied().collect::<Vec<_>>(),
message_count: 2,
})
.await;
res.assert_status_ok();
let r: server::CreateNewSessionOutput = res.json();
let session_id = r.session_id;
// Generate commitments (one SigningCommitments for each message)
// and send them to the server; for each participant
// Map to store the SigningNonces (for each message, for each participant)
let mut nonces_map = BTreeMap::<_, _>::new();
for (identifier, key_package) in key_packages.iter() {
let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), &mut rng);
nonces_map.insert(*identifier, nonces);
// As participant `identifier`
// Get the number of messages (the participants wouldn't know without
// asking the server).
let res = server
.post("/get_session_info")
.json(&server::GetSessionInfoArgs { session_id })
.await;
res.assert_status_ok();
let r: server::GetSessionInfoOutput = res.json();
// Generate SigningCommitments and SigningNonces for each message
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for _ in 0..r.message_count {
let (nonces, commitments) =
frost::round1::commit(key_package.signing_share(), &mut rng);
nonces_vec.push(nonces);
commitments_vec.push(commitments);
}
// Store nonces for later use
nonces_map.insert(*identifier, nonces_vec);
// Send commitments to server
let res = server
.post("/send_commitments")
.json(&server::SendCommitmentsArgs {
identifier: *identifier,
session_id,
commitments,
commitments: commitments_vec,
})
.await;
res.assert_status_ok();
}
// As the coordinator, get the commitments
let res = server
.post("/get_commitments")
.json(&server::GetCommitmentsArgs { session_id })
@ -63,22 +97,40 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
let r: server::GetCommitmentsOutput = res.json();
let commitments = r.commitments;
let message = "Hello, world!".as_bytes();
let signing_package = frost::SigningPackage::new(commitments, message);
let randomized_params =
frost::RandomizedParams::new(pubkeys.verifying_key(), &signing_package, &mut rng)?;
// As the coordinator, choose messages and create one SigningPackage
// and one RandomizedParams for each.
let message1 = "Hello, world!".as_bytes();
let message2 = "Ola mundo!".as_bytes();
let aux_msg = "Aux msg".as_bytes();
let messages = [message1, message2];
let signing_packages = messages
.iter()
.enumerate()
.map(|(i, msg)| frost::SigningPackage::new(commitments[i].clone(), msg))
.collect::<Vec<_>>();
let randomized_params = signing_packages
.iter()
.map(|p| frost::RandomizedParams::new(pubkeys.verifying_key(), p, &mut rng))
.collect::<Result<Vec<_>, _>>()?;
// As the coordinator, send the SigningPackages to the server
let res = server
.post("/send_signing_package")
.json(&server::SendSigningPackageArgs {
session_id,
signing_package: signing_package.clone(),
randomizer: *randomized_params.randomizer(),
signing_package: signing_packages.clone(),
randomizer: randomized_params.iter().map(|p| *p.randomizer()).collect(),
aux_msg: aux_msg.to_owned(),
})
.await;
res.assert_status_ok();
// As each participant, get SigningPackages and generate the SignatureShares
// for each.
for (identifier, key_package) in key_packages.iter() {
// As participant `identifier`
// Get SigningPackages
let res = server
.post("get_signing_package")
.json(&server::GetSigningPackageArgs { session_id })
@ -86,13 +138,23 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
res.assert_status_ok();
let r: server::GetSigningPackageOutput = res.json();
let signature_share = frost::round2::sign(
&r.signing_package,
&nonces_map[identifier],
key_package,
r.randomizer,
)?;
// Generate SignatureShares for each SigningPackage
let signature_share = r
.signing_package
.iter()
.zip(r.randomizer.iter())
.enumerate()
.map(|(i, (signing_package, randomizer))| {
frost::round2::sign(
signing_package,
&nonces_map[identifier][i],
key_package,
*randomizer,
)
})
.collect::<Result<Vec<_>, _>>()?;
// Send SignatureShares to the server
let res = server
.post("/send_signature_share")
.json(&server::SendSignatureShareArgs {
@ -104,6 +166,7 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
res.assert_status_ok();
}
// As the coordinator, get SignatureShares
let res = server
.post("/get_signature_shares")
.json(&server::GetSignatureSharesArgs { session_id })
@ -111,24 +174,25 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
res.assert_status_ok();
let r: server::GetSignatureSharesOutput = res.json();
let signature = frost::aggregate(
&signing_package,
&r.signature_shares,
&pubkeys,
&randomized_params,
)?;
randomized_params
.randomized_verifying_key()
.verify(message, &signature)?;
// Generate the final Signature for each message
let signatures = signing_packages
.iter()
.enumerate()
.map(|(i, p)| frost::aggregate(p, &r.signature_shares[i], &pubkeys, &randomized_params[i]))
.collect::<Result<Vec<_>, _>>()?;
// Close the session
let res = server
.post("/close_session")
.json(&server::CloseSessionArgs { session_id })
.await;
res.assert_status_ok();
println!("{}", res.text());
let _: () = res.json();
// Verify signatures to test if they were generated correctly
for (i, p) in randomized_params.iter().enumerate() {
p.randomized_verifying_key()
.verify(messages[i], &signatures[i])?;
}
Ok(())
}
@ -172,6 +236,7 @@ async fn test_http() -> Result<(), Box<dyn std::error::Error>> {
.post("http://127.0.0.1:2744/create_new_session")
.json(&server::CreateNewSessionArgs {
identifiers: key_packages.keys().copied().collect::<Vec<_>>(),
message_count: 1,
})
.send()
.await?