From 3a268aaa389f5cb6cd25236c7719e7f9ed1ae2fb Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Thu, 3 Jun 2021 02:50:44 +0000 Subject: [PATCH 1/9] Add multisig bulletproof functions Add functions to create and verify a multisignature bulletproof. Callers must go through multiple steps to create the full bulletproof Each party must complete each step in the protocol, and the initiator finalizes the multisignature bulletproof --- core/src/libtx/proof.rs | 305 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 304 insertions(+), 1 deletion(-) diff --git a/core/src/libtx/proof.rs b/core/src/libtx/proof.rs index e5e13fc0fa..eb805702fb 100644 --- a/core/src/libtx/proof.rs +++ b/core/src/libtx/proof.rs @@ -19,7 +19,7 @@ use blake2::blake2b::blake2b; use keychain::extkey_bip32::BIP32GrinHasher; use keychain::{Identifier, Keychain, SwitchCommitmentType, ViewKey}; use std::convert::TryFrom; -use util::secp::key::SecretKey; +use util::secp::key::{PublicKey, SecretKey}; use util::secp::pedersen::{Commitment, ProofMessage, RangeProof}; use util::secp::{self, Secp256k1}; use zeroize::Zeroize; @@ -57,6 +57,48 @@ where )) } +/// Create a multisig bulletproof +pub fn create_multisig( + k: &K, + b: &B, + amount: u64, + key_id: &Identifier, + switch: SwitchCommitmentType, + common_nonce: &SecretKey, + tau_x: Option<&mut SecretKey>, + tau_one: Option<&mut PublicKey>, + tau_two: Option<&mut PublicKey>, + commits: &[Commitment], + step: u8, + extra_data: Option>, +) -> Result, Error> +where + K: Keychain, + B: ProofBuild, +{ + // TODO: proper support for different switch commitment schemes + // The new bulletproof scheme encodes and decodes it, but + // it is not supported at the wallet level (yet). + let secp = k.secp(); + let skey = k.derive_key(amount, key_id, switch)?; + let private_nonce = b.private_nonce(secp, &commits[0])?; + let message = b.proof_message(secp, key_id, switch)?; + + Ok(secp.bullet_proof_multisig( + amount, + skey, + common_nonce.clone(), + extra_data, + Some(message), + tau_x, + tau_one, + tau_two, + commits.to_vec(), + Some(&private_nonce), + step, + )) +} + /// Verify a proof pub fn verify( secp: &Secp256k1, @@ -68,6 +110,17 @@ pub fn verify( result.map(|_| ()) } +/// Verify a multisignature bulletproof +pub fn verify_multisig( + secp: &Secp256k1, + commits: Vec, + proofs: Vec, + extra_data: Option>>, +) -> Result<(), secp::Error> { + let result = secp.verify_bullet_proof_multi(commits, proofs, extra_data); + result.map(|_| ()) +} + /// Rewind a rangeproof to retrieve the amount, derivation path and switch commitment type pub fn rewind( secp: &Secp256k1, @@ -494,6 +547,256 @@ mod tests { assert_ne!(commit_a, commit_b); } + #[test] + fn builder_multisig() { + let rng = &mut thread_rng(); + let a_keychain = ExtKeychain::from_random_seed(true).unwrap(); + let b_keychain = ExtKeychain::from_random_seed(true).unwrap(); + let a_builder = ProofBuilder::new(&a_keychain); + let b_builder = ProofBuilder::new(&b_keychain); + let secp = a_keychain.secp(); + let amount = 12345678; + // ID needs to be the same for both parties to derive the same proof message + let id = ExtKeychain::derive_key_id(3, rng.gen(), rng.gen(), rng.gen(), 0); + let common_nonce = SecretKey::new(secp, rng); + // With switch commitment + let commits_a = { + let switch = SwitchCommitmentType::Regular; + // can't use Keychain::commit here, because the key needs to be derived using amount + // the commit for party a is over the amount, party b commits to zero + let blind_a = a_keychain.derive_key(amount, &id, switch).unwrap(); + let blind_b = b_keychain.derive_key(amount, &id, switch).unwrap(); + let a_commit = secp.commit(amount, blind_a).unwrap(); + let b_commit = secp.commit(0, blind_b).unwrap(); + let commits = vec![secp.commit_sum(vec![a_commit, b_commit], vec![]).unwrap()]; + + // 1st step, create tau_one and tau_two for each party + let mut tau_one_a = PublicKey::new(); + let mut tau_two_a = PublicKey::new(); + let mut res = create_multisig( + &a_keychain, + &a_builder, + amount, + &id, + switch, + &common_nonce, + None, + Some(&mut tau_one_a), + Some(&mut tau_two_a), + &commits, + 1, + None, + ) + .unwrap(); + assert!(res.is_none()); + + let mut tau_one_b = PublicKey::new(); + let mut tau_two_b = PublicKey::new(); + res = create_multisig( + &b_keychain, + &b_builder, + amount, + &id, + switch, + &common_nonce, + None, + Some(&mut tau_one_b), + Some(&mut tau_two_b), + &commits, + 1, + None, + ) + .unwrap(); + assert!(res.is_none()); + + // Sum tau_one and tau_two from each party + let mut tau_one_sum = + PublicKey::from_combination(secp, vec![&tau_one_a, &tau_one_b]).unwrap(); + let mut tau_two_sum = + PublicKey::from_combination(secp, vec![&tau_two_a, &tau_two_b]).unwrap(); + + // 2nd step, create tau_x for each party + let mut tau_x_a = SecretKey::new(secp, rng); + res = create_multisig( + &a_keychain, + &a_builder, + amount, + &id, + switch, + &common_nonce, + Some(&mut tau_x_a), + Some(&mut tau_one_sum), + Some(&mut tau_two_sum), + &commits, + 2, + None, + ) + .unwrap(); + assert!(res.is_none()); + let mut tau_x_b = SecretKey::new(secp, rng); + res = create_multisig( + &b_keychain, + &b_builder, + amount, + &id, + switch, + &common_nonce, + Some(&mut tau_x_b), + Some(&mut tau_one_sum), + Some(&mut tau_two_sum), + &commits, + 2, + None, + ) + .unwrap(); + assert!(res.is_none()); + + // Sum tau_x from each party + let mut tau_x_sum = tau_x_a; + tau_x_sum.add_assign(secp, &tau_x_b).unwrap(); + + // 3rd step, party A finalizes the bulletproof + let proof = create_multisig( + &a_keychain, + &a_builder, + amount, + &id, + switch, + &common_nonce, + Some(&mut tau_x_sum), + Some(&mut tau_one_sum), + Some(&mut tau_two_sum), + &commits, + 0, + None, + ) + .unwrap(); + assert!(proof.is_some()); + + assert!(verify_multisig(secp, commits.clone(), vec![proof.unwrap()], None).is_ok()); + commits + }; + // Without switch commitment + let commits_b = { + let switch = SwitchCommitmentType::None; + // can't use Keychain::commit here, because the key needs to be derived using amount + // the commit for party a is over the amount, party b commits to zero + let blind_a = a_keychain.derive_key(amount, &id, switch).unwrap(); + let blind_b = b_keychain.derive_key(amount, &id, switch).unwrap(); + let a_commit = secp.commit(amount, blind_a).unwrap(); + let b_commit = secp.commit(0, blind_b).unwrap(); + let commits = vec![secp.commit_sum(vec![a_commit, b_commit], vec![]).unwrap()]; + + // 1st step, create tau_one and tau_two for each party + let mut tau_one_a = PublicKey::new(); + let mut tau_two_a = PublicKey::new(); + let mut res = create_multisig( + &a_keychain, + &a_builder, + amount, + &id, + switch, + &common_nonce, + None, + Some(&mut tau_one_a), + Some(&mut tau_two_a), + &commits, + 1, + None, + ) + .unwrap(); + assert!(res.is_none()); + + let mut tau_one_b = PublicKey::new(); + let mut tau_two_b = PublicKey::new(); + res = create_multisig( + &b_keychain, + &b_builder, + amount, + &id, + switch, + &common_nonce, + None, + Some(&mut tau_one_b), + Some(&mut tau_two_b), + &commits, + 1, + None, + ) + .unwrap(); + assert!(res.is_none()); + + // Sum tau_one and tau_two from each party + let mut tau_one_sum = + PublicKey::from_combination(secp, vec![&tau_one_a, &tau_one_b]).unwrap(); + let mut tau_two_sum = + PublicKey::from_combination(secp, vec![&tau_two_a, &tau_two_b]).unwrap(); + + // 2nd step, create tau_x for each party + let mut tau_x_a = SecretKey::new(secp, rng); + res = create_multisig( + &a_keychain, + &a_builder, + amount, + &id, + switch, + &common_nonce, + Some(&mut tau_x_a), + Some(&mut tau_one_sum), + Some(&mut tau_two_sum), + &commits, + 2, + None, + ) + .unwrap(); + assert!(res.is_none()); + let mut tau_x_b = SecretKey::new(secp, rng); + res = create_multisig( + &b_keychain, + &b_builder, + amount, + &id, + switch, + &common_nonce, + Some(&mut tau_x_b), + Some(&mut tau_one_sum), + Some(&mut tau_two_sum), + &commits, + 2, + None, + ) + .unwrap(); + assert!(res.is_none()); + + // Sum tau_x from each party + let mut tau_x_sum = tau_x_a; + tau_x_sum.add_assign(secp, &tau_x_b).unwrap(); + + // 3rd step, party A finalizes the bulletproof + let proof = create_multisig( + &a_keychain, + &a_builder, + amount, + &id, + switch, + &common_nonce, + Some(&mut tau_x_sum), + Some(&mut tau_one_sum), + Some(&mut tau_two_sum), + &commits, + 0, + None, + ) + .unwrap(); + assert!(proof.is_some()); + + assert!(verify_multisig(secp, commits.clone(), vec![proof.unwrap()], None).is_ok()); + commits + }; + // The resulting pedersen commitments should be different + assert_ne!(commits_a, commits_b); + } + #[test] fn view_key() { // TODO From 01ce238b6d4a5f8059c1d8ebcb85821a3118b0da Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 21 Apr 2021 20:54:20 +0000 Subject: [PATCH 2/9] Add serializer for `Option` Adds serde mod for an optional public key. Useful for multisig output and atomic swap transaction flow --- core/src/libtx/secp_ser.rs | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/core/src/libtx/secp_ser.rs b/core/src/libtx/secp_ser.rs index f09aaafa50..130b10f391 100644 --- a/core/src/libtx/secp_ser.rs +++ b/core/src/libtx/secp_ser.rs @@ -51,6 +51,51 @@ pub mod pubkey_serde { } } +/// Serializes an Option to and from hex +pub mod option_pubkey_serde { + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serializer}; + use util::secp::key::PublicKey; + use util::{from_hex, static_secp_instance, ToHex}; + + /// + pub fn serialize(key: &Option, serializer: S) -> Result + where + S: Serializer, + { + match key { + Some(k) => { + let static_secp = static_secp_instance(); + let static_secp = static_secp.lock(); + serializer.serialize_str(&k.serialize_vec(&static_secp, true).to_hex()) + } + None => serializer.serialize_none(), + } + } + + /// + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer).and_then(|res| match res { + Some(string) => { + let static_secp = static_secp_instance(); + let static_secp = static_secp.lock(); + + from_hex(&string) + .map_err(Error::custom) + .and_then(|bytes: Vec| { + Ok(Some( + PublicKey::from_slice(&static_secp, &bytes).map_err(Error::custom)?, + )) + }) + } + None => Ok(None), + }) + } +} + /// Serializes an Option to and from hex pub mod option_sig_serde { use serde::de::Error; From 142c85b65cf05e084c8a1b439d469b2b4044c459 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Thu, 22 Apr 2021 00:57:58 +0000 Subject: [PATCH 3/9] Add option to sign with extra nonce Add option to create an adaptor signature by signing with an extra nonce --- core/src/core/transaction.rs | 3 ++- core/src/libtx/aggsig.rs | 35 ++++++++++++++++++++++++++++++----- core/src/libtx/secp_ser.rs | 2 +- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 3cb6f2e2fd..5386394c9a 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -742,6 +742,7 @@ impl TxKernel { &sig, &self.msg_to_sign()?, None, + None, &pubkey, Some(&pubkey), false, @@ -2400,7 +2401,7 @@ mod test { let pubkey = excess.to_pubkey(&keychain.secp()).unwrap(); let excess_sig = - aggsig::sign_single(&keychain.secp(), &msg, &skey, None, Some(&pubkey)).unwrap(); + aggsig::sign_single(&keychain.secp(), &msg, &skey, None, None, Some(&pubkey)).unwrap(); kernel.excess = excess; kernel.excess_sig = excess_sig; diff --git a/core/src/libtx/aggsig.rs b/core/src/libtx/aggsig.rs index 266c475205..6e30c0cfdc 100644 --- a/core/src/libtx/aggsig.rs +++ b/core/src/libtx/aggsig.rs @@ -86,6 +86,7 @@ pub fn create_secnonce(secp: &Secp256k1) -> Result { /// &secp, /// &secret_key, /// &secret_nonce, +/// None, /// &pub_nonce_sum, /// Some(&pub_key_sum), /// &message, @@ -96,6 +97,7 @@ pub fn calculate_partial_sig( secp: &Secp256k1, sec_key: &SecretKey, sec_nonce: &SecretKey, + sec_nonce_extra: Option<&SecretKey>, nonce_sum: &PublicKey, pubkey_sum: Option<&PublicKey>, msg: &secp::Message, @@ -106,7 +108,7 @@ pub fn calculate_partial_sig( &msg, sec_key, Some(sec_nonce), - None, + sec_nonce_extra, Some(nonce_sum), pubkey_sum, Some(nonce_sum), @@ -156,6 +158,7 @@ pub fn calculate_partial_sig( /// &secp, /// &secret_key, /// &secret_nonce, +/// None, /// &pub_nonce_sum, /// Some(&pub_key_sum), /// &message, @@ -169,6 +172,7 @@ pub fn calculate_partial_sig( /// &secp, /// &sig_part, /// &pub_nonce_sum, +/// None, /// &public_key, /// Some(&pub_key_sum), /// &message, @@ -179,6 +183,7 @@ pub fn verify_partial_sig( secp: &Secp256k1, sig: &Signature, pub_nonce_sum: &PublicKey, + pub_nonce_extra: Option<&PublicKey>, pubkey: &PublicKey, pubkey_sum: Option<&PublicKey>, msg: &secp::Message, @@ -188,6 +193,7 @@ pub fn verify_partial_sig( sig, &msg, Some(&pub_nonce_sum), + pub_nonce_extra, pubkey, pubkey_sum, true, @@ -323,7 +329,7 @@ pub fn verify_single_from_commit( commit: &Commitment, ) -> Result<(), Error> { let pubkey = commit.to_pubkey(secp)?; - if !verify_single(secp, sig, msg, None, &pubkey, Some(&pubkey), false) { + if !verify_single(secp, sig, msg, None, None, &pubkey, Some(&pubkey), false) { return Err(ErrorKind::Signature("Signature validation error".to_string()).into()); } Ok(()) @@ -369,6 +375,7 @@ pub fn verify_single_from_commit( /// &secp, /// &secret_key, /// &secret_nonce, +/// None, /// &pub_nonce_sum, /// Some(&pub_key_sum), /// &message, @@ -391,7 +398,7 @@ pub fn verify_completed_sig( pubkey_sum: Option<&PublicKey>, msg: &secp::Message, ) -> Result<(), Error> { - if !verify_single(secp, sig, msg, None, pubkey, pubkey_sum, true) { + if !verify_single(secp, sig, msg, None, None, pubkey, pubkey_sum, true) { return Err(ErrorKind::Signature("Signature validation error".to_string()).into()); } Ok(()) @@ -414,9 +421,19 @@ pub fn sign_single( msg: &Message, skey: &SecretKey, snonce: Option<&SecretKey>, + snonce_extra: Option<&SecretKey>, pubkey_sum: Option<&PublicKey>, ) -> Result { - let sig = aggsig::sign_single(secp, &msg, skey, snonce, None, None, pubkey_sum, None)?; + let sig = aggsig::sign_single( + secp, + &msg, + skey, + snonce, + snonce_extra, + None, + pubkey_sum, + None, + )?; Ok(sig) } @@ -426,12 +443,20 @@ pub fn verify_single( sig: &Signature, msg: &Message, pubnonce: Option<&PublicKey>, + pubnonce_extra: Option<&PublicKey>, pubkey: &PublicKey, pubkey_sum: Option<&PublicKey>, is_partial: bool, ) -> bool { aggsig::verify_single( - secp, sig, msg, pubnonce, pubkey, pubkey_sum, None, is_partial, + secp, + sig, + msg, + pubnonce, + pubkey, + pubkey_sum, + pubnonce_extra, + is_partial, ) } diff --git a/core/src/libtx/secp_ser.rs b/core/src/libtx/secp_ser.rs index 130b10f391..21343f0c68 100644 --- a/core/src/libtx/secp_ser.rs +++ b/core/src/libtx/secp_ser.rs @@ -433,7 +433,7 @@ mod test { let mut msg = [0u8; 32]; thread_rng().fill(&mut msg); let msg = Message::from_slice(&msg).unwrap(); - let sig = aggsig::sign_single(&secp, &msg, &sk, None, None).unwrap(); + let sig = aggsig::sign_single(&secp, &msg, &sk, None, None, None).unwrap(); let mut commit = [0u8; 33]; commit[0] = 0x09; thread_rng().fill(&mut commit[1..]); From 5b12dcdd1a359d429740da9e71242e3e03200276 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 19 May 2021 19:31:40 +0000 Subject: [PATCH 4/9] Update Cargo.lock --- Cargo.lock | 51 +++++++++------------------------------------------ 1 file changed, 9 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2ee9c76dd..17014115b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -695,7 +695,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "syn 1.0.60", - "synstructure 0.12.4", + "synstructure", ] [[package]] @@ -1055,7 +1055,7 @@ dependencies = [ "serde_derive", "serde_json", "siphasher", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -1076,7 +1076,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2", - "zeroize 1.1.0", + "zeroize", ] [[package]] @@ -1121,9 +1121,9 @@ dependencies = [ [[package]] name = "grin_secp256k1zkp" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca5852d12dbd4df8b7b760eaad791d191ea546bf80c46b033bbcc650b35a5c" +checksum = "3af3c4c4829b3e2e7ee1d9a542833e4244912fbb887fabe44682558159b068a7" dependencies = [ "arrayvec 0.3.25", "cc", @@ -1132,7 +1132,7 @@ dependencies = [ "rustc-serialize", "serde", "serde_json", - "zeroize 0.9.3", + "zeroize", ] [[package]] @@ -1203,7 +1203,7 @@ dependencies = [ "serde", "serde_derive", "walkdir", - "zeroize 1.1.0", + "zeroize", "zip", ] @@ -2678,18 +2678,6 @@ dependencies = [ "unicode-xid 0.2.0", ] -[[package]] -name = "synstructure" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", - "unicode-xid 0.1.0", -] - [[package]] name = "synstructure" version = "0.12.4" @@ -3265,34 +3253,13 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "zeroize" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" -dependencies = [ - "zeroize_derive 0.9.3", -] - [[package]] name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" dependencies = [ - "zeroize_derive 1.0.0", -] - -[[package]] -name = "zeroize_derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080616bd0e31f36095288bb0acdf1f78ef02c2fa15527d7e993f2a6c7591643e" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", - "synstructure 0.10.2", + "zeroize_derive", ] [[package]] @@ -3304,7 +3271,7 @@ dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", "syn 1.0.60", - "synstructure 0.12.4", + "synstructure", ] [[package]] From d6edffd883a5b1fd4addf936a46b5899eb789dd2 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 16 Jun 2021 20:58:53 +0000 Subject: [PATCH 5/9] Add function to build multisig output Build a multisig output with a blank rangeproof, to build the rangeproof over a number of rounds Output commit is a sum of a partial commit to the output value and a commit to zero --- core/src/core/transaction.rs | 2 ++ core/src/libtx/build.rs | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index 5386394c9a..a72b8983bc 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -1980,6 +1980,8 @@ enum_from_primitive! { Plain = 0, /// A coinbase output. Coinbase = 1, + /// Multisignature output with shared ownership + Multisig = 2, } } diff --git a/core/src/libtx/build.rs b/core/src/libtx/build.rs index feea974dca..9715774cc4 100644 --- a/core/src/libtx/build.rs +++ b/core/src/libtx/build.rs @@ -35,6 +35,7 @@ use crate::core::{Input, KernelFeatures, Output, OutputFeatures, Transaction, Tx use crate::libtx::proof::{self, ProofBuild}; use crate::libtx::{aggsig, Error}; use keychain::{BlindSum, BlindingFactor, Identifier, Keychain, SwitchCommitmentType}; +use util::secp::pedersen::{Commitment, RangeProof}; /// Context information available to transaction combinators. pub struct Context<'a, K, B> @@ -143,6 +144,46 @@ where ) } +/// Adds an output with the provided value and key identifier from the +/// keychain. +/// +/// Adds a blank Rangeproof, to build the multiparty bulletproof over +/// a number of rounds +pub fn multisig_output( + value: u64, + key_id: Identifier, + part_commit: Commitment, +) -> Box> +where + K: Keychain, + B: ProofBuild, +{ + Box::new( + move |build, acc| -> Result<(Transaction, BlindSum), Error> { + let (tx, sum) = acc?; + + // TODO: proper support for different switch commitment schemes + let switch = SwitchCommitmentType::Regular; + + // add commit to zero to the initiator's partial commit to the value + let commit_key = build.keychain.derive_key(value, &key_id, switch)?; + let secp = build.keychain.secp(); + let commit = secp.commit(0, commit_key)?; + let commit_sum = secp.commit_sum(vec![commit, part_commit.clone()], vec![])?; + + debug!("Building output: {}, {:?}", value, commit_sum); + + // add zero Rangeproof to build the multiparty rangeproof over a number of steps + let proof = RangeProof::zero(); + + Ok(( + tx.with_output(Output::new(OutputFeatures::Multisig, commit_sum, proof)), + sum.add_key_id(key_id.to_value_path(value)), + )) + }, + ) +} + /// Adds a known excess value on the transaction being built. Usually used in /// combination with the initial_tx function when a new transaction is built /// by adding to a pre-existing one. From fb8652d88f57b75a7f4cb8ae7c660648be6beaa4 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 23 Jun 2021 18:56:05 +0000 Subject: [PATCH 6/9] Deserialize an Identifier from a BIP32 string Add a function to deserialize a keychain `Identifier` from a BIP32 string --- keychain/src/types.rs | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/keychain/src/types.rs b/keychain/src/types.rs index d004d3e029..5979fcfe1f 100644 --- a/keychain/src/types.rs +++ b/keychain/src/types.rs @@ -46,6 +46,7 @@ pub enum Error { Transaction(String), RangeProof(String), SwitchCommitment, + Path(String), } impl From for Error { @@ -194,6 +195,40 @@ impl Identifier { Ok(Identifier::from_bytes(&bytes)) } + /// Return the identifier specified by the provided path + /// + /// FIXME: only supports unhardened paths, modify to support hardened + /// paths when they are implemented in `ExtKeychainPath` + pub fn from_bip_32_string(b32: &str) -> Result { + if b32.len() < 3 || &b32[..2] != "m/" { + return Err(Error::Path(format!( + "path is too short, or has invalid start: {}", + b32.to_string() + ))); + } + let mut depth = 0; + let mut ds = [0u32; 4]; + for nums in b32[2..].split('/') { + if depth > 3 { + return Err(Error::Path(format!( + "path is too long: {}", + b32.to_string() + ))); + } + ds[depth] = nums + .parse::() + .map_err(|_| Error::Path(format!("invalid child number: {}", b32.to_string())))?; + depth += 1; + } + Ok(Identifier::from_path(&ExtKeychainPath::new( + depth as u8, + ds[0], + ds[1], + ds[2], + ds[3], + ))) + } + pub fn to_bip_32_string(&self) -> String { let p = ExtKeychainPath::from_identifier(&self); let mut retval = String::from("m"); @@ -604,4 +639,44 @@ mod test { let expected_id = Identifier::from_path(&expected_path); assert_eq!(expected_id, parent_id); } + + // Check deserializing identifier from a BIP32 path + #[test] + fn from_bip32() { + let path_1 = "m/1"; + let path_2 = "m/1/2"; + let path_3 = "m/1/2/3"; + let path_4 = "m/1/2/3/4"; + let inv_path_none = "m"; + let inv_path_0 = "m/"; + let inv_path_sep = "m?"; + let inv_empty = ""; + let inv_path_5 = "m/1/2/3/4/5"; + let inv_non_m = "n"; + let inv_non_num = "m/a$"; + + let mut ident = Identifier::from_bip_32_string(path_1).unwrap(); + let mut path = ExtKeychainPath::new(1, 1, 0, 0, 0); + assert_eq!(ident.to_path(), path); + + ident = Identifier::from_bip_32_string(path_2).unwrap(); + path = ExtKeychainPath::new(2, 1, 2, 0, 0); + assert_eq!(ident.to_path(), path); + + ident = Identifier::from_bip_32_string(path_3).unwrap(); + path = ExtKeychainPath::new(3, 1, 2, 3, 0); + assert_eq!(ident.to_path(), path); + + ident = Identifier::from_bip_32_string(path_4).unwrap(); + path = ExtKeychainPath::new(4, 1, 2, 3, 4); + assert_eq!(ident.to_path(), path); + + assert!(Identifier::from_bip_32_string(inv_path_none).is_err()); + assert!(Identifier::from_bip_32_string(inv_path_0).is_err()); + assert!(Identifier::from_bip_32_string(inv_path_sep).is_err()); + assert!(Identifier::from_bip_32_string(inv_empty).is_err()); + assert!(Identifier::from_bip_32_string(inv_path_5).is_err()); + assert!(Identifier::from_bip_32_string(inv_non_m).is_err()); + assert!(Identifier::from_bip_32_string(inv_non_num).is_err()); + } } From 949efbf92b78a7c1a9b2e38244ae99aad4d1d586 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 30 Jun 2021 19:41:35 +0000 Subject: [PATCH 7/9] Update rust-secp256k1-zkp dependency Change rust-secp256k1-zkp dependency to include changes necessary for atomic swaps Points to https://github.com/geneferneau/rust-secp256k1-zkp#atomic **DO NOT MERGE** Drop this commit after merging rust-secp256k1-zkp changes upstream --- Cargo.lock | 5 +++-- util/Cargo.toml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17014115b3..abcd54112c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "addr2line" version = "0.12.1" @@ -1122,8 +1124,7 @@ dependencies = [ [[package]] name = "grin_secp256k1zkp" version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af3c4c4829b3e2e7ee1d9a542833e4244912fbb887fabe44682558159b068a7" +source = "git+https://github.com/geneferneau/rust-secp256k1-zkp?branch=atomic#45c27af7ac91d8c4323a19730760922348ee9a08" dependencies = [ "arrayvec 0.3.25", "cc", diff --git a/util/Cargo.toml b/util/Cargo.toml index 83981c75c7..a4515edd8b 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -25,8 +25,9 @@ parking_lot = "0.10" zeroize = { version = "1.1", features =["zeroize_derive"] } [dependencies.grin_secp256k1zkp] -#git = "https://github.com/mimblewimble/rust-secp256k1-zkp" +git = "https://github.com/geneferneau/rust-secp256k1-zkp" +branch = "atomic" #tag = "grin_integration_29" #path = "../../rust-secp256k1-zkp" -version = "0.7.10" +#version = "0.7.10" features = ["bullet-proof-sizing"] From 1ac6973f9c6b14aedfb84bcdf7f7f596f90e4382 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 30 Jun 2021 19:34:38 +0000 Subject: [PATCH 8/9] Build multisig input Add functions to build an input from a multisig output Uses the provided shared commitment to build the transaction input --- core/src/libtx/build.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/src/libtx/build.rs b/core/src/libtx/build.rs index 9715774cc4..54ee7564bf 100644 --- a/core/src/libtx/build.rs +++ b/core/src/libtx/build.rs @@ -98,6 +98,47 @@ where build_input(value, OutputFeatures::Plain, key_id) } +/// Adds a multisig input with the provided value, commit, and blinding key to the transaction +/// being built. +fn build_multisig_input( + value: u64, + features: OutputFeatures, + key_id: Identifier, + commit: Commitment, +) -> Box> +where + K: Keychain, + B: ProofBuild, +{ + Box::new( + move |_build, acc| -> Result<(Transaction, BlindSum), Error> { + if let Ok((tx, sum)) = acc { + let input = Input::new(features, commit); + Ok(( + tx.with_input(input), + sum.sub_key_id(key_id.to_value_path(value)), + )) + } else { + acc + } + }, + ) +} + +/// Adds a multisig input with the provided value and blinding key to the transaction +/// being built. +pub fn multisig_input(value: u64, key_id: Identifier, commit: Commitment) -> Box> +where + K: Keychain, + B: ProofBuild, +{ + debug!( + "Building input (spending multisig output): {}, {}", + value, key_id + ); + build_multisig_input(value, OutputFeatures::Multisig, key_id, commit) +} + /// Adds a coinbase input spending a coinbase output. pub fn coinbase_input(value: u64, key_id: Identifier) -> Box> where From b6b9a037513d331af62bc25e86a72ebf0b6f9023 Mon Sep 17 00:00:00 2001 From: Gene Ferneau Date: Wed, 30 Jun 2021 20:25:53 +0000 Subject: [PATCH 9/9] Add multisig output type Add functionality for determining if an output is multisig --- api/src/types.rs | 3 +++ core/src/core/transaction.rs | 15 +++++++++++++++ doc/api/node_api_v1.md | 10 +++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/api/src/types.rs b/api/src/types.rs index d82a74a7e8..19cdbc2cdc 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -171,6 +171,7 @@ impl TxHashSetNode { pub enum OutputType { Coinbase, Transaction, + Multisig, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -285,6 +286,8 @@ impl OutputPrintable { ) -> Result { let output_type = if output.is_coinbase() { OutputType::Coinbase + } else if output.is_multisig() { + OutputType::Multisig } else { OutputType::Transaction }; diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index a72b8983bc..d694343595 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -2074,6 +2074,11 @@ impl OutputFeatures { pub fn is_plain(self) -> bool { self == OutputFeatures::Plain } + + /// Is this a multisig output? + pub fn is_multisig(self) -> bool { + self == OutputFeatures::Multisig + } } impl Output { @@ -2110,6 +2115,11 @@ impl Output { self.identifier.is_plain() } + /// Is this a multisig output? + pub fn is_multisig(&self) -> bool { + self.identifier.is_multisig() + } + /// Range proof for the output pub fn proof(&self) -> RangeProof { self.proof @@ -2193,6 +2203,11 @@ impl OutputIdentifier { self.features.is_plain() } + /// Is this a multisig output? + pub fn is_multisig(&self) -> bool { + self.features.is_multisig() + } + /// Converts this identifier to a full output, provided a RangeProof pub fn into_output(self, proof: RangeProof) -> Output { Output { diff --git a/doc/api/node_api_v1.md b/doc/api/node_api_v1.md index 722277eb72..9eb61c2fa3 100644 --- a/doc/api/node_api_v1.md +++ b/doc/api/node_api_v1.md @@ -88,7 +88,7 @@ Optionally, Merkle proofs can be excluded from the results by adding `?no_merkle | - total_kernel_offset | string | Total kernel offset since genesis block | | inputs | []string | Input transactions | | outputs | []object | Outputs transactions | - | - output_type | string | The type of output Coinbase|Transaction | + | - output_type | string | The type of output Coinbase|Transaction|Multisig | | - commit | string | The homomorphic commitment representing the output's amount (as hex string) | | - spent | bool | Whether the output has been spent | | - proof | string | Rangeproof (as hex string) | @@ -414,7 +414,7 @@ Retrieves details about specifics outputs. Supports retrieval of multiple output | Field | Type | Description | |:----------------------|:---------|:----------------------------------------------------------------------------| | outputs | []object | Outputs | - | - output_type | string | The type of output Coinbase|Transaction | + | - output_type | string | The type of output Coinbase|Transaction|Multisig | | - commit | string | The homomorphic commitment representing the output's amount (as hex string) | | - spent | bool | Whether the output has been spent | | - proof | string | Rangeproof (as hex string) | @@ -474,7 +474,7 @@ Retrieves details about specifics outputs. Supports retrieval of multiple output | - height | number | Height of this block since the genesis block (height 0) | | - previous | string | Hash of the block previous to this in the chain | | outputs | []object | Outputs | - | - output_type | string | The type of output Coinbase|Transaction | + | - output_type | string | The type of output Coinbase|Transaction|Multisig | | - commit | string | The homomorphic commitment representing the output's amount (as hex string) | | - spent | bool | Whether the output has been spent | | - proof | string | Rangeproof (as hex string) | @@ -790,7 +790,7 @@ UTXO traversal. Retrieves last utxos since a start index until a max. | highest_index | number | The last available output index | | last_retrieved_index | number | The last insertion index retrieved | | outputs | []object | Outputs | - | - output_type | string | The type of output Coinbase|Transaction | + | - output_type | string | The type of output Coinbase|Transaction|Multisig | | - commit | string | The homomorphic commitment representing the output's amount (as hex string) | | - spent | bool | Whether the output has been spent | | - proof | string | Rangeproof (as hex string) | @@ -851,7 +851,7 @@ Build a merkle proof for a given output id and return a dummy output with merkle | Field | Type | Description | |:----------------------|:---------|:----------------------------------------------------------------------------| | outputs | []object | Outputs | - | - output_type | string | The type of output Coinbase|Transaction | + | - output_type | string | The type of output Coinbase|Transaction|Multisig | | - commit | string | The homomorphic commitment representing the output's amount (as hex string) | | - spent | bool | Whether the output has been spent | | - proof | string | Rangeproof (as hex string) |