From 10565277d67c867df5afbd1919892c46402bc0e6 Mon Sep 17 00:00:00 2001 From: Patrick Amato Date: Wed, 18 Sep 2019 20:30:27 -0600 Subject: [PATCH] btc-spv transaction parsing (#5858) * Transaction and input parsing/decoding + utils * Transaction input & output parsing * public struct members, tx parsing test * format and clippy fixes * update block data/test material fetching utils * update tx parsing tests * format changes * rename for consistency --- programs/btc_spv_api/Cargo.toml | 1 + programs/btc_spv_api/src/spv_instruction.rs | 2 +- programs/btc_spv_api/src/spv_processor.rs | 30 ++- programs/btc_spv_api/src/spv_state.rs | 206 +++++++++++++++++--- programs/btc_spv_api/src/testblock.in | Bin 0 -> 9195 bytes programs/btc_spv_api/src/utils.rs | 60 +++++- programs/btc_spv_bin/Cargo.toml | 5 + programs/btc_spv_bin/src/block.rs | 46 +++++ 8 files changed, 321 insertions(+), 29 deletions(-) create mode 100644 programs/btc_spv_api/src/testblock.in create mode 100644 programs/btc_spv_bin/src/block.rs diff --git a/programs/btc_spv_api/Cargo.toml b/programs/btc_spv_api/Cargo.toml index 29620790b..2e5f345cd 100644 --- a/programs/btc_spv_api/Cargo.toml +++ b/programs/btc_spv_api/Cargo.toml @@ -17,6 +17,7 @@ num-traits = "0.2" serde = "1.0.100" serde_derive = "1.0.100" solana-sdk = { path = "../../sdk", version = "0.19.0-pre0"} +hex = "0.3.2" [lib] crate-type = ["lib"] diff --git a/programs/btc_spv_api/src/spv_instruction.rs b/programs/btc_spv_api/src/spv_instruction.rs index 64740230b..5fe91e600 100644 --- a/programs/btc_spv_api/src/spv_instruction.rs +++ b/programs/btc_spv_api/src/spv_instruction.rs @@ -57,7 +57,7 @@ pub fn submit_proof( submitter: &Pubkey, proof: MerkleProof, headers: HeaderChain, - transaction: Transaction, + transaction: BitcoinTransaction, request: &Pubkey, ) -> Instruction { let account_meta = vec![ diff --git a/programs/btc_spv_api/src/spv_processor.rs b/programs/btc_spv_api/src/spv_processor.rs index 257f115ce..1b19aeeca 100644 --- a/programs/btc_spv_api/src/spv_processor.rs +++ b/programs/btc_spv_api/src/spv_processor.rs @@ -3,7 +3,8 @@ use crate::spv_instruction::*; use crate::spv_state::*; #[allow(unused_imports)] -use crate::utils::decode_hex; +use crate::utils::*; +use hex; use log::*; use solana_sdk::account::KeyedAccount; use solana_sdk::instruction::InstructionError; @@ -129,8 +130,8 @@ mod test { fn test_parse_header_hex() -> Result<(), SpvError> { let testheader = "010000008a730974ac39042e95f82d719550e224c1a680a8dc9e8df9d007000000000000f50b20e8720a552dd36eb2ebdb7dceec9569e0395c990c1eb8a4292eeda05a931e1fce4e9a110e1a7a58aeb0"; let testhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103"; - let testheaderbytes = decode_hex(&testheader)?; - let testhashbytes = decode_hex(&testhash)?; + let testheaderbytes = hex::decode(&testheader)?; + let testhashbytes = hex::decode(&testhash)?; let mut blockhash: [u8; 32] = [0; 32]; blockhash.copy_from_slice(&testhashbytes[..32]); @@ -163,4 +164,27 @@ mod test { Ok(()) } + + #[test] + fn test_parse_transaction_hex() { + let testblockhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103"; + let testtxhash = "5b09bbb8d3cb2f8d4edbcf30664419fb7c9deaeeb1f62cb432e7741c80dbe5ba"; + + let mut testdatabytes = include_bytes!("testblock.in"); + let mut headerbytes = hex::encode(&testdatabytes[0..]); + let hbc = &headerbytes[0..80]; + + let mut txdata = &testdatabytes[80..]; + + let vilen = measure_variable_int(&txdata[0..9]).unwrap(); + let txnum = decode_variable_int(&txdata[0..9]).unwrap(); + + txdata = &txdata[vilen..]; + let tx = BitcoinTransaction::new(txdata.to_vec()); + + assert_eq!(tx.inputs.len(), 1); + assert_eq!(txnum, 22); + assert_eq!(tx.outputs.len(), 1); + assert_eq!(tx.version, 1); + } } diff --git a/programs/btc_spv_api/src/spv_state.rs b/programs/btc_spv_api/src/spv_state.rs index 1b3b184a7..681736792 100644 --- a/programs/btc_spv_api/src/spv_state.rs +++ b/programs/btc_spv_api/src/spv_state.rs @@ -71,9 +71,9 @@ impl BlockHeader { return Err(SpvError::InvalidBlockHeader); } - match decode_hex(header) { + match hex::decode(header) { Ok(header) => { - let bhbytes = decode_hex(blockhash)?; + let bhbytes = hex::decode(blockhash)?; const SIZE: usize = 80; let mut hh = [0; SIZE]; hh.copy_from_slice(&header[..header.len()]); @@ -95,33 +95,142 @@ impl BlockHeader { } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] -pub struct Transaction { - inputs: Vec, +pub struct BitcoinTransaction { + pub inputs: Vec, + + pub inputs_num: u64, //input utxos - outputs: Vec, + pub outputs: Vec, + + pub outputs_num: u64, //output utxos - version: u32, + pub version: u32, //bitcoin network version - locktime: u32, + pub lock_time: u32, + + pub bytes_len: usize, } -// impl Transaction { -// fn new(bytes: Vec) -> Self { -// //reinsert later -// } -// fn hexnew(hex: String) -> Self { -// //reinsert later -// } -// } +impl BitcoinTransaction { + pub fn new(txbytes: Vec) -> Self { + let mut ver: [u8; 4] = [0; 4]; + ver.copy_from_slice(&txbytes[..4]); + let version = u32::from_le_bytes(ver); + + let inputs_num: u64 = decode_variable_int(&txbytes[4..13]).unwrap(); + let vinlen: usize = measure_variable_int(&txbytes[4..13]).unwrap(); + let mut inputstart: usize = 4 + vinlen; + let mut inputs = Vec::new(); + + if inputs_num > 0 { + for i in 0..inputs_num { + let mut input = Input::new(txbytes[inputstart..].to_vec()); + inputstart += input.bytes_len; + inputs.push(input); + } + inputs.to_vec(); + } + + let outputs_num: u64 = decode_variable_int(&txbytes[inputstart..9 + inputstart]).unwrap(); + let voutlen: usize = measure_variable_int(&txbytes[inputstart..9 + inputstart]).unwrap(); + + let mut outputstart: usize = inputstart + voutlen; + let mut outputs = Vec::new(); + for i in 0..outputs_num { + let mut output = Output::new(txbytes[outputstart..].to_vec()); + outputstart += output.bytes_len; + outputs.push(output); + } + + let mut lt: [u8; 4] = [0; 4]; + lt.copy_from_slice(&txbytes[outputstart..4 + outputstart]); + let lock_time = u32::from_le_bytes(lt); + + assert_eq!(inputs.len(), inputs_num as usize); + assert_eq!(outputs.len(), outputs_num as usize); + + BitcoinTransaction { + inputs, + inputs_num, + outputs, + outputs_num, + version, + lock_time, + bytes_len: 4 + outputstart, + } + } + pub fn hexnew(hex: String) -> Result { + match hex::decode(&hex) { + Ok(txbytes) => Ok(BitcoinTransaction::new(txbytes)), + Err(e) => Err(SpvError::ParseError), + } + } +} #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Input { - r#type: InputType, + pub input_type: InputType, // Type of the input - position: u32, + pub position: u32, // position of the tx in its Block - txhash: BitcoinTxHash, + pub txhash: BitcoinTxHash, // hash of the transaction + pub script_length: u64, + // length of the spend script + pub script: Vec, + // script bytes + pub sequence: [u8; 4], + // length of the input in bytes + pub bytes_len: usize, +} + +impl Input { + fn new(ibytes: Vec) -> Self { + let mut txhash: [u8; 32] = [0; 32]; + txhash.copy_from_slice(&ibytes[..32]); + + let mut tx_out_index: [u8; 4] = [0; 4]; + tx_out_index.copy_from_slice(&ibytes[32..36]); + let position = u32::from_le_bytes(tx_out_index); + + let script_length: u64 = decode_variable_int(&ibytes[36..45]).unwrap(); + let script_length_len: usize = measure_variable_int(&ibytes[36..45]).unwrap(); + let script_start = 36 + script_length_len; //checkc for correctness + let script_end = script_start + script_length as usize; + let input_end = script_end + 4; + + let script: Vec = ibytes[script_start..script_length as usize].to_vec(); + + let mut sequence: [u8; 4] = [0; 4]; + sequence.copy_from_slice(&ibytes[script_end..input_end]); + + let input_type: InputType = InputType::NONE; // testing measure + + Self { + input_type, + position, + txhash, + script_length, + script, + sequence, + bytes_len: input_end, + } + } + + fn default() -> Self { + let txh: [u8; 32] = [0; 32]; + let seq: [u8; 4] = [0; 4]; + + Self { + input_type: InputType::NONE, + position: 55, + txhash: txh, + script_length: 45, + script: txh.to_vec(), + sequence: seq, + bytes_len: 123, + } + } } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -134,11 +243,53 @@ pub enum InputType { #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Output { - r#type: OutputType, + pub output_type: OutputType, // type of the output - value: u64, + pub value: u64, // amount of btc in sats - payload: Vec, // data sent with the transaction + pub script: Vec, + + pub script_length: u64, + + pub bytes_len: usize, + // payload: Option>, + // // data sent with the transaction (Op return) +} + +impl Output { + fn new(obytes: Vec) -> Self { + let mut val: [u8; 8] = [0; 8]; + val.copy_from_slice(&obytes[..8]); + let value: u64 = u64::from_le_bytes(val); + + let script_start: usize = 8 + measure_variable_int(&obytes[8..17]).unwrap(); + let script_length = decode_variable_int(&obytes[8..script_start]).unwrap(); + let script_end: usize = script_start + script_length as usize; + + let script = obytes[script_start..script_end].to_vec(); + + let output_type = OutputType::WPKH; // temporary hardcode + + Self { + output_type, + value, + script, + script_length, + bytes_len: script_end, + } + } + + fn default() -> Self { + let transaction_hash: [u8; 32] = [0; 32]; + + Self { + output_type: OutputType::WPKH, + value: 55, + script: transaction_hash.to_vec(), + script_length: 45, + bytes_len: 123, + } + } } #[allow(non_camel_case_types)] @@ -150,6 +301,7 @@ pub enum OutputType { PKH, SH, NONSTANDARD, + // https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md } pub type HeaderChain = Vec; @@ -221,7 +373,7 @@ pub struct Proof { // chain of bitcoin headers provifing context for the proof pub headers: HeaderChain, // transaction associated with the Proof - pub transaction: Transaction, + pub transaction: BitcoinTransaction, // public key of the request this proof corresponds to pub request: Pubkey, } @@ -232,6 +384,8 @@ pub enum AccountState { Request(ClientRequestInfo), // Verified Proof Verification(Proof), + // Account holds a HeaderStore structure + Headers(HeaderAccountInfo), // Account's userdata is Unallocated Unallocated, // Invalid @@ -253,6 +407,7 @@ pub enum SpvError { // header store write/read result is invalid ParseError, // other errors with parsing inputs + InvalidAccount, } impl error::Error for SpvError { @@ -274,6 +429,12 @@ impl From for SpvError { } } +impl From for SpvError { + fn from(e: hex::FromHexError) -> Self { + SpvError::ParseError + } +} + // impl fmt::Debug for SpvError { // fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{ // match self { @@ -290,6 +451,7 @@ impl fmt::Display for SpvError { SpvError::InvalidBlockHeader => "BlockHeader is malformed or does not apply ".fmt(f), SpvError::HeaderStoreError => "Placeholder headerstore error text".fmt(f), SpvError::ParseError => "Error parsing blockheaders placceholder text".fmt(f), + SpvError::InvalidAccount => "Provided account is not usable or does not exist".fmt(f), } } } diff --git a/programs/btc_spv_api/src/testblock.in b/programs/btc_spv_api/src/testblock.in new file mode 100644 index 0000000000000000000000000000000000000000..01e7fb15be8800557b08db1bc66105a4a98331dd GIT binary patch literal 9195 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 j3 for DecodeHexError { impl fmt::Display for DecodeHexError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - DecodeHexError::OddLength => "input hex string length is odd ".fmt(f), + DecodeHexError::InvalidLength(LengthError::OddLength) => { + "input hex string length is odd ".fmt(f) + } + DecodeHexError::InvalidLength(LengthError::Maximum(e)) => { + "input exceeds the maximum length".fmt(f) + } + DecodeHexError::InvalidLength(LengthError::Minimum(e)) => { + "input does not meet the minimum length".fmt(f) + } DecodeHexError::ParseInt(e) => e.fmt(f), } } } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum LengthError { + OddLength, + Maximum(u32), + Minimum(u32), +} + pub fn decode_hex(s: &str) -> Result, DecodeHexError> { if s.len() % 2 != 0 { - Err(DecodeHexError::OddLength) + Err(DecodeHexError::InvalidLength(LengthError::OddLength)) } else { (0..s.len()) .step_by(2) @@ -31,3 +47,41 @@ pub fn decode_hex(s: &str) -> Result, DecodeHexError> { .collect() } } + +pub fn measure_variable_int(vint: &[u8]) -> Result { + let ln = vint.len(); + if ln > 9 { + return Err(DecodeHexError::InvalidLength(LengthError::Maximum(9))); + } + + let val: usize = match vint[0] { + 0..=252 => 1, + 253 => 2, + 254 => 5, + 255 => 9, + }; + Ok(val) +} + +pub fn decode_variable_int(vint: &[u8]) -> Result { + let ln = vint.len(); + if ln > 9 { + return Err(DecodeHexError::InvalidLength(LengthError::Maximum(9))); + } + + let val: u64 = match vint[0] { + 0..=252 => u64::from(vint[0]), + 253 => u64::from(vint[1]), + 254 => { + let mut val: [u8; 4] = [0; 4]; + val.copy_from_slice(&vint[1..5]); + u64::from(u32::from_le_bytes(val)) + } + 255 => { + let mut val: [u8; 8] = [0; 8]; + val.copy_from_slice(&vint[1..9]); + u64::from_le_bytes(val) + } + }; + Ok(val) +} diff --git a/programs/btc_spv_bin/Cargo.toml b/programs/btc_spv_bin/Cargo.toml index 7c9e4d479..801976bbb 100644 --- a/programs/btc_spv_bin/Cargo.toml +++ b/programs/btc_spv_bin/Cargo.toml @@ -10,7 +10,12 @@ serde="1.0.100" serde_derive="1.0.100" serde_json = "1.0.40" ureq = { version = "0.11.1", features = ["json"] } +hex = "0.3.2" [[bin]] name = "blockheaders" path = "src/blockheade.rs" + +[[bin]] +name = "blocks" +path = "src/block.rs" diff --git a/programs/btc_spv_bin/src/block.rs b/programs/btc_spv_bin/src/block.rs new file mode 100644 index 000000000..6d2e6012f --- /dev/null +++ b/programs/btc_spv_bin/src/block.rs @@ -0,0 +1,46 @@ +use clap; +use clap::{App, Arg}; +use hex; +use std::fs::File; +use std::io::prelude::*; + +fn get_block_raw(hash: &str) -> String { + let qs = format!("https://blockchain.info/block/{}?format=hex", hash); + let body = ureq::get(&qs).call(); + if body.error() { + panic!("request failed"); + } else { + let textbh: String = body.into_string().unwrap(); + textbh + } +} + +fn write_file(fname: String, bytes: &[u8]) -> std::io::Result<()> { + let mut buffer = File::create(fname)?; + buffer.write_all(bytes)?; + Ok(()) +} + +fn main() { + let matches = App::new("header fetch util") + .arg(Arg::with_name("blockhash")) + .arg(Arg::with_name("output")) + .help("block hash to get header from") + .get_matches(); + + let default_output = "file"; + let output = matches.value_of("output").unwrap_or(default_output); + + let testhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103"; + let blockhash = matches.value_of("blockhash").unwrap_or(testhash); + let blockraw = get_block_raw(&blockhash); + + if default_output == output { + let fname = format!("block-{}.in", blockhash); + let outf = hex::decode(&blockraw).unwrap(); + let arr = &outf[0..]; + write_file(fname, arr).unwrap(); + } else { + println!("{}", blockraw); + } +}