From e3f30644146ed71039b3e6a53371d8b0688e923d Mon Sep 17 00:00:00 2001 From: jaspervdm Date: Wed, 12 Jun 2019 11:28:55 +0200 Subject: [PATCH] Support new Bulletproof rewind scheme (#2848) * Update keychain with new rewind scheme * Refactor: proof builder trait * Update tests, cleanup * rustfmt * Move conversion of SwitchCommitmentType * Add proof build trait to tx builders * Cache hashes in proof builders * Proof builder tests * Add ViewKey struct * Fix some warnings * Zeroize proof builder secrets on drop --- Cargo.lock | 10 +- chain/tests/data_file_integrity.rs | 12 +- chain/tests/mine_simple_chain.rs | 30 +- chain/tests/store_indices.rs | 9 +- chain/tests/test_coinbase_maturity.rs | 19 +- core/Cargo.toml | 1 + core/src/core/transaction.rs | 18 +- core/src/lib.rs | 1 + core/src/libtx/aggsig.rs | 19 +- core/src/libtx/build.rs | 85 ++- core/src/libtx/mod.rs | 1 + core/src/libtx/proof.rs | 742 ++++++++++++++++++++++++-- core/src/libtx/reward.rs | 16 +- core/tests/block.rs | 63 ++- core/tests/common.rs | 23 +- core/tests/core.rs | 61 ++- core/tests/transaction.rs | 6 +- core/tests/verifier_cache.rs | 8 +- keychain/src/extkey_bip32.rs | 4 +- keychain/src/keychain.rs | 131 +++-- keychain/src/lib.rs | 7 +- keychain/src/types.rs | 66 ++- keychain/src/view_key.rs | 195 +++++++ pool/tests/block_building.rs | 9 +- pool/tests/block_max_weight.rs | 9 +- pool/tests/block_reconciliation.rs | 27 +- pool/tests/common.rs | 4 +- pool/tests/transaction_pool.rs | 18 +- servers/src/mining/mine_block.rs | 11 +- util/Cargo.toml | 2 +- 30 files changed, 1399 insertions(+), 208 deletions(-) create mode 100644 keychain/src/view_key.rs diff --git a/Cargo.lock b/Cargo.lock index d7fc33f64..a906eded4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -777,6 +777,7 @@ dependencies = [ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -844,16 +845,17 @@ dependencies = [ [[package]] name = "grin_secp256k1zkp" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", - "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -914,7 +916,7 @@ dependencies = [ "backtrace 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "grin_secp256k1zkp 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "grin_secp256k1zkp 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log4rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2739,7 +2741,7 @@ dependencies = [ "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "591f8be1674b421644b6c030969520bc3fa12114d2eb467471982ed3e9584e71" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum grin_secp256k1zkp 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "75e9a265f3eeea4c204470f7262e2c6fe18f3d8ddf5fb24340cb550ac4f909c5" +"checksum grin_secp256k1zkp 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0e96161c7d923bf094e7f4f583e680a03746b692523f2211bff59f642e05aa85" "checksum h2 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "85ab6286db06040ddefb71641b50017c06874614001a134b423783e2db2920bd" "checksum hashbrown 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "570178d5e4952010d138b0f1d581271ff3a02406d990f887d1e87e3d6e43b0ac" "checksum hmac 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "733e1b3ac906631ca01ebb577e9bb0f5e37a454032b9036b5eaea4013ed6f99a" diff --git a/chain/tests/data_file_integrity.rs b/chain/tests/data_file_integrity.rs index f4f90cce5..23ca6a9cb 100644 --- a/chain/tests/data_file_integrity.rs +++ b/chain/tests/data_file_integrity.rs @@ -76,7 +76,14 @@ fn data_files() { let prev = chain.head_header().unwrap(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier(); - let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &pk, + 0, + false, + ) + .unwrap(); let mut b = core::core::Block::new(&prev, vec![], next_header_info.clone().difficulty, reward) .unwrap(); @@ -154,7 +161,8 @@ fn _prepare_block_nosum( let key_id = ExtKeychainPath::new(1, diff as u32, 0, 0, 0).to_identifier(); let fees = txs.iter().map(|tx| tx.fee()).sum(); - let reward = libtx::reward::output(kc, &key_id, fees, false).unwrap(); + let reward = + libtx::reward::output(kc, &libtx::ProofBuilder::new(kc), &key_id, fees, false).unwrap(); let mut b = match core::core::Block::new( prev, txs.into_iter().cloned().collect(), diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index 514e892c3..9481843ec 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -19,7 +19,7 @@ use self::core::core::verifier_cache::LruVerifierCache; use self::core::core::{Block, BlockHeader, OutputIdentifier, Transaction}; use self::core::genesis; use self::core::global::ChainTypes; -use self::core::libtx::{self, build, reward}; +use self::core::libtx::{self, build, reward, ProofBuilder}; use self::core::pow::Difficulty; use self::core::{consensus, global, pow}; use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain}; @@ -106,7 +106,14 @@ fn mine_genesis_reward_chain() { let mut genesis = genesis::genesis_dev(); let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); let key_id = keychain::ExtKeychain::derive_key_id(0, 1, 0, 0, 0); - let reward = reward::output(&keychain, &key_id, 0, false).unwrap(); + let reward = reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + 0, + false, + ) + .unwrap(); genesis = genesis.with_reward(reward.0, reward.1); let tmp_chain_dir = ".grin.tmp"; @@ -143,7 +150,9 @@ where let prev = chain.head_header().unwrap(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier(); - let reward = libtx::reward::output(keychain, &pk, 0, false).unwrap(); + let reward = + libtx::reward::output(keychain, &libtx::ProofBuilder::new(keychain), &pk, 0, false) + .unwrap(); let mut b = core::core::Block::new(&prev, vec![], next_header_info.clone().difficulty, reward) .unwrap(); @@ -401,6 +410,7 @@ fn spend_in_fork_and_compact() { let chain = setup(".grin6", pow::mine_genesis_block().unwrap()); let prev = chain.head_header().unwrap(); let kc = ExtKeychain::from_random_seed(false).unwrap(); + let pb = ProofBuilder::new(&kc); let mut fork_head = prev; @@ -434,6 +444,7 @@ fn spend_in_fork_and_compact() { build::with_fee(20000), ], &kc, + &pb, ) .unwrap(); @@ -451,6 +462,7 @@ fn spend_in_fork_and_compact() { build::with_fee(20000), ], &kc, + &pb, ) .unwrap(); @@ -540,7 +552,14 @@ fn output_header_mappings() { let prev = chain.head_header().unwrap(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier(); - let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &pk, + 0, + false, + ) + .unwrap(); reward_outputs.push(reward.0.clone()); let mut b = core::core::Block::new(&prev, vec![], next_header_info.clone().difficulty, reward) @@ -643,7 +662,8 @@ where let key_id = ExtKeychainPath::new(1, diff as u32, 0, 0, 0).to_identifier(); let fees = txs.iter().map(|tx| tx.fee()).sum(); - let reward = libtx::reward::output(kc, &key_id, fees, false).unwrap(); + let reward = + libtx::reward::output(kc, &libtx::ProofBuilder::new(kc), &key_id, fees, false).unwrap(); let mut b = match core::core::Block::new( prev, txs.into_iter().cloned().collect(), diff --git a/chain/tests/store_indices.rs b/chain/tests/store_indices.rs index 029af5643..17f545c0f 100644 --- a/chain/tests/store_indices.rs +++ b/chain/tests/store_indices.rs @@ -60,7 +60,14 @@ fn test_various_store_indices() { setup_chain(&genesis, chain_store.clone()).unwrap(); - let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + 0, + false, + ) + .unwrap(); let block = Block::new(&genesis.header, vec![], Difficulty::min(), reward).unwrap(); let block_hash = block.hash(); diff --git a/chain/tests/test_coinbase_maturity.rs b/chain/tests/test_coinbase_maturity.rs index 54a5236c2..04f00fa23 100644 --- a/chain/tests/test_coinbase_maturity.rs +++ b/chain/tests/test_coinbase_maturity.rs @@ -16,7 +16,7 @@ use self::chain::types::NoopAdapter; use self::chain::ErrorKind; use self::core::core::verifier_cache::LruVerifierCache; use self::core::global::{self, ChainTypes}; -use self::core::libtx::{self, build}; +use self::core::libtx::{self, build, ProofBuilder}; use self::core::pow::Difficulty; use self::core::{consensus, pow}; use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain}; @@ -59,13 +59,14 @@ fn test_coinbase_maturity() { let prev = chain.head_header().unwrap(); let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier(); let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier(); let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier(); let key_id4 = ExtKeychainPath::new(1, 4, 0, 0, 0).to_identifier(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); - let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap(); + let reward = libtx::reward::output(&keychain, &builder, &key_id1, 0, false).unwrap(); let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap(); block.header.timestamp = prev.timestamp + Duration::seconds(60); block.header.pow.secondary_scaling = next_header_info.secondary_scaling; @@ -104,12 +105,13 @@ fn test_coinbase_maturity() { build::with_fee(2), ], &keychain, + &builder, ) .unwrap(); let txs = vec![coinbase_txn.clone()]; let fees = txs.iter().map(|tx| tx.fee()).sum(); - let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap(); + let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap(); let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); block.header.timestamp = prev.timestamp + Duration::seconds(60); @@ -141,10 +143,11 @@ fn test_coinbase_maturity() { let prev = chain.head_header().unwrap(); let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); - let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap(); + let reward = libtx::reward::output(&keychain, &builder, &key_id1, 0, false).unwrap(); let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap(); @@ -185,12 +188,13 @@ fn test_coinbase_maturity() { build::with_fee(2), ], &keychain, + &builder, ) .unwrap(); let txs = vec![coinbase_txn.clone()]; let fees = txs.iter().map(|tx| tx.fee()).sum(); - let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap(); + let reward = libtx::reward::output(&keychain, &builder, &key_id3, fees, false).unwrap(); let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); block.header.timestamp = prev.timestamp + Duration::seconds(60); @@ -222,9 +226,10 @@ fn test_coinbase_maturity() { let prev = chain.head_header().unwrap(); let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let pk = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier(); - let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap(); + let reward = libtx::reward::output(&keychain, &builder, &pk, 0, false).unwrap(); let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap(); let next_header_info = @@ -254,7 +259,7 @@ fn test_coinbase_maturity() { let txs = vec![coinbase_txn]; let fees = txs.iter().map(|tx| tx.fee()).sum(); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap()); - let reward = libtx::reward::output(&keychain, &key_id4, fees, false).unwrap(); + let reward = libtx::reward::output(&keychain, &builder, &key_id4, fees, false).unwrap(); let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap(); block.header.timestamp = prev.timestamp + Duration::seconds(60); diff --git a/core/Cargo.toml b/core/Cargo.toml index 92cfd1609..7616d9a1e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -27,6 +27,7 @@ siphasher = "0.2" uuid = { version = "0.6", features = ["serde", "v4"] } log = "0.4" chrono = { version = "0.4.4", features = ["serde"] } +zeroize = "0.8" grin_keychain = { path = "../keychain", version = "2.0.0-beta.1" } grin_util = { path = "../util", version = "2.0.0-beta.1" } diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index c10846675..7b8910c7e 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -1499,14 +1499,16 @@ mod test { use super::*; use crate::core::hash::Hash; use crate::core::id::{ShortId, ShortIdentifiable}; - use crate::keychain::{ExtKeychain, Keychain}; + use crate::keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; use crate::util::secp; #[test] fn test_kernel_ser_deser() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let commit = keychain.commit(5, &key_id).unwrap(); + let commit = keychain + .commit(5, &key_id, &SwitchCommitmentType::Regular) + .unwrap(); // just some bytes for testing ser/deser let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap(); @@ -1552,10 +1554,14 @@ mod test { let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let commit = keychain.commit(1003, &key_id).unwrap(); + let commit = keychain + .commit(1003, &key_id, &SwitchCommitmentType::Regular) + .unwrap(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let commit_2 = keychain.commit(1003, &key_id).unwrap(); + let commit_2 = keychain + .commit(1003, &key_id, &SwitchCommitmentType::Regular) + .unwrap(); assert!(commit == commit_2); } @@ -1564,7 +1570,9 @@ mod test { fn input_short_id() { let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let commit = keychain.commit(5, &key_id).unwrap(); + let commit = keychain + .commit(5, &key_id, &SwitchCommitmentType::Regular) + .unwrap(); let input = Input { features: OutputFeatures::Plain, diff --git a/core/src/lib.rs b/core/src/lib.rs index 4bd824c0a..fdd40b3ea 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -36,6 +36,7 @@ extern crate log; use failure; #[macro_use] extern crate failure_derive; +extern crate zeroize; #[macro_use] pub mod macros; diff --git a/core/src/libtx/aggsig.rs b/core/src/libtx/aggsig.rs index d6a6a8f99..dfdd36775 100644 --- a/core/src/libtx/aggsig.rs +++ b/core/src/libtx/aggsig.rs @@ -21,6 +21,7 @@ use crate::libtx::error::{Error, ErrorKind}; use crate::util::secp::key::{PublicKey, SecretKey}; use crate::util::secp::pedersen::Commitment; use crate::util::secp::{self, aggsig, Message, Secp256k1, Signature}; +use grin_keychain::SwitchCommitmentType; /// Creates a new secure nonce (as a SecretKey), guaranteed to be usable during /// aggsig creation. @@ -231,15 +232,17 @@ pub fn verify_partial_sig( /// use core::libtx::{aggsig, proof}; /// use core::core::transaction::{kernel_sig_msg, KernelFeatures}; /// use core::core::{Output, OutputFeatures}; -/// use keychain::{Keychain, ExtKeychain}; +/// use keychain::{Keychain, ExtKeychain, SwitchCommitmentType}; /// /// let secp = Secp256k1::with_caps(ContextFlag::Commit); /// let keychain = ExtKeychain::from_random_seed(false).unwrap(); /// let fees = 10_000; /// let value = reward(fees); /// let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); -/// let commit = keychain.commit(value, &key_id).unwrap(); -/// let rproof = proof::create(&keychain, value, &key_id, commit, None).unwrap(); +/// let switch = &SwitchCommitmentType::Regular; +/// let commit = keychain.commit(value, &key_id, switch).unwrap(); +/// let builder = proof::ProofBuilder::new(&keychain); +/// let rproof = proof::create(&keychain, &builder, value, &key_id, switch, commit, None).unwrap(); /// let output = Output { /// features: OutputFeatures::Coinbase, /// commit: commit, @@ -266,7 +269,7 @@ pub fn sign_from_key_id( where K: Keychain, { - let skey = k.derive_key(value, key_id)?; + let skey = k.derive_key(value, key_id, &SwitchCommitmentType::Regular)?; // TODO: proper support for different switch commitment schemes let sig = aggsig::sign_single(secp, &msg, &skey, s_nonce, None, None, blind_sum, None)?; Ok(sig) } @@ -296,7 +299,7 @@ where /// use util::secp::{ContextFlag, Secp256k1}; /// use core::core::transaction::{kernel_sig_msg, KernelFeatures}; /// use core::core::{Output, OutputFeatures}; -/// use keychain::{Keychain, ExtKeychain}; +/// use keychain::{Keychain, ExtKeychain, SwitchCommitmentType}; /// /// // Create signature /// let secp = Secp256k1::with_caps(ContextFlag::Commit); @@ -304,8 +307,10 @@ where /// let fees = 10_000; /// let value = reward(fees); /// let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); -/// let commit = keychain.commit(value, &key_id).unwrap(); -/// let rproof = proof::create(&keychain, value, &key_id, commit, None).unwrap(); +/// let switch = &SwitchCommitmentType::Regular; +/// let commit = keychain.commit(value, &key_id, switch).unwrap(); +/// let builder = proof::ProofBuilder::new(&keychain); +/// let rproof = proof::create(&keychain, &builder, value, &key_id, switch, commit, None).unwrap(); /// let output = Output { /// features: OutputFeatures::Coinbase, /// commit: commit, diff --git a/core/src/libtx/build.rs b/core/src/libtx/build.rs index ef2d67c6d..1b1037f63 100644 --- a/core/src/libtx/build.rs +++ b/core/src/libtx/build.rs @@ -27,33 +27,42 @@ use crate::core::{Input, Output, OutputFeatures, Transaction, TxKernel}; use crate::keychain::{BlindSum, BlindingFactor, Identifier, Keychain}; -use crate::libtx::{aggsig, proof, Error}; +use crate::libtx::proof::{self, ProofBuild}; +use crate::libtx::{aggsig, Error}; +use grin_keychain::SwitchCommitmentType; /// Context information available to transaction combinators. -pub struct Context<'a, K> +pub struct Context<'a, K, B> where K: Keychain, + B: ProofBuild, { /// The keychain used for key derivation pub keychain: &'a K, + /// The bulletproof builder + pub builder: &'a B, } /// Function type returned by the transaction combinators. Transforms a /// (Transaction, BlindSum) pair into another, provided some context. -pub type Append = dyn for<'a> Fn( - &'a mut Context<'_, K>, +pub type Append = dyn for<'a> Fn( + &'a mut Context<'_, K, B>, (Transaction, TxKernel, BlindSum), ) -> (Transaction, TxKernel, BlindSum); /// Adds an input with the provided value and blinding key to the transaction /// being built. -fn build_input(value: u64, features: OutputFeatures, key_id: Identifier) -> Box> +fn build_input(value: u64, features: OutputFeatures, key_id: Identifier) -> Box> where K: Keychain, + B: ProofBuild, { Box::new( move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { - let commit = build.keychain.commit(value, &key_id).unwrap(); + let commit = build + .keychain + .commit(value, &key_id, &SwitchCommitmentType::Regular) + .unwrap(); // TODO: proper support for different switch commitment schemes let input = Input::new(features, commit); ( tx.with_input(input), @@ -66,9 +75,10 @@ where /// Adds an input with the provided value and blinding key to the transaction /// being built. -pub fn input(value: u64, key_id: Identifier) -> Box> +pub fn input(value: u64, key_id: Identifier) -> Box> where K: Keychain, + B: ProofBuild, { debug!( "Building input (spending regular output): {}, {}", @@ -78,9 +88,10 @@ where } /// Adds a coinbase input spending a coinbase output. -pub fn coinbase_input(value: u64, key_id: Identifier) -> Box> +pub fn coinbase_input(value: u64, key_id: Identifier) -> Box> where K: Keychain, + B: ProofBuild, { debug!("Building input (spending coinbase): {}, {}", value, key_id); build_input(value, OutputFeatures::Coinbase, key_id) @@ -88,17 +99,30 @@ where /// Adds an output with the provided value and key identifier from the /// keychain. -pub fn output(value: u64, key_id: Identifier) -> Box> +pub fn output(value: u64, key_id: Identifier) -> Box> where K: Keychain, + B: ProofBuild, { Box::new( move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { - let commit = build.keychain.commit(value, &key_id).unwrap(); + // TODO: proper support for different switch commitment schemes + let switch = &SwitchCommitmentType::Regular; + + let commit = build.keychain.commit(value, &key_id, switch).unwrap(); debug!("Building output: {}, {:?}", value, commit); - let rproof = proof::create(build.keychain, value, &key_id, commit, None).unwrap(); + let rproof = proof::create( + build.keychain, + build.builder, + value, + &key_id, + switch, + commit, + None, + ) + .unwrap(); ( tx.with_output(Output { @@ -114,9 +138,10 @@ where } /// Sets the fee on the transaction being built. -pub fn with_fee(fee: u64) -> Box> +pub fn with_fee(fee: u64) -> Box> where K: Keychain, + B: ProofBuild, { Box::new( move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { @@ -126,9 +151,10 @@ where } /// Sets the lock_height on the transaction being built. -pub fn with_lock_height(lock_height: u64) -> Box> +pub fn with_lock_height(lock_height: u64) -> Box> where K: Keychain, + B: ProofBuild, { Box::new( move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { @@ -140,9 +166,10 @@ where /// 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. -pub fn with_excess(excess: BlindingFactor) -> Box> +pub fn with_excess(excess: BlindingFactor) -> Box> where K: Keychain, + B: ProofBuild, { Box::new( move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { @@ -152,9 +179,10 @@ where } /// Sets a known tx "offset". Used in final step of tx construction. -pub fn with_offset(offset: BlindingFactor) -> Box> +pub fn with_offset(offset: BlindingFactor) -> Box> where K: Keychain, + B: ProofBuild, { Box::new( move |_build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) { @@ -166,9 +194,10 @@ where /// Sets an initial transaction to add to when building a new transaction. /// We currently only support building a tx with a single kernel with /// build::transaction() -pub fn initial_tx(mut tx: Transaction) -> Box> +pub fn initial_tx(mut tx: Transaction) -> Box> where K: Keychain, + B: ProofBuild, { assert_eq!(tx.kernels().len(), 1); let kern = tx.kernels_mut().remove(0); @@ -189,14 +218,16 @@ where /// let (tx2, _) = build::transaction(vec![initial_tx(tx1), with_excess(sum), /// output_rand(2)], keychain).unwrap(); /// -pub fn partial_transaction( - elems: Vec>>, +pub fn partial_transaction( + elems: Vec>>, keychain: &K, + builder: &B, ) -> Result<(Transaction, BlindingFactor), Error> where K: Keychain, + B: ProofBuild, { - let mut ctx = Context { keychain }; + let mut ctx = Context { keychain, builder }; let (tx, kern, sum) = elems.iter().fold( (Transaction::empty(), TxKernel::empty(), BlindSum::new()), |acc, elem| elem(&mut ctx, acc), @@ -212,11 +243,16 @@ where } /// Builds a complete transaction. -pub fn transaction(elems: Vec>>, keychain: &K) -> Result +pub fn transaction( + elems: Vec>>, + keychain: &K, + builder: &B, +) -> Result where K: Keychain, + B: ProofBuild, { - let mut ctx = Context { keychain }; + let mut ctx = Context { keychain, builder }; let (mut tx, mut kern, sum) = elems.iter().fold( (Transaction::empty(), TxKernel::empty(), BlindSum::new()), |acc, elem| elem(&mut ctx, acc), @@ -260,6 +296,7 @@ mod test { use crate::core::transaction::Weighting; use crate::core::verifier_cache::{LruVerifierCache, VerifierCache}; use crate::keychain::{ExtKeychain, ExtKeychainPath}; + use crate::libtx::ProofBuilder; fn verifier_cache() -> Arc> { Arc::new(RwLock::new(LruVerifierCache::new())) @@ -268,6 +305,7 @@ mod test { #[test] fn blind_simple_tx() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier(); let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier(); let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier(); @@ -282,6 +320,7 @@ mod test { with_fee(2), ], &keychain, + &builder, ) .unwrap(); @@ -291,6 +330,7 @@ mod test { #[test] fn blind_simple_tx_with_offset() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier(); let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier(); let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier(); @@ -305,6 +345,7 @@ mod test { with_fee(2), ], &keychain, + &builder, ) .unwrap(); @@ -314,6 +355,7 @@ mod test { #[test] fn blind_simpler_tx() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier(); let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier(); @@ -322,6 +364,7 @@ mod test { let tx = transaction( vec![input(6, key_id1), output(2, key_id2), with_fee(4)], &keychain, + &builder, ) .unwrap(); diff --git a/core/src/libtx/mod.rs b/core/src/libtx/mod.rs index e48047e67..d2eaa8cc5 100644 --- a/core/src/libtx/mod.rs +++ b/core/src/libtx/mod.rs @@ -31,6 +31,7 @@ pub mod secp_ser; use crate::consensus; use crate::core::Transaction; +pub use self::proof::ProofBuilder; pub use crate::libtx::error::{Error, ErrorKind}; const DEFAULT_BASE_FEE: u64 = consensus::MILLI_GRIN; diff --git a/core/src/libtx/proof.rs b/core/src/libtx/proof.rs index cc0a6bb50..5c848a998 100644 --- a/core/src/libtx/proof.rs +++ b/core/src/libtx/proof.rs @@ -14,31 +14,47 @@ //! Rangeproof library functions -use crate::keychain::{Identifier, Keychain}; +use crate::blake2::blake2b::blake2b; +use crate::keychain::extkey_bip32::BIP32GrinHasher; +use crate::keychain::{Identifier, Keychain, SwitchCommitmentType, ViewKey}; use crate::libtx::error::{Error, ErrorKind}; use crate::util::secp::key::SecretKey; -use crate::util::secp::pedersen::{Commitment, ProofInfo, ProofMessage, RangeProof}; +use crate::util::secp::pedersen::{Commitment, ProofMessage, RangeProof}; use crate::util::secp::{self, Secp256k1}; +use crate::zeroize::Zeroize; +use std::convert::TryFrom; /// Create a bulletproof -pub fn create( +pub fn create( k: &K, + b: &B, amount: u64, key_id: &Identifier, + switch: &SwitchCommitmentType, _commit: Commitment, extra_data: Option>, ) -> Result where K: Keychain, + B: ProofBuild, { - let commit = k.commit(amount, key_id)?; - let skey = k.derive_key(amount, key_id)?; - let nonce = k - .create_nonce(&commit) - .map_err(|e| ErrorKind::RangeProof(e.to_string()))?; - let message = ProofMessage::from_bytes(&key_id.serialize_path()); - Ok(k.secp() - .bullet_proof(amount, skey, nonce, extra_data, Some(message))) + // 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 commit = k.commit(amount, key_id, switch)?; + let skey = k.derive_key(amount, key_id, switch)?; + let rewind_nonce = b.rewind_nonce(secp, &commit)?; + let private_nonce = b.private_nonce(secp, &commit)?; + let message = b.proof_message(secp, key_id, switch)?; + Ok(secp.bullet_proof( + amount, + skey, + rewind_nonce, + private_nonce, + extra_data, + Some(message), + )) } /// Verify a proof @@ -55,35 +71,689 @@ pub fn verify( } } -/// Rewind a rangeproof to retrieve the amount -pub fn rewind( - k: &K, +/// Rewind a rangeproof to retrieve the amount, derivation path and switch commitment type +pub fn rewind( + secp: &Secp256k1, + b: &B, commit: Commitment, extra_data: Option>, proof: RangeProof, -) -> Result +) -> Result, Error> +where + B: ProofBuild, +{ + let nonce = b + .rewind_nonce(secp, &commit) + .map_err(|e| ErrorKind::RangeProof(e.to_string()))?; + let info = secp.rewind_bullet_proof(commit, nonce, extra_data, proof); + if info.is_err() { + return Ok(None); + } + let info = info.unwrap(); + + let amount = info.value; + let check = b + .check_output(secp, &commit, amount, info.message) + .map_err(|e| ErrorKind::RangeProof(e.to_string()))?; + + Ok(check.map(|(id, switch)| (amount, id, switch))) +} + +/// Used for building proofs and checking if the output belongs to the wallet +pub trait ProofBuild { + /// Create a BP nonce that will allow to rewind the derivation path and flags + fn rewind_nonce(&self, secp: &Secp256k1, commit: &Commitment) -> Result; + + /// Create a BP nonce that blinds the private key + fn private_nonce(&self, secp: &Secp256k1, commit: &Commitment) -> Result; + + /// Create a BP message + fn proof_message( + &self, + secp: &Secp256k1, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result; + + /// Check if the output belongs to this keychain + fn check_output( + &self, + secp: &Secp256k1, + commit: &Commitment, + amount: u64, + message: ProofMessage, + ) -> Result, Error>; +} + +/// The new, more flexible proof builder +pub struct ProofBuilder<'a, K> where K: Keychain, { - let nonce = k - .create_nonce(&commit) - .map_err(|e| ErrorKind::RangeProof(e.to_string()))?; - let proof_message = k - .secp() - .rewind_bullet_proof(commit, nonce, extra_data, proof); - let proof_info = match proof_message { - Ok(p) => p, - Err(_) => ProofInfo { - success: false, - value: 0, - message: ProofMessage::empty(), - blinding: SecretKey([0; secp::constants::SECRET_KEY_SIZE]), - mlen: 0, - min: 0, - max: 0, - exp: 0, - mantissa: 0, - }, - }; - return Ok(proof_info); + keychain: &'a K, + rewind_hash: Vec, + private_hash: Vec, +} + +impl<'a, K> ProofBuilder<'a, K> +where + K: Keychain, +{ + /// Creates a new instance of this proof builder + pub fn new(keychain: &'a K) -> Self { + let private_root_key = keychain + .derive_key(0, &K::root_key_id(), &SwitchCommitmentType::None) + .unwrap(); + + let private_hash = blake2b(32, &[], &private_root_key.0).as_bytes().to_vec(); + + let public_root_key = keychain + .public_root_key() + .serialize_vec(keychain.secp(), true); + let rewind_hash = blake2b(32, &[], &public_root_key[..]).as_bytes().to_vec(); + + Self { + keychain, + rewind_hash, + private_hash, + } + } + + fn nonce(&self, commit: &Commitment, private: bool) -> Result { + let hash = if private { + &self.private_hash + } else { + &self.rewind_hash + }; + let res = blake2b(32, &commit.0, hash); + SecretKey::from_slice(self.keychain.secp(), res.as_bytes()).map_err(|e| { + ErrorKind::RangeProof(format!("Unable to create nonce: {:?}", e).to_string()).into() + }) + } +} + +impl<'a, K> ProofBuild for ProofBuilder<'a, K> +where + K: Keychain, +{ + fn rewind_nonce(&self, _secp: &Secp256k1, commit: &Commitment) -> Result { + self.nonce(commit, false) + } + + fn private_nonce(&self, _secp: &Secp256k1, commit: &Commitment) -> Result { + self.nonce(commit, true) + } + + /// Message bytes: + /// 0: reserved for future use + /// 1: wallet type (0 for standard) + /// 2: switch commitment type + /// 3: path depth + /// 4-19: derivation path + fn proof_message( + &self, + _secp: &Secp256k1, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result { + let mut msg = [0; 20]; + msg[2] = u8::from(switch); + let id_bytes = id.to_bytes(); + for i in 0..17 { + msg[i + 3] = id_bytes[i]; + } + Ok(ProofMessage::from_bytes(&msg)) + } + + fn check_output( + &self, + _secp: &Secp256k1, + commit: &Commitment, + amount: u64, + message: ProofMessage, + ) -> Result, Error> { + if message.len() != 20 { + return Ok(None); + } + let msg = message.as_bytes(); + let exp: [u8; 2] = [0; 2]; + if msg[..2] != exp { + return Ok(None); + } + let switch = match SwitchCommitmentType::try_from(msg[2]) { + Ok(s) => s, + Err(_) => return Ok(None), + }; + let depth = u8::min(msg[3], 4); + let id = Identifier::from_serialized_path(depth, &msg[4..]); + + let commit_exp = self.keychain.commit(amount, &id, &switch)?; + match commit == &commit_exp { + true => Ok(Some((id, switch))), + false => Ok(None), + } + } +} + +impl<'a, K> Zeroize for ProofBuilder<'a, K> +where + K: Keychain, +{ + fn zeroize(&mut self) { + self.rewind_hash.zeroize(); + self.private_hash.zeroize(); + } +} + +impl<'a, K> Drop for ProofBuilder<'a, K> +where + K: Keychain, +{ + fn drop(&mut self) { + self.zeroize(); + } +} + +/// The legacy proof builder, used before the first hard fork +pub struct LegacyProofBuilder<'a, K> +where + K: Keychain, +{ + keychain: &'a K, + root_hash: Vec, +} + +impl<'a, K> LegacyProofBuilder<'a, K> +where + K: Keychain, +{ + /// Creates a new instance of this proof builder + pub fn new(keychain: &'a K) -> Self { + Self { + keychain, + root_hash: keychain + .derive_key(0, &K::root_key_id(), &SwitchCommitmentType::Regular) + .unwrap() + .0 + .to_vec(), + } + } + + fn nonce(&self, commit: &Commitment) -> Result { + let res = blake2b(32, &commit.0, &self.root_hash); + SecretKey::from_slice(self.keychain.secp(), res.as_bytes()).map_err(|e| { + ErrorKind::RangeProof(format!("Unable to create nonce: {:?}", e).to_string()).into() + }) + } +} + +impl<'a, K> ProofBuild for LegacyProofBuilder<'a, K> +where + K: Keychain, +{ + fn rewind_nonce(&self, _secp: &Secp256k1, commit: &Commitment) -> Result { + self.nonce(commit) + } + + fn private_nonce(&self, _secp: &Secp256k1, commit: &Commitment) -> Result { + self.nonce(commit) + } + + /// Message bytes: + /// 0-3: 0 + /// 4-19: derivation path + /// All outputs with this scheme are assumed to use regular switch commitments + fn proof_message( + &self, + _secp: &Secp256k1, + id: &Identifier, + _switch: &SwitchCommitmentType, + ) -> Result { + let mut msg = [0; 20]; + let id_ser = id.serialize_path(); + for i in 0..16 { + msg[i + 4] = id_ser[i]; + } + Ok(ProofMessage::from_bytes(&msg)) + } + + fn check_output( + &self, + _secp: &Secp256k1, + commit: &Commitment, + amount: u64, + message: ProofMessage, + ) -> Result, Error> { + if message.len() != 20 { + return Ok(None); + } + + let msg = message.as_bytes(); + let id = Identifier::from_serialized_path(3, &msg[4..]); + let exp: [u8; 4] = [0; 4]; + if msg[..4] != exp { + return Ok(None); + } + + let commit_exp = self + .keychain + .commit(amount, &id, &SwitchCommitmentType::Regular)?; + match commit == &commit_exp { + true => Ok(Some((id, SwitchCommitmentType::Regular))), + false => Ok(None), + } + } +} + +impl<'a, K> Zeroize for LegacyProofBuilder<'a, K> +where + K: Keychain, +{ + fn zeroize(&mut self) { + self.root_hash.zeroize(); + } +} + +impl<'a, K> Drop for LegacyProofBuilder<'a, K> +where + K: Keychain, +{ + fn drop(&mut self) { + self.zeroize(); + } +} + +impl ProofBuild for ViewKey { + fn rewind_nonce(&self, secp: &Secp256k1, commit: &Commitment) -> Result { + let res = blake2b(32, &commit.0, &self.rewind_hash); + SecretKey::from_slice(secp, res.as_bytes()).map_err(|e| { + ErrorKind::RangeProof(format!("Unable to create nonce: {:?}", e).to_string()).into() + }) + } + + fn private_nonce(&self, _secp: &Secp256k1, _commit: &Commitment) -> Result { + unimplemented!(); + } + + fn proof_message( + &self, + _secp: &Secp256k1, + _id: &Identifier, + _switch: &SwitchCommitmentType, + ) -> Result { + unimplemented!(); + } + + fn check_output( + &self, + secp: &Secp256k1, + commit: &Commitment, + amount: u64, + message: ProofMessage, + ) -> Result, Error> { + if message.len() != 20 { + return Ok(None); + } + let msg = message.as_bytes(); + let exp: [u8; 2] = [0; 2]; + if msg[..2] != exp { + return Ok(None); + } + let switch = match SwitchCommitmentType::try_from(msg[2]) { + Ok(s) => s, + Err(_) => return Ok(None), + }; + let depth = u8::min(msg[3], 4); + let id = Identifier::from_serialized_path(depth, &msg[4..]); + + let path = id.to_path(); + if self.depth > path.depth { + return Ok(None); + } + + // For non-root key, check child number of current depth + if self.depth > 0 + && path.depth > 0 + && self.child_number != path.path[self.depth as usize - 1] + { + return Ok(None); + } + + let mut key = self.clone(); + let mut hasher = BIP32GrinHasher::new(self.is_floo); + for i in self.depth..path.depth { + let child_number = path.path[i as usize]; + if child_number.is_hardened() { + return Ok(None); + } + key = key.ckd_pub(&secp, &mut hasher, child_number)?; + } + let pub_key = key.commit(secp, amount, &switch)?; + if commit.to_pubkey(&secp)? == pub_key { + Ok(Some((id, switch))) + } else { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::keychain::ExtKeychain; + use grin_keychain::ChildNumber; + use rand::{thread_rng, Rng}; + + #[test] + fn legacy_builder() { + let rng = &mut thread_rng(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = LegacyProofBuilder::new(&keychain); + let amount = rng.gen(); + let id = ExtKeychain::derive_key_id(3, rng.gen(), rng.gen(), rng.gen(), 0); + let switch = SwitchCommitmentType::Regular; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + assert!(verify(&keychain.secp(), commit.clone(), proof.clone(), None).is_ok()); + let rewind = rewind(keychain.secp(), &builder, commit, None, proof).unwrap(); + assert!(rewind.is_some()); + let (r_amount, r_id, r_switch) = rewind.unwrap(); + assert_eq!(r_amount, amount); + assert_eq!(r_id, id); + assert_eq!(r_switch, switch); + } + + #[test] + fn builder() { + let rng = &mut thread_rng(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); + let amount = rng.gen(); + let id = ExtKeychain::derive_key_id(3, rng.gen(), rng.gen(), rng.gen(), 0); + // With switch commitment + let commit_a = { + let switch = SwitchCommitmentType::Regular; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + assert!(verify(&keychain.secp(), commit.clone(), proof.clone(), None).is_ok()); + let rewind = rewind(keychain.secp(), &builder, commit.clone(), None, proof).unwrap(); + assert!(rewind.is_some()); + let (r_amount, r_id, r_switch) = rewind.unwrap(); + assert_eq!(r_amount, amount); + assert_eq!(r_id, id); + assert_eq!(r_switch, switch); + commit + }; + // Without switch commitment + let commit_b = { + let switch = SwitchCommitmentType::None; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + assert!(verify(&keychain.secp(), commit.clone(), proof.clone(), None).is_ok()); + let rewind = rewind(keychain.secp(), &builder, commit.clone(), None, proof).unwrap(); + assert!(rewind.is_some()); + let (r_amount, r_id, r_switch) = rewind.unwrap(); + assert_eq!(r_amount, amount); + assert_eq!(r_id, id); + assert_eq!(r_switch, switch); + commit + }; + // The resulting pedersen commitments should be different + assert_ne!(commit_a, commit_b); + } + + #[test] + fn view_key() { + // TODO + /*let rng = &mut thread_rng(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + + let builder = ProofBuilder::new(&keychain); + let mut hasher = keychain.hasher(); + let view_key = ViewKey::create(&keychain, keychain.master.clone(), &mut hasher, false).unwrap(); + assert_eq!(builder.rewind_hash, view_key.rewind_hash); + + let amount = rng.gen(); + //let id = ExtKeychain::derive_key_id(3, rng.gen::() as u32, rng.gen::() as u32, rng.gen::() as u32, 0); + let id = ExtKeychain::derive_key_id(0, 0, 0, 0, 0); + let switch = SwitchCommitmentType::Regular; + println!("commit_0 = {:?}", keychain.commit(amount, &id, &SwitchCommitmentType::None).unwrap().0.to_vec()); + let commit = keychain.commit(amount, &id, &switch).unwrap(); + + // Generate proof with ProofBuilder.. + let proof = create(&keychain, &builder, amount, &id, &switch, commit.clone(), None).unwrap(); + // ..and rewind with ViewKey + let rewind = rewind(keychain.secp(), &view_key, commit.clone(), None, proof); + + assert!(rewind.is_ok()); + let rewind = rewind.unwrap(); + assert!(rewind.is_some()); + let (r_amount, r_id, r_switch) = rewind.unwrap(); + assert_eq!(r_amount, amount); + assert_eq!(r_id, id); + assert_eq!(r_switch, switch);*/ + } + + #[test] + fn view_key_no_switch() { + let rng = &mut thread_rng(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + + let builder = ProofBuilder::new(&keychain); + let mut hasher = keychain.hasher(); + let view_key = + ViewKey::create(&keychain, keychain.master.clone(), &mut hasher, false).unwrap(); + assert_eq!(builder.rewind_hash, view_key.rewind_hash); + + let amount = rng.gen(); + let id = ExtKeychain::derive_key_id( + 3, + rng.gen::() as u32, + rng.gen::() as u32, + rng.gen::() as u32, + 0, + ); + let switch = SwitchCommitmentType::None; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + + // Generate proof with ProofBuilder.. + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + // ..and rewind with ViewKey + let rewind = rewind(keychain.secp(), &view_key, commit.clone(), None, proof); + + assert!(rewind.is_ok()); + let rewind = rewind.unwrap(); + assert!(rewind.is_some()); + let (r_amount, r_id, r_switch) = rewind.unwrap(); + assert_eq!(r_amount, amount); + assert_eq!(r_id, id); + assert_eq!(r_switch, switch); + } + + #[test] + fn view_key_hardened() { + let rng = &mut thread_rng(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + + let builder = ProofBuilder::new(&keychain); + let mut hasher = keychain.hasher(); + let view_key = + ViewKey::create(&keychain, keychain.master.clone(), &mut hasher, false).unwrap(); + assert_eq!(builder.rewind_hash, view_key.rewind_hash); + + let amount = rng.gen(); + let id = ExtKeychain::derive_key_id( + 3, + rng.gen::() as u32, + u32::max_value() - 2, + rng.gen::() as u32, + 0, + ); + let switch = SwitchCommitmentType::None; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + + // Generate proof with ProofBuilder.. + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + // ..and rewind with ViewKey + let rewind = rewind(keychain.secp(), &view_key, commit.clone(), None, proof); + + assert!(rewind.is_ok()); + let rewind = rewind.unwrap(); + assert!(rewind.is_none()); + } + + #[test] + fn view_key_child() { + let rng = &mut thread_rng(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + + let builder = ProofBuilder::new(&keychain); + let mut hasher = keychain.hasher(); + let view_key = + ViewKey::create(&keychain, keychain.master.clone(), &mut hasher, false).unwrap(); + assert_eq!(builder.rewind_hash, view_key.rewind_hash); + + // Same child + { + let child_view_key = view_key + .ckd_pub( + keychain.secp(), + &mut hasher, + ChildNumber::from_normal_idx(10), + ) + .unwrap(); + assert_eq!(child_view_key.depth, 1); + + let amount = rng.gen(); + let id = ExtKeychain::derive_key_id( + 3, + 10, + rng.gen::() as u32, + rng.gen::() as u32, + 0, + ); + let switch = SwitchCommitmentType::None; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + + // Generate proof with ProofBuilder.. + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + // ..and rewind with child ViewKey + let rewind = rewind( + keychain.secp(), + &child_view_key, + commit.clone(), + None, + proof, + ); + + assert!(rewind.is_ok()); + let rewind = rewind.unwrap(); + assert!(rewind.is_some()); + let (r_amount, r_id, r_switch) = rewind.unwrap(); + assert_eq!(r_amount, amount); + assert_eq!(r_id, id); + assert_eq!(r_switch, switch); + } + + // Different child + { + let child_view_key = view_key + .ckd_pub( + keychain.secp(), + &mut hasher, + ChildNumber::from_normal_idx(11), + ) + .unwrap(); + assert_eq!(child_view_key.depth, 1); + + let amount = rng.gen(); + let id = ExtKeychain::derive_key_id( + 3, + 10, + rng.gen::() as u32, + rng.gen::() as u32, + 0, + ); + let switch = SwitchCommitmentType::None; + let commit = keychain.commit(amount, &id, &switch).unwrap(); + + // Generate proof with ProofBuilder.. + let proof = create( + &keychain, + &builder, + amount, + &id, + &switch, + commit.clone(), + None, + ) + .unwrap(); + // ..and rewind with child ViewKey + let rewind = rewind( + keychain.secp(), + &child_view_key, + commit.clone(), + None, + proof, + ); + + assert!(rewind.is_ok()); + let rewind = rewind.unwrap(); + assert!(rewind.is_none()); + } + } } diff --git a/core/src/libtx/reward.rs b/core/src/libtx/reward.rs index 74bf21206..d4bb88c53 100644 --- a/core/src/libtx/reward.rs +++ b/core/src/libtx/reward.rs @@ -19,25 +19,33 @@ use crate::core::transaction::kernel_sig_msg; use crate::core::{KernelFeatures, Output, OutputFeatures, TxKernel}; use crate::keychain::{Identifier, Keychain}; use crate::libtx::error::Error; -use crate::libtx::{aggsig, proof}; +use crate::libtx::{ + aggsig, + proof::{self, ProofBuild}, +}; use crate::util::{secp, static_secp_instance}; +use grin_keychain::SwitchCommitmentType; /// output a reward output -pub fn output( +pub fn output( keychain: &K, + builder: &B, key_id: &Identifier, fees: u64, test_mode: bool, ) -> Result<(Output, TxKernel), Error> where K: Keychain, + B: ProofBuild, { let value = reward(fees); - let commit = keychain.commit(value, key_id)?; + // TODO: proper support for different switch commitment schemes + let switch = &SwitchCommitmentType::Regular; + let commit = keychain.commit(value, key_id, switch)?; trace!("Block reward - Pedersen Commit is: {:?}", commit,); - let rproof = proof::create(keychain, value, key_id, commit, None)?; + let rproof = proof::create(keychain, builder, value, key_id, switch, commit, None)?; let output = Output { features: OutputFeatures::Coinbase, diff --git a/core/tests/block.rs b/core/tests/block.rs index 5307c7c0f..e561f2bcf 100644 --- a/core/tests/block.rs +++ b/core/tests/block.rs @@ -25,6 +25,7 @@ use crate::core::core::{ Block, BlockHeader, CompactBlock, HeaderVersion, KernelFeatures, OutputFeatures, }; use crate::core::libtx::build::{self, input, output, with_fee}; +use crate::core::libtx::ProofBuilder; use crate::core::{global, ser}; use crate::keychain::{BlindingFactor, ExtKeychain, Keychain}; use crate::util::secp; @@ -45,6 +46,7 @@ fn verifier_cache() -> Arc> { #[allow(dead_code)] fn too_large_block() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let max_out = global::max_block_weight() / BLOCK_OUTPUT_WEIGHT; let mut pks = vec![]; @@ -59,12 +61,12 @@ fn too_large_block() { let now = Instant::now(); parts.append(&mut vec![input(500000, pks.pop().unwrap()), with_fee(2)]); - let tx = build::transaction(parts, &keychain).unwrap(); + let tx = build::transaction(parts, &keychain, &builder).unwrap(); println!("Build tx: {}", now.elapsed().as_secs()); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx], &keychain, &prev, &key_id); + let b = new_block(vec![&tx], &keychain, &builder, &prev, &key_id); assert!(b .validate(&BlindingFactor::zero(), verifier_cache()) .is_err()); @@ -86,6 +88,7 @@ fn very_empty_block() { // builds a block with a tx spending another and check that cut_through occurred fn block_with_cut_through() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -94,17 +97,19 @@ fn block_with_cut_through() { let mut btx2 = build::transaction( vec![input(7, key_id1), output(5, key_id2.clone()), with_fee(2)], &keychain, + &builder, ) .unwrap(); // spending tx2 - reuse key_id2 - let mut btx3 = txspend1i1o(5, &keychain, key_id2.clone(), key_id3); + let mut btx3 = txspend1i1o(5, &keychain, &builder, key_id2.clone(), key_id3); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let b = new_block( vec![&mut btx1, &mut btx2, &mut btx3], &keychain, + &builder, &prev, &key_id, ); @@ -120,9 +125,10 @@ fn block_with_cut_through() { #[test] fn empty_block_with_coinbase_is_valid() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![], &keychain, &prev, &key_id); + let b = new_block(vec![], &keychain, &builder, &prev, &key_id); assert_eq!(b.inputs().len(), 0); assert_eq!(b.outputs().len(), 1); @@ -157,9 +163,10 @@ fn empty_block_with_coinbase_is_valid() { // additionally verifying the merkle_inputs_outputs also fails fn remove_coinbase_output_flag() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let mut b = new_block(vec![], &keychain, &prev, &key_id); + let mut b = new_block(vec![], &keychain, &builder, &prev, &key_id); assert!(b.outputs()[0].is_coinbase()); b.outputs_mut()[0].features = OutputFeatures::Plain; @@ -179,9 +186,10 @@ fn remove_coinbase_output_flag() { // invalidates the block and specifically it causes verify_coinbase to fail fn remove_coinbase_kernel_flag() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let mut b = new_block(vec![], &keychain, &prev, &key_id); + let mut b = new_block(vec![], &keychain, &builder, &prev, &key_id); assert!(b.kernels()[0].is_coinbase()); b.kernels_mut()[0].features = KernelFeatures::Plain; @@ -220,9 +228,10 @@ fn serialize_deserialize_header_version() { #[test] fn serialize_deserialize_block_header() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![], &keychain, &prev, &key_id); + let b = new_block(vec![], &keychain, &builder, &prev, &key_id); let header1 = b.header; let mut vec = Vec::new(); @@ -237,9 +246,10 @@ fn serialize_deserialize_block_header() { fn serialize_deserialize_block() { let tx1 = tx1i2o(); let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx1], &keychain, &prev, &key_id); + let b = new_block(vec![&tx1], &keychain, &builder, &prev, &key_id); let mut vec = Vec::new(); ser::serialize(&mut vec, &b).expect("serialization failed"); @@ -255,9 +265,10 @@ fn serialize_deserialize_block() { #[test] fn empty_block_serialized_size() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![], &keychain, &prev, &key_id); + let b = new_block(vec![], &keychain, &builder, &prev, &key_id); let mut vec = Vec::new(); ser::serialize(&mut vec, &b).expect("serialization failed"); let target_len = 1_265; @@ -267,10 +278,11 @@ fn empty_block_serialized_size() { #[test] fn block_single_tx_serialized_size() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let tx1 = tx1i2o(); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx1], &keychain, &prev, &key_id); + let b = new_block(vec![&tx1], &keychain, &builder, &prev, &key_id); let mut vec = Vec::new(); ser::serialize(&mut vec, &b).expect("serialization failed"); let target_len = 2_847; @@ -280,9 +292,10 @@ fn block_single_tx_serialized_size() { #[test] fn empty_compact_block_serialized_size() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![], &keychain, &prev, &key_id); + let b = new_block(vec![], &keychain, &builder, &prev, &key_id); let cb: CompactBlock = b.into(); let mut vec = Vec::new(); ser::serialize(&mut vec, &cb).expect("serialization failed"); @@ -293,10 +306,11 @@ fn empty_compact_block_serialized_size() { #[test] fn compact_block_single_tx_serialized_size() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let tx1 = tx1i2o(); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx1], &keychain, &prev, &key_id); + let b = new_block(vec![&tx1], &keychain, &builder, &prev, &key_id); let cb: CompactBlock = b.into(); let mut vec = Vec::new(); ser::serialize(&mut vec, &cb).expect("serialization failed"); @@ -307,6 +321,7 @@ fn compact_block_single_tx_serialized_size() { #[test] fn block_10_tx_serialized_size() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); global::set_mining_mode(global::ChainTypes::Mainnet); let mut txs = vec![]; @@ -316,7 +331,7 @@ fn block_10_tx_serialized_size() { } let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(txs.iter().collect(), &keychain, &prev, &key_id); + let b = new_block(txs.iter().collect(), &keychain, &builder, &prev, &key_id); let mut vec = Vec::new(); ser::serialize(&mut vec, &b).expect("serialization failed"); let target_len = 17_085; @@ -326,6 +341,7 @@ fn block_10_tx_serialized_size() { #[test] fn compact_block_10_tx_serialized_size() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let mut txs = vec![]; for _ in 0..10 { @@ -334,7 +350,7 @@ fn compact_block_10_tx_serialized_size() { } let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(txs.iter().collect(), &keychain, &prev, &key_id); + let b = new_block(txs.iter().collect(), &keychain, &builder, &prev, &key_id); let cb: CompactBlock = b.into(); let mut vec = Vec::new(); ser::serialize(&mut vec, &cb).expect("serialization failed"); @@ -345,10 +361,11 @@ fn compact_block_10_tx_serialized_size() { #[test] fn compact_block_hash_with_nonce() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let tx = tx1i2o(); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx], &keychain, &prev, &key_id); + let b = new_block(vec![&tx], &keychain, &builder, &prev, &key_id); let cb1: CompactBlock = b.clone().into(); let cb2: CompactBlock = b.clone().into(); @@ -375,10 +392,11 @@ fn compact_block_hash_with_nonce() { #[test] fn convert_block_to_compact_block() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let tx1 = tx1i2o(); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx1], &keychain, &prev, &key_id); + let b = new_block(vec![&tx1], &keychain, &builder, &prev, &key_id); let cb: CompactBlock = b.clone().into(); assert_eq!(cb.out_full().len(), 1); @@ -398,9 +416,10 @@ fn convert_block_to_compact_block() { #[test] fn hydrate_empty_compact_block() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![], &keychain, &prev, &key_id); + let b = new_block(vec![], &keychain, &builder, &prev, &key_id); let cb: CompactBlock = b.clone().into(); let hb = Block::hydrate_from(cb, vec![]).unwrap(); assert_eq!(hb.header, b.header); @@ -411,10 +430,11 @@ fn hydrate_empty_compact_block() { #[test] fn serialize_deserialize_compact_block() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let tx1 = tx1i2o(); let prev = BlockHeader::default(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let b = new_block(vec![&tx1], &keychain, &prev, &key_id); + let b = new_block(vec![&tx1], &keychain, &builder, &prev, &key_id); let mut cb1: CompactBlock = b.into(); @@ -437,6 +457,7 @@ fn serialize_deserialize_compact_block() { #[test] fn same_amount_outputs_copy_range_proof() { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = keychain::ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -449,6 +470,7 @@ fn same_amount_outputs_copy_range_proof() { with_fee(1), ], &keychain, + &builder, ) .unwrap(); @@ -468,6 +490,7 @@ fn same_amount_outputs_copy_range_proof() { kernels.clone(), )], &keychain, + &builder, &prev, &key_id, ); @@ -484,6 +507,7 @@ fn same_amount_outputs_copy_range_proof() { #[test] fn wrong_amount_range_proof() { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = keychain::ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -496,6 +520,7 @@ fn wrong_amount_range_proof() { with_fee(1), ], &keychain, + &builder, ) .unwrap(); let tx2 = build::transaction( @@ -506,6 +531,7 @@ fn wrong_amount_range_proof() { with_fee(1), ], &keychain, + &builder, ) .unwrap(); @@ -525,6 +551,7 @@ fn wrong_amount_range_proof() { kernels.clone(), )], &keychain, + &builder, &prev, &key_id, ); diff --git a/core/tests/common.rs b/core/tests/common.rs index 0ad0dc824..d7256ed24 100644 --- a/core/tests/common.rs +++ b/core/tests/common.rs @@ -21,6 +21,7 @@ use grin_core::core::{ }; use grin_core::libtx::{ build::{self, input, output, with_fee}, + proof::{ProofBuild, ProofBuilder}, reward, }; use grin_core::pow::Difficulty; @@ -29,6 +30,7 @@ use grin_keychain as keychain; // utility producing a transaction with 2 inputs and a single outputs pub fn tx2i1o() -> Transaction { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = keychain::ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -41,6 +43,7 @@ pub fn tx2i1o() -> Transaction { with_fee(2), ], &keychain, + &builder, ) .unwrap() } @@ -48,12 +51,14 @@ pub fn tx2i1o() -> Transaction { // utility producing a transaction with a single input and output pub fn tx1i1o() -> Transaction { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = keychain::ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0); build::transaction( vec![input(5, key_id1), output(3, key_id2), with_fee(2)], &keychain, + &builder, ) .unwrap() } @@ -63,6 +68,7 @@ pub fn tx1i1o() -> Transaction { // Note: this tx has an "offset" kernel pub fn tx1i2o() -> Transaction { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = keychain::ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = keychain::ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = keychain::ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -75,23 +81,26 @@ pub fn tx1i2o() -> Transaction { with_fee(2), ], &keychain, + &builder, ) .unwrap() } // utility to create a block without worrying about the key or previous // header -pub fn new_block( +pub fn new_block( txs: Vec<&Transaction>, keychain: &K, + builder: &B, previous_header: &BlockHeader, key_id: &Identifier, ) -> Block where K: Keychain, + B: ProofBuild, { let fees = txs.iter().map(|tx| tx.fee()).sum(); - let reward_output = reward::output(keychain, &key_id, fees, false).unwrap(); + let reward_output = reward::output(keychain, builder, &key_id, fees, false).unwrap(); Block::new( &previous_header, txs.into_iter().cloned().collect(), @@ -103,13 +112,21 @@ where // utility producing a transaction that spends an output with the provided // value and blinding key -pub fn txspend1i1o(v: u64, keychain: &K, key_id1: Identifier, key_id2: Identifier) -> Transaction +pub fn txspend1i1o( + v: u64, + keychain: &K, + builder: &B, + key_id1: Identifier, + key_id2: Identifier, +) -> Transaction where K: Keychain, + B: ProofBuild, { build::transaction( vec![input(v, key_id1), output(3, key_id2), with_fee(2)], keychain, + builder, ) .unwrap() } diff --git a/core/tests/core.rs b/core/tests/core.rs index cf139fd20..711436df9 100644 --- a/core/tests/core.rs +++ b/core/tests/core.rs @@ -24,6 +24,7 @@ use self::core::core::{aggregate, deaggregate, KernelFeatures, Output, Transacti use self::core::libtx::build::{ self, initial_tx, input, output, with_excess, with_fee, with_lock_height, }; +use self::core::libtx::ProofBuilder; use self::core::ser; use self::keychain::{BlindingFactor, ExtKeychain, Keychain}; use self::util::static_secp_instance; @@ -75,18 +76,15 @@ fn tx_double_ser_deser() { #[test] #[should_panic(expected = "Keychain Error")] fn test_zero_commit_fails() { - let mut keychain = ExtKeychain::from_random_seed(false).unwrap(); - keychain.set_use_switch_commits(false); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); // blinding should fail as signing with a zero r*G shouldn't work build::transaction( - vec![ - input(10, key_id1.clone()), - output(9, key_id1.clone()), - with_fee(1), - ], + vec![input(10, key_id1.clone()), output(10, key_id1.clone())], &keychain, + &builder, ) .unwrap(); } @@ -98,6 +96,7 @@ fn verifier_cache() -> Arc> { #[test] fn build_tx_kernel() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -111,6 +110,7 @@ fn build_tx_kernel() { with_fee(2), ], &keychain, + &builder, ) .unwrap(); @@ -350,6 +350,7 @@ fn basic_transaction_deaggregation() { #[test] fn hash_output() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -362,6 +363,7 @@ fn hash_output() { with_fee(1), ], &keychain, + &builder, ) .unwrap(); let h = tx.outputs()[0].hash(); @@ -407,6 +409,7 @@ fn tx_hash_diff() { #[test] fn tx_build_exchange() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -419,9 +422,12 @@ fn tx_build_exchange() { // Alice builds her transaction, with change, which also produces the sum // of blinding factors before they're obscured. - let (tx, sum) = - build::partial_transaction(vec![in1, in2, output(1, key_id3), with_fee(2)], &keychain) - .unwrap(); + let (tx, sum) = build::partial_transaction( + vec![in1, in2, output(1, key_id3), with_fee(2)], + &keychain, + &builder, + ) + .unwrap(); (tx, sum) }; @@ -436,6 +442,7 @@ fn tx_build_exchange() { output(4, key_id4), ], &keychain, + &builder, ) .unwrap(); @@ -447,11 +454,12 @@ fn tx_build_exchange() { #[test] fn reward_empty_block() { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let previous_header = BlockHeader::default(); - let b = new_block(vec![], &keychain, &previous_header, &key_id); + let b = new_block(vec![], &keychain, &builder, &previous_header, &key_id); b.cut_through() .unwrap() @@ -462,6 +470,7 @@ fn reward_empty_block() { #[test] fn reward_with_tx_block() { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let vc = verifier_cache(); @@ -471,7 +480,13 @@ fn reward_with_tx_block() { let previous_header = BlockHeader::default(); - let block = new_block(vec![&mut tx1], &keychain, &previous_header, &key_id); + let block = new_block( + vec![&mut tx1], + &keychain, + &builder, + &previous_header, + &key_id, + ); block .cut_through() .unwrap() @@ -482,6 +497,7 @@ fn reward_with_tx_block() { #[test] fn simple_block() { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let vc = verifier_cache(); @@ -493,6 +509,7 @@ fn simple_block() { let b = new_block( vec![&mut tx1, &mut tx2], &keychain, + &builder, &previous_header, &key_id, ); @@ -503,7 +520,7 @@ fn simple_block() { #[test] fn test_block_with_timelocked_tx() { let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap(); - + let builder = ProofBuilder::new(&keychain); let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let key_id3 = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); @@ -520,12 +537,19 @@ fn test_block_with_timelocked_tx() { with_lock_height(1), ], &keychain, + &builder, ) .unwrap(); let previous_header = BlockHeader::default(); - let b = new_block(vec![&tx1], &keychain, &previous_header, &key_id3.clone()); + let b = new_block( + vec![&tx1], + &keychain, + &builder, + &previous_header, + &key_id3.clone(), + ); b.validate(&BlindingFactor::zero(), vc.clone()).unwrap(); // now try adding a timelocked tx where lock height is greater than current @@ -538,11 +562,18 @@ fn test_block_with_timelocked_tx() { with_lock_height(2), ], &keychain, + &builder, ) .unwrap(); let previous_header = BlockHeader::default(); - let b = new_block(vec![&tx1], &keychain, &previous_header, &key_id3.clone()); + let b = new_block( + vec![&tx1], + &keychain, + &builder, + &previous_header, + &key_id3.clone(), + ); match b.validate(&BlindingFactor::zero(), vc.clone()) { Err(KernelLockHeight(height)) => { diff --git a/core/tests/transaction.rs b/core/tests/transaction.rs index d215507c1..14a9a40db 100644 --- a/core/tests/transaction.rs +++ b/core/tests/transaction.rs @@ -27,8 +27,10 @@ use grin_keychain as keychain; fn test_output_ser_deser() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let commit = keychain.commit(5, &key_id).unwrap(); - let proof = proof::create(&keychain, 5, &key_id, commit, None).unwrap(); + let switch = &keychain::SwitchCommitmentType::Regular; + let commit = keychain.commit(5, &key_id, switch).unwrap(); + let builder = proof::ProofBuilder::new(&keychain); + let proof = proof::create(&keychain, &builder, 5, &key_id, switch, commit, None).unwrap(); let out = Output { features: OutputFeatures::Plain, diff --git a/core/tests/verifier_cache.rs b/core/tests/verifier_cache.rs index 819057670..002d520eb 100644 --- a/core/tests/verifier_cache.rs +++ b/core/tests/verifier_cache.rs @@ -17,7 +17,7 @@ pub mod common; use self::core::core::verifier_cache::{LruVerifierCache, VerifierCache}; use self::core::core::{Output, OutputFeatures}; use self::core::libtx::proof; -use self::keychain::{ExtKeychain, Keychain}; +use self::keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; use self::util::RwLock; use grin_core as core; use grin_keychain as keychain; @@ -34,8 +34,10 @@ fn test_verifier_cache_rangeproofs() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let commit = keychain.commit(5, &key_id).unwrap(); - let proof = proof::create(&keychain, 5, &key_id, commit, None).unwrap(); + let switch = &SwitchCommitmentType::Regular; + let commit = keychain.commit(5, &key_id, switch).unwrap(); + let builder = proof::ProofBuilder::new(&keychain); + let proof = proof::create(&keychain, &builder, 5, &key_id, switch, commit, None).unwrap(); let out = Output { features: OutputFeatures::Plain, diff --git a/keychain/src/extkey_bip32.rs b/keychain/src/extkey_bip32.rs index ab4dfdd62..533824fbe 100644 --- a/keychain/src/extkey_bip32.rs +++ b/keychain/src/extkey_bip32.rs @@ -149,7 +149,7 @@ impl BIP32Hasher for BIP32GrinHasher { } /// Extended private key -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct ExtendedPrivKey { /// The network this key is to be used on pub network: [u8; 4], @@ -399,7 +399,7 @@ impl ExtendedPrivKey { where H: BIP32Hasher, { - let mut sk: ExtendedPrivKey = *self; + let mut sk: ExtendedPrivKey = self.clone(); for cnum in cnums { sk = sk.ckd_priv(secp, hasher, *cnum)?; } diff --git a/keychain/src/keychain.rs b/keychain/src/keychain.rs index 9f1948990..91bf5ed3c 100644 --- a/keychain/src/keychain.rs +++ b/keychain/src/keychain.rs @@ -17,22 +17,33 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use crate::blake2; +use crate::blake2::blake2b::blake2b; -use crate::extkey_bip32::{BIP32GrinHasher, ExtendedPrivKey}; -use crate::types::{BlindSum, BlindingFactor, Error, ExtKeychainPath, Identifier, Keychain}; -use crate::util::secp::key::SecretKey; +use crate::extkey_bip32::{BIP32GrinHasher, ExtendedPrivKey, ExtendedPubKey}; +use crate::types::{ + BlindSum, BlindingFactor, Error, ExtKeychainPath, Identifier, Keychain, SwitchCommitmentType, +}; +use crate::util::secp::key::{PublicKey, SecretKey}; use crate::util::secp::pedersen::Commitment; use crate::util::secp::{self, Message, Secp256k1, Signature}; #[derive(Clone, Debug)] pub struct ExtKeychain { secp: Secp256k1, - master: ExtendedPrivKey, - use_switch_commits: bool, + pub master: ExtendedPrivKey, hasher: BIP32GrinHasher, } +impl ExtKeychain { + pub fn pub_root_key(&mut self) -> ExtendedPubKey { + ExtendedPubKey::from_private(&self.secp, &self.master, &mut self.hasher) + } + + pub fn hasher(&self) -> BIP32GrinHasher { + self.hasher.clone() + } +} + impl Keychain for ExtKeychain { fn from_seed(seed: &[u8], is_floo: bool) -> Result { let mut h = BIP32GrinHasher::new(is_floo); @@ -41,7 +52,6 @@ impl Keychain for ExtKeychain { let keychain = ExtKeychain { secp: secp, master: master, - use_switch_commits: true, hasher: h, }; Ok(keychain) @@ -54,7 +64,6 @@ impl Keychain for ExtKeychain { let keychain = ExtKeychain { secp: secp, master: master, - use_switch_commits: true, hasher: h, }; Ok(keychain) @@ -63,7 +72,7 @@ impl Keychain for ExtKeychain { /// For testing - probably not a good idea to use outside of tests. fn from_random_seed(is_floo: bool) -> Result { let seed: String = thread_rng().sample_iter(&Alphanumeric).take(16).collect(); - let seed = blake2::blake2b::blake2b(32, &[], seed.as_bytes()); + let seed = blake2b(32, &[], seed.as_bytes()); ExtKeychain::from_seed(seed.as_bytes(), is_floo) } @@ -75,22 +84,39 @@ impl Keychain for ExtKeychain { ExtKeychainPath::new(depth, d1, d2, d3, d4).to_identifier() } - fn derive_key(&self, amount: u64, id: &Identifier) -> Result { + fn public_root_key(&self) -> PublicKey { + let mut hasher = self.hasher.clone(); + ExtendedPubKey::from_private(&self.secp, &self.master, &mut hasher).public_key + } + + fn derive_key( + &self, + amount: u64, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result { let mut h = self.hasher.clone(); let p = id.to_path(); - let mut ext_key = self.master; + let mut ext_key = self.master.clone(); for i in 0..p.depth { ext_key = ext_key.ckd_priv(&self.secp, &mut h, p.path[i as usize])?; } - match self.use_switch_commits { - true => Ok(self.secp.blind_switch(amount, ext_key.secret_key)?), - false => Ok(ext_key.secret_key), + match *switch { + SwitchCommitmentType::Regular => { + Ok(self.secp.blind_switch(amount, ext_key.secret_key)?) + } + SwitchCommitmentType::None => Ok(ext_key.secret_key), } } - fn commit(&self, amount: u64, id: &Identifier) -> Result { - let key = self.derive_key(amount, id)?; + fn commit( + &self, + amount: u64, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result { + let key = self.derive_key(amount, id, switch)?; let commit = self.secp.commit(amount, key)?; Ok(commit) } @@ -100,7 +126,11 @@ impl Keychain for ExtKeychain { .positive_key_ids .iter() .filter_map(|k| { - let res = self.derive_key(k.value, &Identifier::from_path(&k.ext_keychain_path)); + let res = self.derive_key( + k.value, + &Identifier::from_path(&k.ext_keychain_path), + &k.switch, + ); if let Ok(s) = res { Some(s) } else { @@ -113,7 +143,11 @@ impl Keychain for ExtKeychain { .negative_key_ids .iter() .filter_map(|k| { - let res = self.derive_key(k.value, &Identifier::from_path(&k.ext_keychain_path)); + let res = self.derive_key( + k.value, + &Identifier::from_path(&k.ext_keychain_path), + &k.switch, + ); if let Ok(s) = res { Some(s) } else { @@ -122,37 +156,32 @@ impl Keychain for ExtKeychain { }) .collect(); - pos_keys.extend( - &blind_sum - .positive_blinding_factors - .iter() - .filter_map(|b| b.secret_key(&self.secp).ok()) - .collect::>(), - ); + let keys = blind_sum + .positive_blinding_factors + .iter() + .filter_map(|b| b.secret_key(&self.secp).ok().clone()) + .collect::>(); + pos_keys.extend(keys); - neg_keys.extend( - &blind_sum - .negative_blinding_factors - .iter() - .filter_map(|b| b.secret_key(&self.secp).ok()) - .collect::>(), - ); + let keys = blind_sum + .negative_blinding_factors + .iter() + .filter_map(|b| b.secret_key(&self.secp).ok().clone()) + .collect::>(); + neg_keys.extend(keys); let sum = self.secp.blind_sum(pos_keys, neg_keys)?; Ok(BlindingFactor::from_secret_key(sum)) } - fn create_nonce(&self, commit: &Commitment) -> Result { - // hash(commit|wallet root secret key (m)) as nonce - let root_key = self.derive_key(0, &Self::root_key_id())?; - let res = blake2::blake2b::blake2b(32, &commit.0, &root_key.0[..]); - let res = res.as_bytes(); - SecretKey::from_slice(&self.secp, &res) - .map_err(|e| Error::RangeProof(format!("Unable to create nonce: {:?}", e).to_string())) - } - - fn sign(&self, msg: &Message, amount: u64, id: &Identifier) -> Result { - let skey = self.derive_key(amount, id)?; + fn sign( + &self, + msg: &Message, + amount: u64, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result { + let skey = self.derive_key(amount, id, switch)?; let sig = self.secp.sign(msg, &skey)?; Ok(sig) } @@ -167,10 +196,6 @@ impl Keychain for ExtKeychain { Ok(sig) } - fn set_use_switch_commits(&mut self, value: bool) { - self.use_switch_commits = value; - } - fn secp(&self) -> &Secp256k1 { &self.secp } @@ -182,11 +207,13 @@ mod test { use crate::types::{BlindSum, BlindingFactor, ExtKeychainPath, Keychain}; use crate::util::secp; use crate::util::secp::key::SecretKey; + use crate::SwitchCommitmentType; #[test] fn test_key_derivation() { let keychain = ExtKeychain::from_random_seed(false).unwrap(); let secp = keychain.secp(); + let switch = &SwitchCommitmentType::None; let path = ExtKeychainPath::new(1, 1, 0, 0, 0); let key_id = path.to_identifier(); @@ -196,10 +223,10 @@ mod test { // now create a zero commitment using the key on the keychain associated with // the key_id - let commit = keychain.commit(0, &key_id).unwrap(); + let commit = keychain.commit(0, &key_id, switch).unwrap(); // now check we can use our key to verify a signature from this zero commitment - let sig = keychain.sign(&msg, 0, &key_id).unwrap(); + let sig = keychain.sign(&msg, 0, &key_id, switch).unwrap(); secp.verify_from_commit(&msg, &sig, &commit).unwrap(); } @@ -235,9 +262,9 @@ mod test { // create commitments for secret keys 1, 2 and 3 // all committing to the value 0 (which is what we do for tx_kernels) - let commit_1 = keychain.secp.commit(0, skey1).unwrap(); - let commit_2 = keychain.secp.commit(0, skey2).unwrap(); - let commit_3 = keychain.secp.commit(0, skey3).unwrap(); + let commit_1 = keychain.secp.commit(0, skey1.clone()).unwrap(); + let commit_2 = keychain.secp.commit(0, skey2.clone()).unwrap(); + let commit_3 = keychain.secp.commit(0, skey3.clone()).unwrap(); // now sum commitments for keys 1 and 2 let sum = keychain diff --git a/keychain/src/lib.rs b/keychain/src/lib.rs index 40dfc3a9c..b3398a39b 100644 --- a/keychain/src/lib.rs +++ b/keychain/src/lib.rs @@ -25,14 +25,19 @@ extern crate serde_derive; #[macro_use] extern crate lazy_static; +extern crate sha2; + mod base58; pub mod extkey_bip32; pub mod mnemonic; mod types; +pub mod view_key; pub mod keychain; pub use crate::extkey_bip32::ChildNumber; pub use crate::keychain::ExtKeychain; pub use crate::types::{ - BlindSum, BlindingFactor, Error, ExtKeychainPath, Identifier, Keychain, IDENTIFIER_SIZE, + BlindSum, BlindingFactor, Error, ExtKeychainPath, Identifier, Keychain, SwitchCommitmentType, + IDENTIFIER_SIZE, }; +pub use crate::view_key::ViewKey; diff --git a/keychain/src/types.rs b/keychain/src/types.rs index aa67e08bd..c3627e22b 100644 --- a/keychain/src/types.rs +++ b/keychain/src/types.rs @@ -14,6 +14,7 @@ use rand::thread_rng; use std::cmp::min; +use std::convert::TryFrom; use std::io::Cursor; use std::ops::Add; /// Keychain trait and its main supporting types. The Identifier is a @@ -129,9 +130,12 @@ impl Identifier { } pub fn to_value_path(&self, value: u64) -> ValueExtKeychainPath { + // TODO: proper support for different switch commitment schemes + // For now it is assumed all outputs are using the regular switch commitment scheme ValueExtKeychainPath { value, ext_keychain_path: self.to_path(), + switch: SwitchCommitmentType::Regular, } } @@ -318,7 +322,7 @@ impl BlindingFactor { // use blind_sum to subtract skey_1 from our key (to give k = k1 + k2) let skey = self.secret_key(secp)?; - let skey_2 = secp.blind_sum(vec![skey], vec![skey_1])?; + let skey_2 = secp.blind_sum(vec![skey], vec![skey_1.clone()])?; let blind_1 = BlindingFactor::from_secret_key(skey_1); let blind_2 = BlindingFactor::from_secret_key(skey_2); @@ -443,11 +447,12 @@ impl ExtKeychainPath { } } -/// Wrapper for amount + path +/// Wrapper for amount + switch + path #[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize)] pub struct ValueExtKeychainPath { pub value: u64, pub ext_keychain_path: ExtKeychainPath, + pub switch: SwitchCommitmentType, } pub trait Keychain: Sync + Send + Clone { @@ -467,16 +472,61 @@ pub trait Keychain: Sync + Send + Clone { /// Derives a key id from the depth of the keychain and the values at each /// depth level. See `KeychainPath` for more information. fn derive_key_id(depth: u8, d1: u32, d2: u32, d3: u32, d4: u32) -> Identifier; - fn derive_key(&self, amount: u64, id: &Identifier) -> Result; - fn commit(&self, amount: u64, id: &Identifier) -> Result; + + /// The public root key + fn public_root_key(&self) -> PublicKey; + + fn derive_key( + &self, + amount: u64, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result; + fn commit( + &self, + amount: u64, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result; fn blind_sum(&self, blind_sum: &BlindSum) -> Result; - fn create_nonce(&self, commit: &Commitment) -> Result; - fn sign(&self, msg: &Message, amount: u64, id: &Identifier) -> Result; + fn sign( + &self, + msg: &Message, + amount: u64, + id: &Identifier, + switch: &SwitchCommitmentType, + ) -> Result; fn sign_with_blinding(&self, _: &Message, _: &BlindingFactor) -> Result; - fn set_use_switch_commits(&mut self, value: bool); fn secp(&self) -> &Secp256k1; } +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum SwitchCommitmentType { + None, + Regular, +} + +impl TryFrom for SwitchCommitmentType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(SwitchCommitmentType::None), + 1 => Ok(SwitchCommitmentType::Regular), + _ => Err(()), + } + } +} + +impl From<&SwitchCommitmentType> for u8 { + fn from(switch: &SwitchCommitmentType) -> Self { + match *switch { + SwitchCommitmentType::None => 0, + SwitchCommitmentType::Regular => 1, + } + } +} + #[cfg(test)] mod test { use rand::thread_rng; @@ -519,7 +569,7 @@ mod test { fn split_blinding_factor() { let secp = Secp256k1::new(); let skey_in = SecretKey::new(&secp, &mut thread_rng()); - let blind = BlindingFactor::from_secret_key(skey_in); + let blind = BlindingFactor::from_secret_key(skey_in.clone()); let split = blind.split(&secp).unwrap(); // split a key, sum the split keys and confirm the sum matches the original key diff --git a/keychain/src/view_key.rs b/keychain/src/view_key.rs new file mode 100644 index 000000000..706094cad --- /dev/null +++ b/keychain/src/view_key.rs @@ -0,0 +1,195 @@ +use crate::blake2::blake2b::blake2b; +use byteorder::{BigEndian, ByteOrder}; +//use crate::sha2::{Digest, Sha256}; +use super::extkey_bip32::{ + BIP32Hasher, ChainCode, ChildNumber, Error as BIP32Error, ExtendedPrivKey, ExtendedPubKey, + Fingerprint, +}; +use super::types::{Error, Keychain}; +use crate::util::secp::constants::GENERATOR_PUB_J_RAW; +use crate::util::secp::ffi; +use crate::util::secp::key::{PublicKey, SecretKey}; +use crate::util::secp::Secp256k1; +use crate::SwitchCommitmentType; + +/*const VERSION_FLOO_NS: [u8;4] = [0x03, 0x27, 0x3E, 0x4B]; +const VERSION_FLOO: [u8;4] = [0x03, 0x27, 0x3E, 0x4B]; +const VERSION_MAIN_NS: [u8;4] = [0x03, 0x3C, 0x08, 0xDF]; +const VERSION_MAIN: [u8;4] = [0x03, 0x3C, 0x08, 0xDF];*/ + +/// Key that can be used to scan the chain for owned outputs +/// This is a public key, meaning it cannot be used to spend those outputs +/// At the moment only depth 0 keys can be used +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ViewKey { + /// Whether this view key is meant for floonet or not + pub is_floo: bool, + /// How many derivations this key is from the master (which is 0) + pub depth: u8, + /// Fingerprint of the parent key + parent_fingerprint: Fingerprint, + /// Child number of the key used to derive from parent (0 for master) + pub child_number: ChildNumber, + /// Public key + public_key: PublicKey, + /// Switch public key, required to view outputs that use switch commitment + switch_public_key: Option, + /// Chain code + chain_code: ChainCode, + /// Hash used to generate rewind nonce + pub rewind_hash: Vec, +} + +impl ViewKey { + pub fn create( + keychain: &K, + ext_key: ExtendedPrivKey, + hasher: &mut H, + is_floo: bool, + ) -> Result + where + K: Keychain, + H: BIP32Hasher, + { + let secp = keychain.secp(); + + let ExtendedPubKey { + network: _, + depth, + parent_fingerprint, + child_number, + public_key, + chain_code, + } = ExtendedPubKey::from_private(secp, &ext_key, hasher); + + let mut switch_public_key = PublicKey(ffi::PublicKey(GENERATOR_PUB_J_RAW)); + switch_public_key.mul_assign(secp, &ext_key.secret_key)?; + let switch_public_key = Some(switch_public_key); + + let rewind_hash = Self::rewind_hash(secp, keychain.public_root_key()); + + Ok(Self { + is_floo, + depth, + parent_fingerprint, + child_number, + public_key, + switch_public_key, + chain_code, + rewind_hash, + }) + } + + fn rewind_hash(secp: &Secp256k1, public_root_key: PublicKey) -> Vec { + let ser = public_root_key.serialize_vec(secp, true); + blake2b(32, &[], &ser[..]).as_bytes().to_vec() + } + + fn ckd_pub_tweak( + &self, + secp: &Secp256k1, + hasher: &mut H, + i: ChildNumber, + ) -> Result<(SecretKey, ChainCode), Error> + where + H: BIP32Hasher, + { + match i { + ChildNumber::Hardened { .. } => Err(BIP32Error::CannotDeriveFromHardenedKey.into()), + ChildNumber::Normal { index: n } => { + hasher.init_sha512(&self.chain_code[..]); + hasher.append_sha512(&self.public_key.serialize_vec(secp, true)[..]); + let mut be_n = [0; 4]; + BigEndian::write_u32(&mut be_n, n); + hasher.append_sha512(&be_n); + + let result = hasher.result_sha512(); + + let secret_key = SecretKey::from_slice(secp, &result[..32])?; + let chain_code = ChainCode::from(&result[32..]); + Ok((secret_key, chain_code)) + } + } + } + + pub fn ckd_pub( + &self, + secp: &Secp256k1, + hasher: &mut H, + i: ChildNumber, + ) -> Result + where + H: BIP32Hasher, + { + let (secret_key, chain_code) = self.ckd_pub_tweak(secp, hasher, i)?; + + let mut public_key = self.public_key.clone(); + public_key.add_exp_assign(secp, &secret_key)?; + + let switch_public_key = match &self.switch_public_key { + Some(p) => { + let mut j = PublicKey(ffi::PublicKey(GENERATOR_PUB_J_RAW)); + j.mul_assign(secp, &secret_key)?; + Some(PublicKey::from_combination(secp, vec![p, &j])?) + } + None => None, + }; + + Ok(Self { + is_floo: self.is_floo, + depth: self.depth + 1, + parent_fingerprint: self.fingerprint(secp, hasher), + child_number: i, + public_key, + switch_public_key, + chain_code, + rewind_hash: self.rewind_hash.clone(), + }) + } + + pub fn commit( + &self, + secp: &Secp256k1, + amount: u64, + switch: &SwitchCommitmentType, + ) -> Result { + let value_key = secp.commit_value(amount)?.to_pubkey(secp)?; + let pub_key = PublicKey::from_combination(secp, vec![&self.public_key, &value_key])?; + match *switch { + SwitchCommitmentType::None => Ok(pub_key), + SwitchCommitmentType::Regular => { + // TODO: replace this whole block by a libsecp function + /*let switch_pub = self.switch_public_key.ok_or(Error::SwitchCommitment)?; + let switch_ser: Vec = switch_pub.serialize_vec(secp, true)[..].to_vec(); + + let mut commit_ser: Vec = pub_key.serialize_vec(secp, true)[..].to_vec(); + commit_ser[0] += 6; // This only works sometimes + + let mut hasher = Sha256::new(); + hasher.input(&commit_ser); + hasher.input(&switch_ser); + let blind = SecretKey::from_slice(secp, &hasher.result()[..])?; + let mut pub_key = pub_key; + pub_key.add_exp_assign(secp, &blind)?; + + Ok(pub_key)*/ + Err(Error::SwitchCommitment) + } + } + } + + fn identifier(&self, secp: &Secp256k1, hasher: &mut H) -> [u8; 20] + where + H: BIP32Hasher, + { + let sha2_res = hasher.sha_256(&self.public_key.serialize_vec(secp, true)[..]); + hasher.ripemd_160(&sha2_res) + } + + fn fingerprint(&self, secp: &Secp256k1, hasher: &mut H) -> Fingerprint + where + H: BIP32Hasher, + { + Fingerprint::from(&self.identifier(secp, hasher)[0..4]) + } +} diff --git a/pool/tests/block_building.rs b/pool/tests/block_building.rs index 91573d4ec..bfcb14e29 100644 --- a/pool/tests/block_building.rs +++ b/pool/tests/block_building.rs @@ -47,7 +47,14 @@ fn test_transaction_pool_block_building() { let height = prev_header.height + 1; let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0); let fee = txs.iter().map(|x| x.fee()).sum(); - let reward = libtx::reward::output(&keychain, &key_id, fee, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + fee, + false, + ) + .unwrap(); let mut block = Block::new(&prev_header, txs, Difficulty::min(), reward).unwrap(); // Set the prev_root to the prev hash for testing purposes (no MMR to obtain a root from). diff --git a/pool/tests/block_max_weight.rs b/pool/tests/block_max_weight.rs index 8f85ed7fa..8b724dc55 100644 --- a/pool/tests/block_max_weight.rs +++ b/pool/tests/block_max_weight.rs @@ -51,7 +51,14 @@ fn test_block_building_max_weight() { let height = prev_header.height + 1; let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0); let fee = txs.iter().map(|x| x.fee()).sum(); - let reward = libtx::reward::output(&keychain, &key_id, fee, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + fee, + false, + ) + .unwrap(); let mut block = Block::new(&prev_header, txs, Difficulty::min(), reward).unwrap(); // Set the prev_root to the prev hash for testing purposes (no MMR to obtain a root from). diff --git a/pool/tests/block_reconciliation.rs b/pool/tests/block_reconciliation.rs index b34156e3a..9245003eb 100644 --- a/pool/tests/block_reconciliation.rs +++ b/pool/tests/block_reconciliation.rs @@ -45,7 +45,14 @@ fn test_transaction_pool_block_reconciliation() { let header = { let height = 1; let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0); - let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + 0, + false, + ) + .unwrap(); let genesis = BlockHeader::default(); let mut block = Block::new(&genesis, vec![], Difficulty::min(), reward).unwrap(); @@ -65,7 +72,14 @@ fn test_transaction_pool_block_reconciliation() { let block = { let key_id = ExtKeychain::derive_key_id(1, 2, 0, 0, 0); let fees = initial_tx.fee(); - let reward = libtx::reward::output(&keychain, &key_id, fees, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + fees, + false, + ) + .unwrap(); let mut block = Block::new(&header, vec![initial_tx], Difficulty::min(), reward).unwrap(); @@ -159,7 +173,14 @@ fn test_transaction_pool_block_reconciliation() { let block = { let key_id = ExtKeychain::derive_key_id(1, 3, 0, 0, 0); let fees = block_txs.iter().map(|tx| tx.fee()).sum(); - let reward = libtx::reward::output(&keychain, &key_id, fees, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + fees, + false, + ) + .unwrap(); let mut block = Block::new(&header, block_txs, Difficulty::min(), reward).unwrap(); // Set the prev_root to the prev hash for testing purposes (no MMR to obtain a root from). diff --git a/pool/tests/common.rs b/pool/tests/common.rs index 98ce20425..3ddb256bf 100644 --- a/pool/tests/common.rs +++ b/pool/tests/common.rs @@ -195,7 +195,7 @@ where tx_elements.push(libtx::build::with_fee(fees as u64)); - libtx::build::transaction(tx_elements, keychain).unwrap() + libtx::build::transaction(tx_elements, keychain, &libtx::ProofBuilder::new(keychain)).unwrap() } pub fn test_transaction( @@ -225,7 +225,7 @@ where } tx_elements.push(libtx::build::with_fee(fees as u64)); - libtx::build::transaction(tx_elements, keychain).unwrap() + libtx::build::transaction(tx_elements, keychain, &libtx::ProofBuilder::new(keychain)).unwrap() } pub fn test_source() -> TxSource { diff --git a/pool/tests/transaction_pool.rs b/pool/tests/transaction_pool.rs index 265f2646f..eaf630f5c 100644 --- a/pool/tests/transaction_pool.rs +++ b/pool/tests/transaction_pool.rs @@ -44,7 +44,14 @@ fn test_the_transaction_pool() { let header = { let height = 1; let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0); - let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + 0, + false, + ) + .unwrap(); let block = Block::new(&BlockHeader::default(), vec![], Difficulty::min(), reward).unwrap(); chain.update_db_for_block(&block); @@ -246,7 +253,14 @@ fn test_the_transaction_pool() { let header = { let height = 1; let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0); - let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap(); + let reward = libtx::reward::output( + &keychain, + &libtx::ProofBuilder::new(&keychain), + &key_id, + 0, + false, + ) + .unwrap(); let block = Block::new(&BlockHeader::default(), vec![], Difficulty::min(), reward).unwrap(); diff --git a/servers/src/mining/mine_block.rs b/servers/src/mining/mine_block.rs index 8b2e391f2..b5c5a011b 100644 --- a/servers/src/mining/mine_block.rs +++ b/servers/src/mining/mine_block.rs @@ -28,6 +28,7 @@ use crate::common::types::Error; use crate::core::core::verifier_cache::VerifierCache; use crate::core::core::{Output, TxKernel}; use crate::core::libtx::secp_ser; +use crate::core::libtx::ProofBuilder; use crate::core::{consensus, core, global}; use crate::keychain::{ExtKeychain, Identifier, Keychain}; use crate::pool; @@ -223,8 +224,14 @@ fn burn_reward(block_fees: BlockFees) -> Result<(core::Output, core::TxKernel, B warn!("Burning block fees: {:?}", block_fees); let keychain = ExtKeychain::from_random_seed(global::is_floonet())?; let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); - let (out, kernel) = - crate::core::libtx::reward::output(&keychain, &key_id, block_fees.fees, false).unwrap(); + let (out, kernel) = crate::core::libtx::reward::output( + &keychain, + &ProofBuilder::new(&keychain), + &key_id, + block_fees.fees, + false, + ) + .unwrap(); Ok((out, kernel, block_fees)) } diff --git a/util/Cargo.toml b/util/Cargo.toml index 4b084a21c..c027c00a4 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -28,5 +28,5 @@ zeroize = "0.5.2" #git = "https://github.com/mimblewimble/rust-secp256k1-zkp" #tag = "grin_integration_29" #path = "../../rust-secp256k1-zkp" -version = "0.7.5" +version = "0.7.6" features = ["bullet-proof-sizing"]