From 755fb454eb579f12bba0200a191255a6c7fb50b3 Mon Sep 17 00:00:00 2001 From: Tamas Blummer Date: Sat, 10 Mar 2018 15:35:49 +0100 Subject: [PATCH] integration with bitcoinconsenus --- Cargo.toml | 5 ++++ README.md | 21 ++++++++++----- src/blockdata/script.rs | 50 ++++++++++++++++++++++++++++++++++++ src/blockdata/transaction.rs | 47 +++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 5 files changed, 117 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48cc236a..cc5fc7c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ readme = "README.md" name = "bitcoin" path = "src/lib.rs" +[features] +bitcoinconsenus = ["bitcoinconsensus"] + [dependencies] byteorder = "1.1" rand = "0.3" @@ -22,8 +25,10 @@ rust-crypto = "0.2" rustc-serialize = "0.3" serde = "0.6" strason = "0.3" +bitcoinconsensus = { version = "0.16", optional=true } [dependencies.secp256k1] version = "0.8" features = [ "rand", "serde" ] + diff --git a/README.md b/README.md index 7613ccb9..153e1798 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ Supports (or should support) * De/serialization of Bitcoin protocol network messages * De/serialization of blocks and transactions * Script de/serialization -* Blockchain validation * Private keys and address creation, de/serialization and validation (including full BIP32 support) * Pay-to-contract support as in Appendix A of the [Blockstream sidechains whitepaper](https://www.blockstream.com/sidechains.pdf) @@ -27,7 +26,7 @@ To use rust-bitcoin, just add the following to your Cargo.toml. ```toml [dependencies] -bitcoin = "0.10" +bitcoin = "0.12" ``` # Known limitations @@ -35,17 +34,13 @@ bitcoin = "0.10" ## Consensus This library **must not** be used for consensus code (i.e. fully validating -blockchain data). It technically supports doing this, using the feature-gated -script parser, but doing so is very +blockchain data). It technically supports doing this, but doing so is very ill-advised because there are many deviations, known and unknown, between this library and the Bitcoin Core reference implementation. In a consensus based cryptocurrency such as Bitcoin it is critical that all parties are using the same rules to validate data, and this library is simply unable to implement the same rules as Core. -The script interpreter is now gated behind the `broken_consensus_code` flag -for this reason. - Given the complexity of both C++ and Rust, it is unlikely that this will ever be fixed, and there are no plans to do so. Of course, patches to fix specific consensus incompatibilities are welcome. @@ -81,3 +76,15 @@ what they need to change. Remove `num` dependency at Matt's request; agree this is obnoxious to require all downstream users to also have a `num` dependency just so they can use `Uint256::from_u64`. +### 0.12 + +* The in-memory blockchain was moved into a dedicated project rust-bitcoin-chain. + +* Removed old script interpreter + +* A new optional feature "bitcoinconsenus" lets this library use Bitcoin Core's native +script verifier, wrappend into Rust by the rust-bitcoinconsenus project. +See Transaction::verify and Script::verify methods. + +* Replaced Base58 traits with encode_slice, check_encode_slice, from and from_check functions in the base58 module. + diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs index e74144c1..5fdba177 100644 --- a/src/blockdata/script.rs +++ b/src/blockdata/script.rs @@ -35,6 +35,9 @@ use blockdata::opcodes; use network::encodable::{ConsensusDecodable, ConsensusEncodable}; use network::serialize::{SimpleDecoder, SimpleEncoder}; use util::hash::Hash160; +#[cfg(feature="bitcoinconsensus")] use bitcoinconsensus; +#[cfg(feature="bitcoinconsensus")] use std::convert; +#[cfg(feature="bitcoinconsensus")] use util::hash::Sha256dHash; #[derive(Clone, PartialEq, Eq, Hash)] /// A Bitcoin script @@ -150,6 +153,18 @@ pub enum Error { EarlyEndOfScript, /// Tried to read an array off the stack as a number when it was more than 4 bytes NumericOverflow, + #[cfg(feature="bitcoinconsensus")] + /// Error validating the script with bitcoinconsensus library + BitcoinConsensus(bitcoinconsensus::Error), + #[cfg(feature="bitcoinconsensus")] + /// Can not find the spent transaction + UnknownSpentTransaction(Sha256dHash), + #[cfg(feature="bitcoinconsensus")] + /// The spent transaction does not have the referred output + WrongSpentOutputIndex(usize), + #[cfg(feature="bitcoinconsensus")] + /// Can not serialize the spending transaction + SerializationError } impl fmt::Display for Error { @@ -165,10 +180,26 @@ impl error::Error for Error { match *self { Error::EarlyEndOfScript => "unexpected end of script", Error::NumericOverflow => "numeric overflow (number on stack larger than 4 bytes)", + #[cfg(feature="bitcoinconsensus")] + Error::BitcoinConsensus(ref _n) => "bitcoinconsenus verification failed", + #[cfg(feature="bitcoinconsensus")] + Error::UnknownSpentTransaction (ref _hash) => "unknown transaction referred in Transaction::verify()", + #[cfg(feature="bitcoinconsensus")] + Error::WrongSpentOutputIndex(ref _ix) => "unknown output index {} referred in Transaction::verify()", + #[cfg(feature="bitcoinconsensus")] + Error::SerializationError => "can not serialize the spending transaction in Transaction::verify()", } } } +#[cfg(feature="bitcoinconsensus")] +impl convert::From for Error { + fn from(err: bitcoinconsensus::Error) -> Error { + match err { + _ => Error::BitcoinConsensus(err) + } + } +} /// Helper to encode an integer in script format fn build_scriptint(n: i64) -> Vec { if n == 0 { return vec![] } @@ -310,6 +341,16 @@ impl Script { !self.0.is_empty() && (opcodes::All::from(self.0[0]).classify() == opcodes::Class::ReturnOp || opcodes::All::from(self.0[0]).classify() == opcodes::Class::IllegalOp) } + + #[cfg(feature="bitcoinconsensus")] + /// verify spend of an input script + /// # Parameters + /// * index - the index of the output holding this script in its own transaction + /// * amount - the amount this script guards + /// * spending - the transaction that attempts to spend the output holding this script + pub fn verify (&self, index: usize, amount: u64, spending: &[u8]) -> Result<(), Error> { + Ok(bitcoinconsensus::verify (&self.0[..], amount, spending, index)?) + } } impl Default for Script { @@ -690,5 +731,14 @@ mod test { assert_eq!(redeem_script.to_v0_p2wsh(), expected_witout); assert_eq!(redeem_script.to_v0_p2wsh().to_p2sh(), expected_out); } + + #[test] + #[cfg(feature="bitcoinconsensus")] + fn test_bitcoinconsensus () { + // a random segwit transaction from the blockchain using native segwit + let spent = Builder::from("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d".from_hex().unwrap()).into_script(); + let spending = "010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000".from_hex().unwrap(); + spent.verify(0, 18393430, spending.as_slice()).unwrap(); + } } diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index fedcf581..4fd4d122 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -26,9 +26,11 @@ use byteorder::{LittleEndian, WriteBytesExt}; use std::default::Default; use std::fmt; +#[cfg(feature="bitcoinconsensus")] use std::collections::HashMap; use serde; use util::hash::Sha256dHash; +#[cfg(feature="bitcoinconsensus")] use blockdata::script; use blockdata::script::Script; use network::serialize::{serialize, BitcoinHash, SimpleEncoder, SimpleDecoder}; use network::encodable::{ConsensusEncodable, ConsensusDecodable}; @@ -202,6 +204,28 @@ impl Transaction { raw_vec.write_u32::(sighash_u32).unwrap(); Sha256dHash::from_data(&raw_vec) } + + #[cfg(feature="bitcoinconsensus")] + /// Verify that this transaction is able to spend some outputs of spent transactions + pub fn verify (&self, spent : &HashMap) -> Result<(), script::Error> { + if let Ok(tx) = serialize(&*self) { + for input in &self.input { + if let Some(ref s) = spent.get(&input.prev_hash) { + if let Some(ref output) = s.output.get(input.prev_index as usize) { + input.script_sig.verify(input.prev_index as usize, output.value, tx.as_slice())?; + } else { + return Err(script::Error::WrongSpentOutputIndex(input.prev_index as usize)); + } + } else { + return Err(script::Error::UnknownSpentTransaction(input.prev_hash)); + } + } + Ok(()) + } + else { + Err(script::Error::SerializationError) + } + } } impl BitcoinHash for Transaction { @@ -783,5 +807,28 @@ mod tests { run_test_sighash("b3cad3a7041c2c17d90a2cd994f6c37307753fa3635e9ef05ab8b1ff121ca11239a0902e700300000009ab635300006aac5163ffffffffcec91722c7468156dce4664f3c783afef147f0e6f80739c83b5f09d5a09a57040200000004516a6552ffffffff969d1c6daf8ef53a70b7cdf1b4102fb3240055a8eaeaed2489617cd84cfd56cf020000000352ab53ffffffff46598b6579494a77b593681c33422a99559b9993d77ca2fa97833508b0c169f80200000009655300655365516351ffffffff04d7ddf800000000000853536a65ac6351ab09f3420300000000056aab65abac33589d04000000000952656a65655151acac944d6f0400000000006a8004ba", "005165", 1, 1035865506, "fe1dc9e8554deecf8f50c417c670b839cc9d650722ebaaf36572418756075d58"); run_test_sighash("cf781855040a755f5ba85eef93837236b34a5d3daeb2dbbdcf58bb811828d806ed05754ab8010000000351ac53ffffffffda1e264727cf55c67f06ebcc56dfe7fa12ac2a994fecd0180ce09ee15c480f7d00000000096351516a51acac00ab53dd49ff9f334befd6d6f87f1a832cddfd826a90b78fd8cf19a52cb8287788af94e939d6020000000700525251ac526310d54a7e8900ed633f0f6f0841145aae7ee0cbbb1e2a0cae724ee4558dbabfdc58ba6855010000000552536a53abfd1b101102c51f910500000000096300656a525252656a300bee010000000009ac52005263635151abe19235c9", "53005365", 2, 1422854188, "d5981bd4467817c1330da72ddb8760d6c2556cd809264b2d85e6d274609fc3a3"); } + + #[test] + #[cfg(feature="bitcoinconsensus")] + fn test_transaction_verify () { + use serialize::hex::FromHex; + use std::collections::HashMap; + // a random recent segwit transaction from blockchain using both old and segwit inputs + let spending: Transaction = deserialize("020000000001031cfbc8f54fbfa4a33a30068841371f80dbfe166211242213188428f437445c91000000006a47304402206fbcec8d2d2e740d824d3d36cc345b37d9f65d665a99f5bd5c9e8d42270a03a8022013959632492332200c2908459547bf8dbf97c65ab1a28dec377d6f1d41d3d63e012103d7279dfb90ce17fe139ba60a7c41ddf605b25e1c07a4ddcb9dfef4e7d6710f48feffffff476222484f5e35b3f0e43f65fc76e21d8be7818dd6a989c160b1e5039b7835fc00000000171600140914414d3c94af70ac7e25407b0689e0baa10c77feffffffa83d954a62568bbc99cc644c62eb7383d7c2a2563041a0aeb891a6a4055895570000000017160014795d04cc2d4f31480d9a3710993fbd80d04301dffeffffff06fef72f000000000017a91476fd7035cd26f1a32a5ab979e056713aac25796887a5000f00000000001976a914b8332d502a529571c6af4be66399cd33379071c588ac3fda0500000000001976a914fc1d692f8de10ae33295f090bea5fe49527d975c88ac522e1b00000000001976a914808406b54d1044c429ac54c0e189b0d8061667e088ac6eb68501000000001976a914dfab6085f3a8fb3e6710206a5a959313c5618f4d88acbba20000000000001976a914eb3026552d7e3f3073457d0bee5d4757de48160d88ac0002483045022100bee24b63212939d33d513e767bc79300051f7a0d433c3fcf1e0e3bf03b9eb1d70220588dc45a9ce3a939103b4459ce47500b64e23ab118dfc03c9caa7d6bfc32b9c601210354fd80328da0f9ae6eef2b3a81f74f9a6f66761fadf96f1d1d22b1fd6845876402483045022100e29c7e3a5efc10da6269e5fc20b6a1cb8beb92130cc52c67e46ef40aaa5cac5f0220644dd1b049727d991aece98a105563416e10a5ac4221abac7d16931842d5c322012103960b87412d6e169f30e12106bdf70122aabb9eb61f455518322a18b920a4dfa887d30700" + .from_hex().unwrap().as_slice()).unwrap(); + let spent1: Transaction = deserialize("020000000001040aacd2c49f5f3c0968cfa8caf9d5761436d95385252e3abb4de8f5dcf8a582f20000000017160014bcadb2baea98af0d9a902e53a7e9adff43b191e9feffffff96cd3c93cac3db114aafe753122bd7d1afa5aa4155ae04b3256344ecca69d72001000000171600141d9984579ceb5c67ebfbfb47124f056662fe7adbfeffffffc878dd74d3a44072eae6178bb94b9253177db1a5aaa6d068eb0e4db7631762e20000000017160014df2a48cdc53dae1aba7aa71cb1f9de089d75aac3feffffffe49f99275bc8363f5f593f4eec371c51f62c34ff11cc6d8d778787d340d6896c0100000017160014229b3b297a0587e03375ab4174ef56eeb0968735feffffff03360d0f00000000001976a9149f44b06f6ee92ddbc4686f71afe528c09727a5c788ac24281b00000000001976a9140277b4f68ff20307a2a9f9b4487a38b501eb955888ac227c0000000000001976a9148020cd422f55eef8747a9d418f5441030f7c9c7788ac0247304402204aa3bd9682f9a8e101505f6358aacd1749ecf53a62b8370b97d59243b3d6984f02200384ad449870b0e6e89c92505880411285ecd41cf11e7439b973f13bad97e53901210205b392ffcb83124b1c7ce6dd594688198ef600d34500a7f3552d67947bbe392802473044022033dfd8d190a4ae36b9f60999b217c775b96eb10dee3a1ff50fb6a75325719106022005872e4e36d194e49ced2ebcf8bb9d843d842e7b7e0eb042f4028396088d292f012103c9d7cbf369410b090480de2aa15c6c73d91b9ffa7d88b90724614b70be41e98e0247304402207d952de9e59e4684efed069797e3e2d993e9f98ec8a9ccd599de43005fe3f713022076d190cc93d9513fc061b1ba565afac574e02027c9efbfa1d7b71ab8dbb21e0501210313ad44bc030cc6cb111798c2bf3d2139418d751c1e79ec4e837ce360cc03b97a024730440220029e75edb5e9413eb98d684d62a077b17fa5b7cc19349c1e8cc6c4733b7b7452022048d4b9cae594f03741029ff841e35996ef233701c1ea9aa55c301362ea2e2f68012103590657108a72feb8dc1dec022cf6a230bb23dc7aaa52f4032384853b9f8388baf9d20700" + .from_hex().unwrap().as_slice()).unwrap(); + let spent2: Transaction = deserialize("0200000000010166c3d39490dc827a2594c7b17b7d37445e1f4b372179649cd2ce4475e3641bbb0100000017160014e69aa750e9bff1aca1e32e57328b641b611fc817fdffffff01e87c5d010000000017a914f3890da1b99e44cd3d52f7bcea6a1351658ea7be87024830450221009eb97597953dc288de30060ba02d4e91b2bde1af2ecf679c7f5ab5989549aa8002202a98f8c3bd1a5a31c0d72950dd6e2e3870c6c5819a6c3db740e91ebbbc5ef4800121023f3d3b8e74b807e32217dea2c75c8d0bd46b8665b3a2d9b3cb310959de52a09bc9d20700" + .from_hex().unwrap().as_slice()).unwrap(); + let spent3: Transaction = deserialize("01000000027a1120a30cef95422638e8dab9dedf720ec614b1b21e451a4957a5969afb869d000000006a47304402200ecc318a829a6cad4aa9db152adbf09b0cd2de36f47b53f5dade3bc7ef086ca702205722cda7404edd6012eedd79b2d6f24c0a0c657df1a442d0a2166614fb164a4701210372f4b97b34e9c408741cd1fc97bcc7ffdda6941213ccfde1cb4075c0f17aab06ffffffffc23b43e5a18e5a66087c0d5e64d58e8e21fcf83ce3f5e4f7ecb902b0e80a7fb6010000006b483045022100f10076a0ea4b4cf8816ed27a1065883efca230933bf2ff81d5db6258691ff75202206b001ef87624e76244377f57f0c84bc5127d0dd3f6e0ef28b276f176badb223a01210309a3a61776afd39de4ed29b622cd399d99ecd942909c36a8696cfd22fc5b5a1affffffff0200127a000000000017a914f895e1dd9b29cb228e9b06a15204e3b57feaf7cc8769311d09000000001976a9144d00da12aaa51849d2583ae64525d4a06cd70fde88ac00000000" + .from_hex().unwrap().as_slice()).unwrap(); + + let mut spent = HashMap::new(); + spent.insert(spent1.txid(), spent1); + spent.insert(spent2.txid(), spent2); + spent.insert(spent3.txid(), spent3); + + spending.verify (&spent).unwrap(); + } } diff --git a/src/lib.rs b/src/lib.rs index 38c809f9..97c89611 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ extern crate secp256k1; extern crate serde; extern crate strason; #[cfg(all(test, feature = "unstable"))] extern crate test; +#[cfg(feature="bitcoinconsensus")] extern crate bitcoinconsensus; #[cfg(test)] #[macro_use]