server: add multi-message support (#125)
This commit is contained in:
parent
cb77d35f26
commit
601cc8a409
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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?
|
||||
|
|
Loading…
Reference in New Issue