depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit 94b9f89b75ff1925948a896476c91be2dbee310a
parent 4e1f47bcc56639427b99de6bff49830ee586b63d
Author: Antoine A <>
Date:   Fri, 12 Nov 2021 11:54:29 +0100

Improve encode_segwit_msg security and perf

Diffstat:
MCargo.lock | 48++++++++++++++++++++++++++++++++++++++++++++++++
MCargo.toml | 3++-
Mbenches/metadata.rs | 6+++---
Msrc/lib.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/main.rs | 9++-------
5 files changed, 105 insertions(+), 41 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -303,12 +303,14 @@ name = "depolymerization" version = "0.1.0" dependencies = [ "argh", + "bech32", "bitcoin-bech32", "bitcoincore-rpc", "bs58", "criterion", "digest", "fastrand", + "rand", "rustyline", ] @@ -586,6 +588,12 @@ dependencies = [ ] [[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + +[[package]] name = "proc-macro2" version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -614,6 +622,46 @@ dependencies = [ ] [[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] name = "rayon" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -13,6 +13,8 @@ digest = "0.9.0" argh = "0.1.6" rustyline = "9.0.0" bitcoin-bech32 = "0.12.1" +bech32 = "0.8.1" +rand = { version = "0.8.4", features = ["getrandom"] } [dev-dependencies] # statistics-driven micro-benchmarks @@ -21,4 +23,3 @@ criterion = "0.3.5" [[bench]] name = "metadata" harness = false - diff --git a/benches/metadata.rs b/benches/metadata.rs @@ -1,8 +1,8 @@ -use bitcoin_bech32::constants::Network; use criterion::{criterion_group, criterion_main, Criterion}; use depolymerization::{ decode_segwit_msg, encode_segwit_msg, utils::{rand_addresses, rand_key}, + Network, }; fn criterion_benchmark(c: &mut Criterion) { @@ -10,13 +10,13 @@ fn criterion_benchmark(c: &mut Criterion) { group.bench_function("encode", |b| { b.iter_batched( rand_key, - |key| encode_segwit_msg(Network::Bitcoin, &key), + |key| encode_segwit_msg(Network::MainNet, &key), criterion::BatchSize::SmallInput, ); }); group.bench_function("decode", |b| { b.iter_batched( - || rand_addresses(&rand_key()), + || rand_addresses(Network::MainNet, &rand_key()), |addrs| decode_segwit_msg(&addrs), criterion::BatchSize::SmallInput, ); diff --git a/src/lib.rs b/src/lib.rs @@ -1,21 +1,47 @@ -use std::{collections::BTreeMap, iter::repeat_with}; +use std::collections::BTreeMap; -use bitcoin_bech32::{constants::Network, u5, WitnessProgram}; +use bech32::{ToBase32, Variant}; +use bitcoin_bech32::{u5, WitnessProgram}; use bitcoincore_rpc::bitcoin::Amount; +use rand::{rngs::OsRng, RngCore}; + +#[derive(Debug, Clone, Copy)] +pub enum Network { + MainNet, + TestNet, + RegTest, +} + +impl Network { + pub fn segwit_hrp(&self) -> &'static str { + match self { + Network::MainNet => "bc", + Network::TestNet => "tb", + Network::RegTest => "bcrt", + } + } +} pub fn segwit_min_amount() -> Amount { // https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp return Amount::from_sat(294); } -fn encode_segwit_address( +pub fn encode_segwit_addr(network: Network, data: &[u8; 20]) -> String { + // We use the version 0 with bech32 encoding + let mut buf = vec![u5::try_from_u8(0).unwrap()]; + buf.extend_from_slice(&data.to_base32()); + bech32::encode(network.segwit_hrp(), buf, Variant::Bech32).unwrap() +} + +fn encode_segwit_key_half( network: Network, is_first: bool, magic_id: &[u8; 4], key_half: &[u8; 16], ) -> String { // Combine magic_it and the key half - let mut buf = vec![0u8; 20]; + let mut buf = [0u8; 20]; buf[..4].copy_from_slice(magic_id); buf[4..].copy_from_slice(key_half); // Toggle first bit for ordering @@ -25,23 +51,20 @@ fn encode_segwit_address( buf[0] |= 0b1000_0000 // Set first bit } // Encode into an fake segwit address - WitnessProgram::new(u5::try_from_u8(0).unwrap(), buf, network) - .unwrap() - .to_address() + encode_segwit_addr(network, &buf) } pub fn encode_segwit_msg(network: Network, msg: &[u8; 32]) -> [String; 2] { // Generate a random magic identifier let mut magic_id = [0; 4]; - // TODO use secure os based random - magic_id.fill_with(|| fastrand::u8(..)); + OsRng.fill_bytes(&mut magic_id); // Split key in half; let mut split = ([0; 16], [0; 16]); split.0.copy_from_slice(&msg[..16]); split.1.copy_from_slice(&msg[16..]); [ - encode_segwit_address(network, true, &magic_id, &split.0), - encode_segwit_address(network, false, &magic_id, &split.1), + encode_segwit_key_half(network, true, &magic_id, &split.0), + encode_segwit_key_half(network, false, &magic_id, &split.1), ] } @@ -113,11 +136,7 @@ pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], D } pub mod utils { - use std::iter::repeat_with; - - use bitcoin_bech32::{constants::Network, u5, WitnessProgram}; - - use crate::encode_segwit_msg; + use crate::{encode_segwit_addr, encode_segwit_msg, Network}; pub fn rand_key() -> [u8; 32] { let mut key = [0; 32]; @@ -125,17 +144,19 @@ pub mod utils { key } - pub fn rand_addresses(key: &[u8; 32]) -> Vec<String> { - let mut rng_address: Vec<String> = std::iter::repeat_with(|| { - let key: Vec<u8> = repeat_with(|| fastrand::u8(..)).take(20).collect(); - WitnessProgram::new(u5::try_from_u8(0).unwrap(), key, Network::Bitcoin) - .unwrap() - .to_address() - }) - .take(2) - .collect(); + pub fn rand_data() -> [u8; 20] { + let mut key = [0; 20]; + key.fill_with(|| fastrand::u8(..)); + key + } - let mut addresses = encode_segwit_msg(Network::Bitcoin, &key).to_vec(); + pub fn rand_addresses(network: Network, key: &[u8; 32]) -> Vec<String> { + let mut rng_address: Vec<String> = + std::iter::repeat_with(|| encode_segwit_addr(network, &rand_data())) + .take(2) + .collect(); + + let mut addresses = encode_segwit_msg(network, &key).to_vec(); addresses.append(&mut rng_address); fastrand::shuffle(&mut addresses); addresses @@ -144,18 +165,17 @@ pub mod utils { #[cfg(test)] mod test { - use bitcoin_bech32::constants::Network; - use crate::{ decode_segwit_msg, encode_segwit_msg, utils::{rand_addresses, rand_key}, + Network, }; #[test] fn test_shuffle() { for _ in 0..1000 { let key = rand_key(); - let mut addresses = encode_segwit_msg(Network::Bitcoin, &key); + let mut addresses = encode_segwit_msg(Network::RegTest, &key); fastrand::shuffle(&mut addresses); let decoded = decode_segwit_msg(&addresses.iter().map(|s| s.as_str()).collect::<Vec<&str>>()) @@ -168,7 +188,7 @@ mod test { fn test_shuffle_many() { for _ in 0..1000 { let key = rand_key(); - let addresses = rand_addresses(&key); + let addresses = rand_addresses(Network::TestNet, &key); let decoded = decode_segwit_msg(&addresses.iter().map(|s| s.as_str()).collect::<Vec<&str>>()) .unwrap(); diff --git a/src/main.rs b/src/main.rs @@ -1,12 +1,11 @@ use std::{collections::HashSet, iter::repeat_with, path::PathBuf, str::FromStr}; -use bitcoin_bech32::{constants::Network, u5, WitnessProgram}; use bitcoincore_rpc::{ bitcoin::{Address, Amount, Txid}, jsonrpc::serde_json::Value, Auth, Client, RpcApi, }; -use depolymerization::{decode_segwit_msg, encode_segwit_msg, segwit_min_amount}; +use depolymerization::{decode_segwit_msg, encode_segwit_msg, segwit_min_amount, Network}; const CLIENT: &str = "client"; const WIRE: &str = "wire"; @@ -41,10 +40,6 @@ struct Args { /// start as a receiver #[argh(switch, short = 'w')] wire: bool, - - /// a message to send - #[argh(option)] - msg: Option<String>, } fn common_rpc() -> Client { Client::new( @@ -91,7 +86,7 @@ fn send_with_metadata( amount: Amount, metadata: &[u8], ) -> bitcoincore_rpc::Result<Txid> { - let addresses = encode_segwit_msg(Network::Regtest, &metadata.try_into().unwrap()); + let addresses = encode_segwit_msg(Network::RegTest, &metadata.try_into().unwrap()); let mut recipients = vec![(to.to_string(), amount)]; recipients.extend( addresses