Skip to content

Commit

Permalink
Import wasm binding code.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpfs committed Mar 7, 2023
1 parent 76f95af commit 2faeb37
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 0 deletions.
113 changes: 113 additions & 0 deletions src/gg2020/keygen.rs
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)?)
}
}
2 changes: 2 additions & 0 deletions src/gg2020/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod keygen;
pub mod sign;
152 changes: 152 additions & 0 deletions src/gg2020/sign.rs
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)?)
}
}
59 changes: 59 additions & 0 deletions src/lib.rs
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)?)
}
10 changes: 10 additions & 0 deletions src/utils.rs
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))
}

0 comments on commit 2faeb37

Please sign in to comment.