8000 Optional feature gives access to Bitcoin Core's own script validator by tamasblummer · Pull Request #58 · rust-bitcoin/rust-bitcoin · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Optional feature gives access to Bitcoin Core's own script validator #58

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 6D4E ,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("020000000001031cfbc8f54fbfa4a33a30068841371f80dbfe166211242213188428f437445c91000000006a47304402206fbcec8d2d2e740d824d3d36cc345b37d9f65 9E7A d665a99f5bd5c9e8d42270a03a8022013959632492332200c2908459547bf8dbf97c65ab1a28dec377d6f1d41d3d63e012103d7279dfb90ce17fe139ba60a7c41ddf605b25e1c07a4ddcb9dfef4e7d6710f48feffffff476222484f5e35b3f0e43f65fc76e21d8be7818dd6a989c160b1e5039b7835fc00000000171600140914414d3c94af70ac7e25407b0689e0baa10c77feffffffa83d954a62568bbc99cc644c62eb7383d7c2a2563041a0aeb891a6a4055895570000000017160014795d04cc2d4f31480d9a3710993fbd80d04301dffeffffff06fef72f000000000017a91476fd7035cd26f1a32a5ab979e056713aac25796887a5000f00000000001976a914b8332d502a529571c6af4be66399cd33379071c588ac3fda0500000000001976a914fc1d692f8de10ae33295f090bea5fe49527d975c88ac522e1b00000000001976a914808406b54d1044c429ac54c0e189b0d8061667e088ac6eb68501000000001976a914dfab6085f3a8fb3e6710206a5a959313c5618f4d88acbba20000000000001976a914eb3026552d7e3f3073457d0bee5d4757de48160d88ac0002483045022100bee24b63212939d33d513e767bc79300051f7a0d433c3fcf1e0e3bf03b9eb1d70220588dc45a9ce3a939103b4459ce47500b64e23ab118dfc03c9caa7d6bfc32b9c601210354fd80328da0f9ae6eef2b3a81f74f9a6f66761fadf96f1d1d22b1fd6845876402483045022100e29c7e3a5efc10da6269e5fc20b6a1cb8beb92130cc52c67e46ef40aaa5cac5f0220644dd1b049727d991aece98a105563416e10a5ac4221abac7d16931842d5c322012103960b87412d6e169f30e12106bdf70122aabb9eb61f455518322a18b920a4dfa887d30700"
.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