diff --git a/CHANGELOG.md b/CHANGELOG.md index 072ceab9a..eaf3fbc3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). implement bincode or accept byte and sequence forms in serde, `Error` is `FromSliceError`, and `forward_hex`, `backward_hex` and the `schemars` feature are removed +- **Breaking:** `BlsPkBytes::from_hex` and `BlsSigBytes::from_hex` report + `DecodeFixedLengthBytesError` instead of `hex::FromHexError`, and + `dashcore_rpc_json::HexError` wraps `DecodeVariableLengthBytesError` - **Breaking:** hashes honour the format precision, so `{:.8}` prints 8 digits - **Breaking:** `hash_x11::Hash` loses `forward_hex`, `backward_hex`, `from_bytes_ref` and `from_bytes_mut`, `hash_x11::Midstate` is replaced by diff --git a/Cargo.toml b/Cargo.toml index 657504a39..7a68c92f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,6 @@ resolver = "2" bincode = { package = "grovedb-bincode", version = "=2.1.0" } bincode_derive = { package = "grovedb-bincode-derive", version = "=2.1.0" } futures = "0.3" -hex = "0.4" -hex_lit = "0.1.1" rayon = "1.11" secp256k1 = "0.33.1" # must match with version used in `bitcoin-crypto` and `dash-pkc` serde = { version = "1.0.219", default-features = false } @@ -22,6 +20,7 @@ dash-types = { git = "https://github.com/dashpay/base-sdk", rev = "e6402ced257c3 base58ck = { version = "0.5.0" } bitcoin_hashes = { version = "0.14.101", default-features = false } bitcoin-crypto = { git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516", default-features = false } +hex-conservative = { version = "1.3.0" } [workspace.package] authors = ["The rust-dashcore Developers"] diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index 39eed93d5..e1868a5a0 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -27,7 +27,7 @@ dash-network = { path = "../dash-network" } dash-pkc = { workspace = true, features = ["std"] } dash-types = { workspace = true, features = ["codec"] } dashcore_hashes = { path = "../hashes" } -hex = { workspace = true } +hex-conservative = { workspace = true } internals = { path = "../internals", package = "dashcore-private" } secp256k1 = { workspace = true } serde = { workspace = true, features = ["derive", "alloc"], optional = true } @@ -38,5 +38,4 @@ tracing = { version = "0.1", optional = true } unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', 'cfg(kani)'] } [dev-dependencies] -hex_lit = { workspace = true, features = ["rust_v_1_46"] } serde_json = { version = "1.0" } diff --git a/crypto/src/bls.rs b/crypto/src/bls.rs index b9d3dc43a..7255828a6 100644 --- a/crypto/src/bls.rs +++ b/crypto/src/bls.rs @@ -14,7 +14,9 @@ use dash_pkc::bls::{ BlsSecretKey as PkcSecretKey, BlsSignature as PkcSignature, Fr, }; use dash_types::{make_bytes, make_sbytes}; -use hex::FromHexError; +use hex_conservative::DecodeFixedLengthBytesError; +#[cfg(feature = "bls")] +use hex_conservative::DisplayHex; #[cfg(feature = "bls")] use thiserror::Error as ThisError; #[cfg(feature = "bls")] @@ -93,10 +95,8 @@ impl BlsPkBytes { } /// Reads these bytes from a hex string. - pub fn from_hex(s: &str) -> Result { - let mut bytes = [0u8; BLS_PK_LEN]; - hex::decode_to_slice(s, &mut bytes)?; - Ok(Self::from_bytes(bytes)) + pub fn from_hex(s: &str) -> Result { + Ok(Self::from_bytes(hex_conservative::decode_to_array(s)?)) } /// Returns `true` when every byte is zero. @@ -332,10 +332,8 @@ impl BlsSigBytes { } /// Reads these bytes from a hex string. - pub fn from_hex(s: &str) -> Result { - let mut bytes = [0u8; BLS_SIG_LEN]; - hex::decode_to_slice(s, &mut bytes)?; - Ok(Self::from_bytes(bytes)) + pub fn from_hex(s: &str) -> Result { + Ok(Self::from_bytes(hex_conservative::decode_to_array(s)?)) } /// Returns `true` when every byte is zero. @@ -407,7 +405,7 @@ impl BlsSignature { fn point(self) -> Result, BlsError> { PkcSignature::::from_bytes(self.bytes.as_bytes()) - .map_err(|_| BlsError::InvalidSignature(hex::encode(self.bytes.as_bytes()))) + .map_err(|_| BlsError::InvalidSignature(self.bytes.as_bytes().to_lower_hex_string())) } fn verify_secure_in<'a, S, I>( self, @@ -439,7 +437,7 @@ impl BlsSignature { #[cfg(all(test, feature = "bls"))] mod tests { - use hex_lit::hex; + use hex_conservative::hex; /// Operator public keys from the mainnet quorum at height 2300832. const OPERATOR_KEYS: [[u8; 48]; 3] = [ @@ -578,7 +576,7 @@ mod tests { mod benchmarks { use super::super::*; use super::{CHAINLOCK_SIG, OPERATOR_KEYS}; - use hex_lit::hex; + use hex_conservative::hex; use std::time::Instant; #[test] diff --git a/dash-spv-ffi/Cargo.toml b/dash-spv-ffi/Cargo.toml index 023995f15..3e4fa25ad 100644 --- a/dash-spv-ffi/Cargo.toml +++ b/dash-spv-ffi/Cargo.toml @@ -17,7 +17,7 @@ dash-spv = { path = "../dash-spv" } dashcore = { path = "../dash" } dash-network = { path = "../dash-network", features = ["ffi"] } tokio = { version = "1", features = ["full"] } -hex = { workspace = true } +hex-conservative = { workspace = true } tracing = "0.1" # Use key-wallet-ffi for all wallet-related FFI types key-wallet-ffi = { path = "../key-wallet-ffi" } diff --git a/dash-spv-ffi/src/bin/ffi_cli.rs b/dash-spv-ffi/src/bin/ffi_cli.rs index 26cfde573..a139657fe 100644 --- a/dash-spv-ffi/src/bin/ffi_cli.rs +++ b/dash-spv-ffi/src/bin/ffi_cli.rs @@ -5,6 +5,7 @@ use std::ptr; use clap::{Arg, ArgAction, Command}; use dash_network::ffi::FFINetwork; use dash_spv_ffi::*; +use hex_conservative::DisplayHex; use key_wallet_ffi::types::FFIBalance; use key_wallet_ffi::wallet_manager::wallet_manager_add_wallet_from_mnemonic; use key_wallet_ffi::FFIError; @@ -68,7 +69,7 @@ extern "C" fn on_blocks_needed(blocks: *const FFIBlockNeeded, count: u32, _user_ if !blocks.is_null() && count > 0 { let blocks_slice = unsafe { std::slice::from_raw_parts(blocks, count as usize) }; for block in blocks_slice.iter() { - println!(" - height: {}, hash: {}", block.height, hex::encode(block.hash)); + println!(" - height: {}, hash: {}", block.height, block.hash.to_lower_hex_string()); } } } @@ -98,8 +99,8 @@ extern "C" fn on_chainlock_received( validated: bool, _user_data: *mut c_void, ) { - let hash_hex = unsafe { hex::encode(*hash) }; - let signature_hex = unsafe { hex::encode(*signature) }; + let hash_hex = unsafe { (*hash).to_lower_hex_string() }; + let signature_hex = unsafe { (*signature).to_lower_hex_string() }; println!( "[Sync] ChainLock received: height={}, hash={}, signature={}, validated={}", height, hash_hex, signature_hex, validated @@ -113,7 +114,7 @@ extern "C" fn on_instantlock_received( validated: bool, _user_data: *mut c_void, ) { - let txid_hex = unsafe { hex::encode(*txid) }; + let txid_hex = unsafe { (*txid).to_lower_hex_string() }; println!( "[Sync] InstantLock received: txid={}, validated={}, data_len={}", txid_hex, validated, instantlock_len @@ -211,7 +212,7 @@ extern "C" fn on_transaction_detected( } let r = unsafe { &*record }; let b = read_balance(balance); - let txid_hex = hex::encode(r.txid); + let txid_hex = r.txid.to_lower_hex_string(); println!( "[Wallet] TX detected: wallet={}..., txid={}, account_kind={:?}, account_index={}, amount={} duffs, balance[confirmed={}, unconfirmed={}], changed_accounts={}, derived={}", wallet_short, @@ -254,11 +255,11 @@ extern "C" fn on_transactions_swept( println!( "[Wallet] TXs swept: wallet={}..., removed=[{}], superseded_by={}, released=[{}], balance[confirmed={}, unconfirmed={}]", wallet_short, - list.iter().map(hex::encode).collect::>().join(","), - hex::encode(winner), + list.iter().map(|h| h.to_lower_hex_string()).collect::>().join(","), + winner.to_lower_hex_string(), released .iter() - .map(|o| format!("{}:{}", hex::encode(o.txid), o.vout)) + .map(|o| format!("{}:{}", o.txid.to_lower_hex_string(), o.vout)) .collect::>() .join(","), b.confirmed, @@ -283,7 +284,7 @@ extern "C" fn on_transaction_instant_locked( } let txid_bytes = unsafe { &*txid }; let b = read_balance(balance); - let txid_hex = hex::encode(txid_bytes); + let txid_hex = txid_bytes.to_lower_hex_string(); println!( "[Wallet] TX instant-locked: wallet={}..., txid={}, islock_len={}, balance[confirmed={}, unconfirmed={}], changed_accounts={}", wallet_short, diff --git a/dash-spv-ffi/src/callbacks.rs b/dash-spv-ffi/src/callbacks.rs index 8f3a5df5c..159bb50a5 100644 --- a/dash-spv-ffi/src/callbacks.rs +++ b/dash-spv-ffi/src/callbacks.rs @@ -11,6 +11,7 @@ use dash_spv::network::NetworkEvent; use dash_spv::sync::{SyncEvent, SyncProgress}; use dash_spv::EventHandler; use dashcore::hashes::Hash; +use hex_conservative::DisplayHex; use key_wallet::account::AccountType; use key_wallet::WalletCoreBalance; use key_wallet_ffi::managed_account::{FFIAccountType, FFITransactionRecord}; @@ -1120,7 +1121,7 @@ impl FFIWalletEventCallbacks { account_balances, } => { if let Some(cb) = self.on_transactions_swept { - let wallet_id_hex = hex::encode(wallet_id); + let wallet_id_hex = wallet_id.to_lower_hex_string(); let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default(); let raw_txids: Vec<[u8; 32]> = txids.iter().map(|t| t.to_byte_array()).collect(); @@ -1159,7 +1160,7 @@ impl FFIWalletEventCallbacks { // additive, so a consumer that leaves this one unset keeps // transactions the wallet has already dropped. tracing::warn!( - wallet_id = %hex::encode(wallet_id), + wallet_id = %wallet_id.to_lower_hex_string(), swept = txids.len(), %superseded_by, "no on_transactions_swept callback set; the consumer will keep \ @@ -1175,7 +1176,7 @@ impl FFIWalletEventCallbacks { addresses_derived, } => { if let Some(cb) = self.on_transaction_detected { - let wallet_id_hex = hex::encode(wallet_id); + let wallet_id_hex = wallet_id.to_lower_hex_string(); let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default(); let ffi_record = FFITransactionRecord::from(record.as_ref()); let ffi_balance = FFIBalance::from(*balance); @@ -1215,7 +1216,7 @@ impl FFIWalletEventCallbacks { account_balances, } => { if let Some(cb) = self.on_transaction_instant_locked { - let wallet_id_hex = hex::encode(wallet_id); + let wallet_id_hex = wallet_id.to_lower_hex_string(); let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default(); let txid_bytes = *txid.as_byte_array(); let islock_bytes = dashcore::consensus::serialize(instant_lock); @@ -1253,7 +1254,7 @@ impl FFIWalletEventCallbacks { chain_lock, } => { if let Some(cb) = self.on_block_processed { - let wallet_id_hex = hex::encode(wallet_id); + let wallet_id_hex = wallet_id.to_lower_hex_string(); let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default(); let ffi_inserted: Vec = inserted.iter().map(FFITransactionRecord::from).collect(); @@ -1337,7 +1338,7 @@ impl FFIWalletEventCallbacks { height, } => { if let Some(cb) = self.on_sync_height_advanced { - let wallet_id_hex = hex::encode(wallet_id); + let wallet_id_hex = wallet_id.to_lower_hex_string(); let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default(); cb(c_wallet_id.as_ptr(), *height, self.user_data); } @@ -1348,7 +1349,7 @@ impl FFIWalletEventCallbacks { locked_transactions, } => { if let Some(cb) = self.on_chain_lock_processed { - let wallet_id_hex = hex::encode(wallet_id); + let wallet_id_hex = wallet_id.to_lower_hex_string(); let c_wallet_id = CString::new(wallet_id_hex).unwrap_or_default(); let ffi_finalized = FFIChainlockedTxid::from_map(locked_transactions); let finalized_ptr = if ffi_finalized.is_empty() { @@ -1637,7 +1638,11 @@ mod tests { }); let captured = CAPTURED.lock().unwrap().take().expect("callback fired"); - assert_eq!(captured.wallet_id_hex, hex::encode(wallet_id), "wallet_id hex-encoding"); + assert_eq!( + captured.wallet_id_hex, + wallet_id.to_lower_hex_string(), + "wallet_id hex-encoding" + ); assert_eq!(captured.cl_height, 777, "cl_height"); assert_eq!(captured.cl_hash, expected_hash, "cl_hash round-trip"); assert_eq!(captured.cl_signature, expected_sig, "cl_signature round-trip"); diff --git a/dash-spv-ffi/tests/dashd_sync/tests_basic.rs b/dash-spv-ffi/tests/dashd_sync/tests_basic.rs index b742e2c06..946357ac4 100644 --- a/dash-spv-ffi/tests/dashd_sync/tests_basic.rs +++ b/dash-spv-ffi/tests/dashd_sync/tests_basic.rs @@ -4,6 +4,7 @@ use std::sync::atomic::Ordering; use dash_spv::test_utils::{DashdTestContext, TestChain}; use super::context::FFITestContext; +use hex_conservative::DisplayHex; #[test] fn test_wallet_sync_via_ffi() { @@ -17,7 +18,7 @@ fn test_wallet_sync_via_ffi() { let ctx = FFITestContext::new(dashd.addr); let wallet_id = ctx.add_wallet(&dashd.wallet.mnemonic); - tracing::info!("Added wallet, ID: {}", hex::encode(&wallet_id)); + tracing::info!("Added wallet, ID: {}", wallet_id.to_lower_hex_string()); ctx.run(); tracing::info!("FFI client running"); diff --git a/dash-spv/Cargo.toml b/dash-spv/Cargo.toml index cfcc08aba..c6cf1f2b5 100644 --- a/dash-spv/Cargo.toml +++ b/dash-spv/Cargo.toml @@ -42,7 +42,7 @@ chrono = "0.4.20" # Utilities rand = "0.9" -hex = { workspace = true } +hex-conservative = { workspace = true } # Parallelization rayon = { workspace = true } @@ -60,7 +60,6 @@ key-wallet-manager = { path = "../key-wallet-manager", features = ["test-utils"] criterion = { version = "0.8.1", features = ["async_tokio"] } tempfile = "3.0" tokio-test = "0.4" -hex = { workspace = true } test-case = "3.3" [[bench]] diff --git a/dash-spv/src/chain/chain_work.rs b/dash-spv/src/chain/chain_work.rs index 7a0bba655..f94dbf470 100644 --- a/dash-spv/src/chain/chain_work.rs +++ b/dash-spv/src/chain/chain_work.rs @@ -99,7 +99,8 @@ impl ChainWork { let hex = hex.strip_prefix("0x").unwrap_or(hex); // Parse hex string to bytes - let bytes = hex::decode(hex).map_err(|e| format!("Invalid hex: {}", e))?; + let bytes = + hex_conservative::decode_to_vec(hex).map_err(|e| format!("Invalid hex: {}", e))?; if bytes.len() != 32 { return Err(format!("Invalid work length: expected 32 bytes, got {}", bytes.len())); diff --git a/dash-spv/src/chain/checkpoints.rs b/dash-spv/src/chain/checkpoints.rs index 2eec44d34..2b24470f6 100644 --- a/dash-spv/src/chain/checkpoints.rs +++ b/dash-spv/src/chain/checkpoints.rs @@ -7,7 +7,7 @@ //! - Bootstrap masternode lists at specific heights use dashcore::{BlockHash, CompactTarget, Network, Target}; -use dashcore_hashes::{hex, Hash}; +use dashcore_hashes::Hash; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -1164,8 +1164,7 @@ pub fn testnet_checkpoints() -> Vec { /// Helper to parse hex block hash strings fn parse_block_hash(s: &str) -> Result { - use hex::FromHex; - let bytes = Vec::::from_hex(s).map_err(|e| format!("Invalid hex: {}", e))?; + let bytes = hex_conservative::decode_to_vec(s).map_err(|e| format!("Invalid hex: {}", e))?; if bytes.len() != 32 { return Err("Invalid hash length: expected 32 bytes".to_string()); } diff --git a/dash-spv/src/client/queries.rs b/dash-spv/src/client/queries.rs index 48aa38de5..137a1aa5c 100644 --- a/dash-spv/src/client/queries.rs +++ b/dash-spv/src/client/queries.rs @@ -20,6 +20,7 @@ use std::sync::Arc; use tokio::sync::RwLock; use super::DashSpvClient; +use hex_conservative::DisplayHex; impl DashSpvClient { // ============ Peer Queries ============ @@ -107,7 +108,7 @@ impl DashSpvClient ChainLockValidator<'a> { mod tests { use crate::sml_engine::test_support::TestEngine; use dashcore::ChainLock; + use dashcore_hashes::Hash; + use hex_conservative::DisplayHex; #[test] fn chain_locks_verify_against_the_newest_list() { @@ -103,14 +105,22 @@ mod tests { let [chain_lock, next_chain_lock] = ChainLock::mainnet_fixture_pair(); assert_eq!( - hex::encode(chain_lock.request_id().expect("expected to make request id")), + chain_lock + .request_id() + .expect("expected to make request id") + .as_byte_array() + .to_lower_hex_string(), "969ab4a945632f5fba1331f3d2556d317682142cf8aaa6544e407e683c61a177" ); engine.verify_chain_lock(&chain_lock).expect("expected to verify chain lock"); // Another one, so the first was not a 1/4 fluke. assert_eq!( - hex::encode(next_chain_lock.request_id().expect("expected to make request id")), + next_chain_lock + .request_id() + .expect("expected to make request id") + .as_byte_array() + .to_lower_hex_string(), "675aed91d6098cdf575cc09bfd1ff4f750acde1e793f385c3c72bbb400068d28" ); engine.verify_chain_lock(&next_chain_lock).expect("expected to verify chain lock"); diff --git a/dash-spv/src/validation/instantlock.rs b/dash-spv/src/validation/instantlock.rs index 1f139d2c2..123e9ae87 100644 --- a/dash-spv/src/validation/instantlock.rs +++ b/dash-spv/src/validation/instantlock.rs @@ -142,11 +142,12 @@ mod tests { use dashcore::consensus::deserialize; use dashcore::sml::llmq_type::LLMQType; use dashcore::QuorumHash; + use hex_conservative::DisplayHex; use test_case::test_case; /// An IS lock of the mainnet fixture's cycle, signed by its quorum at index 23. fn fixture_is_lock() -> InstantLock { - deserialize(&hex::decode("01018d53e7997ead57409750942af0d5e0aafc06f852a9a52308f4781b6a8220298f00000000c6f9d8c63dd15937ea70aaddb7890daad42c91bf6818e2bf76d183d6f2d9215b4b5f84978fad9dde7ab52bdcc0674be891e9029cc1ef0cb01200000000000000a27c98836c4c04653ab81eb4e07ddfc2c8c2c1036b75247969c05a4f25451cd78913a971f1899d9f2bddec9cf8e0104004f72f20c2856453e5aa3bcd2a8200670ec28feda38f67cc400fc72ef1966956656ec0765478c9d16e9a9e470c07f9ed").unwrap()).unwrap() + deserialize(&hex_conservative::decode_to_vec("01018d53e7997ead57409750942af0d5e0aafc06f852a9a52308f4781b6a8220298f00000000c6f9d8c63dd15937ea70aaddb7890daad42c91bf6818e2bf76d183d6f2d9215b4b5f84978fad9dde7ab52bdcc0674be891e9029cc1ef0cb01200000000000000a27c98836c4c04653ab81eb4e07ddfc2c8c2c1036b75247969c05a4f25451cd78913a971f1899d9f2bddec9cf8e0104004f72f20c2856453e5aa3bcd2a8200670ec28feda38f67cc400fc72ef1966956656ec0765478c9d16e9a9e470c07f9ed").unwrap()).unwrap() } #[test] @@ -177,13 +178,15 @@ mod tests { let request_id = lock.request_id().expect("expected to make request id"); assert_eq!( - hex::encode(request_id), + request_id.as_byte_array().to_lower_hex_string(), "481ca36cf80fde8fda333915e33c27014dad65fa9f3b54bc4d8bc45be7c81ddf" ); let quorum_hash = QuorumHash::from_slice( - hex::decode("00000000000000197368b224f2f01031991dd07aad0b43b2293a51fce8853ba0") - .expect("expected bytes") - .as_slice(), + hex_conservative::decode_to_vec( + "00000000000000197368b224f2f01031991dd07aad0b43b2293a51fce8853ba0", + ) + .expect("expected bytes") + .as_slice(), ) .expect("expected quorum hash") .reverse(); @@ -193,7 +196,7 @@ mod tests { let sign_id = lock.sign_id(LLMQType::Llmqtype60_75, quorum_hash, None).expect("expected sign id"); assert_eq!( - hex::encode(sign_id), + sign_id.as_byte_array().to_lower_hex_string(), "6fcbf58004b118d865a448bf89d9299c64d4ecedd754dabec655090224de91cd" ); validator.validate(&lock).expect("expected to verify is lock"); diff --git a/dash/Cargo.toml b/dash/Cargo.toml index 8c9f3c52a..7075559a6 100644 --- a/dash/Cargo.toml +++ b/dash/Cargo.toml @@ -50,9 +50,8 @@ serde = { workspace = true, features = ["derive", "alloc", "rc"], optional = tru base64 = { version = "0.22", optional = true } bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true } -hex_lit = { workspace = true } anyhow = { version= "1.0" } -hex = { workspace = true } +hex-conservative = { workspace = true } bincode = { workspace = true, optional = true } bincode_derive = { workspace = true, optional = true } blake3 = "1.8.1" diff --git a/dash/src/address.rs b/dash/src/address.rs index bff742223..0a371b3a4 100644 --- a/dash/src/address.rs +++ b/dash/src/address.rs @@ -1482,7 +1482,7 @@ fn segwit_redeem_hash(pubkey_hash: &PubkeyHash) -> crate::hashes::hash160::Hash mod tests { use core::str::FromStr; - use hex_lit::hex; + use hex_conservative::hex; use secp256k1::XOnlyPublicKey; use super::*; diff --git a/dash/src/blockdata/constants.rs b/dash/src/blockdata/constants.rs index a7307c932..19b256404 100644 --- a/dash/src/blockdata/constants.rs +++ b/dash/src/blockdata/constants.rs @@ -11,7 +11,7 @@ use core::default::Default; use hashes::{Hash, sha256d}; -use hex_lit::hex; +use hex_conservative::hex; use crate::blockdata::block::{self, Block}; use crate::blockdata::locktime::absolute; diff --git a/dash/src/blockdata/script/tests.rs b/dash/src/blockdata/script/tests.rs index 69d0cd92e..b9f2762a8 100644 --- a/dash/src/blockdata/script/tests.rs +++ b/dash/src/blockdata/script/tests.rs @@ -1,7 +1,7 @@ use core::str::FromStr; use hashes::Hash; -use hex_lit::hex; +use hex_conservative::hex; use super::*; use crate::blockdata::opcodes; diff --git a/dash/src/blockdata/transaction/mod.rs b/dash/src/blockdata/transaction/mod.rs index 77063cae3..e2005b81d 100644 --- a/dash/src/blockdata/transaction/mod.rs +++ b/dash/src/blockdata/transaction/mod.rs @@ -1302,7 +1302,7 @@ mod tests { #[test] fn deserialize_serialize_coinbase_transaction_in_dml() { let block_hex = include_str!("../../../tests/data/test_DML_diffs/DML_0_2221605.hex"); - let data = hex::decode(block_hex).expect("decode hex"); + let data = hex_conservative::decode_to_vec(block_hex).expect("decode hex"); let mn_list_diff: RawNetworkMessage = deserialize(&data).expect("deserialize MnListDiff"); if let NetworkMessage::MnListDiff(diff) = mn_list_diff.payload { let serialized = serialize(&diff.coinbase_tx); diff --git a/dash/src/blockdata/transaction/special_transaction/asset_unlock/qualified_asset_unlock.rs b/dash/src/blockdata/transaction/special_transaction/asset_unlock/qualified_asset_unlock.rs index 6764d99c7..937b754fe 100644 --- a/dash/src/blockdata/transaction/special_transaction/asset_unlock/qualified_asset_unlock.rs +++ b/dash/src/blockdata/transaction/special_transaction/asset_unlock/qualified_asset_unlock.rs @@ -141,7 +141,6 @@ mod tests { use core::str::FromStr; use hashes::Hash; - use hex::FromHex; use internals::hex::Case; use internals::hex::display::DisplayHex; @@ -178,7 +177,7 @@ mod tests { #[test] fn deserialize() { - let payload_bytes = Vec::from_hex("012d0100000000000070110100250500004acfa5c6d92071d206da5b767039d42f24e7ab1a694a5b8014cddc088311e448aee468c03feec7caada0599457136ef0dfe9365657a42ef81bb4aa53af383d05d90552b2cd23480cae24036b953ba8480d2f98291271a338e4235265dea94feacb54d1fd96083151001eff4156e7475e998154a8e6082575e2ee461b394d24f7") + let payload_bytes = hex_conservative::decode_to_vec("012d0100000000000070110100250500004acfa5c6d92071d206da5b767039d42f24e7ab1a694a5b8014cddc088311e448aee468c03feec7caada0599457136ef0dfe9365657a42ef81bb4aa53af383d05d90552b2cd23480cae24036b953ba8480d2f98291271a338e4235265dea94feacb54d1fd96083151001eff4156e7475e998154a8e6082575e2ee461b394d24f7") .unwrap(); let payload: AssetUnlockPayload = consensus::encode::deserialize(&payload_bytes).unwrap(); @@ -212,7 +211,7 @@ mod tests { quorum_sig: BLSSignature::from_str("aee468c03feec7caada0599457136ef0dfe9365657a42ef81bb4aa53af383d05d90552b2cd23480cae24036b953ba8480d2f98291271a338e4235265dea94feacb54d1fd96083151001eff4156e7475e998154a8e6082575e2ee461b394d24f7").unwrap() }; - let serialized_bytes = hex::encode(consensus::serialize(&payload)); + let serialized_bytes = consensus::serialize(&payload).to_lower_hex_string(); let expected_payload_bytes = "012d0100000000000070110100250500004acfa5c6d92071d206da5b767039d42f24e7ab1a694a5b8014cddc088311e448aee468c03feec7caada0599457136ef0dfe9365657a42ef81bb4aa53af383d05d90552b2cd23480cae24036b953ba8480d2f98291271a338e4235265dea94feacb54d1fd96083151001eff4156e7475e998154a8e6082575e2ee461b394d24f7"; assert_eq!(serialized_bytes, expected_payload_bytes); @@ -220,7 +219,7 @@ mod tests { #[test] fn test_asset_unlock_construction_3() { - let tx_bytes = Vec::from_hex("010009000001c8000000000000001976a914c35b782432294088e354bc28aa56d95736cb630288ac0000000001000000000000000070f915129f05000053c006055af6d0ae9aa9627df8615a71c312421a28c4712c8add83c8e1bfdadd").unwrap(); + let tx_bytes = hex_conservative::decode_to_vec("010009000001c8000000000000001976a914c35b782432294088e354bc28aa56d95736cb630288ac0000000001000000000000000070f915129f05000053c006055af6d0ae9aa9627df8615a71c312421a28c4712c8add83c8e1bfdadd").unwrap(); let tx_asset_unlock = build_asset_unlock_tx(&tx_bytes).unwrap(); let bytes_tx_asset_unlock = consensus::serialize(&tx_asset_unlock); println!("tx_asset_unlock: {:?}", bytes_tx_asset_unlock); diff --git a/dash/src/blockdata/transaction/special_transaction/provider_update_registrar.rs b/dash/src/blockdata/transaction/special_transaction/provider_update_registrar.rs index 372133e48..1a2a11b1e 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_update_registrar.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_update_registrar.rs @@ -159,6 +159,7 @@ mod tests { use crate::transaction::special_transaction::TransactionPayload::ProviderUpdateRegistrarPayloadType; use crate::transaction::special_transaction::provider_update_registrar::ProviderUpdateRegistrarPayload; use crate::{Network, PubkeyHash, ScriptBuf, Transaction, Txid}; + use hex_conservative::DisplayHex; #[test] fn test_provider_update_registrar_transaction() { @@ -199,13 +200,19 @@ mod tests { let operator_key_hex = "139b654f0b1c031e1cf2b934c2d895178875cfe7c6a4f6758f02bc66eea7fc292d0040701acbe31f5e14a911cb061a2f"; assert_eq!( operator_key_hex, - hex::encode(expected_provider_update_registrar_payload.operator_public_key) + expected_provider_update_registrar_payload + .operator_public_key + .as_bytes() + .to_lower_hex_string() ); let voting_key_hash_hex = "6cc4a7bb877a80c11ae06b988d98305773f93b98"; assert_eq!( voting_key_hash_hex, - hex::encode(expected_provider_update_registrar_payload.voting_key_hash) + expected_provider_update_registrar_payload + .voting_key_hash + .as_byte_array() + .to_lower_hex_string() ); let inputs_hash_hex = "cf2b940faa8c46c7981f5bd082e5409bf08cffe3bccfa04093eb152f7a857f2d"; diff --git a/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs b/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs index 02c73698b..4154498ef 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs @@ -478,7 +478,7 @@ mod tests { }; // Decode hex to bytes - let block_bytes = match hex::decode(&block_hex_string) { + let block_bytes = match hex_conservative::decode_to_vec(&block_hex_string) { Ok(bytes) => bytes, Err(e) => { panic!("❌ Failed to decode hex: {}", e); @@ -589,7 +589,7 @@ mod tests { } }; - let block_bytes = match hex::decode(&block_hex_string) { + let block_bytes = match hex_conservative::decode_to_vec(&block_hex_string) { Ok(bytes) => bytes, Err(e) => { panic!("❌ Failed to decode hex: {}", e); diff --git a/dash/src/blockdata/transaction/special_transaction/quorum_commitment.rs b/dash/src/blockdata/transaction/special_transaction/quorum_commitment.rs index 24d917624..fb858168a 100644 --- a/dash/src/blockdata/transaction/special_transaction/quorum_commitment.rs +++ b/dash/src/blockdata/transaction/special_transaction/quorum_commitment.rs @@ -281,7 +281,7 @@ mod tests { #[test] fn deserialize_serialize_mn_list_diff() { let block_hex = include_str!("../../../../tests/data/test_DML_diffs/DML_0_2221605.hex"); - let data = hex::decode(block_hex).expect("decode hex"); + let data = hex_conservative::decode_to_vec(block_hex).expect("decode hex"); let mn_list_diff: RawNetworkMessage = deserialize(&data).expect("deserialize MnListDiff"); if let NetworkMessage::MnListDiff(diff) = mn_list_diff.payload { let quorum = diff.new_quorums.first().expect("expected a quorum"); diff --git a/dash/src/bloom/filter.rs b/dash/src/bloom/filter.rs index a5608edf2..c303fc360 100644 --- a/dash/src/bloom/filter.rs +++ b/dash/src/bloom/filter.rs @@ -313,17 +313,20 @@ mod tests { fn test_bloom_filter_dash_core_compatibility() { let mut filter = BloomFilter::new(3, 0.01, 0, BloomFlags::All).unwrap(); - let data1 = hex::decode("99108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap(); - let data2 = hex::decode("b5a2c786d9ef4658287ced5914b37a1b4aa32eee").unwrap(); - let data3 = hex::decode("b9300670b4c5366e95b2699e8b18bc75e5f729c5").unwrap(); + let data1 = + hex_conservative::decode_to_vec("99108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap(); + let data2 = + hex_conservative::decode_to_vec("b5a2c786d9ef4658287ced5914b37a1b4aa32eee").unwrap(); + let data3 = + hex_conservative::decode_to_vec("b9300670b4c5366e95b2699e8b18bc75e5f729c5").unwrap(); assert!(!filter.contains(&data1)); filter.insert(&data1); assert!(filter.contains(&data1)); - assert!( - !filter.contains(&hex::decode("19108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap()) - ); + assert!(!filter.contains( + &hex_conservative::decode_to_vec("19108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap() + )); filter.insert(&data2); assert!(filter.contains(&data2)); @@ -334,7 +337,7 @@ mod tests { let mut encoded = Vec::new(); filter.consensus_encode(&mut encoded).unwrap(); - let expected = hex::decode("03614e9b050000000000000001").unwrap(); + let expected = hex_conservative::decode_to_vec("03614e9b050000000000000001").unwrap(); assert_eq!(encoded, expected, "Serialized bloom filter must match Dash Core test vector"); } @@ -348,11 +351,12 @@ mod tests { #[test] fn test_bloom_create_insert_key() { // Private key from Dash Core test WIF: 7sQb6QHALg4XyHsJHsSNXnEHGhZfzTTUPJXJqaqK7CavQkiL9Ms - let privkey_bytes: [u8; 32] = - hex::decode("f49addfd726a59abde172c86452f5f73038a02f4415878dc14934175e8418aff") - .unwrap() - .try_into() - .unwrap(); + let privkey_bytes: [u8; 32] = hex_conservative::decode_to_vec( + "f49addfd726a59abde172c86452f5f73038a02f4415878dc14934175e8418aff", + ) + .unwrap() + .try_into() + .unwrap(); let secret_key = secp256k1::SecretKey::from_secret_bytes(privkey_bytes).unwrap(); let privkey = PrivateKey::new_uncompressed(secret_key, crate::network::constants::Network::Mainnet); @@ -371,7 +375,7 @@ mod tests { let mut encoded = Vec::new(); filter.consensus_encode(&mut encoded).unwrap(); - let expected = hex::decode("038fc16b080000000000000001").unwrap(); + let expected = hex_conservative::decode_to_vec("038fc16b080000000000000001").unwrap(); assert_eq!( encoded, expected, "Serialized bloom filter must match Dash Core bloom_create_insert_key test vector" @@ -384,15 +388,18 @@ mod tests { fn test_bloom_filter_dash_core_compatibility_with_tweak() { let mut filter = BloomFilter::new(3, 0.01, 2147483649, BloomFlags::All).unwrap(); - let data1 = hex::decode("99108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap(); - let data2 = hex::decode("b5a2c786d9ef4658287ced5914b37a1b4aa32eee").unwrap(); - let data3 = hex::decode("b9300670b4c5366e95b2699e8b18bc75e5f729c5").unwrap(); + let data1 = + hex_conservative::decode_to_vec("99108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap(); + let data2 = + hex_conservative::decode_to_vec("b5a2c786d9ef4658287ced5914b37a1b4aa32eee").unwrap(); + let data3 = + hex_conservative::decode_to_vec("b9300670b4c5366e95b2699e8b18bc75e5f729c5").unwrap(); filter.insert(&data1); assert!(filter.contains(&data1)); - assert!( - !filter.contains(&hex::decode("19108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap()) - ); + assert!(!filter.contains( + &hex_conservative::decode_to_vec("19108ad8ed9bb6274d3980bab5a85c048f0950c8").unwrap() + )); filter.insert(&data2); assert!(filter.contains(&data2)); @@ -403,7 +410,7 @@ mod tests { let mut encoded = Vec::new(); filter.consensus_encode(&mut encoded).unwrap(); - let expected = hex::decode("03ce4299050000000100008001").unwrap(); + let expected = hex_conservative::decode_to_vec("03ce4299050000000100008001").unwrap(); assert_eq!( encoded, expected, "Serialized bloom filter must match Dash Core test vector (with tweak)" diff --git a/dash/src/ephemerealdata/chain_lock.rs b/dash/src/ephemerealdata/chain_lock.rs index fb8b07881..4901bb3bc 100644 --- a/dash/src/ephemerealdata/chain_lock.rs +++ b/dash/src/ephemerealdata/chain_lock.rs @@ -78,8 +78,6 @@ impl ChainLock { #[cfg(test)] mod tests { - use hex::FromHex; - use super::*; use crate::consensus::deserialize; use crate::internal_macros::hex; @@ -116,7 +114,7 @@ mod tests { let expected_request_id = "5d92e094e2aa582b76e8bf519f42c5e8fc141bbe548e9660726f744adad03966"; - let vec = Vec::from_hex(hex).unwrap(); + let vec = hex_conservative::decode_to_vec(hex).unwrap(); let chain_lokc: ChainLock = deserialize(&vec).unwrap(); diff --git a/dash/src/network/constants.rs b/dash/src/network/constants.rs index 57c8cddec..fc6f4344c 100644 --- a/dash/src/network/constants.rs +++ b/dash/src/network/constants.rs @@ -72,30 +72,34 @@ impl NetworkExt for Network { fn known_genesis_block_hash(&self) -> Option { match self { Network::Mainnet => { - let mut block_hash = - hex::decode("00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6") - .expect("expected valid hex"); + let mut block_hash = hex_conservative::decode_to_vec( + "00000ffd590b1485b3caadc19b22e6379c733355108f107a430458cdf3407ab6", + ) + .expect("expected valid hex"); block_hash.reverse(); Some(BlockHash::from_byte_array(block_hash.try_into().expect("expected 32 bytes"))) } Network::Testnet => { - let mut block_hash = - hex::decode("00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c") - .expect("expected valid hex"); + let mut block_hash = hex_conservative::decode_to_vec( + "00000bafbc94add76cb75e2ec92894837288a481e5c005f6563d91623bf8bc2c", + ) + .expect("expected valid hex"); block_hash.reverse(); Some(BlockHash::from_byte_array(block_hash.try_into().expect("expected 32 bytes"))) } Network::Devnet => { - let mut block_hash = - hex::decode("000008ca1832a4baf228eb1553c03d3a2c8e02399550dd6ea8d65cec3ef23d2e") - .expect("expected valid hex"); + let mut block_hash = hex_conservative::decode_to_vec( + "000008ca1832a4baf228eb1553c03d3a2c8e02399550dd6ea8d65cec3ef23d2e", + ) + .expect("expected valid hex"); block_hash.reverse(); Some(BlockHash::from_byte_array(block_hash.try_into().expect("expected 32 bytes"))) } Network::Regtest => { - let mut block_hash = - hex::decode("000008ca1832a4baf228eb1553c03d3a2c8e02399550dd6ea8d65cec3ef23d2e") - .expect("expected valid hex"); + let mut block_hash = hex_conservative::decode_to_vec( + "000008ca1832a4baf228eb1553c03d3a2c8e02399550dd6ea8d65cec3ef23d2e", + ) + .expect("expected valid hex"); block_hash.reverse(); Some(BlockHash::from_byte_array(block_hash.try_into().expect("expected 32 bytes"))) } diff --git a/dash/src/network/message_network.rs b/dash/src/network/message_network.rs index 343b56428..088e931f4 100644 --- a/dash/src/network/message_network.rs +++ b/dash/src/network/message_network.rs @@ -251,8 +251,10 @@ mod tests { assert!(message.relay); assert_eq!( &message.mn_auth_challenge.to_vec(), - &hex::decode("6a4bbaa5155fa3ed24b60975d2b7b9860ccd64ff39c128622184b794256a0cba") - .expect("expected to get vec") + &hex_conservative::decode_to_vec( + "6a4bbaa5155fa3ed24b60975d2b7b9860ccd64ff39c128622184b794256a0cba" + ) + .expect("expected to get vec") ); assert!(!message.masternode_connection); assert_eq!(serialize(&message), from_sat); diff --git a/dash/src/network/message_qrinfo.rs b/dash/src/network/message_qrinfo.rs index 846c66bbd..511f176a3 100644 --- a/dash/src/network/message_qrinfo.rs +++ b/dash/src/network/message_qrinfo.rs @@ -328,7 +328,7 @@ mod tests { #[test] fn deserialize_qr_info() { let block_hex = include_str!("../../tests/data/test_DML_diffs/QR_INFO_0_2224359.hex"); - let data = hex::decode(block_hex).expect("decode hex"); + let data = hex_conservative::decode_to_vec(block_hex).expect("decode hex"); let network_qr_info: RawNetworkMessage = deserialize(&data).expect("deserialize QR_INFO"); let RawNetworkMessage { @@ -343,7 +343,7 @@ mod tests { fn load_v3_qrinfo_bytes() -> Vec { let hex = include_str!("../../tests/data/test_DML_diffs/qrinfo_core231_paloma.hex"); - let data = hex::decode(hex.trim()).expect("decode hex"); + let data = hex_conservative::decode_to_vec(hex.trim()).expect("decode hex"); assert_eq!(data.len(), 20064); assert_eq!( sha256::Hash::hash(&data).to_string(), diff --git a/dash/src/network/message_sml.rs b/dash/src/network/message_sml.rs index bc75dcab2..e3d206d95 100644 --- a/dash/src/network/message_sml.rs +++ b/dash/src/network/message_sml.rs @@ -107,7 +107,7 @@ mod tests { #[test] fn deserialize_mn_list_diff() { let block_hex = include_str!("../../tests/data/test_DML_diffs/DML_0_2221605.hex"); - let data = hex::decode(block_hex).expect("decode hex"); + let data = hex_conservative::decode_to_vec(block_hex).expect("decode hex"); let mn_list_diff: RawNetworkMessage = deserialize(&data).expect("deserialize MnListDiff"); assert_matches!(mn_list_diff, RawNetworkMessage { magic, payload: NetworkMessage::MnListDiff(_) } if magic == 3177909439); @@ -116,7 +116,7 @@ mod tests { #[test] fn deserialize_serialize_mn_list_diff() { let block_hex = include_str!("../../tests/data/test_DML_diffs/DML_0_2221605.hex"); - let data = hex::decode(block_hex).expect("decode hex"); + let data = hex_conservative::decode_to_vec(block_hex).expect("decode hex"); let mn_list_diff: RawNetworkMessage = deserialize(&data).expect("deserialize MnListDiff"); if let NetworkMessage::MnListDiff(diff) = mn_list_diff.payload { let serialized = serialize(&*diff); diff --git a/dash/src/sml/masternode_list/apply_diff.rs b/dash/src/sml/masternode_list/apply_diff.rs index 5a2b03576..8fb4de434 100644 --- a/dash/src/sml/masternode_list/apply_diff.rs +++ b/dash/src/sml/masternode_list/apply_diff.rs @@ -265,30 +265,30 @@ mod tests { } let expected_signatures: BTreeMap<&str, Vec> = BTreeMap::from([ - ("000000000000000fcc3b58235989afa1962b6d6f238a2201190452123231a704", hex::decode("8ba84befb59e4f16160ca69a5a4785b314bd3f2ed9ae435daacdba23b3079b0fabc909f159ec80243b8ccc4c95f63bdb1176749b83fffc429be426e899982bc50e15f4d923df91b341c2cfdf47620a7ee35502593b1484b9f444466e04da52fd").unwrap()), - ("000000000000000887fa15abc502ec49ec3b318fd79fc7fdfda514f67b895009", hex::decode("b03d75ae15fdaa3fbc72cf548f3cece8be6ad266ae7f4f79755537c80fe0a4b641cf6391ac17105d97d602e86e81d4e80331f9b5fb616cec399230d4b9b7ef9896885b1ad78109973ad5855ea5684994740b7ed710b4b72173c5e170b3df2a46").unwrap()), - ("00000000000000133c9d6e64823bdfd80d7640b255faea18ce1d6419b55e3314", hex::decode("909ca60a8923b631d7d939d005431097a6974eef0e03a09a58c8e6a846c74ca94720eeda407cb20271e8f6e12ec23d0905da732fd1a50e8d1df414aad2094e28eb6dc24b64338add8e6085590c4c5849a9003eeaee91408f5bd4b41eaf1039e3").unwrap()), - ("00000000000000179e5ed3711a8257dcbb0d17f7d5c52c92a9a122ca574f7b1c", hex::decode("885a2ba9ad907d9421c38af7aec35dff7be85d1788ccaef760056e1eef890b83b8a8e1e898dade5d3f52cfbc3b7b9eb5188d15283a43b68fcc1c75920727597ab905a0c18d9d9c335dc66a5cbeb1874f5bb54c4219096800ccfe3dacf3240fe6").unwrap()), - ("0000000000000014a54ccad3b51e1fc6fded48dea59c5dbc17bcb58b5aa95320", hex::decode("8d9bc1065ff57b53302667a1564955ec32e823c0e74272e2e6f45e9bce3f9555bc772ce636cdc0e7ba15bd2f181f669a17e8893f0327fdb6e1af7e74cbdfaa96a630acbd161e110ee3e22dc788c96564ed754594f6d7b02447bf8ef0dae5a93c").unwrap()), - ("000000000000000e7463a65d312855272e68bb03acd989ef36027d584951ca27", hex::decode("802f1cc00ded6f81d1904de5b5d8cfbf28a3165cc9f8f8569720293f400dc81a8427af171c31c63cc29d943c40a1545c03c8a3e3154573f166305f05dd8c7fac2b8abff00d950c042713a2b913748931e9a04fc757a7597f175dca96b753c4e8").unwrap()), - ("00000000000000194f5c21458d718d8b1a2e11a6d4b3a1c1183d70123b8deb36", hex::decode("a46a067f15cb6525cfaa702b585f77115d59642a04032206430325db517522ce4076885859b591b5abcbe6843c1f08e502e4aad1f8124c1bab95ad0feaabe16dff1b0181dd8d7869d6be4e5cf82480cedb76471377c760016d56e5446fe9dc40").unwrap()), - ("0000000000000009bd850bce5941826fdce7a2583644d6c197348b15151cb33c", hex::decode("97f84875bbe040af2ddd38e10c9df84cd2e0ddbc1caa693de2807e42209997f3ed9a6d2a23da02e255de409ae430d7fa121c61ae650b6654e0cabe6e3fe3e1bb557c48fdefb8a6a60d68d2d4ded7b6e4799567942529f3caafbc98a74d4359b8").unwrap()), - ("0000000000000004d810f16edf5e672ee7fb4fe46342a9c28de54db62802334e", hex::decode("879326d10acd1f4299c87e5dfd7832913631afa90ef4aaa31e61d8e5d74b5ed3f1f461918b17cbf1a9a124667ceba0b00745f67b1eb127f5156fbf43145b973bf7ce56da3b3e6f99e5fee0fb863fdafeaa13bad78204933edf5dd74963d22c6b").unwrap()), - ("0000000000000027727e5c45130cef688c056ad1ce1740b6eeb5e7a8a556d24f", hex::decode("829e508a99823b607256ab4297cacc1b7580d49e1e18a2af24aacf157c25f4195be9f7600507e3f5f4a502f08beeb75a048ff280a555705b899733431a7997ac6f98f63c259f83f65fa2548d23b42dbcbd3fcaf17fcdaee183c354f1cb046942").unwrap()), - ("000000000000000e6d139ec023a1fbb12a7a19d7ab5db1c34322445494685b52", hex::decode("8d230edbed207dcb3ff28c72c14a72f1d79f6e8b8345ff6e7b71caa063750193dac0d8047fa89889f517d3579505282115b9078c6ca85cc66a91db407001c9247902456b239a721975f1930cea8e489fae5e2bc714445e86d3d7d58c6b86aa9f").unwrap()), - ("000000000000002910000426717f2e2fe13659de4199ebd2ad0df8acaa40ec55", hex::decode("b82ac105dadd8f22edc80be0d9a3f0565735aec0f5350bd961d01e3b95ad8b6410a15cb97b99fc04e5cbc11e315c2af50eca9ac3829b2321c2c3043eed03f31a8ed91ca1dc25c45c06f74ad6ca399c7e6462bd96c75e4f688ae5fa28e09591db").unwrap()), - ("00000000000000034d13700c17a966c7d4da13134d3928460922dc2122934d5f", hex::decode("aeabf173f885c401dd859d5e743dfa60106ac416e57d2870aa06241ad0133397b88484d7e9f95154a9167537e1dd524f18127854aa270088007b23155c22f6dd07d6696b2fea4599ed72b7be62e0bad519e296da38cd9db0b29dbcde5888be0c").unwrap()), - ("00000000000000059bbc2b37c8d846653c3c7e213ca2507b74b1139fee57346b", hex::decode("b867cacbf145215502344a36a46839255b39c44129da259ad1eb1dab1c33b5ad6cd4e9a34f083590ed7a8153c12ed05c03f170b87dc16cff6a031519dbc60ae83a4713f8fdfeef7b1be66258b053b2865957b61a4d4cea445799a4cfe8ca7590").unwrap()), - ("000000000000003c41c3b18552e0dbddd59ca4e9235ae6799c0f88d5d39b3375", hex::decode("b05f472ea28f41961dbadd4bfc33ad46120a8a5c082b46f88598f263f47033b252f5eb5fb10fd67cb9ca8790c56848550a06661332abc72cd1e1e9bb2e6cc63219f0b05faa981589cddc5dec57118db637c1819f5da023e78db930c4347e3799").unwrap()), - ("0000000000000004beb237cb0c284418129d337ccbacf0ce4bcacaef052aa17b", hex::decode("8305847336eea1f9f502216ba03203b7614a4e6038b315a5342bc100a3bb9fc075df88415c5f9dcb88c35145e7ee44ba178012da65826fb4c6ab7c986dc50daccf383a57d8c8476dd864c24fb8a7c7c040a6dc57c238ee499733b6006b0611b4").unwrap()), - ("0000000000000028c15e263548139cef64e9fcebc6d793bd9448d30797c14f80", hex::decode("a3c7d6d59248387269a928f4b37dad3f6559cae800acecf8e1502c5a7e2862d501013a91f30e7d7f2a63055adefeb7ae16de6020d4b6281c69b80381ee2e8c93b6a148d1934ac10cc71b5dd441bee988b2ee51022c345286ae4b241b149446bd").unwrap()), - ("000000000000000ff31a80c31e6773c572e797cae876b6603b587915d738dc89", hex::decode("b8bbddd9f5214880d65cf7d096cb213b1f5bdd991669487e45812e4efaea8fe0cdc9642c7e9e9d4f8ebc0c1dd607c9eb19bdab1aed6eb4c52789ad7c41e2ce80fd1b8bef393b421089c9ab8b156b7917e3bd39c6b28b8e720212d94c2f7cf857").unwrap()), - ("0000000000000019e3f9a338f32411d2f3e91e623b0bcbf327bca32b9ded4b9d", hex::decode("a12e8c68461a3248cc038ce50fc23ebfeae3718e10ee949e763deee0cde0b9e1a637b19ab18765604ef98be4a49ce6bc0525acb92db0defcfb57d993d0cf63ccc9f4378a11ed5a6707f791ded468a04daf4fa650a0e95689261615360faf80d3").unwrap()), - ("0000000000000025b0f8b6cd855cab58429ee158ddfd32358ab55b98e53feaa2", hex::decode("b0f7402bd4c6c3926431d7c3bcb56ef52caf1d4edc7ab5d01ddd10ac6023aaccc9f336d22eb5c8e2930339875e9159cc0b54de90e5aa28d9bc4db4b3a7e5d6ea1c3c84b6817a5f13557d57b9f841494a831d8e58114710e853454847d1ab53d5").unwrap()), - ("000000000000001912a0ac17300c5b7bfd1385a418137c3bc8d273ac3d9f85d7", hex::decode("ab751a79ea12c823745cccb7600b8aad50b72c0ac0d090e156a84755fe8a8eee9a8e57d076728428fa9d98f571be99d20a93090f1f310a78b66d26668672448b5e564a110640487ec508677faf1f79c14dcee34404e6d8c1c8037151f4ec7e4d").unwrap()), - ("000000000000001d3789f5d1e7318b4350f20bdf1ea4beeeedf26780312114db", hex::decode("ac6edba765e86f2d3c86083c2919bd285e3a635413f2783a7261a0447a135827c73b635277265255cb678df3aa275986198a174c9fa39e499d0a26f45a7a8e3a7559ddebe200c13a96c060a5f7bc689d5fb93f68be9d6113d94acbbab714c52c").unwrap()), - ("0000000000000010b28f1ea61bf3ff88cd2fef7e33a5f1868fb555ec682636eb", hex::decode("b96908533c42ecb7e540cec408f5d2aec93d97df37de695b9e92b50145a001153b3392c7aeab697a168e813c566dace007410e6159db92453732b067c0f22a2df413b113c47e43ace3906572db46b451565531c0a39a6ad9f5fb7e761273d7bb").unwrap()), - ("0000000000000026d8a2480f338951dfedc5e7abdd3704500a10b4a188c89bf8", hex::decode("af13e196c300afce6ced40fa32851d1ff8646e2a1c0f03fc83cce88a44291a400fe8026dcb95edc9f2485b2596731c8e092fd313265269ffb5c5b0d13c4f0cda75ae69db27829c400cd25c2b55ca929ab8ac7a2b29f859e69800796c3d98c5a2").unwrap()) + ("000000000000000fcc3b58235989afa1962b6d6f238a2201190452123231a704", hex_conservative::decode_to_vec("8ba84befb59e4f16160ca69a5a4785b314bd3f2ed9ae435daacdba23b3079b0fabc909f159ec80243b8ccc4c95f63bdb1176749b83fffc429be426e899982bc50e15f4d923df91b341c2cfdf47620a7ee35502593b1484b9f444466e04da52fd").unwrap()), + ("000000000000000887fa15abc502ec49ec3b318fd79fc7fdfda514f67b895009", hex_conservative::decode_to_vec("b03d75ae15fdaa3fbc72cf548f3cece8be6ad266ae7f4f79755537c80fe0a4b641cf6391ac17105d97d602e86e81d4e80331f9b5fb616cec399230d4b9b7ef9896885b1ad78109973ad5855ea5684994740b7ed710b4b72173c5e170b3df2a46").unwrap()), + ("00000000000000133c9d6e64823bdfd80d7640b255faea18ce1d6419b55e3314", hex_conservative::decode_to_vec("909ca60a8923b631d7d939d005431097a6974eef0e03a09a58c8e6a846c74ca94720eeda407cb20271e8f6e12ec23d0905da732fd1a50e8d1df414aad2094e28eb6dc24b64338add8e6085590c4c5849a9003eeaee91408f5bd4b41eaf1039e3").unwrap()), + ("00000000000000179e5ed3711a8257dcbb0d17f7d5c52c92a9a122ca574f7b1c", hex_conservative::decode_to_vec("885a2ba9ad907d9421c38af7aec35dff7be85d1788ccaef760056e1eef890b83b8a8e1e898dade5d3f52cfbc3b7b9eb5188d15283a43b68fcc1c75920727597ab905a0c18d9d9c335dc66a5cbeb1874f5bb54c4219096800ccfe3dacf3240fe6").unwrap()), + ("0000000000000014a54ccad3b51e1fc6fded48dea59c5dbc17bcb58b5aa95320", hex_conservative::decode_to_vec("8d9bc1065ff57b53302667a1564955ec32e823c0e74272e2e6f45e9bce3f9555bc772ce636cdc0e7ba15bd2f181f669a17e8893f0327fdb6e1af7e74cbdfaa96a630acbd161e110ee3e22dc788c96564ed754594f6d7b02447bf8ef0dae5a93c").unwrap()), + ("000000000000000e7463a65d312855272e68bb03acd989ef36027d584951ca27", hex_conservative::decode_to_vec("802f1cc00ded6f81d1904de5b5d8cfbf28a3165cc9f8f8569720293f400dc81a8427af171c31c63cc29d943c40a1545c03c8a3e3154573f166305f05dd8c7fac2b8abff00d950c042713a2b913748931e9a04fc757a7597f175dca96b753c4e8").unwrap()), + ("00000000000000194f5c21458d718d8b1a2e11a6d4b3a1c1183d70123b8deb36", hex_conservative::decode_to_vec("a46a067f15cb6525cfaa702b585f77115d59642a04032206430325db517522ce4076885859b591b5abcbe6843c1f08e502e4aad1f8124c1bab95ad0feaabe16dff1b0181dd8d7869d6be4e5cf82480cedb76471377c760016d56e5446fe9dc40").unwrap()), + ("0000000000000009bd850bce5941826fdce7a2583644d6c197348b15151cb33c", hex_conservative::decode_to_vec("97f84875bbe040af2ddd38e10c9df84cd2e0ddbc1caa693de2807e42209997f3ed9a6d2a23da02e255de409ae430d7fa121c61ae650b6654e0cabe6e3fe3e1bb557c48fdefb8a6a60d68d2d4ded7b6e4799567942529f3caafbc98a74d4359b8").unwrap()), + ("0000000000000004d810f16edf5e672ee7fb4fe46342a9c28de54db62802334e", hex_conservative::decode_to_vec("879326d10acd1f4299c87e5dfd7832913631afa90ef4aaa31e61d8e5d74b5ed3f1f461918b17cbf1a9a124667ceba0b00745f67b1eb127f5156fbf43145b973bf7ce56da3b3e6f99e5fee0fb863fdafeaa13bad78204933edf5dd74963d22c6b").unwrap()), + ("0000000000000027727e5c45130cef688c056ad1ce1740b6eeb5e7a8a556d24f", hex_conservative::decode_to_vec("829e508a99823b607256ab4297cacc1b7580d49e1e18a2af24aacf157c25f4195be9f7600507e3f5f4a502f08beeb75a048ff280a555705b899733431a7997ac6f98f63c259f83f65fa2548d23b42dbcbd3fcaf17fcdaee183c354f1cb046942").unwrap()), + ("000000000000000e6d139ec023a1fbb12a7a19d7ab5db1c34322445494685b52", hex_conservative::decode_to_vec("8d230edbed207dcb3ff28c72c14a72f1d79f6e8b8345ff6e7b71caa063750193dac0d8047fa89889f517d3579505282115b9078c6ca85cc66a91db407001c9247902456b239a721975f1930cea8e489fae5e2bc714445e86d3d7d58c6b86aa9f").unwrap()), + ("000000000000002910000426717f2e2fe13659de4199ebd2ad0df8acaa40ec55", hex_conservative::decode_to_vec("b82ac105dadd8f22edc80be0d9a3f0565735aec0f5350bd961d01e3b95ad8b6410a15cb97b99fc04e5cbc11e315c2af50eca9ac3829b2321c2c3043eed03f31a8ed91ca1dc25c45c06f74ad6ca399c7e6462bd96c75e4f688ae5fa28e09591db").unwrap()), + ("00000000000000034d13700c17a966c7d4da13134d3928460922dc2122934d5f", hex_conservative::decode_to_vec("aeabf173f885c401dd859d5e743dfa60106ac416e57d2870aa06241ad0133397b88484d7e9f95154a9167537e1dd524f18127854aa270088007b23155c22f6dd07d6696b2fea4599ed72b7be62e0bad519e296da38cd9db0b29dbcde5888be0c").unwrap()), + ("00000000000000059bbc2b37c8d846653c3c7e213ca2507b74b1139fee57346b", hex_conservative::decode_to_vec("b867cacbf145215502344a36a46839255b39c44129da259ad1eb1dab1c33b5ad6cd4e9a34f083590ed7a8153c12ed05c03f170b87dc16cff6a031519dbc60ae83a4713f8fdfeef7b1be66258b053b2865957b61a4d4cea445799a4cfe8ca7590").unwrap()), + ("000000000000003c41c3b18552e0dbddd59ca4e9235ae6799c0f88d5d39b3375", hex_conservative::decode_to_vec("b05f472ea28f41961dbadd4bfc33ad46120a8a5c082b46f88598f263f47033b252f5eb5fb10fd67cb9ca8790c56848550a06661332abc72cd1e1e9bb2e6cc63219f0b05faa981589cddc5dec57118db637c1819f5da023e78db930c4347e3799").unwrap()), + ("0000000000000004beb237cb0c284418129d337ccbacf0ce4bcacaef052aa17b", hex_conservative::decode_to_vec("8305847336eea1f9f502216ba03203b7614a4e6038b315a5342bc100a3bb9fc075df88415c5f9dcb88c35145e7ee44ba178012da65826fb4c6ab7c986dc50daccf383a57d8c8476dd864c24fb8a7c7c040a6dc57c238ee499733b6006b0611b4").unwrap()), + ("0000000000000028c15e263548139cef64e9fcebc6d793bd9448d30797c14f80", hex_conservative::decode_to_vec("a3c7d6d59248387269a928f4b37dad3f6559cae800acecf8e1502c5a7e2862d501013a91f30e7d7f2a63055adefeb7ae16de6020d4b6281c69b80381ee2e8c93b6a148d1934ac10cc71b5dd441bee988b2ee51022c345286ae4b241b149446bd").unwrap()), + ("000000000000000ff31a80c31e6773c572e797cae876b6603b587915d738dc89", hex_conservative::decode_to_vec("b8bbddd9f5214880d65cf7d096cb213b1f5bdd991669487e45812e4efaea8fe0cdc9642c7e9e9d4f8ebc0c1dd607c9eb19bdab1aed6eb4c52789ad7c41e2ce80fd1b8bef393b421089c9ab8b156b7917e3bd39c6b28b8e720212d94c2f7cf857").unwrap()), + ("0000000000000019e3f9a338f32411d2f3e91e623b0bcbf327bca32b9ded4b9d", hex_conservative::decode_to_vec("a12e8c68461a3248cc038ce50fc23ebfeae3718e10ee949e763deee0cde0b9e1a637b19ab18765604ef98be4a49ce6bc0525acb92db0defcfb57d993d0cf63ccc9f4378a11ed5a6707f791ded468a04daf4fa650a0e95689261615360faf80d3").unwrap()), + ("0000000000000025b0f8b6cd855cab58429ee158ddfd32358ab55b98e53feaa2", hex_conservative::decode_to_vec("b0f7402bd4c6c3926431d7c3bcb56ef52caf1d4edc7ab5d01ddd10ac6023aaccc9f336d22eb5c8e2930339875e9159cc0b54de90e5aa28d9bc4db4b3a7e5d6ea1c3c84b6817a5f13557d57b9f841494a831d8e58114710e853454847d1ab53d5").unwrap()), + ("000000000000001912a0ac17300c5b7bfd1385a418137c3bc8d273ac3d9f85d7", hex_conservative::decode_to_vec("ab751a79ea12c823745cccb7600b8aad50b72c0ac0d090e156a84755fe8a8eee9a8e57d076728428fa9d98f571be99d20a93090f1f310a78b66d26668672448b5e564a110640487ec508677faf1f79c14dcee34404e6d8c1c8037151f4ec7e4d").unwrap()), + ("000000000000001d3789f5d1e7318b4350f20bdf1ea4beeeedf26780312114db", hex_conservative::decode_to_vec("ac6edba765e86f2d3c86083c2919bd285e3a635413f2783a7261a0447a135827c73b635277265255cb678df3aa275986198a174c9fa39e499d0a26f45a7a8e3a7559ddebe200c13a96c060a5f7bc689d5fb93f68be9d6113d94acbbab714c52c").unwrap()), + ("0000000000000010b28f1ea61bf3ff88cd2fef7e33a5f1868fb555ec682636eb", hex_conservative::decode_to_vec("b96908533c42ecb7e540cec408f5d2aec93d97df37de695b9e92b50145a001153b3392c7aeab697a168e813c566dace007410e6159db92453732b067c0f22a2df413b113c47e43ace3906572db46b451565531c0a39a6ad9f5fb7e761273d7bb").unwrap()), + ("0000000000000026d8a2480f338951dfedc5e7abdd3704500a10b4a188c89bf8", hex_conservative::decode_to_vec("af13e196c300afce6ced40fa32851d1ff8646e2a1c0f03fc83cce88a44291a400fe8026dcb95edc9f2485b2596731c8e092fd313265269ffb5c5b0d13c4f0cda75ae69db27829c400cd25c2b55ca929ab8ac7a2b29f859e69800796c3d98c5a2").unwrap()) ]); let quorums = masternode_list diff --git a/dash/src/sml/masternode_list/debug_helpers.rs b/dash/src/sml/masternode_list/debug_helpers.rs index 86355125a..237dcf062 100644 --- a/dash/src/sml/masternode_list/debug_helpers.rs +++ b/dash/src/sml/masternode_list/debug_helpers.rs @@ -2,6 +2,7 @@ use hashes::Hash; use crate::hash_types::{MerkleRootMasternodeList, MerkleRootQuorums}; use crate::sml::masternode_list::MasternodeList; +use hex_conservative::DisplayHex; impl std::fmt::Debug for MasternodeList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -29,10 +30,12 @@ impl MasternodeList { format!( "\t\t{}: {}:\n\t\t\tmn: \n\t\t\t\troot: {}\n\t\t\t\tcount: {}\n\t\t\tllmq:\n\t\t\t\troot: {}\n\t\t\t\tdesc:\n{}\n", self.known_height, - hex::encode(self.block_hash), - self.masternode_merkle_root.map_or("None".to_string(), hex::encode), + self.block_hash.as_byte_array().to_lower_hex_string(), + self.masternode_merkle_root + .map_or("None".to_string(), |h| h.as_byte_array().to_lower_hex_string()), self.masternodes.len(), - self.llmq_merkle_root.map_or("None".to_string(), hex::encode), + self.llmq_merkle_root + .map_or("None".to_string(), |h| h.as_byte_array().to_lower_hex_string()), self.quorums_short_description() ) } diff --git a/dash/src/sml/masternode_list_entry/hash.rs b/dash/src/sml/masternode_list_entry/hash.rs index dae58fd15..5e8a60447 100644 --- a/dash/src/sml/masternode_list_entry/hash.rs +++ b/dash/src/sml/masternode_list_entry/hash.rs @@ -16,6 +16,7 @@ mod tests { use crate::consensus::deserialize; use crate::network::message_sml::MnListDiff; + use hex_conservative::DisplayHex; // Ground-truth entry hashes produced by Dash Core's `CSimplifiedMNListEntry::CalcHash` // (`CHashWriter(SER_GETHASH, ...)`) for the matching entries in this fixture. `SER_GETHASH` @@ -44,11 +45,13 @@ mod tests { let entry = diff .new_masternodes .iter() - .find(|e| hex::encode(e.pro_reg_tx_hash.to_byte_array()) == pro_reg_tx_hash_hex) + .find(|e| { + e.pro_reg_tx_hash.to_byte_array().to_lower_hex_string() == pro_reg_tx_hash_hex + }) .expect("expected entry present in fixture"); assert_eq!( - hex::encode(entry.calculate_entry_hash().to_byte_array()), + entry.calculate_entry_hash().to_byte_array().to_lower_hex_string(), expected_entry_hash_hex, "entry hash for {} must match Dash Core's CalcHash", pro_reg_tx_hash_hex diff --git a/dash/tests/headers2_compatibility_test.rs b/dash/tests/headers2_compatibility_test.rs index 8006c9c2e..dd88de3ed 100644 --- a/dash/tests/headers2_compatibility_test.rs +++ b/dash/tests/headers2_compatibility_test.rs @@ -8,6 +8,7 @@ use dashcore::hash_types::{BlockHash, TxMerkleNode}; use dashcore::network::message_headers2::{CompressedHeader, CompressionState, Headers2Message}; use dashcore::pow::CompactTarget; use dashcore_hashes::Hash; +use hex_conservative::DisplayHex; fn create_header(version: i32, prev_hash: [u8; 32], time: u32, bits: u32, nonce: u32) -> Header { Header { @@ -241,7 +242,7 @@ fn test_serialization_roundtrip_single_header() { // Serialize let serialized = serialize(&compressed); println!("Serialized first header: {} bytes", serialized.len()); - println!(" hex: {}", hex::encode(&serialized)); + println!(" hex: {}", serialized.to_lower_hex_string()); println!(" flags byte: 0b{:08b}", serialized[0]); // Deserialize diff --git a/key-wallet-ffi/Cargo.toml b/key-wallet-ffi/Cargo.toml index af66befb5..7da9665c2 100644 --- a/key-wallet-ffi/Cargo.toml +++ b/key-wallet-ffi/Cargo.toml @@ -38,7 +38,7 @@ dash-network = { path = "../dash-network", features = ["ffi"] } secp256k1 = { workspace = true } tokio = { version = "1.32", features = ["rt-multi-thread", "sync"] } libc = "0.2" -hex = { workspace = true } +hex-conservative = { workspace = true } [build-dependencies] cbindgen = "0.29" @@ -47,4 +47,3 @@ cbindgen = "0.29" dashcore = { path = "../dash", features = ["test-utils"] } key-wallet = { path = "../key-wallet", features = ["test-utils"] } key-wallet-manager = { path = "../key-wallet-manager", features = ["test-utils"] } -hex = { workspace = true } diff --git a/key-wallet-ffi/src/account.rs b/key-wallet-ffi/src/account.rs index 76b00a1f3..0c5f27a65 100644 --- a/key-wallet-ffi/src/account.rs +++ b/key-wallet-ffi/src/account.rs @@ -4,6 +4,7 @@ use crate::deref_ptr; use crate::error::{FFIError, FFIErrorCode}; use crate::types::{FFIAccountKind, FFIAccountResult, FFIWallet}; use dash_network::ffi::FFINetwork; +use hex_conservative::DisplayHex; #[cfg(feature = "bls")] use key_wallet::account::BLSAccount; #[cfg(feature = "eddsa")] @@ -330,7 +331,7 @@ pub unsafe extern "C" fn bls_account_get_extended_public_key_as_string( // For BLS accounts, we need to encode the extended public key bytes // There's no standard string representation for BLS extended keys let bytes = account.inner().bls_public_key.to_bytes(); - let hex_string = hex::encode(bytes); + let hex_string = bytes.to_lower_hex_string(); match std::ffi::CString::new(hex_string) { Ok(c_str) => c_str.into_raw(), @@ -447,7 +448,7 @@ pub unsafe extern "C" fn eddsa_account_get_extended_public_key_as_string( // For EdDSA accounts, we need to encode the extended public key // There's no standard string representation for Ed25519 extended keys let bytes = account.inner().ed25519_public_key.encode(); - let hex_string = hex::encode(bytes); + let hex_string = bytes.to_lower_hex_string(); match std::ffi::CString::new(hex_string) { Ok(c_str) => c_str.into_raw(), diff --git a/key-wallet-ffi/src/account_derivation.rs b/key-wallet-ffi/src/account_derivation.rs index 49c87c2ea..d1c18705d 100644 --- a/key-wallet-ffi/src/account_derivation.rs +++ b/key-wallet-ffi/src/account_derivation.rs @@ -8,6 +8,7 @@ use crate::account::FFIEdDSAAccount; use crate::error::{FFIError, FFIErrorCode}; use crate::keys::{FFIExtendedPrivKey, FFIPrivateKey}; use crate::{check_ptr, deref_ptr, unwrap_or_return}; +use hex_conservative::DisplayHex; use key_wallet::account::derivation::AccountDerivation; use key_wallet::account::AccountTrait; use std::ffi::CString; @@ -88,7 +89,7 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_seed( account.inner().derive_from_seed_private_key_at(seed_slice, index), error ); - unwrap_or_return!(CString::new(hex::encode(*sk.to_bytes())), error).into_raw() + unwrap_or_return!(CString::new(sk.to_bytes().to_lower_hex_string()), error).into_raw() } /// Derive a BLS private key from a mnemonic + optional passphrase at the given index. @@ -127,7 +128,7 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_mnemonic( account.inner().derive_from_mnemonic_private_key_at(mnemonic_str, passphrase_str, index,), error ); - unwrap_or_return!(CString::new(hex::encode(*sk.to_bytes())), error).into_raw() + unwrap_or_return!(CString::new(sk.to_bytes().to_lower_hex_string()), error).into_raw() } // ========================= EdDSA (feature = "eddsa") ========================= @@ -161,7 +162,7 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_seed( account.inner().derive_from_seed_private_key_at(seed_slice, index), error ); - unwrap_or_return!(CString::new(hex::encode(sk.to_bytes())), error).into_raw() + unwrap_or_return!(CString::new(sk.to_bytes().to_lower_hex_string()), error).into_raw() } /// Derive an EdDSA (ed25519) private key from a mnemonic + optional passphrase at the given index. @@ -199,7 +200,7 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_mnemonic( account.inner().derive_from_mnemonic_private_key_at(mnemonic_str, passphrase_str, index,), error ); - unwrap_or_return!(CString::new(hex::encode(sk.to_bytes())), error).into_raw() + unwrap_or_return!(CString::new(sk.to_bytes().to_lower_hex_string()), error).into_raw() } /// Derive a private key (secp256k1) from an account at a given chain/index, using the provided master xpriv. diff --git a/key-wallet-ffi/src/keys.rs b/key-wallet-ffi/src/keys.rs index 9a39e76f7..e2b059114 100644 --- a/key-wallet-ffi/src/keys.rs +++ b/key-wallet-ffi/src/keys.rs @@ -4,6 +4,7 @@ use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIWallet; use crate::{check_ptr, deref_ptr, unwrap_or_return}; use dash_network::ffi::FFINetwork; +use hex_conservative::DisplayHex; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_uint}; use std::ptr; @@ -453,7 +454,7 @@ pub unsafe extern "C" fn public_key_to_hex( error: *mut FFIError, ) -> *mut c_char { let key = deref_ptr!(key, error); - unwrap_or_return!(CString::new(hex::encode(key.inner.serialize())), error).into_raw() + unwrap_or_return!(CString::new(key.inner.serialize().to_lower_hex_string()), error).into_raw() } /// Convert derivation path string to indices diff --git a/key-wallet-ffi/src/mnemonic_tests.rs b/key-wallet-ffi/src/mnemonic_tests.rs index befa1beb9..f3a4e6c88 100644 --- a/key-wallet-ffi/src/mnemonic_tests.rs +++ b/key-wallet-ffi/src/mnemonic_tests.rs @@ -7,6 +7,7 @@ mod tests { use crate::mnemonic; use std::ffi::CString; + use hex_conservative::DisplayHex; use std::ptr; const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; @@ -774,6 +775,6 @@ mod tests { }; assert!(ok, "French phrase must derive a seed"); assert_eq!(seed_len, 64); - assert_eq!(hex::encode(seed), FRENCH_REFERENCE_SEED_HEX); + assert_eq!(seed.to_lower_hex_string(), FRENCH_REFERENCE_SEED_HEX); } } diff --git a/key-wallet-manager/Cargo.toml b/key-wallet-manager/Cargo.toml index 8b0b73853..9bce3a997 100644 --- a/key-wallet-manager/Cargo.toml +++ b/key-wallet-manager/Cargo.toml @@ -45,7 +45,7 @@ serde = { workspace = true, features = ["derive"], optional = true } key-wallet = { path = "../key-wallet", features = ["test-utils", "bincode"] } dashcore = { path = "../dash", features = ["test-utils"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -hex = { workspace = true } +hex-conservative = { workspace = true } criterion = "0.8.1" [[bench]] diff --git a/key-wallet-manager/examples/wallet_creation.rs b/key-wallet-manager/examples/wallet_creation.rs index 9ba2dad98..86a7b25b5 100644 --- a/key-wallet-manager/examples/wallet_creation.rs +++ b/key-wallet-manager/examples/wallet_creation.rs @@ -5,6 +5,7 @@ //! - Creating wallets from mnemonics //! - Managing wallet accounts and addresses +use hex_conservative::DisplayHex; use key_wallet::account::StandardAccountType; use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::wallet::managed_wallet_info::transaction_building::AccountTypePreference; @@ -26,7 +27,7 @@ fn main() { let wallet_id = match result { Ok(wallet_id) => { println!("✅ Wallet created successfully!"); - println!(" Wallet ID: {}", hex::encode(wallet_id)); + println!(" Wallet ID: {}", wallet_id.to_lower_hex_string()); println!(" Total wallets: {}", manager.wallet_count()); wallet_id } @@ -50,7 +51,7 @@ fn main() { let wallet_id2 = match result { Ok(wallet_id2) => { println!("✅ Wallet created from mnemonic!"); - println!(" Wallet ID: {}", hex::encode(wallet_id2)); + println!(" Wallet ID: {}", wallet_id2.to_lower_hex_string()); wallet_id2 } Err(e) => { diff --git a/key-wallet-manager/tests/test_serialized_wallets.rs b/key-wallet-manager/tests/test_serialized_wallets.rs index c24cda524..17e6e7d58 100644 --- a/key-wallet-manager/tests/test_serialized_wallets.rs +++ b/key-wallet-manager/tests/test_serialized_wallets.rs @@ -1,6 +1,7 @@ #[cfg(feature = "bincode")] #[cfg(test)] mod tests { + use hex_conservative::DisplayHex; use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -24,7 +25,7 @@ mod tests { assert!(result.is_ok()); let (bytes, wallet_id) = result.unwrap(); assert!(!bytes.is_empty()); - println!("Full wallet ID: {}", hex::encode(wallet_id)); + println!("Full wallet ID: {}", wallet_id.to_lower_hex_string()); // The wallet's sync checkpoint should be seeded to birth_height - 1. let info = manager.get_wallet_info(&wallet_id).unwrap(); @@ -47,7 +48,7 @@ mod tests { // Same wallet ID because it's derived from the same root public key assert_eq!(wallet_id, wallet_id2); - println!("Watch-only wallet ID: {}", hex::encode(wallet_id2)); + println!("Watch-only wallet ID: {}", wallet_id2.to_lower_hex_string()); // Test 3: Create externally signable wallet (for hardware wallets) let mut manager3 = WalletManager::::new(Network::Testnet); @@ -62,7 +63,7 @@ mod tests { let (bytes3, wallet_id3) = result.unwrap(); assert!(!bytes3.is_empty()); assert_eq!(wallet_id, wallet_id3); - println!("Externally signable wallet ID: {}", hex::encode(wallet_id3)); + println!("Externally signable wallet ID: {}", wallet_id3.to_lower_hex_string()); // Test 4: Import the serialized wallet back with a specific birth height let mut manager4 = WalletManager::::new(Network::Testnet); diff --git a/key-wallet/Cargo.toml b/key-wallet/Cargo.toml index c8e75ff4c..aecf3e874 100644 --- a/key-wallet/Cargo.toml +++ b/key-wallet/Cargo.toml @@ -45,7 +45,7 @@ getrandom = { version = "0.4", optional = true } bincode = { workspace = true, optional = true } bincode_derive = { workspace = true, optional = true } serde_json = { version = "1.0", optional = true } -hex = { workspace = true } +hex-conservative = { workspace = true } zeroize = { workspace = true } tracing = "0.1" async-trait = "0.1" @@ -53,7 +53,6 @@ async-trait = "0.1" [dev-dependencies] criterion = "0.8.1" dashcore = { path="../dash", features = ["test-utils"] } -hex = { workspace = true } key-wallet = { path = ".", features = ["test-utils", "serde", "bincode", "eddsa", "bls"] } rand = { version = "0.9", features = ["std", "std_rng", "thread_rng"] } test-case = "3.3" diff --git a/key-wallet/README.md b/key-wallet/README.md index 11d102a4c..8e21cc133 100644 --- a/key-wallet/README.md +++ b/key-wallet/README.md @@ -76,6 +76,7 @@ The library is organized into several key modules: ```rust use key_wallet::{Wallet, Mnemonic, Network}; use key_wallet::mnemonic::Language; +use dashcore::hashes::hex::DisplayHex; // Generate a new mnemonic let mnemonic = Mnemonic::generate(24, Language::English)?; @@ -85,7 +86,7 @@ println!("Mnemonic: {}", mnemonic.phrase()); let wallet = Wallet::from_mnemonic(mnemonic.clone(), None, Network::Mainnet)?; // Get wallet ID (unique identifier) -println!("Wallet ID: {:?}", hex::encode(wallet.wallet_id)); +println!("Wallet ID: {}", wallet.wallet_id.to_lower_hex_string()); ``` ### Managing Accounts @@ -162,6 +163,7 @@ let pool_1_address = coinjoin_account.derive_address_at_pool(1, 0)?; ```rust #[cfg(feature = "eddsa")] { + use dashcore::hashes::hex::DisplayHex; use key_wallet::account::EdDSAAccount; // Create identity registration funding account @@ -172,7 +174,7 @@ let pool_1_address = coinjoin_account.derive_address_at_pool(1, 0)?; // Get Ed25519 public key for Platform let pubkey = identity_account.get_public_key_bytes(); - println!("Identity public key: {}", hex::encode(pubkey)); + println!("Identity public key: {}", pubkey.to_lower_hex_string()); } ``` diff --git a/key-wallet/examples/basic_usage.rs b/key-wallet/examples/basic_usage.rs index 98eb224ba..d4086daf4 100644 --- a/key-wallet/examples/basic_usage.rs +++ b/key-wallet/examples/basic_usage.rs @@ -18,7 +18,7 @@ fn main() -> core::result::Result<(), Box> { // 2. Generate seed println!("\n2. Generating seed..."); let seed = mnemonic.to_seed(""); - println!(" Seed: {}", hex::encode(&seed[..32])); // Show first 32 bytes + println!(" Seed: {}", seed[..32].to_lower_hex_string()); // Show first 32 bytes // 3. Create master key println!("\n3. Creating master key..."); diff --git a/key-wallet/src/account/eddsa_account.rs b/key-wallet/src/account/eddsa_account.rs index eef13c5c1..07e92ccf1 100644 --- a/key-wallet/src/account/eddsa_account.rs +++ b/key-wallet/src/account/eddsa_account.rs @@ -430,6 +430,7 @@ mod tests { use super::*; use crate::account::account_type::StandardAccountType; use crate::managed_account::address_pool::AddressPoolType; + use hex_conservative::DisplayHex; #[test] fn test_eddsa_account_creation() { @@ -529,7 +530,7 @@ mod tests { fn test_derive_address_at_uses_tenderdash_node_id() { use dashcore::hashes::Hash; - let seed = hex::decode( + let seed = hex_conservative::decode_to_vec( "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc1\ 9a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4", ) @@ -557,7 +558,10 @@ mod tests { let dashcore::address::Payload::PubkeyHash(hash) = address.payload() else { panic!("platform pseudo-addresses use P2PKH-style payloads"); }; - assert_eq!(hex::encode(hash.to_byte_array()), "302f2615e6955cce8ed3cff81e8011bfd3a2991f"); + assert_eq!( + hash.to_byte_array().to_lower_hex_string(), + "302f2615e6955cce8ed3cff81e8011bfd3a2991f" + ); } #[test] diff --git a/key-wallet/src/bip32.rs b/key-wallet/src/bip32.rs index 1735a5437..18aa9d695 100644 --- a/key-wallet/src/bip32.rs +++ b/key-wallet/src/bip32.rs @@ -1952,6 +1952,7 @@ mod tests { use super::ChildNumber::{Hardened, Normal}; use super::*; use dashcore::Network::{self, Mainnet}; + use hex_conservative::DisplayHex; #[test] fn test_parse_derivation_path() { @@ -2562,12 +2563,11 @@ mod tests { let ascending: [u8; 32] = core::array::from_fn(|i| i as u8); let descending: [u8; 32] = core::array::from_fn(|i| 0xff - i as u8); let public_key_hex = |path: DerivationPath, network| { - hex::encode( - path.derive_pub_ecdsa_for_master_seed(&seed, network) - .unwrap() - .public_key - .serialize(), - ) + path.derive_pub_ecdsa_for_master_seed(&seed, network) + .unwrap() + .public_key + .serialize() + .to_lower_hex_string() }; let session = |identity_id, request_id| { @@ -2616,8 +2616,8 @@ mod tests { // The builder's path is the documented string, byte order included. let parsed: DerivationPath = format!( "m/9'/1'/5'/6'/0'/0x{}'/0x{}'", - hex::encode(ascending), - hex::encode(descending) + ascending.to_lower_hex_string(), + descending.to_lower_hex_string() ) .parse() .unwrap(); @@ -2636,7 +2636,7 @@ mod tests { let path = DerivationPath::bip_44_account(Network::Mainnet, 0); let sk = path .derive_priv_ecdsa_for_master_seed( - hex::decode(HEX_SEED).unwrap().as_ref(), + hex_conservative::decode_to_vec(HEX_SEED).unwrap().as_ref(), Network::Mainnet, ) .unwrap(); @@ -2652,7 +2652,7 @@ mod tests { let path = DerivationPath::bip_44_account(Network::Mainnet, 0); let pk = path .derive_pub_ecdsa_for_master_seed( - hex::decode(HEX_SEED).unwrap().as_ref(), + hex_conservative::decode_to_vec(HEX_SEED).unwrap().as_ref(), Network::Mainnet, ) .unwrap(); @@ -2668,7 +2668,7 @@ mod tests { let path = DerivationPath::bip_44_payment_path(Network::Mainnet, 0, true, 3); let sk = path .derive_priv_ecdsa_for_master_seed( - hex::decode(HEX_SEED).unwrap().as_ref(), + hex_conservative::decode_to_vec(HEX_SEED).unwrap().as_ref(), Network::Mainnet, ) .unwrap(); @@ -2681,7 +2681,7 @@ mod tests { let path = DerivationPath::bip_44_payment_path(Network::Mainnet, 0, false, 3); let sk = path .derive_priv_ecdsa_for_master_seed( - hex::decode(HEX_SEED).unwrap().as_ref(), + hex_conservative::decode_to_vec(HEX_SEED).unwrap().as_ref(), Network::Mainnet, ) .unwrap(); @@ -2694,7 +2694,7 @@ mod tests { let path = DerivationPath::bip_44_payment_path(Network::Mainnet, 0, true, 3); let sk = path .derive_pub_ecdsa_for_master_seed( - hex::decode(HEX_SEED).unwrap().as_ref(), + hex_conservative::decode_to_vec(HEX_SEED).unwrap().as_ref(), Network::Mainnet, ) .unwrap(); @@ -2709,7 +2709,7 @@ mod tests { let path = DerivationPath::bip_44_payment_path(Network::Mainnet, 0, false, 3); let sk = path .derive_pub_ecdsa_for_master_seed( - hex::decode(HEX_SEED).unwrap().as_ref(), + hex_conservative::decode_to_vec(HEX_SEED).unwrap().as_ref(), Network::Mainnet, ) .unwrap(); diff --git a/key-wallet/src/derivation.rs b/key-wallet/src/derivation.rs index 7a87b0209..b3c68a025 100644 --- a/key-wallet/src/derivation.rs +++ b/key-wallet/src/derivation.rs @@ -279,10 +279,8 @@ mod tests { // ✓ Test BIP32 derivation with exact DashSync test vectors #[test] fn test_bip32_derivation_vectors() { - use hex::FromHex; - // Test vector from DashSync DSBIP32Tests.m - seed "000102030405060708090a0b0c0d0e0f" - let seed = Vec::from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + let seed = hex_conservative::decode_to_vec("000102030405060708090a0b0c0d0e0f").unwrap(); // Create master key let master_key = ExtendedPrivKey::new_master(crate::Network::Mainnet, &seed).unwrap(); @@ -305,9 +303,10 @@ mod tests { // The DashSync test expects this private key at m/0'/1/2': // DashSync includes a network prefix byte (0xCC for mainnet) before the key // "cccbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca" - let expected_with_prefix = - Vec::from_hex("cccbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca") - .unwrap(); + let expected_with_prefix = hex_conservative::decode_to_vec( + "cccbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca", + ) + .unwrap(); // Skip the first byte (network prefix) and compare the actual 32-byte key assert_eq!(&derived_key.private_key.to_secret_bytes(), &expected_with_prefix[1..]); @@ -327,18 +326,17 @@ mod tests { let derived_key_zero = master_key.derive_priv(&path_zero_padding).unwrap(); // DashSync expects: "00136c1ad038f9a00871895322a487ed14f1cdc4d22ad351cfa1a0d235975dd7" - let expected_zero_padded = - Vec::from_hex("00136c1ad038f9a00871895322a487ed14f1cdc4d22ad351cfa1a0d235975dd7") - .unwrap(); + let expected_zero_padded = hex_conservative::decode_to_vec( + "00136c1ad038f9a00871895322a487ed14f1cdc4d22ad351cfa1a0d235975dd7", + ) + .unwrap(); assert_eq!(&derived_key_zero.private_key.to_secret_bytes(), &expected_zero_padded[..]); } // ✓ Test extended key serialization (from DashSync DSBIP32Tests.m) #[test] fn test_extended_key_serialization() { - use hex::FromHex; - - let seed = Vec::from_hex("000102030405060708090a0b0c0d0e0f").unwrap(); + let seed = hex_conservative::decode_to_vec("000102030405060708090a0b0c0d0e0f").unwrap(); // Test master key serialization (m) let master_key = ExtendedPrivKey::new_master(crate::Network::Mainnet, &seed).unwrap(); diff --git a/key-wallet/src/derivation_bls_bip32.rs b/key-wallet/src/derivation_bls_bip32.rs index ef04afbff..70fe81c8d 100644 --- a/key-wallet/src/derivation_bls_bip32.rs +++ b/key-wallet/src/derivation_bls_bip32.rs @@ -51,6 +51,7 @@ use dashcore::Network; use serde::Deserialize; use crate::bip32::{ChainCode, ChildNumber, DerivationPath, Fingerprint}; +use hex_conservative::DisplayHex; /// The HMAC key used for generating the master key. const MASTER_HMAC_KEY: &[u8] = b"BLS HD seed"; @@ -167,8 +168,8 @@ impl ExtendedBLSPrivKey { // #[cfg(test)] // { // eprintln!("Seed length: {}", seed.len()); - // eprintln!("Seed||0 (hex): {}", hex::encode(&seed_with_suffix)); - // eprintln!("HMAC output (hex): {}", hex::encode(private_key_bytes)); + // eprintln!("Seed||0 (hex): {}", seed_with_suffix.to_lower_hex_string()); + // eprintln!("HMAC output (hex): {}", private_key_bytes.to_lower_hex_string()); // } // The C++ implementation reduces modulo the curve order @@ -179,7 +180,7 @@ impl ExtendedBLSPrivKey { // #[cfg(test)] // { - // eprintln!("After from_be_bytes (hex): {}", hex::encode(*private_key.to_bytes())); + // eprintln!("After from_be_bytes (hex): {}", (*private_key.to_bytes()).to_lower_hex_string()); // } // Second HMAC with seed||1 for the chain code @@ -553,7 +554,7 @@ impl fmt::Debug for ExtendedBLSPubKey { .field("parent_fingerprint", &self.parent_fingerprint) .field("child_number", &self.child_number) .field("chain_code", &self.chain_code) - .field("public_key", &hex::encode(self.public_key.to_bytes())) + .field("public_key", &self.public_key.to_bytes().to_lower_hex_string()) .finish() } } @@ -805,7 +806,7 @@ mod tests { const SEED64: &str = "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4"; fn master_from_seed64() -> ExtendedBLSPrivKey { - let seed = hex::decode(SEED64).unwrap(); + let seed = hex_conservative::decode_to_vec(SEED64).unwrap(); ExtendedBLSPrivKey::new_master(Network::Mainnet, &seed).unwrap() } @@ -1422,19 +1423,19 @@ mod tests { fn master_from_seed() { let master = master_from_seed64(); assert_eq!( - hex::encode(master.private_key.to_bytes()), + master.private_key.to_bytes().to_lower_hex_string(), "27d1e600fe5ce42e9a18fe064aa0c1b8ee6754289013a86eb1e8af985ddc55c5" ); assert_eq!( - hex::encode(&master.chain_code[..]), + master.chain_code[..].to_lower_hex_string(), "2a680de50ab918089c65f47e6f32363eb8fbb915a61e9a10e0f882aa1c12aef9" ); assert_eq!( - hex::encode(master.public_key_bytes_legacy().unwrap()), + master.public_key_bytes_legacy().unwrap().to_lower_hex_string(), "883389cd6c289b97bfa18cc7b7c873397b4d753269d47d2fa29dda1682c1565687ccb19dd016398da7c9724f8a58bdef" ); assert_eq!( - hex::encode(master.public_key_bytes().unwrap()), + master.public_key_bytes().unwrap().to_lower_hex_string(), "a83389cd6c289b97bfa18cc7b7c873397b4d753269d47d2fa29dda1682c1565687ccb19dd016398da7c9724f8a58bdef" ); } @@ -1454,11 +1455,11 @@ mod tests { .unwrap(); assert_eq!( - hex::encode(account.private_key.to_bytes()), + account.private_key.to_bytes().to_lower_hex_string(), "5f36c0e346c6e6275d6550a09857325e3f54f2a962eb09a48f61756f7b4bbfb0" ); assert_eq!( - hex::encode(&account.chain_code[..]), + account.chain_code[..].to_lower_hex_string(), "d9659c1bde2fd0e0f799f2f66bbbcfc7378fdea624d73d5d4749dc7222daea5e" ); @@ -1485,15 +1486,15 @@ mod tests { let child = account .derive_priv_legacy(ChildNumber::from_normal_idx(i as u32).unwrap()) .unwrap(); - assert_eq!(hex::encode(child.private_key.to_bytes()), *sk, "sk {}", i); + assert_eq!(child.private_key.to_bytes().to_lower_hex_string(), *sk, "sk {}", i); assert_eq!( - hex::encode(child.public_key_bytes_legacy().unwrap()), + child.public_key_bytes_legacy().unwrap().to_lower_hex_string(), *pk_legacy, "pk_legacy {}", i ); assert_eq!( - hex::encode(child.public_key_bytes().unwrap()), + child.public_key_bytes().unwrap().to_lower_hex_string(), *pk_modern, "pk_modern {}", i @@ -1504,7 +1505,7 @@ mod tests { let account_pub = account.to_extended_pub_key().unwrap(); let child0_pub = account_pub.derive_pub_legacy(ChildNumber::from_normal_idx(0).unwrap()).unwrap(); - assert_eq!(hex::encode(child0_pub.to_bytes_legacy().unwrap()), expected[0].1); + assert_eq!(child0_pub.to_bytes_legacy().unwrap().to_lower_hex_string(), expected[0].1); } #[test] @@ -1521,17 +1522,17 @@ mod tests { .derive_priv(hardened(3)) .unwrap(); assert_eq!( - hex::encode(account.private_key.to_bytes()), + account.private_key.to_bytes().to_lower_hex_string(), "05e18aebbe5c73f4dde3dd6a4a204da46c6efa38a38ff4fa5548b1c171154bda" ); let child0 = account.derive_priv_legacy(ChildNumber::from_normal_idx(0).unwrap()).unwrap(); assert_eq!( - hex::encode(child0.private_key.to_bytes()), + child0.private_key.to_bytes().to_lower_hex_string(), "3346dfd71627f9f31cad3ee66fe7b673c32cb077b2eb38c621d7e61c30e46dbd" ); assert_eq!( - hex::encode(child0.public_key_bytes_legacy().unwrap()), + child0.public_key_bytes_legacy().unwrap().to_lower_hex_string(), "09d8beabae708de1638487f1aff44b38e8c07d9b09f22d76329d6c8ec01e2ad4d030b660bca40ddbd222373a72c5bcef" ); } @@ -1542,33 +1543,33 @@ mod tests { let seed = [1u8, 50, 6, 244, 24, 199, 1, 25]; let master = ExtendedBLSPrivKey::new_master(Network::Testnet, &seed).unwrap(); assert_eq!( - hex::encode(master.private_key.to_bytes()), + master.private_key.to_bytes().to_lower_hex_string(), "3e9f7b3846c1803703f94c764b51f5ace513b2f02c4d6b2c452d8ce66e5975bd" ); assert_eq!( - hex::encode(&master.chain_code[..]), + master.chain_code[..].to_lower_hex_string(), "d8b12555b4cc5578951e4a7c80031e22019cc0dce168b3ed88115311b8feb1e3" ); // Hardened child 77' let c77h = master.derive_priv(hardened(77)).unwrap(); assert_eq!( - hex::encode(c77h.private_key.to_bytes()), + c77h.private_key.to_bytes().to_lower_hex_string(), "51b31efbd83aeead1e324c5c8248f5a13bb17ba7afe29aeb5ceef7eaff49ed6f" ); assert_eq!( - hex::encode(&c77h.chain_code[..]), + c77h.chain_code[..].to_lower_hex_string(), "f2c8e4269bb3e54f8179a5c6976d92ca14c3260dd729981e9d15f53049fd698b" ); // Non-hardened child 77 (legacy serialization in HMAC input) let c77 = master.derive_priv_legacy(ChildNumber::from_normal_idx(77).unwrap()).unwrap(); assert_eq!( - hex::encode(c77.private_key.to_bytes()), + c77.private_key.to_bytes().to_lower_hex_string(), "3ef4f8b4d262fb8981665532b531c7889798044f7cbe4d5fae5e30435f746044" ); assert_eq!( - hex::encode(&c77.chain_code[..]), + c77.chain_code[..].to_lower_hex_string(), "f428f5f011f52569c0b2004aaeda0744259f4247fd5e77649d3d25e6b491cc53" ); } @@ -1581,15 +1582,15 @@ mod tests { let master = ExtendedBLSPrivKey::new_master(Network::Testnet, &seed).unwrap(); let c77 = master.derive_priv(ChildNumber::from_normal_idx(77).unwrap()).unwrap(); assert_eq!( - hex::encode(c77.private_key.to_bytes()), + c77.private_key.to_bytes().to_lower_hex_string(), "0f9b101b475e449c9995032e138b432a330738b6401f675f0632385fe8d349bf" ); assert_eq!( - hex::encode(&c77.chain_code[..]), + c77.chain_code[..].to_lower_hex_string(), "c7b09e00d6b9b1676e8714e1060e0324787734809ae557a4bc8c07e9b1304ed0" ); assert_eq!( - hex::encode(c77.public_key_bytes().unwrap()), + c77.public_key_bytes().unwrap().to_lower_hex_string(), "a63fa533db03b400030a5eb163433ac7c8700d2301c4242e03db58d516dea0d52768d1b0d29e9f28f7707ce96d2d6108" ); @@ -1608,11 +1609,11 @@ mod tests { let child0_modern = account.derive_priv(ChildNumber::from_normal_idx(0).unwrap()).unwrap(); assert_eq!( - hex::encode(child0_modern.private_key.to_bytes()), + child0_modern.private_key.to_bytes().to_lower_hex_string(), "1669d6cc8ac08fa377d63dafcf83f1fa6aee09e2df58c490b1b1a0b0999417ec" ); assert_eq!( - hex::encode(child0_modern.public_key_bytes().unwrap()), + child0_modern.public_key_bytes().unwrap().to_lower_hex_string(), "8f5d504fee1026394728781f004fee70480335c1f53156124b23e45386c7c1e2973efee3eab4ae60650fdaa8ae4460d0" ); @@ -1643,7 +1644,7 @@ mod tests { "9bbf8d5427fa6176cd52d60e544299be4224f82b9012046db1e8af975ddc55c6"; fn parse_bytes_32(hex_str: &str) -> [u8; 32] { - hex::decode(hex_str).unwrap().try_into().unwrap() + hex_conservative::decode_to_vec(hex_str).unwrap().try_into().unwrap() } /// Constructs a secret key from the supplied scalar and extracts it to find the settled value. diff --git a/key-wallet/src/derivation_slip10.rs b/key-wallet/src/derivation_slip10.rs index 0e725ea4d..d67cc9926 100644 --- a/key-wallet/src/derivation_slip10.rs +++ b/key-wallet/src/derivation_slip10.rs @@ -643,6 +643,7 @@ impl<'de, C> bincode::BorrowDecode<'de, C> for ExtendedEd25519PubKey { #[cfg(test)] mod test { use super::*; + use hex_conservative::DisplayHex; const CASE_1_SEED: &str = "000102030405060708090a0b0c0d0e0f"; @@ -780,18 +781,18 @@ mod test { } fn derive_ed25519_private_key_hex(seed_hex: &str, indexes: &[u32]) -> String { - let seed = hex::decode(seed_hex).unwrap(); + let seed = hex_conservative::decode_to_vec(seed_hex).unwrap(); let private_key = derive_ed25519_private_key(&seed, indexes); - hex::encode(private_key) + private_key.to_lower_hex_string() } #[test] fn test_zeroize_clears_key_material() { use zeroize::Zeroize; - let seed = hex::decode(CASE_1_SEED).unwrap(); + let seed = hex_conservative::decode_to_vec(CASE_1_SEED).unwrap(); let mut key = ExtendedEd25519PrivKey::new_master(Network::Mainnet, &seed).unwrap(); assert_ne!(*key.private_key.to_bytes(), [0u8; 32]); assert_ne!(key.chain_code.as_ref(), &[0u8; 32]); @@ -806,7 +807,7 @@ mod test { #[test] fn test_partial_eq_matches_field_equality() { - let seed = hex::decode(CASE_1_SEED).unwrap(); + let seed = hex_conservative::decode_to_vec(CASE_1_SEED).unwrap(); let master = ExtendedEd25519PrivKey::new_master(Network::Mainnet, &seed).unwrap(); assert_eq!(master, master.clone()); @@ -826,7 +827,7 @@ mod test { fn test_256_bit_children_are_refused() { let master = ExtendedEd25519PrivKey::new_master( Network::Mainnet, - &hex::decode(CASE_1_SEED).unwrap(), + &hex_conservative::decode_to_vec(CASE_1_SEED).unwrap(), ) .unwrap(); let child = ChildNumber::from_hardened_idx_256([0x35; 32]); diff --git a/key-wallet/src/managed_account/platform_address.rs b/key-wallet/src/managed_account/platform_address.rs index 31c15dc1c..38c476cc2 100644 --- a/key-wallet/src/managed_account/platform_address.rs +++ b/key-wallet/src/managed_account/platform_address.rs @@ -10,6 +10,7 @@ use dashcore::address::Payload; use dashcore::hashes::hash160::Hash as Hash160; use dashcore::hashes::Hash as HashTrait; use dashcore::Address; +use hex_conservative::DisplayHex; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -95,14 +96,14 @@ impl PlatformP2PKHAddress { impl fmt::Debug for PlatformP2PKHAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "PlatformP2PKHAddress({})", hex::encode(self.0)) + write!(f, "PlatformP2PKHAddress({})", self.0.to_lower_hex_string()) } } impl fmt::Display for PlatformP2PKHAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Display as hex by default - write!(f, "{}", hex::encode(self.0)) + write!(f, "{}", self.0.to_lower_hex_string()) } } diff --git a/key-wallet/src/mnemonic.rs b/key-wallet/src/mnemonic.rs index f78e94ed2..a7f104c1a 100644 --- a/key-wallet/src/mnemonic.rs +++ b/key-wallet/src/mnemonic.rs @@ -439,7 +439,7 @@ impl fmt::Display for Mnemonic { #[cfg(test)] mod tests { use super::*; - use hex::FromHex; + use hex_conservative::DisplayHex; #[test] #[cfg(feature = "getrandom")] @@ -532,17 +532,17 @@ mod tests { #[test] fn test_bip39_test_vectors() { // Test vector 1: all zeros entropy - let entropy = Vec::from_hex("00000000000000000000000000000000").unwrap(); + let entropy = hex_conservative::decode_to_vec("00000000000000000000000000000000").unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; assert_eq!(mnemonic.phrase(), expected_phrase); let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); // Test vector 2: 0x7f7f... entropy - let entropy = Vec::from_hex("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f").unwrap(); + let entropy = hex_conservative::decode_to_vec("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f").unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "legal winner thank year wave sausage worth useful legal winner thank yellow"; @@ -550,10 +550,10 @@ mod tests { let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); // Test vector 3: 0x8080... entropy - let entropy = Vec::from_hex("80808080808080808080808080808080").unwrap(); + let entropy = hex_conservative::decode_to_vec("80808080808080808080808080808080").unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "letter advice cage absurd amount doctor acoustic avoid letter advice cage above"; @@ -561,24 +561,26 @@ mod tests { let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); // Test vector 4: all ones entropy - let entropy = Vec::from_hex("ffffffffffffffffffffffffffffffff").unwrap(); + let entropy = hex_conservative::decode_to_vec("ffffffffffffffffffffffffffffffff").unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"; assert_eq!(mnemonic.phrase(), expected_phrase); let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); } // ✓ Test 18-word mnemonics (from DashSync) #[test] fn test_18_word_mnemonics() { // Test 18-word mnemonic - let entropy = Vec::from_hex("000000000000000000000000000000000000000000000000").unwrap(); + let entropy = + hex_conservative::decode_to_vec("000000000000000000000000000000000000000000000000") + .unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent"; assert_eq!(mnemonic.phrase(), expected_phrase); @@ -586,16 +588,17 @@ mod tests { let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); } // ✓ Test 24-word mnemonics (from DashSync) #[test] fn test_24_word_mnemonics() { // Test 24-word mnemonic - let entropy = - Vec::from_hex("0000000000000000000000000000000000000000000000000000000000000000") - .unwrap(); + let entropy = hex_conservative::decode_to_vec( + "0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art"; assert_eq!(mnemonic.phrase(), expected_phrase); @@ -603,14 +606,14 @@ mod tests { let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); } // ✓ Test random entropy examples (from DashSync) #[test] fn test_random_entropy_examples() { // Test random entropy 1 - let entropy = Vec::from_hex("77c2b00716cec7213839159e404db50d").unwrap(); + let entropy = hex_conservative::decode_to_vec("77c2b00716cec7213839159e404db50d").unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge"; @@ -618,17 +621,19 @@ mod tests { let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f672a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); // Test random entropy 2 - let entropy = Vec::from_hex("b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b").unwrap(); + let entropy = + hex_conservative::decode_to_vec("b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b") + .unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy, Language::English).unwrap(); let expected_phrase = "renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap"; assert_eq!(mnemonic.phrase(), expected_phrase); let seed = mnemonic.to_seed("TREZOR"); let expected_seed = "9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); } // ✓ Test Unicode normalization (from DashSync - Czech test case) @@ -661,7 +666,7 @@ mod tests { // English (test_mnemonic_validation) and Portuguese // (test_portuguese_mnemonic); the other languages were only built via // from_entropy in test_multiple_languages, never validate()'d. - let entropy = Vec::from_hex("00000000000000000000000000000000").unwrap(); + let entropy = hex_conservative::decode_to_vec("00000000000000000000000000000000").unwrap(); for language in [ Language::French, Language::Spanish, @@ -690,7 +695,7 @@ mod tests { // Test that we can create mnemonics in different languages // (We can't easily test actual phrases without the word lists, but we can test the API) - let entropy = Vec::from_hex("00000000000000000000000000000000").unwrap(); + let entropy = hex_conservative::decode_to_vec("00000000000000000000000000000000").unwrap(); let _mnemonic_fr = Mnemonic::from_entropy(&entropy, Language::French).unwrap(); let _mnemonic_es = Mnemonic::from_entropy(&entropy, Language::Spanish).unwrap(); @@ -709,7 +714,7 @@ mod tests { #[test] fn test_portuguese_mnemonic() { // Test with known entropy - let entropy = Vec::from_hex("00000000000000000000000000000000").unwrap(); + let entropy = hex_conservative::decode_to_vec("00000000000000000000000000000000").unwrap(); let mnemonic_pt = Mnemonic::from_entropy(&entropy, Language::Portuguese).unwrap(); // Portuguese phrase for all zeros entropy @@ -721,7 +726,7 @@ mod tests { let seed = mnemonic_pt.to_seed("TREZOR"); // Portuguese mnemonic with same entropy produces a different seed than English let expected_seed = "ab9742b024a1e8bd241b76f8b3a157e9d442da60277bc8f36b8b23afe163de79414fb49fd1a8dd26f4ea7f0dc965c760b3b80727557bdca61e1f0b0f069952f2"; - assert_eq!(hex::encode(seed), expected_seed); + assert_eq!(seed.to_lower_hex_string(), expected_seed); // Test parsing a Portuguese mnemonic phrase let phrase_pt = "abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abacate abater"; @@ -1153,7 +1158,10 @@ mod tests { mnemonic.language(), Language::ChineseSimplified | Language::ChineseTraditional )); - assert_eq!(mnemonic.to_seed("").to_vec(), Vec::from_hex(DUAL_LIST_SEED_HEX).unwrap()); + assert_eq!( + mnemonic.to_seed("").to_vec(), + hex_conservative::decode_to_vec(DUAL_LIST_SEED_HEX).unwrap() + ); } #[test] @@ -1169,7 +1177,7 @@ mod tests { assert_eq!(mnemonic.language(), Language::French); assert_eq!( mnemonic.to_seed("TREZOR").to_vec(), - Vec::from_hex(FRENCH_SEED_TREZOR_HEX).unwrap() + hex_conservative::decode_to_vec(FRENCH_SEED_TREZOR_HEX).unwrap() ); } diff --git a/key-wallet/src/tests/provider_key_derivation_tests.rs b/key-wallet/src/tests/provider_key_derivation_tests.rs index 5a0d05f3d..01792e2ac 100644 --- a/key-wallet/src/tests/provider_key_derivation_tests.rs +++ b/key-wallet/src/tests/provider_key_derivation_tests.rs @@ -6,6 +6,10 @@ //! Ed25519), so the keys match dashwallet-ios / DashSync for the same //! mnemonic. See . +#[cfg(feature = "eddsa")] +use dashcore::eddsa::{EddsaPkBytes, EddsaPkHash, EddsaSecretKey, EddsaSkBytes}; +use hex_conservative::DisplayHex; + use crate::account::derivation::AccountDerivation; #[cfg(feature = "eddsa")] use crate::account::eddsa_account::EdDSAAccount; @@ -14,8 +18,6 @@ use crate::mnemonic::Mnemonic; use crate::wallet::initialization::WalletAccountCreationOptions; use crate::wallet::Wallet; use crate::{ChildNumber, Network}; -#[cfg(feature = "eddsa")] -use dashcore::eddsa::{EddsaPkBytes, EddsaPkHash, EddsaSecretKey, EddsaSkBytes}; const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; @@ -43,7 +45,7 @@ fn bls_operator_keys_match_dashbls_reference() { // The stored account xpub must be the account-level key at m/9'/5'/3'/3'. assert_eq!( - hex::encode(account.bls_public_key.to_bytes_legacy().unwrap()), + account.bls_public_key.to_bytes_legacy().unwrap().to_lower_hex_string(), "8d794d053504db3727c1f51aea2112e440fadbade687a9c0243b61523c8ab8eb64061f0a5ec5d8df4b7ec8bdfe722c19" ); @@ -51,26 +53,26 @@ fn bls_operator_keys_match_dashbls_reference() { let key0_pub = account.bls_public_key.derive_pub_legacy(ChildNumber::from_normal_idx(0).unwrap()).unwrap(); assert_eq!( - hex::encode(key0_pub.to_bytes_legacy().unwrap()), + key0_pub.to_bytes_legacy().unwrap().to_lower_hex_string(), "078cad04aae29eb76171937eb7101452b401b026efbc27db840f130374e6a9ec8443d917277f8921e0ba6678a7709875" ); // Same point in modern/IETF (basic-scheme) serialization, as it appears in // v19+ ProRegTx payloads. assert_eq!( - hex::encode(key0_pub.to_bytes()), + key0_pub.to_bytes().to_lower_hex_string(), "878cad04aae29eb76171937eb7101452b401b026efbc27db840f130374e6a9ec8443d917277f8921e0ba6678a7709875" ); // Operator secret key 0 via the seed-based signing path. - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let sk0 = account.derive_from_seed_private_key_at(&seed, 0).unwrap(); assert_eq!( - hex::encode(*sk0.to_bytes()), + (*sk0.to_bytes()).to_lower_hex_string(), "11122e1ad656d0610ce0f80d40da874d67ea656a3e66ed371c915ec3a488a43a" ); let sk1 = account.derive_from_seed_private_key_at(&seed, 1).unwrap(); assert_eq!( - hex::encode(*sk1.to_bytes()), + (*sk1.to_bytes()).to_lower_hex_string(), "1a4e3318640cd4e50222184d0ea111abf8a0c18a0e5dc3ed45dad85009db4e31" ); } @@ -88,14 +90,14 @@ fn bls_operator_keys_testnet_match_dashbls_reference() { let key0_pub = account.bls_public_key.derive_pub_legacy(ChildNumber::from_normal_idx(0).unwrap()).unwrap(); assert_eq!( - hex::encode(key0_pub.to_bytes_legacy().unwrap()), + key0_pub.to_bytes_legacy().unwrap().to_lower_hex_string(), "09d8beabae708de1638487f1aff44b38e8c07d9b09f22d76329d6c8ec01e2ad4d030b660bca40ddbd222373a72c5bcef" ); - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let sk0 = account.derive_from_seed_private_key_at(&seed, 0).unwrap(); assert_eq!( - hex::encode(*sk0.to_bytes()), + (*sk0.to_bytes()).to_lower_hex_string(), "3346dfd71627f9f31cad3ee66fe7b673c32cb077b2eb38c621d7e61c30e46dbd" ); } @@ -112,8 +114,10 @@ fn ed25519_platform_node_keys_match_slip10_reference() { .expect("platform node account should be auto-created for mnemonic wallets"); // The stored account key must be the account-level key at m/9'/5'/3'/4'. - let expected_account_sk = - hex::decode("80035d9c2f89971a9c9fad826bba8be9328f1686ae555e912949c2c32800c379").unwrap(); + let expected_account_sk = hex_conservative::decode_to_vec( + "80035d9c2f89971a9c9fad826bba8be9328f1686ae555e912949c2c32800c379", + ) + .unwrap(); let expected_account_pk: EddsaPkBytes = EddsaSecretKey::from(EddsaSkBytes::from_bytes( expected_account_sk.as_slice().try_into().unwrap(), )) @@ -122,10 +126,10 @@ fn ed25519_platform_node_keys_match_slip10_reference() { assert_eq!(account.ed25519_public_key.public_key, expected_account_pk); // Platform node key 0 (hardened child 0') via the seed-based signing path. - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let sk0 = account.derive_from_seed_private_key_at(&seed, 0).unwrap(); assert_eq!( - hex::encode(sk0.to_bytes()), + sk0.to_bytes().to_lower_hex_string(), "5fa238b12be77347abf9b5957bd902d16c6aaca28d25c4267ffacbd7458dceb1" ); } @@ -140,15 +144,15 @@ fn ed25519_platform_node_id_matches_tenderdash_convention() { // Platform node key 0 (m/9'/5'/3'/4'/0') for TEST_SEED — the same key the // SLIP-0010 vector above pins. Public key and node id cross-checked with // an independent Ed25519 implementation (pyca/cryptography). - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let sk0 = EdDSAAccount::platform_node_key_at(&seed, Network::Mainnet, 0).unwrap(); let pubkey: EddsaPkBytes = EddsaSecretKey::from(sk0).public_key().into(); assert_eq!( - hex::encode(pubkey.to_bytes()), + pubkey.to_bytes().to_lower_hex_string(), "3130c14339391cf26a68d86879e180ee9a16b660f5aa91f560f67c0abe8cf789" ); assert_eq!( - hex::encode(EddsaPkHash::from(pubkey).to_canonical_bytes()), + EddsaPkHash::from(pubkey).to_canonical_bytes().to_lower_hex_string(), "302f2615e6955cce8ed3cff81e8011bfd3a2991f" ); } @@ -167,7 +171,7 @@ fn platform_pool_entries_are_keyed_by_tenderdash_node_id() { let wallet = test_wallet(Network::Mainnet); let mut info = ManagedWalletInfo::from_wallet_with_name(&wallet, "node-id".to_string(), 0); - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let master = ExtendedEd25519PrivKey::new_master(Network::Mainnet, &seed).unwrap(); let account_xpriv = master .derive_priv(&AccountType::ProviderPlatformKeys.derivation_path(Network::Mainnet).unwrap()) @@ -180,7 +184,7 @@ fn platform_pool_entries_are_keyed_by_tenderdash_node_id() { .expect("derive platform key 0"); assert_eq!( - hex::encode(verifying_key.to_bytes()), + verifying_key.to_bytes().to_lower_hex_string(), "3130c14339391cf26a68d86879e180ee9a16b660f5aa91f560f67c0abe8cf789" ); // The pool entry's payload is the Tenderdash node id — the value a real @@ -188,7 +192,10 @@ fn platform_pool_entries_are_keyed_by_tenderdash_node_id() { let dashcore::address::Payload::PubkeyHash(hash) = entry.address.payload() else { panic!("platform pool entries use P2PKH-style payloads"); }; - assert_eq!(hex::encode(hash.to_byte_array()), "302f2615e6955cce8ed3cff81e8011bfd3a2991f"); + assert_eq!( + hash.to_byte_array().to_lower_hex_string(), + "302f2615e6955cce8ed3cff81e8011bfd3a2991f" + ); } /// The gate-free provider-key entry points must work identically on resident @@ -219,32 +226,32 @@ fn operator_key_at_is_wallet_state_agnostic() { for account in [resident, &watch_only] { let key0 = account.operator_public_key_at(0).expect("gate-free derivation must succeed"); assert_eq!( - hex::encode(key0.to_bytes_legacy().unwrap()), + key0.to_bytes_legacy().unwrap().to_lower_hex_string(), "078cad04aae29eb76171937eb7101452b401b026efbc27db840f130374e6a9ec8443d917277f8921e0ba6678a7709875" ); assert_eq!( - hex::encode(key0.to_bytes()), + key0.to_bytes().to_lower_hex_string(), "878cad04aae29eb76171937eb7101452b401b026efbc27db840f130374e6a9ec8443d917277f8921e0ba6678a7709875" ); } // Seed-based private derivation needs no account state at all. - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let sk0 = BLSAccount::operator_private_key_at(&seed, Network::Mainnet, 0).unwrap(); assert_eq!( - hex::encode(*sk0.to_bytes()), + (*sk0.to_bytes()).to_lower_hex_string(), "11122e1ad656d0610ce0f80d40da874d67ea656a3e66ed371c915ec3a488a43a" ); let sk1 = BLSAccount::operator_private_key_at(&seed, Network::Mainnet, 1).unwrap(); assert_eq!( - hex::encode(*sk1.to_bytes()), + (*sk1.to_bytes()).to_lower_hex_string(), "1a4e3318640cd4e50222184d0ea111abf8a0c18a0e5dc3ed45dad85009db4e31" ); // Testnet vectors (coin type 1: m/9'/1'/3'/3'). let sk0_testnet = BLSAccount::operator_private_key_at(&seed, Network::Testnet, 0).unwrap(); assert_eq!( - hex::encode(*sk0_testnet.to_bytes()), + (*sk0_testnet.to_bytes()).to_lower_hex_string(), "3346dfd71627f9f31cad3ee66fe7b673c32cb077b2eb38c621d7e61c30e46dbd" ); } @@ -256,7 +263,7 @@ fn operator_key_at_is_wallet_state_agnostic() { fn operator_public_key_at_rejects_non_operator_accounts() { use crate::account::bls_account::BLSAccount; - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let account = BLSAccount::from_seed(None, AccountType::IdentityRegistration, &seed, Network::Mainnet) .unwrap(); @@ -270,10 +277,10 @@ fn platform_node_key_at_is_wallet_state_agnostic() { // Seed-based hardened derivation, pinned to the SLIP-0010 reference // vector for m/9'/5'/3'/4'/0'. No account state is involved. - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let sk0 = EdDSAAccount::platform_node_key_at(&seed, Network::Mainnet, 0).unwrap(); assert_eq!( - hex::encode(sk0.to_bytes()), + sk0.to_bytes().to_lower_hex_string(), "5fa238b12be77347abf9b5957bd902d16c6aaca28d25c4267ffacbd7458dceb1" ); @@ -299,7 +306,7 @@ fn seedless_wallet_skips_provider_operator_and_platform_accounts() { use crate::bip32::ExtendedPrivKey; use crate::Error; - let seed = hex::decode(TEST_SEED_HEX).unwrap(); + let seed = hex_conservative::decode_to_vec(TEST_SEED_HEX).unwrap(); let master = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap(); let mut wallet = Wallet::from_extended_key(master, WalletAccountCreationOptions::Default).unwrap(); diff --git a/key-wallet/src/wallet/helper.rs b/key-wallet/src/wallet/helper.rs index 32e4e6d4d..a6c3c3e1d 100644 --- a/key-wallet/src/wallet/helper.rs +++ b/key-wallet/src/wallet/helper.rs @@ -7,7 +7,7 @@ use super::{Wallet, WalletType}; use crate::account::{Account, AccountType, StandardAccountType}; use crate::error::Result; use crate::Error; -use hex; +use hex_conservative::DisplayHex; impl Wallet { /// Get a bip44 account and index @@ -542,7 +542,7 @@ impl Wallet { // Return as hex string let serialized = public_key.serialize(); // compressed - Ok(hex::encode(serialized)) + Ok(serialized.to_lower_hex_string()) } /// Get the extended public key for a specific account type diff --git a/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs b/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs index 0c3312339..87ffa57e4 100644 --- a/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs +++ b/key-wallet/src/wallet/managed_wallet_info/transaction_builder.rs @@ -1174,7 +1174,6 @@ mod tests { use dashcore::consensus::serialize; use dashcore::{OutPoint, Txid}; use dashcore_hashes::{sha256d, Hash}; - use hex; fn op_return_script(data: &[u8]) -> ScriptBuf { let push_bytes = <&PushBytes>::try_from(data).expect("test OP_RETURN bytes"); @@ -1345,7 +1344,7 @@ mod tests { fn test_asset_lock_transaction() { // Test based on DSTransactionTests.m testAssetLockTx1 use dashcore::consensus::Decodable; - let hex_data = hex::decode("0300080001eecf4e8f1ffd3a3a4e5033d618231fd05e5f08c1a727aac420f9a26db9bf39eb010000006a473044022026f169570532332f857cb64a0b7d9c0837d6f031633e1d6c395d7c03b799460302207eba4c4575a66803cecf50b61ff5f2efc2bd4e61dff00d9d4847aa3d8b1a5e550121036cd0b73d304bacc80fa747d254fbc5f0bf944dd8c8b925cd161bb499b790d08d0000000002317dd0be030000002321022ca85dba11c4e5a6da3a00e73a08765319a5d66c2f6434b288494337b0c9ed2dac6df29c3b00000000026a000000000046010200e1f505000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac6d11a735000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac").unwrap(); + let hex_data = hex_conservative::decode_to_vec("0300080001eecf4e8f1ffd3a3a4e5033d618231fd05e5f08c1a727aac420f9a26db9bf39eb010000006a473044022026f169570532332f857cb64a0b7d9c0837d6f031633e1d6c395d7c03b799460302207eba4c4575a66803cecf50b61ff5f2efc2bd4e61dff00d9d4847aa3d8b1a5e550121036cd0b73d304bacc80fa747d254fbc5f0bf944dd8c8b925cd161bb499b790d08d0000000002317dd0be030000002321022ca85dba11c4e5a6da3a00e73a08765319a5d66c2f6434b288494337b0c9ed2dac6df29c3b00000000026a000000000046010200e1f505000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac6d11a735000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac").unwrap(); let mut cursor = std::io::Cursor::new(hex_data); let tx = Transaction::consensus_decode(&mut cursor).unwrap(); @@ -1372,7 +1371,7 @@ mod tests { fn test_coinbase_transaction() { // Test based on DSTransactionTests.m testCoinbaseTransaction use dashcore::consensus::Decodable; - let hex_data = hex::decode("03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502f6050105ffffffff0200c11a3d050000002321038df098a36af5f1b7271e32ad52947f64c1ad70c16a8a1a987105eaab5daa7ad2ac00c11a3d050000001976a914bfb885c89c83cd44992a8ade29b610e6ddf00c5788ac00000000260100f6050000aaaec8d6a8535a01bd844817dea1faed66f6c397b1dcaec5fe8c5af025023c35").unwrap(); + let hex_data = hex_conservative::decode_to_vec("03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502f6050105ffffffff0200c11a3d050000002321038df098a36af5f1b7271e32ad52947f64c1ad70c16a8a1a987105eaab5daa7ad2ac00c11a3d050000001976a914bfb885c89c83cd44992a8ade29b610e6ddf00c5788ac00000000260100f6050000aaaec8d6a8535a01bd844817dea1faed66f6c397b1dcaec5fe8c5af025023c35").unwrap(); let mut cursor = std::io::Cursor::new(hex_data); let tx = Transaction::consensus_decode(&mut cursor).unwrap(); diff --git a/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs b/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs index 23269cc4c..e82a1212e 100644 --- a/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs +++ b/key-wallet/src/wallet/managed_wallet_info/transaction_building.rs @@ -454,9 +454,8 @@ mod tests { fn test_asset_lock_transaction() { // Test based on DSTransactionTests.m testAssetLockTx1 use dashcore::consensus::Decodable; - use hex; - let hex_data = hex::decode("0300080001eecf4e8f1ffd3a3a4e5033d618231fd05e5f08c1a727aac420f9a26db9bf39eb010000006a473044022026f169570532332f857cb64a0b7d9c0837d6f031633e1d6c395d7c03b799460302207eba4c4575a66803cecf50b61ff5f2efc2bd4e61dff00d9d4847aa3d8b1a5e550121036cd0b73d304bacc80fa747d254fbc5f0bf944dd8c8b925cd161bb499b790d08d0000000002317dd0be030000002321022ca85dba11c4e5a6da3a00e73a08765319a5d66c2f6434b288494337b0c9ed2dac6df29c3b00000000026a000000000046010200e1f505000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac6d11a735000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac").unwrap(); + let hex_data = hex_conservative::decode_to_vec("0300080001eecf4e8f1ffd3a3a4e5033d618231fd05e5f08c1a727aac420f9a26db9bf39eb010000006a473044022026f169570532332f857cb64a0b7d9c0837d6f031633e1d6c395d7c03b799460302207eba4c4575a66803cecf50b61ff5f2efc2bd4e61dff00d9d4847aa3d8b1a5e550121036cd0b73d304bacc80fa747d254fbc5f0bf944dd8c8b925cd161bb499b790d08d0000000002317dd0be030000002321022ca85dba11c4e5a6da3a00e73a08765319a5d66c2f6434b288494337b0c9ed2dac6df29c3b00000000026a000000000046010200e1f505000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac6d11a735000000001976a9147c75beb097957cc09537b615dde9ea6807719cdf88ac").unwrap(); let mut cursor = std::io::Cursor::new(hex_data); let tx = Transaction::consensus_decode(&mut cursor).unwrap(); @@ -483,9 +482,8 @@ mod tests { fn test_coinbase_transaction() { // Test based on DSTransactionTests.m testCoinbaseTransaction use dashcore::consensus::Decodable; - use hex; - let hex_data = hex::decode("03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502f6050105ffffffff0200c11a3d050000002321038df098a36af5f1b7271e32ad52947f64c1ad70c16a8a1a987105eaab5daa7ad2ac00c11a3d050000001976a914bfb885c89c83cd44992a8ade29b610e6ddf00c5788ac00000000260100f6050000aaaec8d6a8535a01bd844817dea1faed66f6c397b1dcaec5fe8c5af025023c35").unwrap(); + let hex_data = hex_conservative::decode_to_vec("03000500010000000000000000000000000000000000000000000000000000000000000000ffffffff0502f6050105ffffffff0200c11a3d050000002321038df098a36af5f1b7271e32ad52947f64c1ad70c16a8a1a987105eaab5daa7ad2ac00c11a3d050000001976a914bfb885c89c83cd44992a8ade29b610e6ddf00c5788ac00000000260100f6050000aaaec8d6a8535a01bd844817dea1faed66f6c397b1dcaec5fe8c5af025023c35").unwrap(); let mut cursor = std::io::Cursor::new(hex_data); let tx = Transaction::consensus_decode(&mut cursor).unwrap(); diff --git a/key-wallet/tests/bip32_tests.rs b/key-wallet/tests/bip32_tests.rs index b5357f296..86b9550a9 100644 --- a/key-wallet/tests/bip32_tests.rs +++ b/key-wallet/tests/bip32_tests.rs @@ -6,7 +6,7 @@ use std::str::FromStr; #[test] fn test_extended_key_derivation() { // Test vector from BIP32 - let seed = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let seed = hex_conservative::decode_to_vec("000102030405060708090a0b0c0d0e0f").unwrap(); let master = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); // m/0' @@ -35,7 +35,7 @@ fn test_derivation_path_parsing() { #[test] fn test_extended_key_serialization() { - let seed = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let seed = hex_conservative::decode_to_vec("000102030405060708090a0b0c0d0e0f").unwrap(); let master = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); // Serialize and deserialize @@ -52,7 +52,7 @@ fn test_extended_key_serialization() { #[test] fn test_public_key_derivation() { - let seed = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let seed = hex_conservative::decode_to_vec("000102030405060708090a0b0c0d0e0f").unwrap(); let master = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); let master_pub = ExtendedPubKey::from_priv(&master); @@ -68,7 +68,7 @@ fn test_public_key_derivation() { #[test] fn test_fingerprint_calculation() { - let seed = hex::decode("000102030405060708090a0b0c0d0e0f").unwrap(); + let seed = hex_conservative::decode_to_vec("000102030405060708090a0b0c0d0e0f").unwrap(); let master = ExtendedPrivKey::new_master(Network::Mainnet, &seed).unwrap(); let child = master.ckd_priv(ChildNumber::from_normal_idx(0).unwrap()).unwrap(); diff --git a/key-wallet/tests/derivation_tests.rs b/key-wallet/tests/derivation_tests.rs index 0d7a7ae5f..31b6045a5 100644 --- a/key-wallet/tests/derivation_tests.rs +++ b/key-wallet/tests/derivation_tests.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use dashcore::hashes::Hash; +use hex_conservative::DisplayHex; use key_wallet::mnemonic::Mnemonic; use key_wallet::{DerivationPath, ExtendedPrivKey, ExtendedPubKey, Network}; @@ -42,7 +43,7 @@ fn test_dip17_platform_payment_vector1_mainnet() { let xprv = master_key.derive_priv(&path).unwrap(); // Verify private key matches DIP-17 test vector - let privkey_hex = hex::encode(xprv.private_key.to_secret_bytes()); + let privkey_hex = xprv.private_key.to_secret_bytes().to_lower_hex_string(); assert_eq!( privkey_hex, "6bca392f43453b7bc33a9532b69221ce74906a8815281637e0c9d0bee35361fe", "Private key mismatch for DIP-17 vector 1" @@ -51,7 +52,7 @@ fn test_dip17_platform_payment_vector1_mainnet() { // Get compressed public key let xpub = ExtendedPubKey::from_priv(&xprv); let pubkey = PublicKey::new(xpub.public_key); - let pubkey_hex = hex::encode(pubkey.to_bytes()); + let pubkey_hex = pubkey.to_bytes().to_lower_hex_string(); assert_eq!( pubkey_hex, "03de102ed1fc43cbdb16af02e294945ffaed8e0595d3072f4c592ae80816e6859e", "Public key mismatch for DIP-17 vector 1" @@ -59,7 +60,7 @@ fn test_dip17_platform_payment_vector1_mainnet() { // Verify HASH160 let pubkey_hash = pubkey.pubkey_hash(); - let hash160_hex = hex::encode(pubkey_hash.to_byte_array()); + let hash160_hex = pubkey_hash.to_byte_array().to_lower_hex_string(); assert_eq!( hash160_hex, "f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525", "HASH160 mismatch for DIP-17 vector 1" @@ -114,7 +115,7 @@ fn test_dip17_platform_payment_vector2() { let xprv_mainnet = master_mainnet.derive_priv(&path_mainnet).unwrap(); // Verify private key - let privkey_hex = hex::encode(xprv_mainnet.private_key.to_secret_bytes()); + let privkey_hex = xprv_mainnet.private_key.to_secret_bytes().to_lower_hex_string(); assert_eq!( privkey_hex, "eef58ce73383f63d5062f281ed0c1e192693c170fbc0049662a73e48a1981523", "Private key mismatch for DIP-17 vector 2" @@ -124,7 +125,7 @@ fn test_dip17_platform_payment_vector2() { let pubkey_mainnet = PublicKey::new(xpub_mainnet.public_key); // Verify public key - let pubkey_hex = hex::encode(pubkey_mainnet.to_bytes()); + let pubkey_hex = pubkey_mainnet.to_bytes().to_lower_hex_string(); assert_eq!( pubkey_hex, "02269ff766fcd04184bc314f5385a04498df215ce1e7193cec9a607f69bc8954da", "Public key mismatch for DIP-17 vector 2" @@ -132,7 +133,7 @@ fn test_dip17_platform_payment_vector2() { // Verify HASH160 let pubkey_hash_mainnet = pubkey_mainnet.pubkey_hash(); - let hash160_hex = hex::encode(pubkey_hash_mainnet.to_byte_array()); + let hash160_hex = pubkey_hash_mainnet.to_byte_array().to_lower_hex_string(); assert_eq!( hash160_hex, "a5ff0046217fd1c7d238e3e146cc5bfd90832a7e", "HASH160 mismatch for DIP-17 vector 2" @@ -172,7 +173,7 @@ fn test_dip17_platform_payment_vector3_non_default_key_class() { let xprv_mainnet = master_mainnet.derive_priv(&path_mainnet).unwrap(); // Verify private key - let privkey_hex = hex::encode(xprv_mainnet.private_key.to_secret_bytes()); + let privkey_hex = xprv_mainnet.private_key.to_secret_bytes().to_lower_hex_string(); assert_eq!( privkey_hex, "cc05b4389712a2e724566914c256217685d781503d7cc05af6642e60260830db", "Private key mismatch for DIP-17 vector 3" @@ -182,7 +183,7 @@ fn test_dip17_platform_payment_vector3_non_default_key_class() { let pubkey_mainnet = PublicKey::new(xpub_mainnet.public_key); // Verify public key - let pubkey_hex = hex::encode(pubkey_mainnet.to_bytes()); + let pubkey_hex = pubkey_mainnet.to_bytes().to_lower_hex_string(); assert_eq!( pubkey_hex, "0317a3ed70c141cffafe00fa8bf458cec119f6fc039a7ba9a6b7303dc65b27bed3", "Public key mismatch for DIP-17 vector 3" @@ -190,7 +191,7 @@ fn test_dip17_platform_payment_vector3_non_default_key_class() { // Verify HASH160 let pubkey_hash_mainnet = pubkey_mainnet.pubkey_hash(); - let hash160_hex = hex::encode(pubkey_hash_mainnet.to_byte_array()); + let hash160_hex = pubkey_hash_mainnet.to_byte_array().to_lower_hex_string(); assert_eq!( hash160_hex, "6d92674fd64472a3dfcfc3ebcfed7382bf699d7b", "HASH160 mismatch for DIP-17 vector 3" diff --git a/rpc-client/Cargo.toml b/rpc-client/Cargo.toml index 65d7b18f2..36d224fec 100644 --- a/rpc-client/Cargo.toml +++ b/rpc-client/Cargo.toml @@ -27,5 +27,3 @@ tracing = "0.1" # Used for deserialization of JSON. serde = { workspace = true, features = ["std", "derive"] } serde_json = { version="1.0", features=["preserve_order"] } - -hex = { workspace = true, features = ["serde"] } diff --git a/rpc-client/src/client.rs b/rpc-client/src/client.rs index f8e4d6d6e..2cd8dac16 100644 --- a/rpc-client/src/client.rs +++ b/rpc-client/src/client.rs @@ -25,6 +25,7 @@ use crate::dashcore::{ScriptBuf, block, consensus}; use crate::error::*; use crate::json; use crate::queryable; +use dashcore::hashes::Hash; use dashcore::hashes::hex::FromHex; use dashcore::secp256k1::ecdsa::Signature; use dashcore::{ @@ -158,7 +159,7 @@ pub trait RawTx: Sized + Clone { impl RawTx for &Transaction { fn raw_hex(self) -> String { - hex::encode(consensus::encode::serialize(&self)) + consensus::encode::serialize(&self).to_lower_hex_string() } } @@ -1625,8 +1626,8 @@ pub trait RpcApi: Sized { /// If the returned height is higher that the given chain lock this means that we ignored the chain lock because core had something better. fn submit_chain_lock(&self, chain_lock: &ChainLock) -> Result { let mut args = [ - into_json(hex::encode(chain_lock.block_hash))?, - into_json(hex::encode(chain_lock.signature.as_bytes()))?, + into_json(chain_lock.block_hash.as_byte_array().to_lower_hex_string())?, + into_json(chain_lock.signature.as_bytes().to_lower_hex_string())?, into_json(chain_lock.block_height)?, ]; self.call::("submitchainlock", handle_defaults(&mut args, &[null()])) diff --git a/rpc-integration-test/Cargo.toml b/rpc-integration-test/Cargo.toml index fac9c99ac..7997aea1b 100644 --- a/rpc-integration-test/Cargo.toml +++ b/rpc-integration-test/Cargo.toml @@ -11,7 +11,7 @@ version.workspace = true [dependencies] dashcore-rpc = { path = "../rpc-client" } lazy_static = "1.4.0" -hex = { workspace = true } +hex-conservative = { workspace = true } dotenvy = "0.15.7" tracing = "0.1" tracing-log = "0.2" diff --git a/rpc-integration-test/src/main.rs b/rpc-integration-test/src/main.rs index 4f5b31a1d..f245deeb4 100644 --- a/rpc-integration-test/src/main.rs +++ b/rpc-integration-test/src/main.rs @@ -38,6 +38,7 @@ use dashcore_rpc::dashcore_rpc_json::{ }; use dashcore_rpc::json::ProTxListType; use dashcore_rpc::json::QuorumType::LlmqTest; +use hex_conservative::DisplayHex; const FAUCET_WALLET_NAME: &str = "main"; const TEST_WALLET_NAME: &str = "testwallet"; @@ -454,7 +455,7 @@ fn test_get_block(cl: &Client) { let block = cl.get_block(&tip).unwrap(); let hex = cl.get_block_hex(&tip).unwrap(); assert_eq!(block, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); - assert_eq!(hex, hex::encode(serialize(&block))); + assert_eq!(hex, serialize(&block).to_lower_hex_string()); let tip = cl.get_best_block_hash().unwrap(); let info = cl.get_block_info(&tip).unwrap(); @@ -618,8 +619,8 @@ fn test_get_raw_transaction(cl: &Client) { cl.send_to_address(&addr, btc(1), None, None, None, None, None, None, None, None).unwrap(); let tx = cl.get_raw_transaction(&txid, None).unwrap(); let hex = cl.get_raw_transaction_hex(&txid, None).unwrap(); - assert_eq!(tx, deserialize(&hex::decode(&hex).unwrap()).unwrap()); - assert_eq!(hex, hex::encode(serialize(&tx))); + assert_eq!(tx, deserialize(&hex_conservative::decode_to_vec(&hex).unwrap()).unwrap()); + assert_eq!(hex, serialize(&tx).to_lower_hex_string()); let info = cl.get_raw_transaction_info(&txid, None).unwrap(); assert_eq!(info.txid, txid); @@ -811,8 +812,8 @@ fn test_create_raw_transaction(cl: &Client) { let tx = cl.create_raw_transaction(std::slice::from_ref(&input), &output, Some(500_000)).unwrap(); let hex = cl.create_raw_transaction_hex(&[input], &output, Some(500_000)).unwrap(); - assert_eq!(tx, deserialize(&hex::decode(&hex).unwrap()).unwrap()); - assert_eq!(hex, hex::encode(serialize(&tx))); + assert_eq!(tx, deserialize(&hex_conservative::decode_to_vec(&hex).unwrap()).unwrap()); + assert_eq!(hex, serialize(&tx).to_lower_hex_string()); } fn test_fund_raw_transaction(cl: &Client) { diff --git a/rpc-json/Cargo.toml b/rpc-json/Cargo.toml index 6d300c373..0b568a997 100644 --- a/rpc-json/Cargo.toml +++ b/rpc-json/Cargo.toml @@ -23,7 +23,7 @@ serde = { workspace = true, features = ["std", "derive"] } serde_json = { version="1.0", features=["preserve_order"] } serde_with = "2.1.0" serde_repr = "0.1" -hex = { workspace = true, features = ["serde"] } +hex-conservative = { workspace = true } key-wallet = { path = "../key-wallet", default-features = false, features=["serde", "getrandom"] } dashcore = { path = "../dash", features=["secp-recovery", "rand-std", "signer", "serde"] } diff --git a/rpc-json/src/lib.rs b/rpc-json/src/lib.rs index 07f1cae4a..b27f39dbc 100644 --- a/rpc-json/src/lib.rs +++ b/rpc-json/src/lib.rs @@ -36,7 +36,7 @@ use dashcore::{ Address, Amount, BlockHash, PrivateKey, ProTxHash, PublicKey, QuorumHash, Script, ScriptBuf, SignedAmount, Transaction, TxMerkleNode, Txid, bip158, }; -use hex::FromHexError; +use hex_conservative::DecodeVariableLengthBytesError; use key_wallet::bip32; use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; @@ -182,9 +182,9 @@ impl Eq for ScanningDetails {} pub struct CoinbaseTxDetails { pub version: usize, pub height: i32, - #[serde(rename = "merkleRootMNList", with = "hex")] + #[serde(rename = "merkleRootMNList", with = "serde_hex")] merkle_root_mn_list: Vec, - #[serde(rename = "merkleRootQuorums", with = "hex")] + #[serde(rename = "merkleRootQuorums", with = "serde_hex")] merkle_root_quorums: Vec, } @@ -192,7 +192,7 @@ pub struct CoinbaseTxDetails { pub struct GetBestChainLockResult { pub blockhash: BlockHash, pub height: u32, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub signature: Vec, pub known_block: bool, } @@ -230,7 +230,7 @@ pub struct GetBlockHeaderResult { pub confirmations: i32, pub height: usize, pub version: Version, - #[serde(default, with = "hex")] + #[serde(default, with = "serde_hex")] pub version_hex: Vec, #[serde(rename = "merkleroot")] pub merkle_root: TxMerkleNode, @@ -240,7 +240,7 @@ pub struct GetBlockHeaderResult { pub nonce: u32, pub bits: String, pub difficulty: f64, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub chainwork: Vec, pub n_tx: usize, #[serde(rename = "previousblockhash")] @@ -514,7 +514,7 @@ pub struct GetMiningInfoResult { #[serde(rename_all = "camelCase")] pub struct GetRawTransactionResultVinScriptSig { pub asm: String, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub hex: Vec, } @@ -553,7 +553,7 @@ impl GetRawTransactionResultVin { #[serde(rename_all = "camelCase")] pub struct GetRawTransactionResultVoutScriptPubKey { pub asm: String, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub hex: Vec, #[serde(rename = "reqSigs")] pub req_sigs: Option, @@ -596,7 +596,7 @@ pub struct GetRawTransactionResult { pub extra_payload_size: Option, #[serde(default, deserialize_with = "deserialize_hex_opt")] pub extra_payload: Option>, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub hex: Vec, pub blockhash: Option, pub height: Option, @@ -612,7 +612,7 @@ pub struct GetRawTransactionResult { #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct GetBlockFilterResult { pub header: dashcore::FilterHash, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub filter: Vec, } @@ -807,7 +807,7 @@ pub struct ListReceivedByAddressResult { #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct SignRawTransactionResult { - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub hex: Vec, pub complete: bool, } @@ -967,7 +967,7 @@ pub struct GetBlockchainInfoResult { #[serde(rename = "initialblockdownload")] pub initial_block_download: bool, /// Total amount of work in active chain, in hexadecimal - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub chainwork: Vec, /// The estimated size of the block and undo files on disk pub size_on_disk: u64, @@ -1481,7 +1481,7 @@ pub struct GetBlockTemplateResult { // TODO figure out what is the data is represented to coinbasetxn // pub coinbasetxn: /// The number which valid hashes must be less than, in big-endian - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub target: Vec, /// The minimum timestamp appropriate for the next block time. Expressed as /// UNIX timestamp. @@ -1493,7 +1493,7 @@ pub struct GetBlockTemplateResult { // TODO figure out what is the data is represented to value // pub value: /// A range of valid nonces - #[serde(with = "hex", rename = "noncerange")] + #[serde(with = "serde_hex", rename = "noncerange")] pub nonce_range: Vec, /// Block sigops limit #[serde(rename = "sigoplimit")] @@ -1507,9 +1507,9 @@ pub struct GetBlockTemplateResult { #[serde(rename = "curtime")] pub current_time: u64, /// The compressed difficulty in hexadecimal - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub bits: Vec, - #[serde(with = "hex", rename = "previousbits")] + #[serde(with = "serde_hex", rename = "previousbits")] pub previous_bits: Vec, /// The height of the block we will be mining: `current height + 1` pub height: u64, @@ -1528,7 +1528,7 @@ pub struct GetBlockTemplateResult { /// Models a single transaction entry in the result of "getblocktemplate" #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct GetBlockTemplateResultTransaction { - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub data: Vec, pub hash: BlockHash, /// Transactions that must be in present in the final block if this one is. @@ -1752,7 +1752,7 @@ pub struct FundRawTransactionOptions { #[derive(Deserialize, Clone, PartialEq, Eq, Debug)] #[serde(rename_all = "camelCase")] pub struct FundRawTransactionResult { - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub hex: Vec, #[serde(with = "dashcore::amount::serde::as_btc")] pub fee: Amount, @@ -2210,7 +2210,7 @@ pub struct DMNState { /// extended-address ProTx version on. #[serde(default, skip_serializing_if = "Option::is_none")] pub payouts: Option>, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub pub_key_operator: Vec, #[serde(default, deserialize_with = "deserialize_address_optional")] pub operator_payout_address: Option<[u8; 20]>, @@ -2367,7 +2367,7 @@ impl TryFrom for DMNStateDiff { let platform_node_id = platform_node_id .map(|address| { - let address = hex::decode(address) + let address = hex_conservative::decode_to_vec(&address) .map_err(|_| encode::Error::ParseFailed("invalid hex in platform node id"))?; let len = address.len(); address.try_into().map_err(|_| encode::Error::InvalidVectorSize { @@ -2680,7 +2680,7 @@ pub struct MasternodeStatus { pub pro_tx_hash: ProTxHash, #[serde(rename = "type")] pub node_type: String, - #[serde(rename = "collateralHash", with = "hex")] + #[serde(rename = "collateralHash", with = "serde_hex")] pub collateral_hash: Vec, #[serde(rename = "collateralIndex")] pub collateral_index: u32, @@ -2937,7 +2937,7 @@ pub struct QuorumListResultInternal { #[serde(rename_all = "camelCase")] pub struct QuorumMember { pub pro_tx_hash: ProTxHash, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub pub_key_operator: Vec, pub valid: bool, #[serde(default, deserialize_with = "deserialize_hex_opt")] @@ -2952,10 +2952,10 @@ pub struct QuorumInfoResult { pub quorum_type: QuorumType, pub quorum_hash: QuorumHash, pub quorum_index: u32, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub mined_block: Vec, pub members: Vec, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub quorum_public_key: Vec, #[serde(default, deserialize_with = "deserialize_hex_opt")] pub secret_key_share: Option>, @@ -3082,13 +3082,13 @@ pub struct QuorumSignature { pub llmq_type: QuorumType, pub quorum_hash: QuorumHash, pub quorum_member: Option, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub id: Vec, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub msg_hash: Vec, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub sign_hash: Vec, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub signature: Vec, } @@ -3106,9 +3106,9 @@ pub struct QuorumMemberOf { #[serde(rename = "type", deserialize_with = "deserialize_quorum_type")] pub quorum_type: QuorumType, pub quorum_hash: QuorumHash, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub mined_block: Vec, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub quorum_public_key: Vec, pub is_valid_member: bool, pub member_index: u32, @@ -3129,13 +3129,13 @@ pub struct QuorumSnapshot { #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct QuorumMasternodeListItem { - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub pro_reg_tx_hash: Vec, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub confirmed_hash: Vec, #[serde_as(serialize_as = "DisplayFromStr", deserialize_as = "ServiceOrUnspecified")] pub service: SocketAddr, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub pub_key_operator: Vec, #[serde_as(as = "Bytes")] pub voting_address: Vec, @@ -3157,9 +3157,9 @@ pub struct MasternodeDiff { pub mn_list: Vec, pub deleted_quorums: Vec, pub new_quorums: Vec, - #[serde(rename = "merkleRootMNList", with = "hex")] + #[serde(rename = "merkleRootMNList", with = "serde_hex")] pub merkle_root_mn_list: Vec, - #[serde(rename = "merkleRootQuorums", with = "hex")] + #[serde(rename = "merkleRootQuorums", with = "serde_hex")] pub merkle_root_quorums: Vec, } @@ -3322,7 +3322,7 @@ pub struct ProTxInfo { pub mn_type: Option, #[serde(rename = "proTxHash")] pub pro_tx_hash: ProTxHash, - #[serde(with = "hex")] + #[serde(with = "serde_hex")] pub collateral_hash: Vec, pub collateral_index: u32, /// `None` when Core prints no `collateralAddress`: the collateral output has no address (a @@ -3366,8 +3366,27 @@ pub enum ProTxRevokeReason { // Custom deserializer functions. +/// Serializes byte vectors as lowercase hex strings, for +/// `#[serde(with = "serde_hex")]`. +mod serde_hex { + use hex_conservative::DisplayHex; + use serde::de::Error as _; + use serde::{Deserialize, Deserializer, Serializer}; + + use super::HexError; + + pub fn serialize(bytes: &[u8], serializer: S) -> Result { + serializer.collect_str(&bytes.as_hex()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let s = String::deserialize(deserializer)?; + hex_conservative::decode_to_vec(&s).map_err(|e| D::Error::custom(HexError::from(e))) + } +} + #[derive(Debug)] -pub struct HexError(FromHexError); +pub struct HexError(DecodeVariableLengthBytesError); impl Display for HexError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { @@ -3377,8 +3396,8 @@ impl Display for HexError { impl Error for HexError {} -impl From for HexError { - fn from(err: FromHexError) -> HexError { +impl From for HexError { + fn from(err: DecodeVariableLengthBytesError) -> HexError { HexError(err) } } @@ -3388,7 +3407,7 @@ where D: Deserializer<'de>, { match String::deserialize(deserializer) { - Ok(s) => match hex::decode(s) { + Ok(s) => match hex_conservative::decode_to_vec(&s) { Ok(v) => Ok(Some(v)), Err(err) => Err(D::Error::custom(HexError::from(err))), }, @@ -3403,7 +3422,7 @@ where D: Deserializer<'de>, { match String::deserialize(deserializer) { - Ok(s) => match hex::decode(s) { + Ok(s) => match hex_conservative::decode_to_vec(&s) { Ok(v) => match v.clone().try_into() { Ok(array) => Ok(Some(array)), Err(_) => Err(D::Error::custom(ArrayConversionError(v))), @@ -3634,6 +3653,23 @@ mod tests { MasternodeAddresses, MasternodeListDiff, MasternodeStatus, MnSyncStatus, ProTxInfo, QuorumMasternodeListItem, QuorumType, deserialize_u32_opt, parse_host_port, }; + use hex_conservative::DisplayHex; + + #[test] + fn serde_hex_round_trips() { + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Wrapper { + #[serde(with = "crate::serde_hex")] + bytes: Vec, + } + + let wrapper = Wrapper { + bytes: vec![0x00, 0xab, 0xcd, 0xef], + }; + let json = serde_json::to_value(&wrapper).unwrap(); + assert_eq!(json, json!({ "bytes": "00abcdef" })); + assert_eq!(serde_json::from_value::(json).unwrap(), wrapper); + } #[test] fn test_deserialize_u32_opt() { @@ -3804,7 +3840,7 @@ mod tests { assert_eq!( "8ed3f0c208efbcfc815cbfb94490dc68cf2e29d44dd9f8a91e20e06057aa110d7062c8ab7ccc85a9ff0c88760157f563".to_string(), - hex::encode(result.added_mns[0].state.pub_key_operator.clone()), + result.added_mns[0].state.pub_key_operator.to_lower_hex_string(), "invalid pub_key_operator" ); } @@ -4741,7 +4777,7 @@ mod tests { } fn hash160(hex: &str) -> [u8; 20] { - hex::decode(hex).expect("valid hex").try_into().expect("20 bytes") + hex_conservative::decode_to_vec(hex).expect("valid hex").try_into().expect("20 bytes") } #[test] @@ -4954,7 +4990,7 @@ mod tests { let older = core_v24_listdiff().added_mns[0].state.clone(); let mut newer = older.clone(); newer.voting_address = [0xee; 20]; - newer.pub_key_operator = hex::decode("a792ce1af5f7bb9281053b3934cb8b08d00d075a56498e1a525388ce467f188e8a80911fd96a20982baa9b9678452534").expect("valid hex"); + newer.pub_key_operator = hex_conservative::decode_to_vec("a792ce1af5f7bb9281053b3934cb8b08d00d075a56498e1a525388ce467f188e8a80911fd96a20982baa9b9678452534").expect("valid hex"); let diff = older.compare_to_newer_dmn_state(&newer).expect("the states differ"); assert_eq!(diff.owner_address, None);