-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
//! Key generation. | ||
use curv::elliptic::curves::secp256_k1::Secp256k1; | ||
use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::{ | ||
Keygen, LocalKey, ProtocolMessage, | ||
}; | ||
|
||
use wasm_bindgen::prelude::*; | ||
|
||
use crate::Parameters; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use round_based::{Msg, StateMachine}; | ||
|
||
//use crate::{console_log, log}; | ||
|
||
/// Wrapper for a round `Msg` that includes the round | ||
/// number so that we can ensure round messages are grouped | ||
/// together and out of order messages can thus be handled correctly. | ||
#[derive(Serialize)] | ||
struct RoundMsg { | ||
round: u16, | ||
sender: u16, | ||
receiver: Option<u16>, | ||
body: ProtocolMessage, | ||
} | ||
|
||
impl RoundMsg { | ||
fn from_round( | ||
round: u16, | ||
messages: Vec<Msg<<Keygen as StateMachine>::MessageBody>>, | ||
) -> Vec<Self> { | ||
messages | ||
.into_iter() | ||
.map(|m| RoundMsg { | ||
round, | ||
sender: m.sender, | ||
receiver: m.receiver, | ||
body: m.body, | ||
}) | ||
.collect::<Vec<_>>() | ||
} | ||
} | ||
|
||
/// Session information for a single party. | ||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] | ||
pub struct PartySignup { | ||
/// Unique index for the party. | ||
pub number: u16, | ||
/// Session identifier. | ||
pub uuid: String, | ||
} | ||
|
||
/// Generated key share. | ||
#[derive(Clone, Debug, Serialize, Deserialize)] | ||
pub struct KeyShare { | ||
/// The secret private key. | ||
#[serde(rename = "localKey")] | ||
pub local_key: LocalKey<Secp256k1>, | ||
/// The public key. | ||
#[serde(rename = "publicKey")] | ||
pub public_key: Vec<u8>, | ||
/// Address generated from the public key. | ||
pub address: String, | ||
} | ||
|
||
/// Round-based key share generator. | ||
#[wasm_bindgen] | ||
pub struct KeyGenerator { | ||
inner: Keygen, | ||
} | ||
|
||
#[wasm_bindgen] | ||
impl KeyGenerator { | ||
/// Create a key generator. | ||
#[wasm_bindgen(constructor)] | ||
pub fn new(parameters: JsValue, party_signup: JsValue) -> Result<KeyGenerator, JsError> { | ||
let params: Parameters = parameters.into_serde()?; | ||
let PartySignup { number, uuid } = party_signup.into_serde::<PartySignup>()?; | ||
let (party_num_int, _uuid) = (number, uuid); | ||
Ok(Self { | ||
inner: Keygen::new(party_num_int, params.threshold, params.parties)?, | ||
}) | ||
} | ||
|
||
/// Handle an incoming message. | ||
#[wasm_bindgen(js_name = "handleIncoming")] | ||
pub fn handle_incoming(&mut self, message: JsValue) -> Result<(), JsError> { | ||
let message: Msg<<Keygen as StateMachine>::MessageBody> = message.into_serde()?; | ||
self.inner.handle_incoming(message)?; | ||
Ok(()) | ||
} | ||
|
||
/// Proceed to the next round. | ||
pub fn proceed(&mut self) -> Result<JsValue, JsError> { | ||
self.inner.proceed()?; | ||
let messages = self.inner.message_queue().drain(..).collect(); | ||
let round = self.inner.current_round(); | ||
let messages = RoundMsg::from_round(round, messages); | ||
Ok(JsValue::from_serde(&(round, &messages))?) | ||
} | ||
|
||
/// Create the key share. | ||
pub fn create(&mut self) -> Result<JsValue, JsError> { | ||
let local_key = self.inner.pick_output().unwrap()?; | ||
let public_key = local_key.public_key().to_bytes(false).to_vec(); | ||
let key_share = KeyShare { | ||
local_key, | ||
address: crate::utils::address(&public_key), | ||
public_key, | ||
}; | ||
Ok(JsValue::from_serde(&key_share)?) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod keygen; | ||
pub mod sign; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
//! Message signing. | ||
use curv::{arithmetic::Converter, elliptic::curves::Secp256k1, BigInt}; | ||
use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ | ||
party_i::{verify, SignatureRecid}, | ||
state_machine::{ | ||
keygen::LocalKey, | ||
sign::{ | ||
CompletedOfflineStage, OfflineProtocolMessage, OfflineStage, PartialSignature, | ||
SignManual, | ||
}, | ||
}, | ||
}; | ||
|
||
use round_based::{Msg, StateMachine}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::convert::TryInto; | ||
use wasm_bindgen::prelude::*; | ||
|
||
//use crate::{console_log, log}; | ||
|
||
const ERR_COMPLETED_OFFLINE_STAGE: &str = | ||
"completed offline stage unavailable, has partial() been called?"; | ||
|
||
/// Wrapper for a round `Msg` that includes the round | ||
/// number so that we can ensure round messages are grouped | ||
/// together and out of order messages can thus be handled correctly. | ||
#[derive(Serialize)] | ||
struct RoundMsg { | ||
round: u16, | ||
sender: u16, | ||
receiver: Option<u16>, | ||
body: OfflineProtocolMessage, | ||
} | ||
|
||
impl RoundMsg { | ||
fn from_round( | ||
round: u16, | ||
messages: Vec<Msg<<OfflineStage as StateMachine>::MessageBody>>, | ||
) -> Vec<Self> { | ||
messages | ||
.into_iter() | ||
.map(|m| RoundMsg { | ||
round, | ||
sender: m.sender, | ||
receiver: m.receiver, | ||
body: m.body, | ||
}) | ||
.collect::<Vec<_>>() | ||
} | ||
} | ||
|
||
/// Signature generated by a signer. | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Signature { | ||
/// The generated ECDSA signature. | ||
pub signature: SignatureRecid, | ||
/// The public key. | ||
#[serde(rename = "publicKey")] | ||
pub public_key: Vec<u8>, | ||
/// Address generated from the public key. | ||
pub address: String, | ||
} | ||
|
||
/// Round-based signing protocol. | ||
#[wasm_bindgen] | ||
pub struct Signer { | ||
inner: OfflineStage, | ||
completed: Option<(CompletedOfflineStage, BigInt)>, | ||
} | ||
|
||
#[wasm_bindgen] | ||
impl Signer { | ||
/// Create a signer. | ||
#[wasm_bindgen(constructor)] | ||
pub fn new( | ||
index: JsValue, | ||
participants: JsValue, | ||
local_key: JsValue, | ||
) -> Result<Signer, JsError> { | ||
let index: u16 = index.into_serde()?; | ||
let participants: Vec<u16> = participants.into_serde()?; | ||
let local_key: LocalKey<Secp256k1> = local_key.into_serde()?; | ||
Ok(Signer { | ||
inner: OfflineStage::new(index, participants.clone(), local_key)?, | ||
completed: None, | ||
}) | ||
} | ||
|
||
/// Handle an incoming message. | ||
#[wasm_bindgen(js_name = "handleIncoming")] | ||
pub fn handle_incoming(&mut self, message: JsValue) -> Result<(), JsError> { | ||
let message: Msg<<OfflineStage as StateMachine>::MessageBody> = message.into_serde()?; | ||
self.inner.handle_incoming(message)?; | ||
Ok(()) | ||
} | ||
|
||
/// Proceed to the next round. | ||
pub fn proceed(&mut self) -> Result<JsValue, JsError> { | ||
if self.inner.wants_to_proceed() { | ||
self.inner.proceed()?; | ||
let messages = self.inner.message_queue().drain(..).collect(); | ||
let round = self.inner.current_round(); | ||
let messages = RoundMsg::from_round(round, messages); | ||
Ok(JsValue::from_serde(&(round, &messages))?) | ||
} else { | ||
Ok(JsValue::from_serde(&false)?) | ||
} | ||
} | ||
|
||
/// Generate the completed offline stage and store the result | ||
/// internally to be used when `create()` is called. | ||
/// | ||
/// Return a partial signature that must be sent to the other | ||
/// signing participents. | ||
pub fn partial(&mut self, message: JsValue) -> Result<JsValue, JsError> { | ||
let message: Vec<u8> = message.into_serde()?; | ||
let message: [u8; 32] = message.as_slice().try_into()?; | ||
let completed_offline_stage = self.inner.pick_output().unwrap()?; | ||
let data = BigInt::from_bytes(&message); | ||
let (_sign, partial) = SignManual::new(data.clone(), completed_offline_stage.clone())?; | ||
|
||
self.completed = Some((completed_offline_stage, data)); | ||
|
||
Ok(JsValue::from_serde(&partial)?) | ||
} | ||
|
||
/// Create and verify the signature. | ||
pub fn create(&mut self, partials: JsValue) -> Result<JsValue, JsError> { | ||
let partials: Vec<PartialSignature> = partials.into_serde()?; | ||
|
||
let (completed_offline_stage, data) = self | ||
.completed | ||
.take() | ||
.ok_or_else(|| JsError::new(ERR_COMPLETED_OFFLINE_STAGE))?; | ||
let pk = completed_offline_stage.public_key().clone(); | ||
|
||
let (sign, _partial) = SignManual::new(data.clone(), completed_offline_stage.clone())?; | ||
|
||
let signature = sign.complete(&partials)?; | ||
verify(&signature, &pk, &data) | ||
.map_err(|e| JsError::new(&format!("failed to verify signature: {:?}", e)))?; | ||
|
||
let public_key = pk.to_bytes(false).to_vec(); | ||
let result = Signature { | ||
signature, | ||
address: crate::utils::address(&public_key), | ||
public_key, | ||
}; | ||
|
||
Ok(JsValue::from_serde(&result)?) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
//! Webassembly bindings to the GG2020 protocol in [multi-party-ecdsa](https://github.com/ZenGo-X/multi-party-ecdsa) for MPC key generation and signing. | ||
#![deny(missing_docs)] | ||
use serde::{Deserialize, Serialize}; | ||
use wasm_bindgen::prelude::*; | ||
|
||
extern crate wasm_bindgen; | ||
|
||
#[cfg(all(test, target_arch = "wasm32"))] | ||
extern crate wasm_bindgen_test; | ||
|
||
#[doc(hidden)] | ||
#[wasm_bindgen(start)] | ||
pub fn start() { | ||
console_error_panic_hook::set_once(); | ||
if let Ok(_) = wasm_log::try_init(wasm_log::Config::new(log::Level::Debug)) { | ||
log::info!("WASM logger initialized"); | ||
} | ||
log::info!("WASM: module started {:?}", std::thread::current().id()); | ||
} | ||
|
||
// Required for rayon thread support | ||
pub use wasm_bindgen_rayon::init_thread_pool; | ||
|
||
mod gg2020; | ||
mod utils; | ||
|
||
// Expose these types for API documentation. | ||
pub use gg2020::keygen::{KeyGenerator, KeyShare, PartySignup}; | ||
pub use gg2020::sign::{Signature, Signer}; | ||
|
||
/// Parameters used during key generation. | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Parameters { | ||
/// Number of parties `n`. | ||
pub parties: u16, | ||
/// Threshold for signing `t`. | ||
/// | ||
/// The threshold must be crossed (`t + 1`) for signing | ||
/// to commence. | ||
pub threshold: u16, | ||
} | ||
|
||
impl Default for Parameters { | ||
fn default() -> Self { | ||
return Self { | ||
parties: 3, | ||
threshold: 1, | ||
}; | ||
} | ||
} | ||
|
||
/// Compute the Keccak256 hash of a value. | ||
#[wasm_bindgen] | ||
pub fn keccak256(message: JsValue) -> Result<JsValue, JsError> { | ||
use sha3::{Digest, Keccak256}; | ||
let message: Vec<u8> = message.into_serde()?; | ||
let digest = Keccak256::digest(&message).to_vec(); | ||
Ok(JsValue::from_serde(&digest)?) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
use sha3::{Digest, Keccak256}; | ||
|
||
/// Compute the address of an uncompressed public key (65 bytes). | ||
pub(crate) fn address(public_key: &Vec<u8>) -> String { | ||
// Remove the leading 0x04 | ||
let bytes = &public_key[1..]; | ||
let digest = Keccak256::digest(bytes); | ||
let final_bytes = &digest[12..]; | ||
format!("0x{}", hex::encode(&final_bytes)) | ||
} |