depolymerization

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

commit 17bbd0949052db402f9fbb532439fd03ab46d219
parent 0b51ecda47a222c02b66f65d2d55d104ecfab7e4
Author: Antoine A <>
Date:   Thu, 17 Feb 2022 13:03:25 +0100

btc-wire: rename magic id to random prefix and improve deposit metadata format documentation

Diffstat:
Mbtc-wire/README.md | 45+++++++++++++++++++++++++++++++++++++++++----
Mbtc-wire/src/bin/segwit-demo.rs | 25++++++++++++++++++-------
Mbtc-wire/src/segwit.rs | 44++++++++++++++++++++++----------------------
Mtest/common.sh | 2+-
4 files changed, 82 insertions(+), 34 deletions(-)

diff --git a/btc-wire/README.md b/btc-wire/README.md @@ -52,12 +52,49 @@ with the same random pattern, at the exception of the first bit with must be 0 for the first half and 1 for the second one. You must then send a single transaction with the three addresses as recipients. -As a few lines of code can carry more meaning that many words you can find a +Segwit addresses are encoded using a bitcoin specific format: +[bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) + +As a few lines of code can carry more meaning that many words, you can find a [simple rust example](src/bin/segwit-demo.rs) in this project and run it with -`make segwit_demo`. +`make segwit_demo`. -Segwit addresses are encoded using a bitcoin specific format: [bech32]( -[bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)) +``` +Ⅰ - Parse payto uri +Got payto uri: payto://bitcoin/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4?amount=BTC:0.1&subject=0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00 +Send 0.1 BTC to bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 with reserve public key 0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00 + +Ⅱ - Generate fake segwit addresses +Decode reserve public key: 0x07f3d46620a0c138f5131f82d553706df68175cf4b6018b097a5c77e7b4453c0 +Generate random prefix 0x7ea4c272 +Split reserve public key in two: +0x07f3d46620a0c138f5131f82d553706d +0xf68175cf4b6018b097a5c77e7b4453c0 +Concatenate random prefix with each reserve public key half: +0x7ea4c27207f3d46620a0c138f5131f82d553706d +0x7ea4c272f68175cf4b6018b097a5c77e7b4453c0 +Set first bit of the first half: +0x7ea4c27207f3d46620a0c138f5131f82d553706d +Unset first bit of the second half: +0xfea4c272f68175cf4b6018b097a5c77e7b4453c0 +Encode each half using bech32 to generate a segwit address: +bc1q06jvyus8702xvg9qcyu02yclst24xurdjvsnqz +bc1ql6jvyuhks96u7jmqrzcf0fw80ea5g57q2eccn6 + +Ⅲ - Send to many +Send a single bitcoin transaction with the three addresses as recipient as follow: + +In bitcoincore wallet use 'Add Recipient' button to add two additional recipient and copy adresses and amounts +bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 0.10000000 BTC +bc1q06jvyus8702xvg9qcyu02yclst24xurdjvsnqz 0.00000294 BTC +bc1ql6jvyuhks96u7jmqrzcf0fw80ea5g57q2eccn6 0.00000294 BTC + +In Electrum wallet paste the following three lines in 'Pay to' field : +bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4,0.10000000 +bc1q06jvyus8702xvg9qcyu02yclst24xurdjvsnqz,0.00000294 +bc1ql6jvyuhks96u7jmqrzcf0fw80ea5g57q2eccn6,0.00000294 +Make sure the amount show 0.10000588 BTC, else you have to change the base unit to BTC +``` ## Implementation details diff --git a/btc-wire/src/bin/segwit-demo.rs b/btc-wire/src/bin/segwit-demo.rs @@ -6,7 +6,7 @@ use common::{ }; pub fn main() { - let address = "bc1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj"; + let address = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"; let amount = Amount::from_sat(10000000); let reserve_pub = "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00"; let btc = amount.as_btc(); @@ -21,17 +21,17 @@ pub fn main() { .try_into() .unwrap(); println!("Decode reserve public key: 0x{}", hex::encode(&decoded[..])); - let magic_id: [u8; 4] = rand_slice(); - println!("Generate magic id: 0x{}", hex::encode(&magic_id)); + let prefix: [u8; 4] = rand_slice(); + println!("Generate random prefix 0x{}", hex::encode(&prefix)); println!( "Split reserve public key in two:\n0x{}\n0x{}", hex::encode(&decoded[..16]), hex::encode(&decoded[16..]) ); - let mut first_half = [&magic_id, &decoded[..16]].concat(); - let mut second_half = [&magic_id, &decoded[16..]].concat(); + let mut first_half = [&prefix, &decoded[..16]].concat(); + let mut second_half = [&prefix, &decoded[16..]].concat(); println!( - "Concatenate magic id with each reserve public key half:\n0x{}\n0x{}", + "Concatenate random prefix with each reserve public key half:\n0x{}\n0x{}", hex::encode(&first_half), hex::encode(&second_half) ); @@ -52,5 +52,16 @@ pub fn main() { println!("\nⅢ - Send to many"); let minimum = rpc_utils::segwit_min_amount().as_btc(); - println!("Send a single bitcoin transaction with the three addresses as recipient as follow:\n{address} {btc}BTC\n{first} {minimum}BTC\n{second} {minimum}BTC"); + println!("Send a single bitcoin transaction with the three addresses as recipient as follow:"); + println!("\nIn bitcoincore wallet use 'Add Recipient' button to add two additional recipient and copy adresses and amounts"); + for (address, amount) in [(address, btc), (&first, minimum), (&second, minimum)] { + println!("{address} {amount:.8} BTC"); + } + println!("\nIn Electrum wallet paste the following three lines in 'Pay to' field :"); + for (address, amount) in [(address, btc), (&first, minimum), (&second, minimum)] { + println!("{address},{amount:.8}"); + } + println!( + "Make sure the amount show 0.10000588 BTC, else you have to change the base unit to BTC" + ) } diff --git a/btc-wire/src/segwit.rs b/btc-wire/src/segwit.rs @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ use bech32::{u5, FromBase32, ToBase32, Variant}; -use common::rand::{rngs::OsRng, RngCore}; +use common::{rand::rngs::OsRng, rand_slice}; /// Encode metadata into a segwit address pub fn encode_segwit_addr(hrp: &str, metada: &[u8; 20]) -> String { @@ -28,12 +28,12 @@ pub fn encode_segwit_addr(hrp: &str, metada: &[u8; 20]) -> String { fn encode_segwit_key_half( hrp: &str, is_first: bool, - magic_id: &[u8; 4], + prefix: &[u8; 4], key_half: &[u8; 16], ) -> String { - // Combine magic_it and the key half + // Combine prefix and the key half let mut buf = [0u8; 20]; - buf[..4].copy_from_slice(magic_id); + buf[..4].copy_from_slice(prefix); buf[4..].copy_from_slice(key_half); // Toggle first bit for ordering if is_first { @@ -47,15 +47,14 @@ fn encode_segwit_key_half( /// Encode a 32B key into two segwit adresses pub fn encode_segwit_key(hrp: &str, msg: &[u8; 32]) -> [String; 2] { - // Generate a random magic identifier - let mut magic_id = [0; 4]; - OsRng.fill_bytes(&mut magic_id); + // Generate a random prefix + let prefix = rand_slice(); // Split key in half; let split: (&[u8; 16], &[u8; 16]) = (msg[..16].try_into().unwrap(), msg[16..].try_into().unwrap()); [ - encode_segwit_key_half(hrp, true, &magic_id, split.0), - encode_segwit_key_half(hrp, false, &magic_id, split.1), + encode_segwit_key_half(hrp, true, &prefix, split.0), + encode_segwit_key_half(hrp, false, &prefix, split.1), ] } @@ -63,28 +62,28 @@ pub fn encode_segwit_key(hrp: &str, msg: &[u8; 32]) -> [String; 2] { pub enum DecodeSegWitErr { #[error("There is less than 2 segwit addresses")] MissingSegWitAddress, - #[error("No adresses are sharing a magic id")] - NoMagicIdMatch, - #[error("More than two addresses are sharing a magic id")] - MagicIdCollision, + #[error("No adresses are sharing a common prefix")] + NoPrefixMatch, + #[error("More than two addresses are sharing a common prefix")] + PrefixCollision, } /// Decode a 32B key into from adresses pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], DecodeSegWitErr> { // Extract parts from every addresses - let decoded: Vec<(bool, [u8; 4], [u8; 16])> = segwit_addrs + let mut decoded: Vec<(bool, [u8; 4], [u8; 16])> = segwit_addrs .iter() .filter_map(|addr| { bech32::decode(addr.as_ref()).ok().and_then(|(_, wp, _)| { // Skip version let pg: Vec<u8> = Vec::from_base32(&wp[1..]).unwrap(); if pg.len() == 20 { - let mut magic_id: [u8; 4] = pg[..4].try_into().unwrap(); + let mut prefix: [u8; 4] = pg[..4].try_into().unwrap(); let key_half: [u8; 16] = pg[4..].try_into().unwrap(); let is_first = !pg[0] & 0b1000_0000 == 0; // Clear first bit - magic_id[0] &= 0b0111_1111; - Some((is_first, magic_id, key_half)) + prefix[0] &= 0b0111_1111; + Some((is_first, prefix, key_half)) } else { None } @@ -95,20 +94,21 @@ pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], D if decoded.len() < 2 { return Err(DecodeSegWitErr::MissingSegWitAddress); } - // Keep only the addresses with duplicated magic id + // Keep only the addresses with duplicated prefix + // TODO use sort_unstable_by and partition_dedup_by_key when stable let matches: Vec<&(bool, [u8; 4], [u8; 16])> = decoded .iter() - .filter(|(_, magic, _)| { + .filter(|(_, prefix, _)| { decoded .iter() - .filter(|(_, other, _)| other == magic) + .filter(|(_, other, _)| other == prefix) .count() > 1 }) .collect(); if matches.len() > 2 { - return Err(DecodeSegWitErr::MagicIdCollision); + return Err(DecodeSegWitErr::PrefixCollision); } else if matches.len() < 2 { return Err(DecodeSegWitErr::MissingSegWitAddress); } @@ -122,7 +122,7 @@ pub fn decode_segwit_msg(segwit_addrs: &[impl AsRef<str>]) -> Result<[u8; 32], D // TODO find a way to hide that function while using it in test and benchmark pub fn rand_addresses(hrp: &str, key: &[u8; 32]) -> Vec<String> { - use common::{rand::prelude::SliceRandom, rand_slice}; + use common::rand::prelude::SliceRandom; let mut rng_address: Vec<String> = std::iter::repeat_with(|| encode_segwit_addr(hrp, &rand_slice())) diff --git a/test/common.sh b/test/common.sh @@ -291,7 +291,7 @@ function eth_deco() { function eth_fork() { $WIRE_UTILS2 mine $RESERVE ${1:-} $WIRE_UTILS connect $WIRE_DIR2 - sleep 5 + sleep 6 } # Restart an initialized geth dev node