Advertisement
coolMIB

sigdump.rs

Jun 25th, 2025
4,586
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Rust 14.07 KB | Cryptocurrency | 0 0
  1. use std::fs::{self, File};
  2. use std::io::{BufWriter, Write};
  3. use std::path::{PathBuf};
  4.  
  5. use clap::{App, Arg, ArgMatches, SubCommand};
  6. use ecdsa::Signature;
  7. use p256::{
  8.     NistP256, NonZeroScalar,
  9. };
  10. use bitcoin_explorer::{BitcoinDB, Txid, Transaction, FromHex};
  11.  
  12. use blockchain::proto::script::ScriptPattern::ScriptSig;
  13. use blockchain::proto::ToRaw;
  14. use blockchain::proto::tx::TxOutpoint;
  15. use blockchain::proto::varuint::VarUint;
  16.  
  17. use crate::blockchain::parser::types::CoinType;
  18. use crate::blockchain::proto::block::Block;
  19. use crate::blockchain::proto::tx::{EvaluatedTx, EvaluatedTxOut, TxInput};
  20. use crate::blockchain::proto::Hashed;
  21. use crate::callbacks::Callback;
  22. use crate::common::utils;
  23. use crate::errors::OpResult;
  24.  
  25. /// Dumps the whole blockchain into csv files
  26. pub struct SigDump {
  27.     // Each structure gets stored in a separate csv file
  28.     dump_folder: PathBuf,
  29.     sig_writer: BufWriter<File>,
  30.  
  31.     start_height: u64,
  32.     end_height: u64,
  33.     tx_count: u64,
  34.     in_count: u64,
  35.     out_count: u64,
  36.     blocks_count: u64,
  37.     db: BitcoinDB,
  38. }
  39.  
  40. impl SigDump {
  41.     fn create_writer(cap: usize, path: PathBuf) -> OpResult<BufWriter<File>> {
  42.         Ok(BufWriter::with_capacity(cap, File::create(&path)?))
  43.     }
  44.  
  45.     fn get_previous_outputs(&mut self, previous_txid: Vec<u8>) -> Option<Vec<Vec<u8>>> {
  46.         let txid_str = utils::arr_to_hex_swapped(&previous_txid);
  47.         let txid = Txid::from_hex(&txid_str).ok()?;
  48.         let tx: Transaction = self.db.get_transaction(&txid).ok()?;
  49.  
  50.         let mut outputs: Vec<Vec<u8>> = Vec::new();
  51.  
  52.         for output in tx.output {
  53.             outputs.push(output.script_pubkey.to_bytes());
  54.         }
  55.  
  56.         Some(outputs)
  57.     }
  58. }
  59.  
  60.  
  61. impl Callback for SigDump {
  62.     fn build_subcommand<'a, 'b>() -> App<'a, 'b>
  63.         where
  64.             Self: Sized,
  65.     {
  66.         SubCommand::with_name("sigdump")
  67.             .about("Dumps signatures to CSV file")
  68.             .version("0.1")
  69.             .author("Nils Amiet <[email protected]>")
  70.             .arg(
  71.                 Arg::with_name("dump-folder")
  72.                     .help("Folder to store csv files")
  73.                     .index(1)
  74.                     .required(true),
  75.             )
  76.             .arg(
  77.                 Arg::with_name("bitcoin-folder")
  78.                     .help("Path to the .bitcoin folder")
  79.                     .index(2)
  80.                     .required(true),
  81.             )
  82.     }
  83.  
  84.     fn new(matches: &ArgMatches) -> OpResult<Self>
  85.         where
  86.             Self: Sized,
  87.     {
  88.         let dump_folder = &PathBuf::from(matches.value_of("dump-folder").unwrap());
  89.         if !dump_folder.exists() {
  90.             eprintln!("Dump folder {} does not exist. Attempting to create it...", dump_folder.display());
  91.             match fs::create_dir_all(dump_folder) {
  92.                 Ok(_) => {
  93.                     eprintln!("Successfully created dump folder.")
  94.                 }
  95.                 Err(_) => {
  96.                     panic!("Failed to create dump folder: {}", dump_folder.display());
  97.                 }
  98.             }
  99.         }
  100.         let bitcoin_folder = &PathBuf::from(matches.value_of("bitcoin-folder").unwrap());
  101.         let cap = 4000000;
  102.         let db = BitcoinDB::new(bitcoin_folder, true).expect("Failure to create Bitcoin DB");
  103.         let cb = SigDump {
  104.             dump_folder: PathBuf::from(dump_folder),
  105.             sig_writer: SigDump::create_writer(cap, dump_folder.join("signatures.csv.tmp"))?,
  106.             start_height: 0,
  107.             end_height: 0,
  108.             tx_count: 0,
  109.             in_count: 0,
  110.             out_count: 0,
  111.             blocks_count: 0,
  112.             db,
  113.         };
  114.         Ok(cb)
  115.     }
  116.  
  117.     fn on_start(&mut self, _: &CoinType, block_height: u64) -> OpResult<()> {
  118.         self.start_height = block_height;
  119.         info!(target: "callback", "Using `sigdump` with dump folder: {} ...", &self.dump_folder.display());
  120.         Ok(())
  121.     }
  122.  
  123.     fn on_block(&mut self, block: &Block, _block_height: u64) -> OpResult<()> {
  124.         self.blocks_count += 1;
  125.         let block_time: u32 = block.header.value.timestamp;
  126.  
  127.         for tx in &block.txs {
  128.             let txid_str = utils::arr_to_hex_swapped(&tx.hash);
  129.  
  130.             let mut message_to_be_signed: Vec<u8> = Vec::new();
  131.             let version = tx.value.version.to_le_bytes();
  132.             let tx_in_count = tx.value.in_count.clone().to_bytes();
  133.             message_to_be_signed.extend_from_slice(&version);
  134.             message_to_be_signed.extend_from_slice(&tx_in_count);
  135.  
  136.             // serialize inputs
  137.             let mut input_index = 0;
  138.             for input in &tx.value.inputs {
  139.                 match &input.script.pattern {
  140.                     ScriptSig(sig, pubkey) => {
  141.                         // actually parse signature
  142.                         let only_sig = &sig[..sig.len() - 1];
  143.                         let hash_type: u8 = sig[sig.len() - 1];
  144.                         match Signature::<NistP256>::from_der(only_sig) {
  145.                             Ok(esig) => {
  146.                                 let r: NonZeroScalar = esig.r();
  147.                                 let s: NonZeroScalar = esig.s();
  148.  
  149.                                 // make a copy of message to be signed
  150.                                 let mut tbs_message = message_to_be_signed.clone();
  151.  
  152.                                 // build modified inputs and add them to message to be signed
  153.                                 let mut raw_input_index = 0;
  154.                                 for raw_input in &tx.value.inputs {
  155.                                     let mut r_input = TxInput {
  156.                                         outpoint: TxOutpoint {
  157.                                             txid: raw_input.input.outpoint.txid,
  158.                                             index: raw_input.input.outpoint.index,
  159.                                         },
  160.                                         script_len: 0u8.into(),
  161.                                         script_sig: [].to_vec(),
  162.                                         seq_no: raw_input.input.seq_no,
  163.                                     };
  164.  
  165.                                     if raw_input_index == input_index {
  166.                                         // replace script_len and script_sig with script from previous output
  167.                                         let previous_output_txid = raw_input.input.outpoint.txid.to_vec();
  168.                                         let previous_output_index = raw_input.input.outpoint.index as usize;
  169.  
  170.                                         let previous_outputs = self.get_previous_outputs(previous_output_txid);
  171.                                         let empty_script = vec![];
  172.                                         let subscript = match &previous_outputs {
  173.                                             Some(prev_outs) => &prev_outs[previous_output_index],
  174.                                             None => {
  175.                                                 &empty_script
  176.                                             }
  177.                                         };
  178.  
  179.                                         let script_len = VarUint::from(subscript.len() as u8);
  180.                                         r_input.script_sig = subscript.clone();
  181.                                         r_input.script_len = script_len;
  182.                                     }
  183.  
  184.                                     // add modified input to message to be signed
  185.                                     let mut input_bytes = Vec::with_capacity(36 + 5 + r_input.script_len.value as usize + 4);
  186.                                     let outpoint_txid = r_input.outpoint.txid;
  187.                                     let outpoint_index = r_input.outpoint.index.to_le_bytes();
  188.                                     let script_len = r_input.script_len.to_bytes();
  189.                                     let script_sig = r_input.script_sig.clone();
  190.                                     let sequence = r_input.seq_no.to_le_bytes();
  191.                                     input_bytes.extend_from_slice(&outpoint_txid);
  192.                                     input_bytes.extend_from_slice(&outpoint_index);
  193.                                     input_bytes.extend_from_slice(&script_len);
  194.                                     input_bytes.extend_from_slice(&script_sig);
  195.                                     input_bytes.extend_from_slice(&sequence);
  196.  
  197.                                     tbs_message.extend_from_slice(&input_bytes);
  198.                                     raw_input_index += 1;
  199.                                 }
  200.  
  201.                                 // add number of outputs to message to be signed
  202.                                 let tx_out_count = tx.value.out_count.clone().to_bytes();
  203.                                 tbs_message.extend_from_slice(&tx_out_count);
  204.  
  205.                                 // add outputs to message to be signed
  206.                                 for output in &tx.value.outputs {
  207.                                     let output_value = output.out.value.to_le_bytes();
  208.                                     let script_len = output.out.script_len.to_bytes();
  209.                                     let pubkey_script = output.out.script_pubkey.clone();
  210.                                     let mut output_bytes = Vec::with_capacity(8 + 5 + output.out.script_len.value as usize);
  211.                                     output_bytes.extend_from_slice(&output_value);
  212.                                     output_bytes.extend_from_slice(&script_len);
  213.                                     output_bytes.extend_from_slice(&pubkey_script);
  214.  
  215.                                     tbs_message.extend_from_slice(&output_bytes);
  216.                                 }
  217.  
  218.                                 // finalize message hash
  219.                                 let locktime = tx.value.locktime.to_le_bytes().to_vec();
  220.                                 let hash_code_type = (hash_type as u32).to_le_bytes().to_vec();
  221.                                 tbs_message.extend_from_slice(&locktime);
  222.                                 tbs_message.extend_from_slice(&hash_code_type);
  223.  
  224.                                 // double sha256
  225.                                 let mut message_hash = utils::sha256(&tbs_message);
  226.                                 message_hash = utils::sha256(&message_hash);
  227.  
  228.                                 let message_hash_str = utils::arr_to_hex(&message_hash);
  229.  
  230.                                 self.sig_writer
  231.                                     .write_all(
  232.                                         input.as_csv(
  233.                                             r, s, pubkey, &txid_str, message_hash_str, block_time,
  234.                                         ).as_bytes())?;
  235.                             }
  236.                             Err(_e) => {}
  237.                         }
  238.                     }
  239.                     _ => {}
  240.                 }
  241.                 input_index += 1;
  242.             } // end for input
  243.             self.in_count += tx.value.in_count.value;
  244.             self.out_count += tx.value.out_count.value;
  245.         } // end for tx
  246.         self.tx_count += block.tx_count.value;
  247.         Ok(())
  248.     }
  249.  
  250.     fn on_complete(&mut self, block_height: u64) -> OpResult<()> {
  251.         self.end_height = block_height;
  252.  
  253.         // Keep in sync with c'tor
  254.         for f in &["signatures"] {
  255.             // Rename temp files
  256.             fs::rename(
  257.                 self.dump_folder.as_path().join(format!("{}.csv.tmp", f)),
  258.                 self.dump_folder.as_path().join(format!(
  259.                     "{}-{}-{}.csv",
  260.                     f, self.start_height, self.end_height
  261.                 )),
  262.             )?;
  263.         }
  264.  
  265.         info!(target: "callback", "Done.\nDumped all {} blocks:\n\
  266.                                   \t-> transactions: {:9}\n\
  267.                                   \t-> inputs:       {:9}\n\
  268.                                   \t-> outputs:      {:9}",
  269.              self.end_height, self.tx_count, self.in_count, self.out_count);
  270.         Ok(())
  271.     }
  272. }
  273.  
  274. impl Block {
  275.     #[inline]
  276.     pub fn as_csv(&self, block_height: u64) -> String {
  277.         // (@hash, height, version, blocksize, @hashPrev, @hashMerkleRoot, nTime, nBits, nNonce)
  278.         format!(
  279.             "{};{};{};{};{};{};{};{};{}\n",
  280.             &utils::arr_to_hex_swapped(&self.header.hash),
  281.             &block_height,
  282.             &self.header.value.version,
  283.             &self.size,
  284.             &utils::arr_to_hex_swapped(&self.header.value.prev_hash),
  285.             &utils::arr_to_hex_swapped(&self.header.value.merkle_root),
  286.             &self.header.value.timestamp,
  287.             &self.header.value.bits,
  288.             &self.header.value.nonce
  289.         )
  290.     }
  291. }
  292.  
  293. impl Hashed<EvaluatedTx> {
  294.     #[inline]
  295.     pub fn as_csv(&self, block_hash: &str) -> String {
  296.         // (@txid, @hashBlock, version, lockTime)
  297.         format!(
  298.             "{};{};{};{}\n",
  299.             &utils::arr_to_hex_swapped(&self.hash),
  300.             &block_hash,
  301.             &self.value.version,
  302.             &self.value.locktime
  303.         )
  304.     }
  305. }
  306.  
  307. impl TxInput {
  308.     #[inline]
  309.     pub fn as_csv(&self, txid: &str) -> String {
  310.         // (@txid, @hashPrevOut, indexPrevOut, scriptSig, sequence)
  311.         format!(
  312.             "{};{};{};{};{}\n",
  313.             &txid,
  314.             &utils::arr_to_hex_swapped(&self.outpoint.txid),
  315.             &self.outpoint.index,
  316.             &utils::arr_to_hex(&self.script_sig),
  317.             &self.seq_no
  318.         )
  319.     }
  320. }
  321.  
  322. impl EvaluatedTxOut {
  323.     #[inline]
  324.     pub fn as_csv(&self, txid: &str, index: u32) -> String {
  325.         let address = match self.script.address.clone() {
  326.             Some(address) => address,
  327.             None => {
  328.                 debug!(target: "sigdump", "Unable to evaluate address for utxo in txid: {} ({})", txid, self.script.pattern);
  329.                 String::new()
  330.             }
  331.         };
  332.  
  333.         // (@txid, indexOut, value, @scriptPubKey, address)
  334.         format!(
  335.             "{};{};{};{};{}\n",
  336.             &txid,
  337.             &index,
  338.             &self.out.value,
  339.             &utils::arr_to_hex(&self.out.script_pubkey),
  340.             &address
  341.         )
  342.     }
  343. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement