Skip to content
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

Improvements to bitcoin primitive types #1 #53

Merged
merged 14 commits into from
Aug 1, 2023
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
57 changes: 57 additions & 0 deletions primitives/src/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Bitcoin protocol primitives library.
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2019-2023 by
// Dr Maxim Orlovsky <[email protected]>
//
// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use amplify::{Bytes32, Wrapper};

use crate::LIB_NAME_BITCOIN;

#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Display, From)]
#[display(LowerHex)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
#[wrapper(BorrowSlice, Index, RangeOps)]
pub struct BlockHash(
#[from]
#[from([u8; 32])]
Bytes32,
);
impl_sha256d_hashtype!(BlockHash, "BlockHash");

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct BlockHeader {
/// Block version, now repurposed for soft fork signalling.
pub version: i32,
/// Reference to the previous block in the chain.
pub prev_block_hash: BlockHash,
/// The root hash of the merkle tree of transactions in the block.
pub merkle_root: Bytes32,
/// The timestamp of the block, as claimed by the miner.
pub time: u32,
/// The target value below which the blockhash must lie.
pub bits: u32,
/// The nonce, selected to obtain a low enough blockhash.
pub nonce: u32,
}
6 changes: 5 additions & 1 deletion primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Coding conventions

// Coding conventions
#![deny(
non_upper_case_globals,
non_camel_case_types,
Expand All @@ -44,6 +44,9 @@ extern crate serde_crate as serde;
/// Re-export of `secp256k1` crate.
pub extern crate secp256k1;

#[macro_use]
mod macros;
mod block;
pub mod opcodes;
mod script;
mod segwit;
Expand All @@ -53,6 +56,7 @@ mod util;
#[cfg(feature = "stl")]
pub mod stl;

pub use block::{BlockHash, BlockHeader};
pub use script::{OpCode, ScriptPubkey, SigScript};
pub use segwit::*;
pub use taproot::*;
Expand Down
74 changes: 74 additions & 0 deletions primitives/src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Bitcoin protocol primitives library.
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2019-2023 by
// Dr Maxim Orlovsky <[email protected]>
//
// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a
/// big endian order. Thus we need this manual implementation.
macro_rules! impl_sha256d_hashtype {
($ty:ident, $name:literal) => {
mod _sha256_hash_impl {
use core::fmt::{self, Debug, Formatter, LowerHex, UpperHex};
use core::str::FromStr;

use amplify::hex::{self, FromHex, ToHex};
use amplify::{Bytes32, RawArray, Wrapper};

use super::$ty;

impl From<$ty> for [u8; 32] {
fn from(value: $ty) -> Self { value.0.into_inner() }
}

impl Debug for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple($name).field(&self.to_hex()).finish()
}
}

impl LowerHex for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut slice = self.to_raw_array();
slice.reverse();
f.write_str(&slice.to_hex())
}
}

impl UpperHex for $ty {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_hex().to_uppercase())
}
}

impl FromStr for $ty {
type Err = hex::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
}

impl FromHex for $ty {
fn from_byte_iter<I>(iter: I) -> Result<Self, hex::Error>
where I: Iterator<Item = Result<u8, hex::Error>>
+ ExactSizeIterator
+ DoubleEndedIterator {
Bytes32::from_byte_iter(iter.rev()).map(Self::from)
}
}
}
};
}
82 changes: 80 additions & 2 deletions primitives/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use amplify::confinement::Confined;
use crate::opcodes::*;
use crate::{ScriptBytes, LIB_NAME_BITCOIN};

#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
// TODO: Replace `try_from` with `from` since opcodes cover whole range of u8
#[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)]
Expand Down Expand Up @@ -63,6 +63,46 @@ pub enum OpCode {
/// Push the array `0x01` onto the stack.
#[display("OP_PUSHNUM_1")]
PushNum1 = OP_PUSHNUM_1,

/// Duplicates the top stack item.
#[display("OP_DUP")]
Dup = OP_DUP,

/// Pushes 1 if the inputs are exactly equal, 0 otherwise.
#[display("OP_EQUAL")]
Equal = OP_EQUAL,

/// Returns success if the inputs are exactly equal, failure otherwise.
#[display("OP_EQUALVERIFY")]
EqualVerify = OP_EQUALVERIFY,

/// Pop the top stack item and push its RIPEMD160 hash.
#[display("OP_RIPEMD160")]
Ripemd160 = OP_RIPEMD160,

/// Pop the top stack item and push its SHA1 hash.
#[display("OP_SHA1")]
Sha1 = OP_SHA1,

/// Pop the top stack item and push its SHA256 hash.
#[display("OP_SHA256")]
Sha256 = OP_SHA256,

/// Pop the top stack item and push its RIPEMD(SHA256) hash.
#[display("OP_HASH160")]
Hash160 = OP_HASH160,

/// Pop the top stack item and push its SHA256(SHA256) hash.
#[display("OP_HASH256")]
Hash256 = OP_HASH256,

/// <https://en.bitcoin.it/wiki/OP_CHECKSIG> pushing 1/0 for success/failure.
#[display("OP_CHECKSIG")]
CheckSig = OP_CHECKSIG,

/// <https://en.bitcoin.it/wiki/OP_CHECKSIG> returning success/failure.
#[display("OP_CHECKSIGVERIFY")]
CheckSigVerify = OP_CHECKSIGVERIFY,
}

#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)]
Expand Down Expand Up @@ -104,13 +144,51 @@ impl ScriptPubkey {
Self(ScriptBytes::from(Confined::with_capacity(capacity)))
}

pub fn p2pkh(hash: impl Into<[u8; 20]>) -> Self {
let mut script = Self::with_capacity(25);
script.push_opcode(OpCode::Dup);
script.push_opcode(OpCode::Hash160);
script.push_slice(&hash.into());
script.push_opcode(OpCode::EqualVerify);
script.push_opcode(OpCode::CheckSig);
script
}

pub fn p2sh(hash: impl Into<[u8; 20]>) -> Self {
let mut script = Self::with_capacity(23);
script.push_opcode(OpCode::Hash160);
script.push_slice(&hash.into());
script.push_opcode(OpCode::Equal);
script
}

pub fn op_return(data: &[u8]) -> Self {
let mut script = Self::with_capacity(ScriptBytes::len_for_slice(data.len()) + 1);
script.push_opcode(OpCode::Return);
script.0.push_slice(data);
script.push_slice(data);
script
}

/// Checks whether a script pubkey is a P2PKH output.
#[inline]
pub fn is_p2pkh(&self) -> bool {
self.0.len() == 25 &&
self.0[0] == OP_DUP &&
self.0[1] == OP_HASH160 &&
self.0[2] == OP_PUSHBYTES_20 &&
self.0[23] == OP_EQUALVERIFY &&
self.0[24] == OP_CHECKSIG
}

/// Checks whether a script pubkey is a P2SH output.
#[inline]
pub fn is_p2sh(&self) -> bool {
self.0.len() == 23 &&
self.0[0] == OP_HASH160 &&
self.0[1] == OP_PUSHBYTES_20 &&
self.0[22] == OP_EQUAL
}

pub fn is_op_return(&self) -> bool { self[0] == OpCode::Return as u8 }

/// Adds a single opcode to the script.
Expand Down
82 changes: 76 additions & 6 deletions primitives/src/segwit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@ pub enum WitnessVer {
impl WitnessVer {
/// Converts bitcoin script opcode into [`WitnessVer`] variant.
///
/// # Returns
/// Version of the Witness program.
///
/// # Errors
/// If the opcode does not correspond to any witness version, errors with
/// [`SegwitError::MalformedWitnessVersion`].
Expand All @@ -146,12 +143,45 @@ impl WitnessVer {
}
}

/// Converts witness version ordinal number into [`WitnessVer`] variant.
///
/// # Errors
/// If the witness version number exceeds 16, errors with
/// [`SegwitError::MalformedWitnessVersion`].
pub fn from_version_no(no: u8) -> Result<Self, SegwitError> {
let op = OpCode::try_from(no).map_err(|_| SegwitError::MalformedWitnessVersion)?;
Self::from_op_code(op)
}

/// Converts [`WitnessVer`] instance into corresponding Bitcoin op-code.
// TODO: Replace `try_from` with `from` since opcodes cover whole range of
// u8
// u8
pub fn op_code(self) -> OpCode {
OpCode::try_from(self as u8).expect("full range of u8 is covered")
}

/// Converts [`WitnessVer`] into ordinal version number.
pub fn version_no(self) -> u8 {
match self {
WitnessVer::V0 => 0,
WitnessVer::V1 => 1,
WitnessVer::V2 => 2,
WitnessVer::V3 => 3,
WitnessVer::V4 => 4,
WitnessVer::V5 => 5,
WitnessVer::V6 => 6,
WitnessVer::V7 => 7,
WitnessVer::V8 => 8,
WitnessVer::V9 => 9,
WitnessVer::V10 => 10,
WitnessVer::V11 => 11,
WitnessVer::V12 => 12,
WitnessVer::V13 => 13,
WitnessVer::V14 => 14,
WitnessVer::V15 => 15,
WitnessVer::V16 => 16,
}
}
}

/// Witness program as defined in BIP141.
Expand Down Expand Up @@ -187,17 +217,57 @@ impl WitnessProgram {
}

impl ScriptPubkey {
pub fn p2wpkh(hash: impl Into<[u8; 20]>) -> Self {
Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into())
}

pub fn p2wsh(hash: impl Into<[u8; 32]>) -> Self {
Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into())
}

pub fn is_p2wpkh(&self) -> bool {
self.len() == 22 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_20
}

pub fn is_p2wsh(&self) -> bool {
self.len() == 34 && self[0] == WitnessVer::V0.op_code() as u8 && self[1] == OP_PUSHBYTES_32
}

/// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`].
pub fn from_witness_program(witness_program: &WitnessProgram) -> Self {
Self::with_segwit_unchecked(witness_program.version, witness_program.program())
Self::with_witness_program_unchecked(witness_program.version, witness_program.program())
}

/// Generates P2WSH-type of scriptPubkey with a given [`WitnessVer`] and
/// the program bytes. Does not do any checks on version or program length.
pub(crate) fn with_segwit_unchecked(ver: WitnessVer, prog: &[u8]) -> Self {
pub(crate) fn with_witness_program_unchecked(ver: WitnessVer, prog: &[u8]) -> Self {
let mut script = Self::with_capacity(ScriptBytes::len_for_slice(prog.len()) + 2);
script.push_opcode(ver.op_code());
script.push_slice(prog);
script
}

/// Checks whether a script pubkey is a Segregated Witness (segwit) program.
#[inline]
pub fn is_witness_program(&self) -> bool {
// A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a
// 1-byte push opcode (for 0 to 16) followed by a data push between 2
// and 40 bytes gets a new special meaning. The value of the first push
// is called the "version byte". The following byte vector pushed is
// called the "witness program".
let script_len = self.len();
if !(4..=42).contains(&script_len) {
return false;
}
// Version 0 or PUSHNUM_1-PUSHNUM_16
let Ok(ver_opcode) = OpCode::try_from(self[0]) else {
return false;
};
let push_opbyte = self[1]; // Second byte push opcode 2-40 bytes
WitnessVer::from_op_code(ver_opcode).is_ok()
&& push_opbyte >= OP_PUSHBYTES_2
&& push_opbyte <= OP_PUSHBYTES_40
// Check that the rest of the script has the correct size
&& script_len - 2 == push_opbyte as usize
}
}
Loading
Loading