Skip to content
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
13 changes: 6 additions & 7 deletions crates/bitcoin/src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,16 +392,15 @@ mod tests {
#[test]
fn test_format_block_header() {
let hex_header = parser::tests::sample_block_header();
let raw_header = RawBlockHeader::from_hex(&hex_header).unwrap();
let parsed_header = parser::parse_block_header(&raw_header).unwrap();
assert_eq!(parsed_header.try_format().unwrap(), raw_header.as_bytes());
let parsed_header = BlockHeader::from_hex(&hex_header).unwrap();
assert_eq!(hex::encode(parsed_header.try_format().unwrap()), hex_header);
}

#[test]
fn test_format_block_header_testnet() {
let hex_header = "00000020b0b3d77b97015b519553423c96642b33ca534c50ecefd133640000000000000029a0a725684aeca24af83e3ba0a3e3ee56adfdf032d19e5acba6d0a262e1580ca354915fd4c8001ac42a7b3a".to_string();
let raw_header = RawBlockHeader::from_hex(&hex_header).unwrap();
let parsed_header = parser::parse_block_header(&raw_header).unwrap();
let raw_header = hex::decode(&hex_header).unwrap();
let parsed_header = BlockHeader::from_bytes(&raw_header).unwrap();

assert_eq!(
parsed_header,
Expand All @@ -410,15 +409,15 @@ mod tests {
target: U256::from_dec_str("1260618571951953247774709397757627131971305851995253681160192").unwrap(),
timestamp: 1603359907,
version: 536870912,
hash: raw_header.hash(),
hash: sha256d_le(&raw_header),
hash_prev_block: H256Le::from_hex_be(
"000000000000006433d1efec504c53ca332b64963c425395515b01977bd7b3b0"
),
nonce: 981150404,
}
);

assert_eq!(parsed_header.try_format().unwrap(), raw_header.as_bytes());
assert_eq!(hex::encode(parsed_header.try_format().unwrap()), hex_header);
}

// taken from https://bitcoin.org/en/developer-reference#block-headers
Expand Down
4 changes: 3 additions & 1 deletion crates/bitcoin/src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::{
utils::hash256_merkle_step,
Error,
};
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_std::prelude::*;

// Values taken from https://github.com/bitcoin/bitcoin/blob/78dae8caccd82cfbfd76557f1fb7d7557c7b5edb/src/consensus/consensus.h
Expand All @@ -24,7 +26,7 @@ const MAX_TRANSACTIONS_IN_PROOF: u32 = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT
pub struct MerkleTree;

/// Stores the content of a merkle proof
#[derive(Clone)]
#[derive(Encode, Decode, TypeInfo, PartialEq, Default, Clone)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct MerkleProof {
pub block_header: BlockHeader,
Expand Down
49 changes: 14 additions & 35 deletions crates/bitcoin/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use sha2::{Digest, Sha256};
#[cfg(test)]
use mocktopus::macros::mockable;

use crate::{Error, SetCompact};
use crate::{utils::sha256d_le, Error, SetCompact};
use sp_core::U256;
use sp_std::{prelude::*, vec};

Expand Down Expand Up @@ -68,8 +68,7 @@ impl Parsable for CompactUint {

impl Parsable for BlockHeader {
fn parse(raw_bytes: &[u8], position: usize) -> Result<(BlockHeader, usize), Error> {
let slice = raw_bytes.get(position..position + 80).ok_or(Error::EndOfFile)?;
let header_bytes = RawBlockHeader::from_bytes(slice)?;
let header_bytes = raw_bytes.get(position..position + 80).ok_or(Error::EndOfFile)?;
let block_header = parse_block_header(&header_bytes)?;
Ok((block_header, 80))
}
Expand Down Expand Up @@ -224,21 +223,24 @@ pub trait FromLeBytes: Sized {

impl FromLeBytes for BlockHeader {
fn from_le_bytes(bytes: &[u8]) -> Result<BlockHeader, Error> {
parse_block_header(&RawBlockHeader::from_bytes(bytes)?)
parse_block_header(bytes)
}
}

// like parse_block_header, but without the version check. This is needed in testing, to test
// with historical data
pub fn parse_block_header_lenient(raw_header: &RawBlockHeader) -> Result<BlockHeader, Error> {
let mut parser = BytesParser::new(raw_header.as_bytes());
/// Parses the raw bitcoin header into a Rust struct
///
/// # Arguments
///
/// * `header` - An 80-byte Bitcoin header
pub fn parse_block_header(bytes: &[u8]) -> Result<BlockHeader, Error> {
let mut parser = BytesParser::new(bytes);
let version: i32 = parser.parse()?;
let hash_prev_block: H256Le = parser.parse()?;
let merkle_root: H256Le = parser.parse()?;
let timestamp: u32 = parser.parse()?;
let target: U256 = parser.parse()?;
let nonce: u32 = parser.parse()?;
let hash: H256Le = raw_header.hash();
let hash: H256Le = sha256d_le(bytes);

let block_header = BlockHeader {
merkle_root,
Expand All @@ -253,26 +255,6 @@ pub fn parse_block_header_lenient(raw_header: &RawBlockHeader) -> Result<BlockHe
Ok(block_header)
}

/// Parses the raw bitcoin header into a Rust struct
///
/// # Arguments
///
/// * `header` - An 80-byte Bitcoin header
pub fn parse_block_header(raw_header: &RawBlockHeader) -> Result<BlockHeader, Error> {
let block_header = parse_block_header_lenient(raw_header)?;

if block_header.version < 4 {
// as per bip65, we reject block versions less than 4. Note that the reason
// we can hardcode this, is that bitcoin switched to version 4 in december
// 2015, and the genesis of the bridge will never be set to a genesis from
// before that date.
// see https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki#spv-clients
return Err(Error::InvalidBlockVersion);
}

Ok(block_header)
}

/// Returns the value of a compactly encoded uint and the number of bytes consumed
///
/// # Arguments
Expand Down Expand Up @@ -528,8 +510,7 @@ pub(crate) mod tests {
#[test]
fn test_parse_block_header() {
let hex_header = sample_block_header();
let raw_header = RawBlockHeader::from_hex(&hex_header).unwrap();
let parsed_header = parse_block_header(&raw_header).unwrap();
let parsed_header = BlockHeader::from_hex(&hex_header).unwrap();
assert_eq!(parsed_header.version, 4);
assert_eq!(parsed_header.timestamp, 1415239972);
assert_eq!(
Expand All @@ -550,11 +531,9 @@ pub(crate) mod tests {
let valid_header_hex = "04".to_string() + hex_header_without_version;
let invalid_header_hex = "03".to_string() + hex_header_without_version;

assert_ok!(parse_block_header(
&RawBlockHeader::from_hex(&valid_header_hex).unwrap()
));
assert_ok!(BlockHeader::from_hex(&valid_header_hex));
assert_err!(
parse_block_header(&RawBlockHeader::from_hex(&invalid_header_hex).unwrap()),
BlockHeader::from_hex(&invalid_header_hex).unwrap().ensure_version(),
Error::InvalidBlockVersion
);
}
Expand Down
4 changes: 3 additions & 1 deletion crates/bitcoin/src/script.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::{formatter::Formattable, parser::extract_op_return_data, types::*, Error};
use codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_std::{prelude::*, vec};

#[cfg(feature = "std")]
use codec::alloc::string::String;

/// Bitcoin script
#[derive(PartialEq, Debug, Clone)]
#[derive(Encode, Decode, TypeInfo, PartialEq, Debug, Clone)]
pub struct Script {
pub(crate) bytes: Vec<u8>,
}
Expand Down
131 changes: 53 additions & 78 deletions crates/bitcoin/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ pub use crate::merkle::MerkleProof;
use crate::{
formatter::{Formattable, TryFormattable},
merkle::MerkleTree,
parser::{extract_address_hash_scriptsig, extract_address_hash_witness},
parser::{extract_address_hash_scriptsig, extract_address_hash_witness, parse_block_header},
utils::{log2, reverse_endianness, sha256d_le},
Address, Error, PublicKey, Script,
};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
pub use sp_core::{H160, H256, U256};
use sp_std::{convert::TryFrom, prelude::*, vec};
use sp_std::{prelude::*, vec};

#[cfg(feature = "std")]
use codec::alloc::string::String;
Expand Down Expand Up @@ -161,76 +161,6 @@ pub enum OpCode {

/// Custom Types

/// Bitcoin raw block header (80 bytes)
#[derive(Encode, Decode, Copy, Clone, TypeInfo, MaxEncodedLen)]
pub struct RawBlockHeader([u8; 80]);

impl Default for RawBlockHeader {
fn default() -> Self {
Self([0; 80])
}
}

impl TryFrom<Vec<u8>> for RawBlockHeader {
type Error = Error;

fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
RawBlockHeader::from_bytes(v)
}
}

impl RawBlockHeader {
/// Returns a raw block header from a bytes slice
///
/// # Arguments
///
/// * `bytes` - A slice containing the header
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<RawBlockHeader, Error> {
let slice = bytes.as_ref();
if slice.len() != 80 {
return Err(Error::InvalidHeaderSize);
}
let mut result = [0u8; 80];
result.copy_from_slice(slice);
Ok(RawBlockHeader(result))
}

/// Returns a raw block header from a bytes slice
///
/// # Arguments
///
/// * `bytes` - A slice containing the header
#[cfg(feature = "std")]
pub fn from_hex<T: AsRef<[u8]>>(hex_string: T) -> Result<RawBlockHeader, Error> {
let bytes = hex::decode(hex_string).map_err(|_e| Error::MalformedHeader)?;
Self::from_bytes(&bytes)
}

/// Returns the hash of the block header using Bitcoin's double sha256
pub fn hash(&self) -> H256Le {
sha256d_le(self.as_bytes())
}

/// Returns the block header as a slice
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}

impl PartialEq for RawBlockHeader {
fn eq(&self, other: &Self) -> bool {
let self_bytes = &self.0[..];
let other_bytes = &other.0[..];
self_bytes == other_bytes
}
}

impl sp_std::fmt::Debug for RawBlockHeader {
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
f.debug_list().entries(self.0.iter()).finish()
}
}

// Constants
pub const P2PKH_SCRIPT_SIZE: u32 = 25;
pub const P2SH_SCRIPT_SIZE: u32 = 23;
Expand All @@ -250,21 +180,66 @@ pub struct BlockHeader {
pub target: U256,
pub timestamp: u32,
pub version: i32,
// TODO: remove hash
pub hash: H256Le,
pub hash_prev_block: H256Le,
pub nonce: u32,
}

impl BlockHeader {
/// Returns the hash of the block header using Bitcoin's double sha256
pub fn hash(&self) -> Result<H256Le, Error> {
Ok(sha256d_le(&self.try_format()?))
}

pub fn ensure_version(&self) -> Result<(), Error> {
if self.version < 4 {
// as per bip65, we reject block versions less than 4. Note that the reason
// we can hardcode this, is that bitcoin switched to version 4 in december
// 2015, and the genesis of the bridge will never be set to a genesis from
// before that date.
// see https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki#spv-clients
Err(Error::InvalidBlockVersion)
} else {
Ok(())
}
}

pub fn update_hash(&mut self) -> Result<H256Le, Error> {
let new_hash = sha256d_le(&self.try_format()?);
let new_hash = self.hash()?;

self.hash = new_hash;
Ok(self.hash)
}

/// Returns a block header from a bytes slice
///
/// # Arguments
///
/// * `bytes` - A slice containing the header
pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<BlockHeader, Error> {
let slice = bytes.as_ref();
if slice.len() != 80 {
return Err(Error::InvalidHeaderSize);
}
let mut result = [0u8; 80];
result.copy_from_slice(slice);
parse_block_header(&result)
}

/// Returns a block header from a hex string
///
/// # Arguments
///
/// * `data` - A string containing the header
#[cfg(feature = "std")]
pub fn from_hex<T: AsRef<[u8]>>(data: T) -> Result<BlockHeader, Error> {
let bytes = hex::decode(data).map_err(|_e| Error::MalformedHeader)?;
Self::from_bytes(&bytes)
}
}

#[derive(PartialEq, Clone, Debug)]
#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, Debug)]
pub enum TransactionInputSource {
/// Spending from transaction with the given hash, from output with the given index
FromOutput(H256Le, u32),
Expand All @@ -273,7 +248,7 @@ pub enum TransactionInputSource {
}

/// Bitcoin transaction input
#[derive(PartialEq, Clone, Debug)]
#[derive(Encode, Decode, TypeInfo, PartialEq, Clone, Debug)]
pub struct TransactionInput {
pub source: TransactionInputSource,
pub script: Vec<u8>,
Expand Down Expand Up @@ -302,7 +277,7 @@ impl TransactionInput {
pub type Value = i64;

/// Bitcoin transaction output
#[derive(PartialEq, Debug, Clone)]
#[derive(Encode, Decode, TypeInfo, PartialEq, Debug, Clone)]
pub struct TransactionOutput {
pub value: Value,
pub script: Script,
Expand Down Expand Up @@ -330,7 +305,7 @@ impl TransactionOutput {

/// Bitcoin transaction
// Note: the `default` implementation is used only for testing code
#[derive(PartialEq, Debug, Clone, Default)]
#[derive(Encode, Decode, TypeInfo, Default, PartialEq, Debug, Clone)]
pub struct Transaction {
pub version: i32,
pub inputs: Vec<TransactionInput>,
Expand All @@ -350,7 +325,7 @@ impl Transaction {
}

// https://en.bitcoin.it/wiki/NLockTime
#[derive(PartialEq, Debug, Clone)]
#[derive(Encode, Decode, TypeInfo, PartialEq, Debug, Clone)]
pub enum LockTime {
/// time as unix timestamp
Time(u32),
Expand Down
Loading