Skip to content

Commit

Permalink
integration with bitcoinconsenus
Browse files Browse the repository at this point in the history
  • Loading branch information
tamasblummer committed Mar 12, 2018
1 parent cdeff99 commit 755fb45
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 7 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ readme = "README.md"
name = "bitcoin"
path = "src/lib.rs"

[features]
bitcoinconsenus = ["bitcoinconsensus"]

[dependencies]
byteorder = "1.1"
rand = "0.3"
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" ]


21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -27,25 +26,21 @@ To use rust-bitcoin, just add the following to your Cargo.toml.

```toml
[dependencies]
bitcoin = "0.10"
bitcoin = "0.12"
```

# Known limitations

## 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.
Expand Down Expand Up @@ -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.

50 changes: 50 additions & 0 deletions src/blockdata/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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<bitcoinconsensus::Error> 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<u8> {
if n == 0 { return vec![] }
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
}
}

47 changes: 47 additions & 0 deletions src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -202,6 +204,28 @@ impl Transaction {
raw_vec.write_u32::<LittleEndian>(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<Sha256dHash, Transaction>) -> 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 {
Expand Down Expand Up @@ -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();
}
}

1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 755fb45

Please sign in to comment.