diff --git a/Cargo.lock b/Cargo.lock index 75d7ea002a..b1aa68ebf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,6 +2006,16 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "multisig" +version = "0.1.0" +dependencies = [ + "crypto 0.14.0-pre", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "native-tls" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 09b24de7c9..34f8926035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ ckb-bin = { path = "ckb-bin" } [workspace] members = [ + "util/multisig", "util/logger", "util/hash", "util/merkle-tree", diff --git a/util/crypto/src/secp/pubkey.rs b/util/crypto/src/secp/pubkey.rs index 5852800dde..82a1da9059 100644 --- a/util/crypto/src/secp/pubkey.rs +++ b/util/crypto/src/secp/pubkey.rs @@ -7,7 +7,7 @@ use secp256k1::key; use secp256k1::Message as SecpMessage; use std::{fmt, ops}; -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Hash, Clone)] pub struct Pubkey { inner: H512, } diff --git a/util/multisig/Cargo.toml b/util/multisig/Cargo.toml new file mode 100644 index 0000000000..15ae610cdb --- /dev/null +++ b/util/multisig/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "multisig" +version = "0.1.0" +license = "MIT" +authors = ["Nervos Core Dev "] +edition = "2018" + +[dependencies] +crypto = { path = "../crypto" } +failure = "0.1.5" +log = "0.4" + +[dev-dependencies] +rand = "0.6.5" + diff --git a/util/multisig/src/error.rs b/util/multisig/src/error.rs new file mode 100644 index 0000000000..5cf8a454e7 --- /dev/null +++ b/util/multisig/src/error.rs @@ -0,0 +1,36 @@ +use failure::Context; + +#[derive(Debug)] +pub struct Error { + inner: Context, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] +pub enum ErrorKind { + #[fail(display = "The count of sigs should less than pks.")] + SigCountOverflow, + #[fail(display = "The count of sigs less than threshold.")] + SigNotEnough, + #[fail(display = "Failed to meet threshold {:?}.", _0)] + Threshold { threshold: usize, pass_sigs: usize }, +} + +impl Error { + pub fn kind(&self) -> ErrorKind { + *self.inner.get_context() + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + Error { + inner: Context::new(kind), + } + } +} + +impl From> for Error { + fn from(inner: Context) -> Error { + Error { inner } + } +} diff --git a/util/multisig/src/lib.rs b/util/multisig/src/lib.rs new file mode 100644 index 0000000000..a955b195b1 --- /dev/null +++ b/util/multisig/src/lib.rs @@ -0,0 +1,5 @@ +#[macro_use] +extern crate failure; + +pub mod error; +pub mod secp256k1; diff --git a/util/multisig/src/secp256k1.rs b/util/multisig/src/secp256k1.rs new file mode 100644 index 0000000000..2a55615295 --- /dev/null +++ b/util/multisig/src/secp256k1.rs @@ -0,0 +1,170 @@ +use crate::error::{Error, ErrorKind}; +pub use crypto::secp::{Error as Secp256k1Error, Message, Privkey, Pubkey, Signature}; +use log::{debug, trace}; +use std::collections::HashSet; +use std::hash::BuildHasher; + +/// verify m of n signatures +/// Example 2 of 3 sigs: [s1, s3], pks: [pk1, pk2, pk3] +pub fn verify_m_of_n( + message: &Message, + m_threshold: usize, + sigs: &[Signature], + pks: &HashSet, +) -> Result<(), Error> +where + S: BuildHasher, +{ + if sigs.len() > pks.len() { + Err(ErrorKind::SigCountOverflow)?; + } + if m_threshold > sigs.len() { + Err(ErrorKind::SigNotEnough)?; + } + + let mut used_pks: HashSet = HashSet::with_capacity(m_threshold); + let verified_sig_count = sigs + .iter() + .filter_map(|sig| { + trace!( + target: "multisig", + "recover sig {:x?} with message {:x?}", + &sig.serialize()[..], + &message[..] + ); + match sig.recover(&message) { + Ok(pubkey) => Some(pubkey), + Err(err) => { + debug!(target: "multisig", "recover secp256k1 sig error: {}", err); + None + } + } + }) + .filter(|rec_pk| pks.contains(rec_pk) && used_pks.insert(rec_pk.to_owned())) + .take(m_threshold) + .count(); + if verified_sig_count < m_threshold { + Err(ErrorKind::Threshold { + pass_sigs: verified_sig_count, + threshold: m_threshold, + })? + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::{thread_rng, Rng}; + + fn random_message() -> Message { + let mut data = [0; 32]; + thread_rng().fill(&mut data[..]); + loop { + if let Ok(msg) = Message::from_slice(&data) { + return msg; + } + } + } + + fn random_signature(message: &Message) -> Signature { + let secret_key = random_secret_key(); + secret_key.sign_recoverable(message).expect("sign") + } + + fn random_secret_key() -> Privkey { + let mut data = [0; 32]; + thread_rng().fill(&mut data[..]); + loop { + let key = Privkey::from_slice(&data); + if key.pubkey().is_ok() { + return key; + } + } + } + + #[test] + fn test_m_of_n() { + // (thresholds, sigs: [is_valid], pks, result) + let test_set = [ + (2, vec![true, true], 3, Ok(())), + (2, vec![true, true, true], 3, Ok(())), + (3, vec![true, true, true], 3, Ok(())), + (3, vec![true, false, true, true], 4, Ok(())), + ( + 2, + vec![true, true, true], + 1, + Err(ErrorKind::SigCountOverflow), + ), + (3, vec![true, true], 3, Err(ErrorKind::SigNotEnough)), + ( + 3, + vec![true, true, false], + 3, + Err(ErrorKind::Threshold { + pass_sigs: 2, + threshold: 3, + }), + ), + ]; + for (threshold, sigs, pks, result) in test_set.iter() { + let message = random_message(); + let sks: Vec = (0..sigs.len()).map(|_| random_secret_key()).collect(); + let pks: HashSet = sks + .iter() + .enumerate() + .map(|(_i, sk)| sk.pubkey().expect("pk")) + .take(*pks) + .collect(); + let sigs: Vec = sigs + .iter() + .enumerate() + .map(|(i, valid)| { + if *valid { + sks[i].sign_recoverable(&message).expect("sign") + } else { + random_signature(&message) + } + }) + .collect(); + let verify_result = + verify_m_of_n(&message, *threshold, &sigs, &pks).map_err(|err| err.kind()); + assert_eq!(&verify_result, result); + } + } + + #[test] + fn test_2_of_3_with_wrong_signature() { + let message = random_message(); + let sks: Vec = (0..3).map(|_| random_secret_key()).collect(); + let pks: HashSet = sks.iter().map(|sk| sk.pubkey().expect("pk")).collect(); + let sigs: Vec = vec![ + sks[0].sign_recoverable(&message).expect("sign"), + sks[2].sign_recoverable(&message).expect("sign"), + random_signature(&message), + ]; + let verify_result = verify_m_of_n(&message, 2, &sigs, &pks); + assert!(verify_result.is_ok()); + } + + #[test] + fn test_duplicate_pubkeys() { + let message = random_message(); + let sks: Vec = (0..3).map(|_| random_secret_key()).collect(); + let pks: HashSet = sks.iter().map(|sk| sk.pubkey().expect("pk")).collect(); + let sigs: Vec = vec![ + sks[0].sign_recoverable(&message).expect("sign"), + sks[0].sign_recoverable(&message).expect("sign"), + random_signature(&message), + ]; + let verify_result = verify_m_of_n(&message, 2, &sigs, &pks).map_err(|err| err.kind()); + assert_eq!( + verify_result, + Err(ErrorKind::Threshold { + pass_sigs: 1, + threshold: 2 + }) + ); + } +}