From 15e8bf32195b103b2f980d774ff52e7be39a5848 Mon Sep 17 00:00:00 2001 From: Maxim Orlovsky Date: Sat, 29 Jul 2023 17:00:06 +0200 Subject: [PATCH 01/14] primitives: add WitnessVer::from_version_no --- primitives/src/segwit.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index 40d1396a..96338632 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -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`]. @@ -146,6 +143,16 @@ 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 { + 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 From e2cfd6356915108566ed548ee571f32707497312 Mon Sep 17 00:00:00 2001 From: Maxim Orlovsky Date: Sat, 29 Jul 2023 17:14:32 +0200 Subject: [PATCH 02/14] primitives: add P2PKH and P2SH handling methods to ScriptPubkey --- primitives/src/script.rs | 80 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/primitives/src/script.rs b/primitives/src/script.rs index 663abf79..02fc20ef 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -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, + + /// pushing 1/0 for success/failure. + #[display("OP_CHECKSIG")] + CheckSig = OP_CHECKSIG, + + /// returning success/failure. + #[display("OP_CHECKSIGVERIFY")] + CheckSigVerify = OP_CHECKSIGVERIFY, } #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] @@ -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. From 484369caa56e7c5b5220e6107dfb2fb93c905957 Mon Sep 17 00:00:00 2001 From: Maxim Orlovsky Date: Sat, 29 Jul 2023 17:17:55 +0200 Subject: [PATCH 03/14] primitives: add P2WSH and P2WPKH handling methods to ScriptPubkey --- primitives/src/segwit.rs | 12 ++++++++++-- primitives/src/taproot.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index 96338632..5bae77a5 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -194,14 +194,22 @@ impl WitnessProgram { } impl ScriptPubkey { + pub fn p2wkh(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()) + } + /// 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); diff --git a/primitives/src/taproot.rs b/primitives/src/taproot.rs index e22056df..8aa1baf3 100644 --- a/primitives/src/taproot.rs +++ b/primitives/src/taproot.rs @@ -423,7 +423,7 @@ impl ScriptPubkey { pub fn p2tr_tweaked(output_key: XOnlyPublicKey) -> Self { // output key is 32 bytes long, so it's safe to use // `new_witness_program_unchecked` (Segwitv1) - Self::with_segwit_unchecked(WitnessVer::V1, &output_key.serialize()) + Self::with_witness_program_unchecked(WitnessVer::V1, &output_key.serialize()) } pub fn is_p2tr(&self) -> bool { From cd5038dd09da47e04616e1a24a3fa8b508cd90a6 Mon Sep 17 00:00:00 2001 From: Maxim Orlovsky Date: Sat, 29 Jul 2023 17:22:43 +0200 Subject: [PATCH 04/14] primitives: add witness checking methods to ScriptPubkey --- primitives/src/segwit.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index 5bae77a5..e4a39460 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -194,7 +194,7 @@ impl WitnessProgram { } impl ScriptPubkey { - pub fn p2wkh(hash: impl Into<[u8; 20]>) -> Self { + pub fn p2wpkh(hash: impl Into<[u8; 20]>) -> Self { Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into()) } @@ -202,6 +202,14 @@ impl ScriptPubkey { Self::with_witness_program_unchecked(WitnessVer::V0, &hash.into()) } + pub fn is_p2wpkh(&self) -> bool { + self.len() == 22 && self[0] == WitnessVer::V1.op_code() as u8 && self[1] == OP_PUSHBYTES_20 + } + + pub fn is_p2wsh(&self) -> bool { + self.len() == 34 && self[0] == WitnessVer::V1.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_witness_program_unchecked(witness_program.version, witness_program.program()) @@ -215,4 +223,28 @@ impl ScriptPubkey { 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 + } } From 76959e5f9198c9d6e5bfe129ba3ab8fe3c84b64e Mon Sep 17 00:00:00 2001 From: Maxim Orlovsky Date: Sat, 29 Jul 2023 17:41:48 +0200 Subject: [PATCH 05/14] primitives: add P2TR constructor convenience methods --- primitives/src/taproot.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/primitives/src/taproot.rs b/primitives/src/taproot.rs index 8aa1baf3..86761a13 100644 --- a/primitives/src/taproot.rs +++ b/primitives/src/taproot.rs @@ -420,6 +420,16 @@ impl ScriptPubkey { Self::p2tr_tweaked(output_key) } + pub fn p2tr_key_only(internal_key: InternalPk) -> Self { + let output_key = internal_key.to_output_key(None::); + Self::p2tr_tweaked(output_key) + } + + pub fn p2tr_scripted(internal_key: InternalPk, merkle_root: impl IntoTapHash) -> Self { + let output_key = internal_key.to_output_key(Some(merkle_root)); + Self::p2tr_tweaked(output_key) + } + pub fn p2tr_tweaked(output_key: XOnlyPublicKey) -> Self { // output key is 32 bytes long, so it's safe to use // `new_witness_program_unchecked` (Segwitv1) From c6b5b7b150060411b49af6e24c85018487578f9e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 29 Jul 2023 23:24:32 +0200 Subject: [PATCH 06/14] primitives: add BlockHeader and BlockHash --- primitives/src/block.rs | 108 ++++++++++++++++++++++++++++++++++++++++ primitives/src/lib.rs | 4 +- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 primitives/src/block.rs diff --git a/primitives/src/block.rs b/primitives/src/block.rs new file mode 100644 index 00000000..41716715 --- /dev/null +++ b/primitives/src/block.rs @@ -0,0 +1,108 @@ +// Bitcoin protocol primitives library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// 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 std::fmt::{self, Debug, Formatter, LowerHex, UpperHex}; +use std::str::FromStr; + +use amplify::hex::{FromHex, ToHex}; +use amplify::{hex, Bytes32, RawArray, 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 AsRef<[u8; 32]> for BlockHash { + fn as_ref(&self) -> &[u8; 32] { self.0.as_inner() } +} + +impl AsRef<[u8]> for BlockHash { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl From for [u8; 32] { + fn from(value: BlockHash) -> Self { value.0.into_inner() } +} + +impl Debug for BlockHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_tuple("BlockHash").field(&self.to_hex()).finish() + } +} + +/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a +/// big endian order. Thus we need this manual implementation. +impl LowerHex for BlockHash { + 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 BlockHash { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_hex().to_uppercase()) + } +} + +impl FromStr for BlockHash { + type Err = hex::Error; + fn from_str(s: &str) -> Result { Self::from_hex(s) } +} + +/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a +/// big endian order. Thus we need this manual implementation. +impl FromHex for BlockHash { + fn from_byte_iter(iter: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + Bytes32::from_byte_iter(iter.rev()).map(Self::from) + } +} + +#[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, +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 72e085d5..f66c5fab 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -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, @@ -44,6 +44,7 @@ extern crate serde_crate as serde; /// Re-export of `secp256k1` crate. pub extern crate secp256k1; +mod block; pub mod opcodes; mod script; mod segwit; @@ -53,6 +54,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::*; From 73483b4d553dd7150e09cfaff00072e8e097e959 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 29 Jul 2023 23:24:49 +0200 Subject: [PATCH 07/14] primitives: extend Txid API --- primitives/src/tx.rs | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index aeff7482..f36d5a33 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{self, Debug, Formatter}; +use std::fmt::{self, Debug, Formatter, LowerHex, UpperHex}; use std::num::ParseIntError; use std::str::FromStr; @@ -30,7 +30,7 @@ use super::{VarIntArray, LIB_NAME_BITCOIN}; use crate::{NonStandardValue, ScriptPubkey, SigScript}; #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Display, From)] -#[display(Self::to_hex)] +#[display(LowerHex)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[derive(CommitEncode)] @@ -40,7 +40,7 @@ use crate::{NonStandardValue, ScriptPubkey, SigScript}; derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -#[wrapper(Index, RangeOps, BorrowSlice)] +#[wrapper(BorrowSlice, Index, RangeOps)] // all-zeros used in coinbase pub struct Txid( #[from] @@ -48,27 +48,45 @@ pub struct Txid( Bytes32, ); +impl AsRef<[u8; 32]> for Txid { + fn as_ref(&self) -> &[u8; 32] { self.0.as_inner() } +} + +impl AsRef<[u8]> for Txid { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl From for [u8; 32] { + fn from(value: Txid) -> Self { value.0.into_inner() } +} + impl Debug for Txid { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_tuple("Txid").field(&self.to_hex()).finish() } } -impl FromStr for Txid { - type Err = hex::Error; - fn from_str(s: &str) -> Result { Self::from_hex(s) } -} - /// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a /// big endian order. Thus we need this manual implementation. -impl ToHex for Txid { - fn to_hex(&self) -> String { +impl LowerHex for Txid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut slice = self.to_raw_array(); slice.reverse(); - slice.to_hex() + f.write_str(&slice.to_hex()) } } +impl UpperHex for Txid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&self.to_hex().to_uppercase()) + } +} + +impl FromStr for Txid { + type Err = hex::Error; + fn from_str(s: &str) -> Result { Self::from_hex(s) } +} + /// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a /// big endian order. Thus we need this manual implementation. impl FromHex for Txid { @@ -78,6 +96,10 @@ impl FromHex for Txid { } } +impl Txid { + pub fn coinbase() -> Self { Self(zero!()) } +} + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] From 13a5443fbb46a8fb444cb9f1383f298361cd71f0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 30 Jul 2023 09:06:39 +0200 Subject: [PATCH 08/14] primitives: optimize SHA256d-hash type derivation with macro --- primitives/src/block.rs | 55 ++--------------------------- primitives/src/lib.rs | 2 ++ primitives/src/macros.rs | 74 ++++++++++++++++++++++++++++++++++++++++ primitives/src/tx.rs | 56 +++--------------------------- 4 files changed, 83 insertions(+), 104 deletions(-) create mode 100644 primitives/src/macros.rs diff --git a/primitives/src/block.rs b/primitives/src/block.rs index 41716715..ef4876cd 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -19,11 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{self, Debug, Formatter, LowerHex, UpperHex}; -use std::str::FromStr; - -use amplify::hex::{FromHex, ToHex}; -use amplify::{hex, Bytes32, RawArray, Wrapper}; +use amplify::{Bytes32, Wrapper}; use crate::LIB_NAME_BITCOIN; @@ -42,54 +38,7 @@ pub struct BlockHash( #[from([u8; 32])] Bytes32, ); - -impl AsRef<[u8; 32]> for BlockHash { - fn as_ref(&self) -> &[u8; 32] { self.0.as_inner() } -} - -impl AsRef<[u8]> for BlockHash { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } -} - -impl From for [u8; 32] { - fn from(value: BlockHash) -> Self { value.0.into_inner() } -} - -impl Debug for BlockHash { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("BlockHash").field(&self.to_hex()).finish() - } -} - -/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a -/// big endian order. Thus we need this manual implementation. -impl LowerHex for BlockHash { - 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 BlockHash { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_hex().to_uppercase()) - } -} - -impl FromStr for BlockHash { - type Err = hex::Error; - fn from_str(s: &str) -> Result { Self::from_hex(s) } -} - -/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a -/// big endian order. Thus we need this manual implementation. -impl FromHex for BlockHash { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - Bytes32::from_byte_iter(iter.rev()).map(Self::from) - } -} +impl_sha256d_hashtype!(BlockHash, "BlockHash"); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct BlockHeader { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index f66c5fab..78e1d5d6 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -44,6 +44,8 @@ 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; diff --git a/primitives/src/macros.rs b/primitives/src/macros.rs new file mode 100644 index 00000000..93f8ec79 --- /dev/null +++ b/primitives/src/macros.rs @@ -0,0 +1,74 @@ +// Bitcoin protocol primitives library. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// 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::from_hex(s) } + } + + impl FromHex for $ty { + fn from_byte_iter(iter: I) -> Result + where I: Iterator> + + ExactSizeIterator + + DoubleEndedIterator { + Bytes32::from_byte_iter(iter.rev()).map(Self::from) + } + } + } + }; +} diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index f36d5a33..6f2ab3b0 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -19,12 +19,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::{self, Debug, Formatter, LowerHex, UpperHex}; +use std::fmt::Debug; use std::num::ParseIntError; use std::str::FromStr; -use amplify::hex::{self, FromHex, ToHex}; -use amplify::{Bytes32, RawArray, Wrapper}; +use amplify::{Bytes32, Wrapper}; use super::{VarIntArray, LIB_NAME_BITCOIN}; use crate::{NonStandardValue, ScriptPubkey, SigScript}; @@ -47,54 +46,7 @@ pub struct Txid( #[from([u8; 32])] Bytes32, ); - -impl AsRef<[u8; 32]> for Txid { - fn as_ref(&self) -> &[u8; 32] { self.0.as_inner() } -} - -impl AsRef<[u8]> for Txid { - fn as_ref(&self) -> &[u8] { self.0.as_ref() } -} - -impl From for [u8; 32] { - fn from(value: Txid) -> Self { value.0.into_inner() } -} - -impl Debug for Txid { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("Txid").field(&self.to_hex()).finish() - } -} - -/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a -/// big endian order. Thus we need this manual implementation. -impl LowerHex for Txid { - 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 Txid { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_hex().to_uppercase()) - } -} - -impl FromStr for Txid { - type Err = hex::Error; - fn from_str(s: &str) -> Result { Self::from_hex(s) } -} - -/// Satoshi made all SHA245d-based hashes to be displayed as hex strings in a -/// big endian order. Thus we need this manual implementation. -impl FromHex for Txid { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { - Bytes32::from_byte_iter(iter.rev()).map(Self::from) - } -} +impl_sha256d_hashtype!(Txid, "Txid"); impl Txid { pub fn coinbase() -> Self { Self(zero!()) } @@ -273,6 +225,8 @@ pub struct Tx { #[cfg(test)] mod test { + use amplify::hex::{FromHex, ToHex}; + use super::*; #[test] From e13eb2897f6a53d5815c6542b2c5c5c9a1d5d424 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 30 Jul 2023 13:09:22 +0200 Subject: [PATCH 09/14] primitives: add Hash derivations to all data types --- primitives/src/script.rs | 2 +- primitives/src/taproot.rs | 2 +- primitives/src/tx.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/primitives/src/script.rs b/primitives/src/script.rs index 02fc20ef..6d724b3a 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -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)] diff --git a/primitives/src/taproot.rs b/primitives/src/taproot.rs index 86761a13..fffa9304 100644 --- a/primitives/src/taproot.rs +++ b/primitives/src/taproot.rs @@ -356,7 +356,7 @@ impl LeafScript { pub fn tap_leaf_hash(&self) -> TapLeafHash { TapLeafHash::with_leaf_script(self) } } -#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN, tags = repr, into_u8, try_from_u8)] #[repr(u8)] diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 6f2ab3b0..215a085e 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -93,7 +93,7 @@ impl Outpoint { } } -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -111,7 +111,7 @@ impl SeqNo { pub const fn to_consensus_u32(&self) -> u32 { self.0 } } -#[derive(Wrapper, Clone, Eq, PartialEq, Debug, From)] +#[derive(Wrapper, Clone, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, Index, RangeOps)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -129,7 +129,7 @@ impl Witness { } } -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] @@ -140,7 +140,7 @@ pub struct TxIn { pub witness: Witness, } -#[derive(Copy, Clone, Eq, PartialEq, Debug, From)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -150,7 +150,7 @@ pub struct TxIn { )] pub struct Sats(u64); -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] @@ -159,7 +159,7 @@ pub struct TxOut { pub script_pubkey: ScriptPubkey, } -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] @@ -194,7 +194,7 @@ impl TxVer { pub const fn to_consensus_u32(&self) -> i32 { self.0 } } -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -212,7 +212,7 @@ impl LockTime { pub const fn to_consensus_u32(&self) -> u32 { self.0 } } -#[derive(Clone, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] From 14afda8a9a5801e2b8d704bc1c2a1e9045dfb3af Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 30 Jul 2023 17:12:58 +0200 Subject: [PATCH 10/14] primitives: fix segwit address format detection --- primitives/src/segwit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index e4a39460..3d296ee6 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -203,11 +203,11 @@ impl ScriptPubkey { } pub fn is_p2wpkh(&self) -> bool { - self.len() == 22 && self[0] == WitnessVer::V1.op_code() as u8 && self[1] == OP_PUSHBYTES_20 + 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::V1.op_code() as u8 && self[1] == OP_PUSHBYTES_32 + 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`]. From 61f6ecfd136cb657f7a178b1cd0562a8ef11dad3 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 30 Jul 2023 17:16:54 +0200 Subject: [PATCH 11/14] primitives: add WitnessVer:version_no --- primitives/src/segwit.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index 3d296ee6..a95c1a09 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -155,10 +155,33 @@ impl WitnessVer { /// 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. From dd69a7383902b0b9a4a47049e30b034ae3b8fde5 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 31 Jul 2023 15:43:16 +0200 Subject: [PATCH 12/14] primitives: add Sats methods --- primitives/src/tx.rs | 78 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 215a085e..4f36a746 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -148,7 +148,41 @@ pub struct TxIn { derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct Sats(u64); +pub struct Sats(pub u64); + +impl Sats { + pub const ZERO: Self = Sats(0); + pub const BTC: Self = Sats(1_000_000_00); + + pub const fn from_btc(btc: u32) -> Self { Self(btc as u64 * Self::BTC.0) } + + pub const fn btc_round(&self) -> u64 { + if self.0 == 0 { + return 0; + } + let inc = 2 * self.sats_rem() / Self::BTC.0; + self.0 / Self::BTC.0 + inc + } + + pub const fn btc_ceil(&self) -> u64 { + if self.0 == 0 { + return 0; + } + let inc = if self.sats_rem() > 0 { 1 } else { 0 }; + self.0 / Self::BTC.0 + inc + } + + pub const fn btc_floor(&self) -> u64 { + if self.0 == 0 { + return 0; + } + self.0 / Self::BTC.0 + } + + pub const fn sats(&self) -> u64 { self.0 } + + pub const fn sats_rem(&self) -> u64 { self.0 % Self::BTC.0 } +} #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] @@ -237,4 +271,46 @@ mod test { assert_eq!(from_str, from_hex); assert_eq!(from_str.to_string(), from_str.to_hex()); } + + #[test] + fn sats() { + assert_eq!(Sats(0).0, 0); + assert_eq!(Sats(0).btc_round(), 0); + assert_eq!(Sats(0).btc_ceil(), 0); + assert_eq!(Sats(0).btc_floor(), 0); + assert_eq!(Sats(0).sats(), 0); + assert_eq!(Sats(0).sats_rem(), 0); + + assert_eq!(Sats(1000).0, 1000); + assert_eq!(Sats(1000).btc_round(), 0); + assert_eq!(Sats(1000).btc_ceil(), 1); + assert_eq!(Sats(1000).btc_floor(), 0); + assert_eq!(Sats(1000).sats(), 1000); + assert_eq!(Sats(1000).sats_rem(), 1000); + + assert_eq!(Sats(49_999_999).btc_round(), 0); + assert_eq!(Sats(49_999_999).btc_ceil(), 1); + assert_eq!(Sats(49_999_999).btc_floor(), 0); + assert_eq!(Sats(50_000_000).0, 50_000_000); + assert_eq!(Sats(50_000_000).btc_round(), 1); + assert_eq!(Sats(50_000_000).btc_ceil(), 1); + assert_eq!(Sats(50_000_000).btc_floor(), 0); + assert_eq!(Sats(50_000_000).sats(), 50_000_000); + assert_eq!(Sats(50_000_000).sats_rem(), 50_000_000); + + assert_eq!(Sats(99_999_999).btc_round(), 1); + assert_eq!(Sats(99_999_999).btc_ceil(), 1); + assert_eq!(Sats(99_999_999).btc_floor(), 0); + assert_eq!(Sats(100_000_000), Sats::from_btc(1)); + assert_eq!(Sats(100_000_000).0, 100_000_000); + assert_eq!(Sats(100_000_000).btc_round(), 1); + assert_eq!(Sats(100_000_000).btc_ceil(), 1); + assert_eq!(Sats(100_000_000).btc_floor(), 1); + assert_eq!(Sats(100_000_000).sats(), 100_000_000); + assert_eq!(Sats(100_000_000).sats_rem(), 0); + assert_eq!(Sats(100_000_001).sats(), 100_000_001); + assert_eq!(Sats(100_000_001).sats_rem(), 1); + assert_eq!(Sats(110_000_000).sats(), 110_000_000); + assert_eq!(Sats(110_000_000).sats_rem(), 10_000_000); + } } From 0d9814ed72fbcfb84bd06bb3c07d23bc32058feb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 31 Jul 2023 15:58:59 +0200 Subject: [PATCH 13/14] primitives: add Display and math derives to Sats --- primitives/src/tx.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 4f36a746..e017e51e 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -140,7 +140,10 @@ pub struct TxIn { pub witness: Witness, } -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)] +#[derive(Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Hash, Debug, Display, From)] +#[wrapper(Add, Sub, Mul, Div)] +#[wrapper_mut(MathAssign)] +#[display(inner)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( From e898de22761940ac7d67c89c95b9e0836b6c732c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 1 Aug 2023 12:43:04 +0200 Subject: [PATCH 14/14] primitives: add FromStr and better Display derives for Sats --- primitives/src/tx.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index e017e51e..5b7cd9b3 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -19,7 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::Debug; +use std::fmt::{self, Debug, Display, Formatter}; use std::num::ParseIntError; use std::str::FromStr; @@ -140,10 +140,9 @@ pub struct TxIn { pub witness: Witness, } -#[derive(Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Hash, Debug, Display, From)] -#[wrapper(Add, Sub, Mul, Div)] +#[derive(Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Hash, Debug, From)] +#[wrapper(Add, Sub, Mul, Div, FromStr)] #[wrapper_mut(MathAssign)] -#[display(inner)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -187,6 +186,10 @@ impl Sats { pub const fn sats_rem(&self) -> u64 { self.0 % Self::BTC.0 } } +impl Display for Sats { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) } +} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)]