diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index a1831753a..c130bca1b 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -23,7 +23,7 @@ memzero = "0.1.0" rand = "0.7.3" ring = "0.16.11" secp256k1 = { version = "0.22.2", features = ["global-context"] } -serde = { version = "1.0.104", optional = true } +serde = { version = "1.0.104", optional = true, features=["derive"] } sha2 = "0.8.1" tiny-bip39 = "0.7.0" diff --git a/crypto/src/key.rs b/crypto/src/key.rs index 9d99dc546..78f9b7c99 100644 --- a/crypto/src/key.rs +++ b/crypto/src/key.rs @@ -180,7 +180,7 @@ impl ExtendedSK { } /// Create a new extended secret key from the given slip32-encoded string. - pub fn from_slip32(slip32: &str) -> Result<(Self, KeyPath), KeyError> { + pub fn from_slip32(slip32: &str) -> Result<(Self, KeyPath, Option>), KeyError> { let (hrp, data) = bech32::decode(slip32).map_err(KeyError::deserialization_err)?; if hrp.as_str() != "xprv" { @@ -195,12 +195,12 @@ impl ExtendedSK { let mut cursor = io::Cursor::new(bytes); let depth = cursor.read_u8()? as usize; let len = depth * 4; - let expected_len = len + 66; // 66 = 1 (depth) 32 (chain code) + 33 (private key) + let base_len = len + 66; // 66 = 1 (depth) 32 (chain code) + 33 (private key) - if expected_len != actual_len { + if actual_len < base_len { return Err(KeyError::Deserialization(failure::format_err!( - "invalid data length, expected: {}, got: {}", - expected_len, + "invalid data length, expected at least {} bytes, got {} bytes", + base_len, actual_len ))); } @@ -217,17 +217,42 @@ impl ExtendedSK { let mut secret = Protected::new(vec![0; 32]); cursor.read_exact(secret.as_mut())?; + // If there are bytes yet to read, put them into `extra_data` + let extra_len = actual_len - base_len; + let extra_data = if extra_len > 0 { + let mut extra_data = vec![0; extra_len]; + cursor.read_exact(extra_data.as_mut())?; + + Some(extra_data) + } else { + None + }; + let sk = SK::from_slice(secret.as_ref())?; let extended_sk = Self::new(sk, chain_code); - Ok((extended_sk, path.into())) + Ok((extended_sk, path.into(), extra_data)) } /// Serialize the key following the SLIP32 spec. /// /// See https://github.com/satoshilabs/slips/blob/master/slip-0032.md#serialization-format - pub fn to_slip32(&self, path: &KeyPath) -> Result { + pub fn to_slip32( + &self, + path: &KeyPath, + extra_data: Option>, + ) -> Result { let depth = path.depth(); + let extra_data = extra_data.unwrap_or_default(); + + let capacity = 1 // 1 byte for depth + + 4 * depth // 4 * depth bytes for path + + 32 // 32 bytes for chain code + + 33 // 33 bytes for 0x00 || private key + + extra_data.len(); // any arbitrary amount of extra bytes + let mut bytes = Protected::new(vec![0; capacity]); + let mut slice = bytes.as_mut(); + let depth = u8::try_from(depth).map_err(|_| { KeyError::Serialization(failure::format_err!( "path depth '{}' is greater than 255", @@ -235,14 +260,6 @@ impl ExtendedSK { )) })?; - let capacity = 1 // 1 byte for depth - + 4 * depth // 4 * depth bytes for path - + 32 // 32 bytes for chain code - + 33 // 33 bytes for 0x00 || private key - ; - let mut bytes = Protected::new(vec![0; usize::from(capacity)]); - let mut slice = bytes.as_mut(); - slice.write_all(&[depth])?; for index in path.iter() { slice.write_all(&index.as_ref().to_be_bytes())?; @@ -250,6 +267,7 @@ impl ExtendedSK { slice.write_all(self.chain_code.as_ref())?; slice.write_all(&[0])?; slice.write_all(self.secret().as_ref())?; + slice.write_all(extra_data.as_ref())?; let encoded = bech32::encode("xprv", bytes.as_ref().to_base32()) .map_err(KeyError::serialization_err)?; @@ -321,7 +339,6 @@ impl From for SK { /// /// It can be used to derive other HD-Wallets public keys. #[derive(Clone, PartialEq, Eq, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ExtendedPK { /// Public key pub key: PK, @@ -470,6 +487,40 @@ fn get_chain_code_and_secret( Ok((chain_code, secret_key)) } +/// Exports a master key into SLIP-32 format with 2 extra `u32` fields inside the SLIP-32 data +/// holding the "next index" for the external and internal keychains. +pub fn slip32_from_master_and_indexes( + master_key: ExtendedSK, + external_index: u32, + internal_index: u32, +) -> Result { + let external_index = external_index.to_be_bytes().to_vec(); + let internal_index = internal_index.to_be_bytes().to_vec(); + + let indexes = [&external_index[..], &internal_index[..]].concat(); + + master_key.to_slip32(&KeyPath::default(), Some(indexes)) +} + +/// Exports the concatenation of the SLIP-32 serialization of the `m_1` and `m_0` keychains, each +/// with 1 extra `u32` field inside the SLIP-32 data holding their own keychain's "next index". +pub fn double_slip32_from_keychains_and_indexes( + m_0: &ExtendedSK, + m_1: &ExtendedSK, + external_index: u32, + internal_index: u32, +) -> Result { + let external_index = external_index.to_be_bytes().to_vec(); + let internal_index = internal_index.to_be_bytes().to_vec(); + + let mut internal_secret_key = m_1.to_slip32(&KeyPath::default(), Some(internal_index))?; + let external_secret_key = m_0.to_slip32(&KeyPath::default(), Some(external_index))?; + + internal_secret_key.push_str(&external_secret_key); + + Ok(internal_secret_key) +} + #[cfg(test)] mod tests { use super::*; @@ -574,16 +625,34 @@ mod tests { let seed = mnemonic.seed(&"".into()); let master_key = MasterKeyGen::new(&seed).generate().unwrap(); - for (expected, keypath) in slip32_vectors() { - let key = master_key.derive(&keypath).unwrap(); - let xprv = key.to_slip32(&keypath).unwrap(); + for (expected_xprv, expected_path, expected_extra_data) in slip32_vectors() { + let expected_key = master_key.derive(&expected_path).unwrap(); + let xprv = expected_key + .to_slip32(&expected_path, expected_extra_data.clone()) + .unwrap(); - assert_eq!(expected, xprv); + assert_eq!(expected_xprv, xprv); - let (recovered_key, path) = ExtendedSK::from_slip32(&xprv).unwrap(); + let (key, path, extra_data) = ExtendedSK::from_slip32(&xprv).unwrap(); - assert_eq!(keypath, path); - assert_eq!(key, recovered_key); + assert_eq!(expected_path, path); + assert_eq!(expected_key, key); + assert_eq!(expected_extra_data, extra_data); } } + + #[test] + fn test_xprvdouble() { + let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + let mnemonic = bip39::Mnemonic::from_phrase(phrase.into()).unwrap(); + let seed = mnemonic.seed(&"".into()); + let master_key = MasterKeyGen::new(&seed).generate().unwrap(); + + let m_0 = master_key.derive(&KeyPath::default().index(0)).unwrap(); + let m_1 = master_key.derive(&KeyPath::default().index(1)).unwrap(); + + let double_hex = double_slip32_from_keychains_and_indexes(&m_0, &m_1, 123, 321).unwrap(); + + assert_eq!(double_hex, "xprv1qpwy3ytadqutve4wky02clz0nruqwaum2lr4yt3c2zt3nm43u7jeyqxph6hlp3xmnpr8pfqvd8pfg7uax0xh7mn5n3n7rl94ccgcmksjsgqqqq2pwje96dxprv1qrswv5p6cptu7hw8dcrntetd63x3jwewncn3esk5d0r4njvmqg0rcq964zdghhtpch3zh8csvqwc0ywflr7yktaxm7wk35ek7r4s8vrwkcqqqqrm76e6en") + } } diff --git a/crypto/src/test_vectors.rs b/crypto/src/test_vectors.rs index ce85cc34e..2bd38cefb 100644 --- a/crypto/src/test_vectors.rs +++ b/crypto/src/test_vectors.rs @@ -108,51 +108,87 @@ pub const TREZOR_MNEMONICS: &[(&str, &str)] = &[ /// abandon abandon abandon abandon abandon abandon about /// /// See: https://github.com/satoshilabs/slips/blob/master/slip-0032.md#test-vectors -pub fn slip32_vectors() -> Vec<(&'static str, KeyPath)> { +pub fn slip32_vectors() -> Vec<(&'static str, KeyPath, Option>)> { vec![ ( "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvu566tn6", KeyPath::default(), + None, ), ( "xprv1qyqqqqqqurn9qwkq2l84m3mwqu672mw5f5vnkt57yuwv94rtcavunxczrc7qpw4gn29a6cw9ug4e7yrqrkrerj0cl39jlfkln45dxdhsavpmqm4krfqykk", - KeyPath::default().index(0) + KeyPath::default().index(0), + None, ), ( "xprv1qyqqqqqpt3yfzltg8zmxdt43r6k8cnuclqrh0x6hcafzuwzsjuv7av085kfqpsd74lcvfkucgec2grrfc228h8fne4lkuayuvlsledwxzxxa5y5zefalyg", - KeyPath::default().index(1) + KeyPath::default().index(1), + None, ), ( "xprv1qxqqqqqq78qr7hlewyyfzt74vasa87k63pu7g9e6hfzlzrdyh0v5k8zfw9sqpsyv7vcejeyzcpkm85jel7vmujlhpquzf4f3sh3nry0w0n4jh7t0jhc039", - KeyPath::default().hardened(0) + KeyPath::default().hardened(0), + None, ), ( "xprv1qxqqqqqpg0xyhjjecen2taujv52gzfvq9mfva3rd78zu4rn2qkx6k5j6w0csq0hs9lznqqr59zgleyz93w57mjpk8k837fn7xf43q7r3p37mxn095hysnx", - KeyPath::default().hardened(1) + KeyPath::default().hardened(1), + None, ), ( "xprv1qwqqqqpvsqqqqqyqqqqqq0dyhsvs5f5qzywnr7klmjg972nldnnhcmcsnyv3zme984p5g5seqrlxftuztddhs42vxw3gkgcgtlqg9a53k0r39nqafenwzvef0k585enml6g", - KeyPath::default().hardened(44).hardened(0).hardened(0) + KeyPath::default().hardened(44).hardened(0).hardened(0), + None, ), ( "xprv1qwqqqqpvsqqqqqyqqqqqz2t3lgkmpl6ad8skdfqxsya28k0dp8z2mtpwpn3n2g7633tqna85qzy9th76xllxvwllcqfkvxzsfc7t6lveyy6xp880vxguw2fnn4wx2mhtjy8", - KeyPath::default().hardened(44).hardened(0).hardened(1) + KeyPath::default().hardened(44).hardened(0).hardened(1), + None, ), ( "xprv1qwqqqqpvsqqqqq5qqqqqpp5u2pz7tlrcjert40x3jcd3qx7rre6lu5xl3fv9c7dsth9q436cqzvre5gd352pvzcshxjtkceq062c2p22xyekr82hk782n4d2xprdysp4gxc", - KeyPath::default().hardened(44).hardened(2).hardened(0) + KeyPath::default().hardened(44).hardened(2).hardened(0), + None, ), ( "xprv1qwqqqqp3sqqqqqyqqqqqqm42udj6urs2p24cgvjule7dwmpmjzgrt7yfulflrwz84xs8jlktqzyq65t490dyryrq0cretzxn7ezdj6l6qdzxhn5neh0a50z2n8r7vumvllf", - KeyPath::default().hardened(49).hardened(0).hardened(0) + KeyPath::default().hardened(49).hardened(0).hardened(0), + None, ), ( "xprv1qwqqqqp3sqqqqq5qqqqqqeahu8w9cu9fx5zzrrx0grz844rdf2wgtqvkxakwp6zn4jnmupycqr8jytxzuztsf8lzefmxymqecln68muhrv0kgx2htz4neqeyv070gg6dcn7", - KeyPath::default().hardened(49).hardened(2).hardened(0) + KeyPath::default().hardened(49).hardened(2).hardened(0), + None, ), ( "xprv1qwqqqqz5sqqqqqyqqqqqqjjn5z4jrwwujkrfcn5j59s3jnsrcrhnlagpftrf9apnc3m9fy8uqrs57f6dzm9qmygrrwvtzcnpspsaqwfslgup4ak5et6ykqvpn2mdggeaxrp", - KeyPath::default().hardened(84).hardened(0).hardened(0) - ) + KeyPath::default().hardened(84).hardened(0).hardened(0), + None, + ), + ( + "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvllsrvlvcr", + KeyPath::default(), + Some(vec![255]), + ), + ( + "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvuqqqqqqqqqqqqqs0qn8x", + KeyPath::default(), + Some(vec![0; 8]), + ), + ( + "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvuqsyqcyq5rqwzqf37gqu5", + KeyPath::default(), + Some(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), + ), + ( + "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvaqyjr7mxq83mn", + KeyPath::default(), + Some(core::f32::consts::PI.to_be_bytes().into()), + ), + ( + "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvaqqjg0m23zz6xqh2eujk", + KeyPath::default(), + Some(core::f64::consts::PI.to_be_bytes().into()), + ), ] } diff --git a/data_structures/src/chain/tapi.rs b/data_structures/src/chain/tapi.rs index de1dd515d..ec000ec1e 100644 --- a/data_structures/src/chain/tapi.rs +++ b/data_structures/src/chain/tapi.rs @@ -138,7 +138,7 @@ impl TapiEngine { } } for n in 0..self.bit_tapi_counter.len() { - if let Some(mut bit_counter) = self.bit_tapi_counter.get_mut(n, &epoch_to_update) { + if let Some(bit_counter) = self.bit_tapi_counter.get_mut(n, &epoch_to_update) { if !self.wip_activation.contains_key(&bit_counter.wip) && !avoid_wip_list.contains(&bit_counter.wip) { @@ -639,7 +639,7 @@ mod tests { assert_eq!(tapi_counter.current_length, 1); assert_eq!(tapi_counter.get(0, &100).unwrap().votes, 0); - let mut votes_counter = tapi_counter.get_mut(0, &100).unwrap(); + let votes_counter = tapi_counter.get_mut(0, &100).unwrap(); votes_counter.votes += 1; assert_eq!(tapi_counter.get(0, &100).unwrap().votes, 1); diff --git a/node/src/actors/inventory_manager/handlers.rs b/node/src/actors/inventory_manager/handlers.rs index 2bc85836e..7b6484104 100644 --- a/node/src/actors/inventory_manager/handlers.rs +++ b/node/src/actors/inventory_manager/handlers.rs @@ -512,7 +512,7 @@ mod tests { // Start relevant actors config_mngr::start(config); storage_mngr::start(); - let inventory_manager = InventoryManager::default().start(); + let inventory_manager = InventoryManager.start(); // Create first block with value transfer transactions let block = build_block_with_vt_transactions(1); diff --git a/node/src/actors/json_rpc/api.rs b/node/src/actors/json_rpc/api.rs index 2a007ea69..756446cd9 100644 --- a/node/src/actors/json_rpc/api.rs +++ b/node/src/actors/json_rpc/api.rs @@ -1425,7 +1425,7 @@ pub async fn master_key_export() -> JsonRpcResult { res.map_err(internal_error) .and_then(move |(_extended_pk, extended_sk)| { let master_path = KeyPath::default(); - let secret_key_hex = extended_sk.to_slip32(&master_path); + let secret_key_hex = extended_sk.to_slip32(&master_path, None); let secret_key_hex = match secret_key_hex { Ok(x) => x, Err(e) => return Err(internal_error_s(e)), diff --git a/node/src/actors/node.rs b/node/src/actors/node.rs index 9db6d4de7..088b9c0f9 100644 --- a/node/src/actors/node.rs +++ b/node/src/actors/node.rs @@ -51,7 +51,7 @@ pub fn run(config: Arc, ops: NodeOps, callback: fn()) -> Result<(), fail SystemRegistry::set(peers_manager_addr); // Start ConnectionsManager actor - let connections_manager_addr = ConnectionsManager::default().start(); + let connections_manager_addr = ConnectionsManager.start(); SystemRegistry::set(connections_manager_addr); // Start SessionManager actor @@ -69,7 +69,7 @@ pub fn run(config: Arc, ops: NodeOps, callback: fn()) -> Result<(), fail SystemRegistry::set(chain_manager_addr); // Start InventoryManager actor - let inventory_manager_addr = InventoryManager::default().start(); + let inventory_manager_addr = InventoryManager.start(); SystemRegistry::set(inventory_manager_addr); // Start RadManager actor diff --git a/node/src/signature_mngr.rs b/node/src/signature_mngr.rs index 97ba78e89..f97cef040 100644 --- a/node/src/signature_mngr.rs +++ b/node/src/signature_mngr.rs @@ -595,7 +595,7 @@ fn master_key_import_from_file(master_key_path: &Path) -> Result { + Ok((extended_master_key, key_path, _)) => { if key_path.is_master() { log::info!("Successfully imported master key from file"); Ok(extended_master_key) diff --git a/partial_struct/tests/partial_struct_derive.rs b/partial_struct/tests/partial_struct_derive.rs index 7723c90ad..8f987cbd5 100644 --- a/partial_struct/tests/partial_struct_derive.rs +++ b/partial_struct/tests/partial_struct_derive.rs @@ -81,5 +81,5 @@ fn test_partial_attr_partial() { let p = PartialObj::default(); - assert_eq!(p.f, PartialAnotherObj::default()); + assert_eq!(p.f, PartialAnotherObj); } diff --git a/wallet/src/actors/app/handlers/create_data_req.rs b/wallet/src/actors/app/handlers/create_data_req.rs index 77581f838..6b71bcd0f 100644 --- a/wallet/src/actors/app/handlers/create_data_req.rs +++ b/wallet/src/actors/app/handlers/create_data_req.rs @@ -33,6 +33,8 @@ pub struct CreateDataReqRequest { #[serde(deserialize_with = "deserialize_fee_backwards_compatible")] fee: Fee, fee_type: Option, + #[serde(default)] + preview: bool, } #[derive(Debug, Serialize, Deserialize)] @@ -81,7 +83,11 @@ impl Handler for app::App { let fee = fee_compat(msg.fee, msg.fee_type); let f = fut::result(validated).and_then(move |request, slf: &mut Self, _ctx| { - let params = types::DataReqParams { request, fee }; + let params = types::DataReqParams { + request, + fee, + preview: msg.preview, + }; slf.create_data_req(&msg.session_id, &msg.wallet_id, params) .map_ok( diff --git a/wallet/src/actors/app/handlers/create_vtt.rs b/wallet/src/actors/app/handlers/create_vtt.rs index 3b9d673c6..1c87ccf61 100644 --- a/wallet/src/actors/app/handlers/create_vtt.rs +++ b/wallet/src/actors/app/handlers/create_vtt.rs @@ -65,6 +65,8 @@ pub struct CreateVttRequest { utxo_strategy: UtxoSelectionStrategy, #[serde(default)] selected_utxos: HashSet, + #[serde(default)] + preview: bool, } /// Part of CreateVttResponse struct, containing additional data to be displayed in clients @@ -124,6 +126,7 @@ impl Handler for app::App { outputs, utxo_strategy: msg.utxo_strategy.clone(), selected_utxos: msg.selected_utxos.iter().map(|x| x.into()).collect(), + preview: msg.preview, }; act.create_vtt(&msg.session_id, &msg.wallet_id, params) diff --git a/wallet/src/actors/app/handlers/refresh_session.rs b/wallet/src/actors/app/handlers/refresh_session.rs index 04368f6f6..df9902f9a 100644 --- a/wallet/src/actors/app/handlers/refresh_session.rs +++ b/wallet/src/actors/app/handlers/refresh_session.rs @@ -21,7 +21,7 @@ impl Handler for app::App { type Result = ::Result; fn handle(&mut self, msg: RefreshSessionRequest, ctx: &mut Self::Context) -> Self::Result { - let mut session = self + let session = self .state .sessions .get_mut(&msg.session_id) diff --git a/wallet/src/actors/app/routes.rs b/wallet/src/actors/app/routes.rs index fb211e81f..f902c0e77 100644 --- a/wallet/src/actors/app/routes.rs +++ b/wallet/src/actors/app/routes.rs @@ -15,7 +15,7 @@ macro_rules! routes { { let api_addr = $api.clone(); $io.add_method($method_jsonrpc, move |params: Params| { - log::debug!("Handling request for method: {}", $method_jsonrpc); + log::debug!("Handling request for method {}: {:?}", $method_jsonrpc, params); let addr = api_addr.clone(); // Try to parse the request params into the actor message let fut03 = future::ready(params.parse::<$actor_msg>()) diff --git a/wallet/src/actors/worker/methods.rs b/wallet/src/actors/worker/methods.rs index 15193d76c..e06c03280 100644 --- a/wallet/src/actors/worker/methods.rs +++ b/wallet/src/actors/worker/methods.rs @@ -88,8 +88,9 @@ impl Worker { ) -> Result { let (id, default_account, master_key) = match source { types::SeedSource::XprvDouble((internal, external)) => { - let (external_key, external_path) = ExtendedSK::from_slip32(external.as_ref()) - .map_err(|e| Error::KeyGen(crypto::Error::Deserialization(e)))?; + let (external_key, external_path, _) = + ExtendedSK::from_slip32(external.as_ref()) + .map_err(|e| Error::KeyGen(crypto::Error::Deserialization(e)))?; if !external_path.is_master() { return Err(Error::KeyGen(crypto::Error::InvalidKeyPath(format!( "{}", @@ -97,8 +98,9 @@ impl Worker { )))); } - let (internal_key, internal_path) = ExtendedSK::from_slip32(internal.as_ref()) - .map_err(|e| Error::KeyGen(crypto::Error::Deserialization(e)))?; + let (internal_key, internal_path, _) = + ExtendedSK::from_slip32(internal.as_ref()) + .map_err(|e| Error::KeyGen(crypto::Error::Deserialization(e)))?; if !internal_path.is_master() { return Err(Error::KeyGen(crypto::Error::InvalidKeyPath(format!( @@ -243,7 +245,7 @@ impl Worker { pub fn check_wallet_seed(&self, seed: types::SeedSource) -> Result<(bool, String)> { let id = match seed { types::SeedSource::XprvDouble((_, external)) => { - let (external_key, _) = ExtendedSK::from_slip32(external.as_ref()) + let (external_key, _, _) = ExtendedSK::from_slip32(external.as_ref()) .map_err(|e| Error::KeyGen(crypto::Error::Deserialization(e)))?; crypto::gen_wallet_id( @@ -352,7 +354,7 @@ impl Worker { let address = if external { wallet.gen_external_address(label)? } else { - wallet.gen_internal_address(label)? + wallet.gen_internal_address(label, false)? }; Ok(address) diff --git a/wallet/src/crypto.rs b/wallet/src/crypto.rs index 6f390685c..facdc0b1c 100644 --- a/wallet/src/crypto.rs +++ b/wallet/src/crypto.rs @@ -1,3 +1,4 @@ +use bech32::ToBase32; use failure::Fail; use witnet_crypto::{ @@ -22,6 +23,9 @@ pub enum Error { /// The deserialization of the master key failed. #[fail(display = "Deserialization of key failed: {}", _0)] Deserialization(#[cause] KeyError), + /// The serialization of the master key failed. + #[fail(display = "Serialization of key failed: {}", _0)] + Serialization(#[cause] KeyError), /// The key path of the slip32-serialized key is not of a master key. #[fail( display = "Imported key is not a master key according to its path: {}", @@ -30,6 +34,8 @@ pub enum Error { InvalidKeyPath(String), #[fail(display = "The AES encryption/decryption failed: {}", _0)] Aes(#[cause] cipher::Error), + #[fail(display = "The Bech32 (de)serialization failed: {}", _0)] + Bech32(#[cause] bech32::Error), } /// Result type for cryptographic operations that can fail. @@ -49,7 +55,7 @@ pub fn gen_master_key(seed: &str, salt: &[u8], source: &types::SeedSource) -> Re .map_err(Error::Generation)? } types::SeedSource::Xprv(slip32) => { - let (key, path) = + let (key, path, _) = ExtendedSK::from_slip32(slip32.as_ref()).map_err(Error::Deserialization)?; if !path.is_master() { return Err(Error::InvalidKeyPath(format!("{}", path))); @@ -142,3 +148,11 @@ pub fn decrypt_cbc(ciphertext: &[u8], password: &[u8]) -> Result> { Ok(plaintext) } + +/// Encrypts a key using AES-CBC and serializes it with bech32. +pub fn bech32_encrypt(tag: &str, key: &str, password: &str) -> Result { + let encrypted_key = encrypt_cbc(key.as_bytes(), password.as_bytes())?; + let output = bech32::encode(tag, encrypted_key.to_base32()).map_err(Error::Bech32)?; + + Ok(output) +} diff --git a/wallet/src/repository/wallet/mod.rs b/wallet/src/repository/wallet/mod.rs index 0ccb1aefc..1304069c5 100644 --- a/wallet/src/repository/wallet/mod.rs +++ b/wallet/src/repository/wallet/mod.rs @@ -8,8 +8,10 @@ use std::{ sync::{Arc, RwLock, RwLockReadGuard}, }; -use bech32::ToBase32; use state::State; +use witnet_crypto::key::{ + double_slip32_from_keychains_and_indexes, slip32_from_master_and_indexes, +}; use witnet_crypto::{ hash::calculate_sha256, key::{ExtendedPK, ExtendedSK, KeyPath, PK}, @@ -35,8 +37,9 @@ use witnet_data_structures::{ use witnet_rad::{error::RadError, types::RadonTypes}; use witnet_util::timestamp::get_timestamp; +use crate::crypto::bech32_encrypt; use crate::{ - constants, crypto, + constants, db::{Database, WriteBatch as _}, model, params::Params, @@ -501,10 +504,14 @@ where } /// Generate an address in the internal keychain (WIP-0001). - pub fn gen_internal_address(&self, label: Option) -> Result> { + pub fn gen_internal_address( + &self, + label: Option, + preview: bool, + ) -> Result> { let mut state = self.state.write()?; - self._gen_internal_address(&mut state, label) + self._gen_internal_address(&mut state, label, preview) } /// Return a list of the generated external addresses that. @@ -1082,6 +1089,7 @@ where outputs, utxo_strategy, selected_utxos, + preview, }: types::VttParams, ) -> Result<(model::ExtendedTransaction, AbsoluteFee)> { let mut state = self.state.write()?; @@ -1095,6 +1103,7 @@ where fee, &utxo_strategy, selected_utxos, + preview, )?; let pointers_as_inputs = inputs @@ -1118,14 +1127,18 @@ where pub fn create_data_req( &self, - types::DataReqParams { fee, request }: types::DataReqParams, + types::DataReqParams { + fee, + request, + preview, + }: types::DataReqParams, ) -> Result<(model::ExtendedTransaction, AbsoluteFee)> { let mut state = self.state.write()?; let TransactionComponents { fee, inputs, outputs, - } = self.create_dr_transaction_components(&mut state, request.clone(), fee)?; + } = self.create_dr_transaction_components(&mut state, request.clone(), fee, preview)?; let pointers_as_inputs = inputs .pointers @@ -1190,6 +1203,7 @@ where fee: Fee, utxo_strategy: &UtxoSelectionStrategy, selected_utxos: HashSet, + preview: bool, ) -> Result { let timestamp = u64::try_from(get_timestamp()).unwrap(); @@ -1203,6 +1217,7 @@ where utxo_strategy, self.params.max_vt_weight, selected_utxos, + preview, ) } @@ -1211,6 +1226,7 @@ where state: &mut State, request: DataRequestOutput, fee: Fee, + preview: bool, ) -> Result { let utxo_strategy = UtxoSelectionStrategy::Random { from: None }; let timestamp = u64::try_from(get_timestamp()).unwrap(); @@ -1225,6 +1241,7 @@ where &utxo_strategy, self.params.max_dr_weight, HashSet::default(), + preview, ) } @@ -1233,6 +1250,7 @@ where &self, state: &mut State, force_input: Option, + preview: bool, ) -> Result { let pkh = if let Some(input) = force_input { let forced = input.output_pointer(); @@ -1240,7 +1258,7 @@ where key_balance.pkh } else { - self._gen_internal_address(state, None)?.pkh + self._gen_internal_address(state, None, preview)?.pkh }; Ok(pkh) @@ -1263,6 +1281,7 @@ where utxo_strategy: &UtxoSelectionStrategy, max_weight: u32, selected_utxos: HashSet, + preview: bool, ) -> Result { let empty_hashset = HashSet::default(); let unconfirmed_transactions = if self.params.use_unconfirmed_utxos { @@ -1298,6 +1317,7 @@ where let change_pkh = self.calculate_change_address( state, dr_output.and_then(|_| inputs.pointers.get(0).cloned().map(Input::new)), + preview, )?; let mut outputs = outputs; @@ -1318,16 +1338,22 @@ where &self, state: &mut State, label: Option, + preview: bool, ) -> Result> { let keychain = constants::INTERNAL_KEYCHAIN; let account = state.account; let index = state.next_internal_index; let parent_key = &state.keychains[keychain as usize]; + // `preview` is negated because it turns into `persist_db` let (address, next_index) = - self.derive_and_persist_address(label, parent_key, account, keychain, index, true)?; + self.derive_and_persist_address(label, parent_key, account, keychain, index, !preview)?; - state.next_internal_index = next_index; + // Don't advance the internal index if we are simply previewing + if !preview { + state.next_internal_index = next_index; + log::debug!("Internal keychain advanced to index #{next_index}"); + } Ok(address) } @@ -1441,7 +1467,19 @@ where let mut state = self.state.write()?; // This line is needed because of this error: // - Cannot borrow `state` as mutable because it is also borrowed as immutable - let mut state = &mut *state; + let state = &mut *state; + + // Move internal keychain forward if we used a new change address + let next = self._gen_internal_address(state, None, true)?; + if match &txn.transaction { + Transaction::ValueTransfer(vtt) => { + vtt.body.outputs.iter().any(|vto| vto.pkh == next.pkh) + } + Transaction::DataRequest(dr) => dr.body.outputs.iter().any(|vto| vto.pkh == next.pkh), + _ => false, + } { + let _ = self._gen_internal_address(state, None, false); + } // Mark UTXOs as used so we don't double spend // Save the timestamp to after which the UTXO can be spent again @@ -1569,7 +1607,7 @@ where state.transient_external_addresses.remove(&addr.pkh); } for _ in state.next_internal_index..new_internal_index { - let addr = self._gen_internal_address(&mut state, None)?; + let addr = self._gen_internal_address(&mut state, None, false)?; state.transient_internal_addresses.remove(&addr.pkh); } @@ -1953,35 +1991,51 @@ where && state.transient_external_addresses.is_empty())) } + /// Export the master key of a wallet, encrypted with AES-CBC and serialized with bech32. + /// + /// When possible, uses the SLIP-32 spec to encode the master key in its extended form and + /// also bundles the next index to be used for each keychain as 2 extra `u32` fields in the + /// SLIP-32 data. + /// + /// If the master key was not stored (e.g. legacy wallets), uses the concatenation of the + /// SLIP-32 serialization of the separate keychains, each with its own single `u32` field in the + /// SLIP-32 data. pub fn export_master_key(&self, password: types::Password) -> Result { let state = self.state.read()?; + + // If we have the master key, use the SLIP-32 serialization of it let (tag, key) = if let Some(master_key) = self.db.get_opt(&keys::master_key())? { - let master_key_string = match master_key.to_slip32(&KeyPath::default()) { - Ok(x) => x, - Err(_e) => return Err(Error::KeySerialization), - }; - ("xprv", master_key_string) + let slip32 = slip32_from_master_and_indexes( + master_key, + state.next_external_index, + state.next_internal_index, + ) + .map_err(crate::crypto::Error::Serialization) + .map_err(Error::Crypto)?; + + ("xprv", slip32) + // Otherwise, let's go "double mode" and use the concatenation of the SLIP-32 of each + // keychain } else { - let internal_parent_key = &state.keychains[constants::INTERNAL_KEYCHAIN as usize]; - let external_parent_key = &state.keychains[constants::EXTERNAL_KEYCHAIN as usize]; - let internal_secret_key = internal_parent_key.to_slip32(&KeyPath::default()); - let mut internal_secret_key_hex = match internal_secret_key { - Ok(x) => x, - Err(_e) => return Err(Error::KeySerialization), - }; - let external_secret_key = external_parent_key.to_slip32(&KeyPath::default()); - let external_secret_key_hex = match external_secret_key { - Ok(x) => x, - Err(_e) => return Err(Error::KeySerialization), - }; - internal_secret_key_hex.push_str(&external_secret_key_hex); - ("xprvdouble", internal_secret_key_hex) + let m_0 = &state.keychains[constants::EXTERNAL_KEYCHAIN as usize]; + let m_1 = &state.keychains[constants::INTERNAL_KEYCHAIN as usize]; + let double_hex = double_slip32_from_keychains_and_indexes( + m_0, + m_1, + state.next_external_index, + state.next_internal_index, + ) + .map_err(crate::crypto::Error::Serialization) + .map_err(Error::Crypto)?; + + ("xprvdouble", double_hex) }; - let encrypted_final_key = - crypto::encrypt_cbc(key.as_ref(), password.as_ref()).map_err(Error::Crypto)?; - let final_key = - bech32::encode(tag, encrypted_final_key.to_base32()).map_err(Error::Bech32)?; - Ok(final_key) + + // Finally, encrypt using AES-CBC and the given password; and serialize into bech32 using + // "xprv" as the tag + let xprv = bech32_encrypt(tag, &key, password.as_ref()).map_err(Error::Crypto)?; + + Ok(xprv) } } @@ -2249,78 +2303,3 @@ where Ok(state.utxo_set.clone()) } } - -#[test] -fn test_get_tx_ranges_inner_range() { - let local_total = 10; - let pending_total = 10; - let db_total = 10; - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(5, 4, local_total, pending_total, db_total); - assert_eq!(local_range, Some(1..5)); - assert_eq!(pending_range, None); - assert_eq!(db_range, None); - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(15, 4, local_total, pending_total, db_total); - assert_eq!(local_range, None); - assert_eq!(pending_range, Some(1..5)); - assert_eq!(db_range, None); - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(25, 4, local_total, pending_total, db_total); - assert_eq!(local_range, None); - assert_eq!(pending_range, None); - assert_eq!(db_range, Some(1..5)); -} - -#[test] -fn test_get_tx_ranges_overlap() { - let local_total = 10; - let pending_total = 10; - let db_total = 10; - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(5, 10, local_total, pending_total, db_total); - assert_eq!(local_range, Some(0..5)); - assert_eq!(pending_range, Some(5..10)); - assert_eq!(db_range, None); - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(15, 10, local_total, pending_total, db_total); - assert_eq!(local_range, None); - assert_eq!(pending_range, Some(0..5)); - assert_eq!(db_range, Some(5..10)); - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(5, 20, local_total, pending_total, db_total); - assert_eq!(local_range, Some(0..5)); - assert_eq!(pending_range, Some(0..10)); - assert_eq!(db_range, Some(5..10)); -} - -#[test] -fn test_get_tx_ranges_exceed() { - let local_total = 10; - let pending_total = 10; - let db_total = 10; - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(5, 40, local_total, pending_total, db_total); - assert_eq!(local_range, Some(0..5)); - assert_eq!(pending_range, Some(0..10)); - assert_eq!(db_range, Some(0..10)); - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(15, 40, local_total, pending_total, db_total); - assert_eq!(local_range, None); - assert_eq!(pending_range, Some(0..5)); - assert_eq!(db_range, Some(0..10)); - - let (local_range, pending_range, db_range) = - calculate_transaction_ranges(25, 40, local_total, pending_total, db_total); - assert_eq!(local_range, None); - assert_eq!(pending_range, None); - assert_eq!(db_range, Some(0..5)); -} diff --git a/wallet/src/repository/wallet/tests/mod.rs b/wallet/src/repository/wallet/tests/mod.rs index fab35d896..175a257d9 100644 --- a/wallet/src/repository/wallet/tests/mod.rs +++ b/wallet/src/repository/wallet/tests/mod.rs @@ -1,5 +1,12 @@ use std::{collections::HashMap, iter::FromIterator as _, mem}; +use witnet_crypto::{ + key::{ + double_slip32_from_keychains_and_indexes, slip32_from_master_and_indexes, KeyPath, + MasterKeyGen, + }, + mnemonic::Mnemonic, +}; use witnet_data_structures::{ chain::Hashable, transaction::VTTransaction, transaction_factory::calculate_weight, }; @@ -10,6 +17,108 @@ use super::*; mod factories; +#[test] +fn test_get_tx_ranges_inner_range() { + let local_total = 10; + let pending_total = 10; + let db_total = 10; + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(5, 4, local_total, pending_total, db_total); + assert_eq!(local_range, Some(1..5)); + assert_eq!(pending_range, None); + assert_eq!(db_range, None); + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(15, 4, local_total, pending_total, db_total); + assert_eq!(local_range, None); + assert_eq!(pending_range, Some(1..5)); + assert_eq!(db_range, None); + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(25, 4, local_total, pending_total, db_total); + assert_eq!(local_range, None); + assert_eq!(pending_range, None); + assert_eq!(db_range, Some(1..5)); +} + +#[test] +fn test_get_tx_ranges_overlap() { + let local_total = 10; + let pending_total = 10; + let db_total = 10; + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(5, 10, local_total, pending_total, db_total); + assert_eq!(local_range, Some(0..5)); + assert_eq!(pending_range, Some(5..10)); + assert_eq!(db_range, None); + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(15, 10, local_total, pending_total, db_total); + assert_eq!(local_range, None); + assert_eq!(pending_range, Some(0..5)); + assert_eq!(db_range, Some(5..10)); + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(5, 20, local_total, pending_total, db_total); + assert_eq!(local_range, Some(0..5)); + assert_eq!(pending_range, Some(0..10)); + assert_eq!(db_range, Some(5..10)); +} + +#[test] +fn test_get_tx_ranges_exceed() { + let local_total = 10; + let pending_total = 10; + let db_total = 10; + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(5, 40, local_total, pending_total, db_total); + assert_eq!(local_range, Some(0..5)); + assert_eq!(pending_range, Some(0..10)); + assert_eq!(db_range, Some(0..10)); + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(15, 40, local_total, pending_total, db_total); + assert_eq!(local_range, None); + assert_eq!(pending_range, Some(0..5)); + assert_eq!(db_range, Some(0..10)); + + let (local_range, pending_range, db_range) = + calculate_transaction_ranges(25, 40, local_total, pending_total, db_total); + assert_eq!(local_range, None); + assert_eq!(pending_range, None); + assert_eq!(db_range, Some(0..5)); +} + +#[test] +fn test_bech32_export_master() { + let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + let mnemonic = Mnemonic::from_phrase(phrase.into()).unwrap(); + let seed = mnemonic.seed(&"".into()); + let master_key = MasterKeyGen::new(&seed).generate().unwrap(); + + let slip32 = slip32_from_master_and_indexes(master_key, 123, 321).unwrap(); + + assert_eq!(slip32, "xprv1qpujxsyd4hfu0dtwa524vac84e09mjsgnh5h9crl8wrqg58z5wmsuqqcxlqmar3fjhkprndzkpnp2xlze76g4hu7g7c4r4r2m2e6y8xlvuqqqqrmqqqqzsgrtdzdt"); +} + +#[test] +fn test_bech32_export_legacy() { + let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + let mnemonic = Mnemonic::from_phrase(phrase.into()).unwrap(); + let seed = mnemonic.seed(&"".into()); + let master_key = MasterKeyGen::new(&seed).generate().unwrap(); + + let m_0 = master_key.derive(&KeyPath::default().index(0)).unwrap(); + let m_1 = master_key.derive(&KeyPath::default().index(1)).unwrap(); + + let double_hex = double_slip32_from_keychains_and_indexes(&m_0, &m_1, 123, 321).unwrap(); + + assert_eq!(double_hex, "xprv1qpwy3ytadqutve4wky02clz0nruqwaum2lr4yt3c2zt3nm43u7jeyqxph6hlp3xmnpr8pfqvd8pfg7uax0xh7mn5n3n7rl94ccgcmksjsgqqqq2pwje96dxprv1qrswv5p6cptu7hw8dcrntetd63x3jwewncn3esk5d0r4njvmqg0rcq964zdghhtpch3zh8csvqwc0ywflr7yktaxm7wk35ek7r4s8vrwkcqqqqrm76e6en"); +} + #[test] fn test_wallet_public_data() { let (wallet, _db) = factories::wallet(None); @@ -132,9 +241,9 @@ fn test_gen_external_address_associates_pkh_to_account_in_db() { fn test_list_internal_addresses() { let (wallet, _db) = factories::wallet(None); - let address1 = (*wallet.gen_internal_address(None).unwrap()).clone(); - let address2 = (*wallet.gen_internal_address(None).unwrap()).clone(); - let address3 = (*wallet.gen_internal_address(None).unwrap()).clone(); + let address1 = (*wallet.gen_internal_address(None, false).unwrap()).clone(); + let address2 = (*wallet.gen_internal_address(None, false).unwrap()).clone(); + let address3 = (*wallet.gen_internal_address(None, false).unwrap()).clone(); let offset = 0; let limit = 10; @@ -150,9 +259,9 @@ fn test_list_internal_addresses() { fn test_list_internal_addresses_paginated() { let (wallet, _db) = factories::wallet(None); - let _ = wallet.gen_internal_address(None).unwrap(); - let address = (*wallet.gen_internal_address(None).unwrap()).clone(); - let _ = wallet.gen_internal_address(None).unwrap(); + let _ = wallet.gen_internal_address(None, false).unwrap(); + let address = (*wallet.gen_internal_address(None, false).unwrap()).clone(); + let _ = wallet.gen_internal_address(None, false).unwrap(); let offset = 1; let limit = 1; @@ -185,13 +294,15 @@ fn test_get_address() { fn test_gen_internal_address() { let (wallet, _db) = factories::wallet(None); let label = "address label".to_string(); - let address = wallet.gen_internal_address(Some(label.clone())).unwrap(); + let address = wallet + .gen_internal_address(Some(label.clone()), false) + .unwrap(); assert!(address.address.starts_with("wit")); assert_eq!("m/3'/4919'/0'/1/0", &address.path); assert_eq!(Some(label), address.info.label); - let address_no_label = wallet.gen_internal_address(None).unwrap(); + let address_no_label = wallet.gen_internal_address(None, false).unwrap(); assert_eq!(None, address_no_label.info.label); } @@ -199,12 +310,12 @@ fn test_gen_internal_address() { #[test] fn test_gen_internal_address_creates_different_addresses() { let (wallet, _db) = factories::wallet(None); - let address = wallet.gen_internal_address(None).unwrap(); + let address = wallet.gen_internal_address(None, false).unwrap(); assert_eq!("m/3'/4919'/0'/1/0", &address.path); assert_eq!(0, address.index); - let new_address = wallet.gen_internal_address(None).unwrap(); + let new_address = wallet.gen_internal_address(None, false).unwrap(); assert_eq!("m/3'/4919'/0'/1/1", &new_address.path); assert_eq!(1, new_address.index); @@ -215,7 +326,7 @@ fn test_gen_internal_address_stores_next_address_index_in_db() { let (wallet, db) = factories::wallet(None); let account = 0; let keychain = constants::INTERNAL_KEYCHAIN; - wallet.gen_internal_address(None).unwrap(); + wallet.gen_internal_address(None, false).unwrap(); assert_eq!( 1, @@ -223,7 +334,7 @@ fn test_gen_internal_address_stores_next_address_index_in_db() { .unwrap() ); - wallet.gen_internal_address(None).unwrap(); + wallet.gen_internal_address(None, false).unwrap(); assert_eq!( 2, @@ -239,7 +350,9 @@ fn test_gen_internal_address_saves_details_in_db() { let keychain = constants::INTERNAL_KEYCHAIN; let index = 0; let label = "address label".to_string(); - let address = wallet.gen_internal_address(Some(label.clone())).unwrap(); + let address = wallet + .gen_internal_address(Some(label.clone()), false) + .unwrap(); assert_eq!( address.address, @@ -269,7 +382,7 @@ fn test_gen_internal_address_associates_pkh_to_account_in_db() { let (wallet, db) = factories::wallet(None); let account = 0; let keychain = constants::INTERNAL_KEYCHAIN; - let address = wallet.gen_internal_address(None).unwrap(); + let address = wallet.gen_internal_address(None, false).unwrap(); let pkh = &address.pkh; let path: model::Path = db.get(&keys::pkh(pkh)).unwrap(); @@ -342,6 +455,7 @@ fn test_create_transaction_components_when_wallet_have_no_utxos() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -398,6 +512,7 @@ fn test_create_transaction_components_without_a_change_address() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -454,6 +569,7 @@ fn test_create_transaction_components_with_a_change_address() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -525,6 +641,7 @@ fn test_create_transaction_components_which_value_overflows() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -589,6 +706,7 @@ fn test_create_vtt_does_not_spend_utxos() { }], utxo_strategy, selected_utxos: HashSet::default(), + preview: false, }) .unwrap(); @@ -674,7 +792,11 @@ fn test_create_data_request_does_not_spend_utxos() { let fee = Fee::absolute_from_nanowits(123); let (extended, _) = wallet - .create_data_req(types::DataReqParams { fee, request }) + .create_data_req(types::DataReqParams { + fee, + request, + preview: false, + }) .unwrap(); // the extended transaction should actually contain a data request transaction @@ -1015,6 +1137,7 @@ fn test_index_transaction_vtt_created_by_wallet() { }], utxo_strategy: UtxoSelectionStrategy::Random { from: None }, selected_utxos: HashSet::default(), + preview: false, }) .unwrap(); @@ -1138,6 +1261,7 @@ fn test_get_transaction() { }], utxo_strategy: UtxoSelectionStrategy::Random { from: None }, selected_utxos: HashSet::default(), + preview: false, }) .unwrap(); @@ -1220,6 +1344,7 @@ fn test_get_transactions() { }], utxo_strategy: UtxoSelectionStrategy::Random { from: None }, selected_utxos: HashSet::default(), + preview: false, }) .unwrap(); @@ -1301,6 +1426,7 @@ fn test_create_vtt_with_locked_balance() { }], utxo_strategy: UtxoSelectionStrategy::Random { from: None }, selected_utxos: HashSet::default(), + preview: false, }) .unwrap_err(); @@ -1364,6 +1490,7 @@ fn test_create_vtt_with_multiple_outputs() { ], utxo_strategy: UtxoSelectionStrategy::Random { from: None }, selected_utxos: HashSet::default(), + preview: false, }) .unwrap(); @@ -1461,6 +1588,7 @@ fn test_create_vt_components_weighted_fee() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -1533,6 +1661,7 @@ fn test_create_vt_components_weighted_fee_2() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -1600,6 +1729,7 @@ fn test_create_vt_components_weighted_fee_3() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -1692,6 +1822,7 @@ fn test_create_vt_components_weighted_fee_4() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -1781,6 +1912,7 @@ fn test_create_vt_components_weighted_fee_5() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -1876,6 +2008,7 @@ fn test_create_vt_components_weighted_fee_6() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -1920,6 +2053,7 @@ fn test_create_vt_components_weighted_fee_without_outputs() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -1977,6 +2111,7 @@ fn test_create_vt_components_weighted_fee_with_too_large_fee() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -2039,6 +2174,7 @@ fn test_create_vt_weight_too_large() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -2090,7 +2226,7 @@ fn test_create_dr_components_weighted_fee_1() { let mut state = wallet.state.write().unwrap(); let fee = Fee::relative_from_float(1.0); let TransactionComponents { inputs, .. } = wallet - .create_dr_transaction_components(&mut state, request, fee) + .create_dr_transaction_components(&mut state, request, fee, false) .unwrap(); assert_eq!(inputs.pointers.len(), 1); @@ -2137,7 +2273,7 @@ fn test_create_dr_components_weighted_fee_2_not_enough_funds() { let mut state = wallet.state.write().unwrap(); let fee = Fee::relative_from_float(1.0); let err = wallet - .create_dr_transaction_components(&mut state, request, fee) + .create_dr_transaction_components(&mut state, request, fee, false) .unwrap_err(); assert!( @@ -2188,7 +2324,7 @@ fn test_create_dr_components_weighted_fee_3_funds_splitted() { let fee = Fee::relative_from_float(1.0); let TransactionComponents { inputs, .. } = wallet - .create_dr_transaction_components(&mut state, request.clone(), fee) + .create_dr_transaction_components(&mut state, request.clone(), fee, false) .unwrap(); let weight = calculate_weight(inputs.pointers.len(), 1, Some(&request), u32::MAX).unwrap(); @@ -2245,7 +2381,7 @@ fn test_create_dr_components_weighted_fee_3_funds_splitted() { let mut state_2 = wallet_2.state.write().unwrap(); let TransactionComponents { inputs, .. } = wallet_2 - .create_dr_transaction_components(&mut state_2, request, fee) + .create_dr_transaction_components(&mut state_2, request, fee, false) .unwrap(); assert_eq!(inputs.pointers.len(), 3); @@ -2280,7 +2416,7 @@ fn test_create_dr_components_weighted_fee_without_outputs() { let fee = Fee::relative_from_float(1.0); let err = wallet - .create_dr_transaction_components(&mut state, request, fee) + .create_dr_transaction_components(&mut state, request, fee, false) .unwrap_err(); assert!( @@ -2333,7 +2469,7 @@ fn test_create_dr_components_weighted_fee_weight_too_large() { }; let fee = Fee::relative_from_float(0); let err = wallet - .create_dr_transaction_components(&mut state, request.clone(), fee) + .create_dr_transaction_components(&mut state, request.clone(), fee, false) .unwrap_err(); assert_eq!( @@ -2387,7 +2523,7 @@ fn test_create_dr_components_weighted_fee_fee_too_large() { let fee = Fee::relative_from_float(f64::MAX / 2.0); let err = wallet - .create_dr_transaction_components(&mut state, request, fee) + .create_dr_transaction_components(&mut state, request, fee, false) .unwrap_err(); assert_eq!( @@ -2461,6 +2597,7 @@ fn test_create_transaction_components_filter_from_address() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -2533,6 +2670,7 @@ fn test_create_transaction_components_filter_from_address_2() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -2605,6 +2743,7 @@ fn test_create_transaction_components_filter_from_address_3() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -2683,6 +2822,7 @@ fn test_create_transaction_components_does_not_use_unconfirmed_utxos() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap_err(); @@ -2708,6 +2848,7 @@ fn test_create_transaction_components_does_not_use_unconfirmed_utxos() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -2786,6 +2927,7 @@ fn test_create_transaction_components_uses_unconfirmed_utxos() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -2856,6 +2998,7 @@ fn test_create_vtt_selecting_utxos() { fee, &utxo_strategy, vec![out_pointer_0].into_iter().collect(), + false, ) .unwrap_err(); @@ -2875,6 +3018,7 @@ fn test_create_vtt_selecting_utxos() { fee, &utxo_strategy, vec![out_pointer_1].into_iter().collect(), + false, ) .unwrap(); @@ -2893,6 +3037,7 @@ fn test_create_vtt_selecting_utxos() { fee, &utxo_strategy, HashSet::default(), + false, ) .unwrap(); @@ -2956,6 +3101,7 @@ fn test_create_transaction_components_does_not_use_unconfirmed_utxos_and_selecti fee, &utxo_strategy, vec![pending_outptr].into_iter().collect(), + false, ) .unwrap_err(); diff --git a/wallet/src/types.rs b/wallet/src/types.rs index d3917c17d..84c9a9513 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -149,11 +149,13 @@ pub struct VttParams { pub outputs: Vec, pub utxo_strategy: UtxoSelectionStrategy, pub selected_utxos: HashSet, + pub preview: bool, } pub struct DataReqParams { pub fee: Fee, pub request: DataRequestOutput, + pub preview: bool, } #[derive(Debug, PartialEq, Eq)]