From 323c75a40c26b53ebb9217d5cfcdce4e7cc3f08b Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 7 Aug 2023 18:59:33 +0200 Subject: [PATCH 01/20] primitives: add checked Sats arithmetic operations --- primitives/src/tx.rs | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 5b7cd9b3..c90fd6c9 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -20,6 +20,7 @@ // limitations under the License. use std::fmt::{self, Debug, Display, Formatter}; +use std::iter::Sum; use std::num::ParseIntError; use std::str::FromStr; @@ -150,7 +151,13 @@ pub struct TxIn { derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -pub struct Sats(pub u64); +pub struct Sats( + #[from] + #[from(u32)] + #[from(u16)] + #[from(u8)] + pub u64, +); impl Sats { pub const ZERO: Self = Sats(0); @@ -184,6 +191,56 @@ impl Sats { pub const fn sats(&self) -> u64 { self.0 } pub const fn sats_rem(&self) -> u64 { self.0 % Self::BTC.0 } + + pub fn checked_add(&self, other: impl Into) -> Option { + self.0.checked_add(other.into().0).map(Self) + } + pub fn checked_sub(&self, other: impl Into) -> Option { + self.0.checked_sub(other.into().0).map(Self) + } + + pub fn checked_add_assign(&mut self, other: impl Into) -> bool { + self.0 + .checked_add(other.into().0) + .map(Self) + .map(|sum| *self = sum) + .map(|_| true) + .unwrap_or_default() + } + pub fn checked_sub_assign(&mut self, other: impl Into) -> bool { + self.0 + .checked_sub(other.into().0) + .map(Self) + .map(|sum| *self = sum) + .map(|_| true) + .unwrap_or_default() + } + + pub fn saturating_add(&self, other: impl Into) -> Self { + self.0.saturating_add(other.into().0).into() + } + pub fn saturating_sub(&self, other: impl Into) -> Self { + self.0.saturating_sub(other.into().0).into() + } + + pub fn saturating_add_assign(&mut self, other: impl Into) { + *self = self.0.saturating_add(other.into().0).into(); + } + pub fn saturating_sub_assign(&mut self, other: impl Into) { + *self = self.0.saturating_sub(other.into().0).into(); + } +} + +impl Sum for Sats { + fn sum>(iter: I) -> Self { + iter.fold(Sats::ZERO, |sum, value| sum.saturating_add(value)) + } +} + +impl Sum for Sats { + fn sum>(iter: I) -> Self { + iter.fold(Sats::ZERO, |sum, value| sum.saturating_add(value)) + } } impl Display for Sats { From 4efb8f148b4e1402d357a872eb1d74d8ec105226 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 11 Aug 2023 13:29:49 +0200 Subject: [PATCH 02/20] primitives: add serde to BlockHeader --- primitives/src/block.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/primitives/src/block.rs b/primitives/src/block.rs index ef4876cd..b79e5da5 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -41,6 +41,11 @@ pub struct BlockHash( impl_sha256d_hashtype!(BlockHash, "BlockHash"); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] pub struct BlockHeader { /// Block version, now repurposed for soft fork signalling. pub version: i32, From c7bc1338a1f555e8be347cb2e5d1854f169da628 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 11 Aug 2023 13:37:52 +0200 Subject: [PATCH 03/20] primitives: impl FromStr for Outpoint --- primitives/src/tx.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index c90fd6c9..a42c1072 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -24,7 +24,7 @@ use std::iter::Sum; use std::num::ParseIntError; use std::str::FromStr; -use amplify::{Bytes32, Wrapper}; +use amplify::{hex, Bytes32, Wrapper}; use super::{VarIntArray, LIB_NAME_BITCOIN}; use crate::{NonStandardValue, ScriptPubkey, SigScript}; @@ -94,6 +94,33 @@ impl Outpoint { } } +#[derive(Clone, Eq, PartialEq, Debug, Display, From, Error)] +#[display(doc_comments)] +pub enum OutpointParseError { + /// malformed string representation of outoint '{0}' lacking txid and vout + /// separator ':' + MalformedSeparator(String), + + /// malformed outpoint output number. Details: {0} + #[from] + InvalidVout(ParseIntError), + + /// malformed outpoint txid value. Details: {0} + #[from] + InvalidTxid(hex::Error), +} + +impl FromStr for Outpoint { + type Err = OutpointParseError; + + fn from_str(s: &str) -> Result { + let (txid, vout) = s + .split_once(':') + .ok_or_else(|| OutpointParseError::MalformedSeparator(s.to_owned()))?; + Ok(Outpoint::new(txid.parse()?, Vout::from_str(vout)?)) + } +} + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] From 60517997fa08fbe90306b388bd8ab97327020057 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 11 Aug 2023 13:39:04 +0200 Subject: [PATCH 04/20] primitives: rename all serde fields to camelCase --- primitives/src/tx.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index a42c1072..cbe1812a 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -160,7 +160,11 @@ impl Witness { #[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"))] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] pub struct TxIn { pub prev_output: Outpoint, pub sig_script: SigScript, @@ -277,7 +281,11 @@ impl Display for Sats { #[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"))] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] pub struct TxOut { pub value: Sats, pub script_pubkey: ScriptPubkey, @@ -339,7 +347,11 @@ impl LockTime { #[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"))] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] pub struct Tx { pub version: TxVer, pub inputs: VarIntArray, From fdf4167076ca6034837187d5b5dd5e3c6593f7ad Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 11 Aug 2023 13:55:51 +0200 Subject: [PATCH 05/20] primitives: export OutpointParseError --- primitives/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 78e1d5d6..3b264166 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -60,7 +60,10 @@ pub use block::{BlockHash, BlockHeader}; pub use script::{OpCode, ScriptPubkey, SigScript}; pub use segwit::*; pub use taproot::*; -pub use tx::{LockTime, Outpoint, Sats, SeqNo, Tx, TxIn, TxOut, TxVer, Txid, Vout, Witness}; +pub use tx::{ + LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxVer, Txid, Vout, + Witness, +}; pub use types::{ScriptBytes, VarIntArray}; pub use util::{Chain, NonStandardValue}; From b0c8538c223c29e85ad58fe4f6be59d6c8555515 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 11 Aug 2023 14:52:35 +0200 Subject: [PATCH 06/20] primitives: fix WitnessVer::from_version_no --- primitives/src/segwit.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/primitives/src/segwit.rs b/primitives/src/segwit.rs index a95c1a09..464e3b6c 100644 --- a/primitives/src/segwit.rs +++ b/primitives/src/segwit.rs @@ -149,8 +149,26 @@ impl WitnessVer { /// 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) + Ok(match no { + v if v == Self::V0.version_no() => Self::V0, + v if v == Self::V1.version_no() => Self::V1, + v if v == Self::V2.version_no() => Self::V2, + v if v == Self::V3.version_no() => Self::V3, + v if v == Self::V4.version_no() => Self::V4, + v if v == Self::V5.version_no() => Self::V5, + v if v == Self::V6.version_no() => Self::V6, + v if v == Self::V7.version_no() => Self::V7, + v if v == Self::V8.version_no() => Self::V8, + v if v == Self::V9.version_no() => Self::V9, + v if v == Self::V10.version_no() => Self::V10, + v if v == Self::V11.version_no() => Self::V11, + v if v == Self::V12.version_no() => Self::V12, + v if v == Self::V13.version_no() => Self::V13, + v if v == Self::V14.version_no() => Self::V14, + v if v == Self::V15.version_no() => Self::V15, + v if v == Self::V16.version_no() => Self::V16, + _ => return Err(SegwitError::InvalidWitnessVersion(no)), + }) } /// Converts [`WitnessVer`] instance into corresponding Bitcoin op-code. From 58cf4b150ab5417dac4fbec64726f3c02dffffbe Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Sep 2023 11:18:42 +0200 Subject: [PATCH 07/20] primitives: add Chain::is_testnet method --- primitives/src/util.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/primitives/src/util.rs b/primitives/src/util.rs index 96c2c646..d7c70642 100644 --- a/primitives/src/util.rs +++ b/primitives/src/util.rs @@ -82,3 +82,13 @@ impl FromStr for Chain { }) } } + +impl Chain { + #[inline] + pub fn is_test_chain(self) -> bool { + match self { + Chain::Bitcoin => false, + Chain::Testnet3 | Chain::Regtest | Chain::Signet => true, + } + } +} From 25a11b3f0db99bed34900c945151f845062e8a68 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Mon, 18 Sep 2023 11:25:50 +0200 Subject: [PATCH 08/20] primitives: export ChainParseError --- primitives/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 3b264166..e816e27b 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -65,7 +65,7 @@ pub use tx::{ Witness, }; pub use types::{ScriptBytes, VarIntArray}; -pub use util::{Chain, NonStandardValue}; +pub use util::{Chain, ChainParseError, NonStandardValue}; pub const LIB_NAME_BITCOIN: &str = "Bitcoin"; From 9f636cf372ebeda7b866217796fd1a241be36767 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 16:26:22 +0200 Subject: [PATCH 09/20] chore: update to amplify v4.1.0 --- Cargo.lock | 43 +++++++++------------------------------- Cargo.toml | 6 +++++- seals/src/txout/blind.rs | 2 +- 3 files changed, 15 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea8e6a46..91e82ad7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,8 @@ version = 3 [[package]] name = "amplify" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4707ab08f19a25ba492cbf61713591b7f022b54ee188f35457e6de22f367df4a" +version = "4.0.2" +source = "git+https://github.com/rust-amplify/rust-amplify?branch=feat/array-reverse#dd1debe29309af7ebced4d33e7120283c3145f00" dependencies = [ "amplify_apfloat", "amplify_derive 3.0.1", @@ -313,12 +312,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "generic-array" version = "0.14.7" @@ -355,12 +348,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - [[package]] name = "heck" version = "0.4.1" @@ -374,17 +361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -556,11 +533,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.25" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ - "indexmap 2.0.0", + "indexmap", "itoa", "ryu", "serde", @@ -590,8 +567,7 @@ dependencies = [ [[package]] name = "strict_encoding" version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b1c91b79e62afc09025d828fa0b8dbf4105513de38f126a017919c09bca472" +source = "git+https://github.com/strict-types/strict-encoding?branch=chore/amplify-4.1.0#b54ba615b3a2d32278ba8b2ad1ec207dd5d4f853" dependencies = [ "amplify", "half", @@ -601,8 +577,7 @@ dependencies = [ [[package]] name = "strict_encoding_derive" version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5adae55367464f5a229bfd539682c94f870b98a220be6e61dc43f85d612e7e" +source = "git+https://github.com/strict-types/strict-encoding?branch=chore/amplify-4.1.0#b54ba615b3a2d32278ba8b2ad1ec207dd5d4f853" dependencies = [ "amplify_syn", "heck", @@ -621,7 +596,7 @@ dependencies = [ "baid58", "base64", "half", - "indexmap 1.9.3", + "indexmap", "sha2", "strict_encoding", ] diff --git a/Cargo.toml b/Cargo.toml index 025ffde6..a415f868 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ edition = "2021" license = "Apache-2.0" [workspace.dependencies] -amplify = "4.0.1" +amplify = "4.0.2" strict_encoding = "2.5.0" commit_verify = "0.10.5" single_use_seals = "0.10.0" @@ -72,3 +72,7 @@ stl = ["strict_types", "strict_types/base64", "bp-primitives/stl", "commit_verif [package.metadata.docs.rs] features = [ "all" ] + +[patch.crates-io] +amplify = { git = "https://github.com/rust-amplify/rust-amplify", branch = "feat/array-reverse" } +strict_encoding = { git = "https://github.com/strict-types/strict-encoding", branch = "chore/amplify-4.1.0" } \ No newline at end of file diff --git a/seals/src/txout/blind.rs b/seals/src/txout/blind.rs index c84951f8..68c23323 100644 --- a/seals/src/txout/blind.rs +++ b/seals/src/txout/blind.rs @@ -533,7 +533,7 @@ mod test { ); assert_eq!( ChainBlindSeal::from_str("tapret1st:rvgbdg:5#0x78ca69"), - Err(ParseError::WrongTxid(hex::Error::InvalidChar(b'g'))) + Err(ParseError::WrongTxid(hex::Error::InvalidChar(b'r'))) ); assert_eq!( ChainBlindSeal::from_str( From 272faaff97b00b9f184e50b325dba0615ac1fae6 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 16:29:23 +0200 Subject: [PATCH 10/20] primitives: fix Txid and BlockHash types serde serialization --- primitives/src/block.rs | 18 ++++++---- primitives/src/lib.rs | 2 -- primitives/src/macros.rs | 74 ---------------------------------------- primitives/src/tx.rs | 23 +++++++++---- 4 files changed, 28 insertions(+), 89 deletions(-) delete mode 100644 primitives/src/macros.rs diff --git a/primitives/src/block.rs b/primitives/src/block.rs index b79e5da5..a8375cf0 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -19,12 +19,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use amplify::{Bytes32, Wrapper}; +use amplify::hex::{self, FromHex}; +use amplify::{Bytes32, Bytes32StrRev, Wrapper}; use crate::LIB_NAME_BITCOIN; -#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Display, From)] -#[display(LowerHex)] +#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -32,13 +32,19 @@ use crate::LIB_NAME_BITCOIN; derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -#[wrapper(BorrowSlice, Index, RangeOps)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, Display, FromStr)] pub struct BlockHash( #[from] #[from([u8; 32])] - Bytes32, + Bytes32StrRev, ); -impl_sha256d_hashtype!(BlockHash, "BlockHash"); + +impl FromHex for BlockHash { + fn from_byte_iter(iter: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + Bytes32StrRev::from_byte_iter(iter).map(Self) + } +} #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[cfg_attr( diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index e816e27b..f3c0facb 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -44,8 +44,6 @@ 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 deleted file mode 100644 index 93f8ec79..00000000 --- a/primitives/src/macros.rs +++ /dev/null @@ -1,74 +0,0 @@ -// 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 cbe1812a..5d509cca 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -24,13 +24,13 @@ use std::iter::Sum; use std::num::ParseIntError; use std::str::FromStr; -use amplify::{hex, Bytes32, Wrapper}; +use amplify::hex::FromHex; +use amplify::{hex, Bytes32StrRev, Wrapper}; use super::{VarIntArray, LIB_NAME_BITCOIN}; use crate::{NonStandardValue, ScriptPubkey, SigScript}; -#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Display, From)] -#[display(LowerHex)] +#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[derive(CommitEncode)] @@ -40,19 +40,25 @@ use crate::{NonStandardValue, ScriptPubkey, SigScript}; derive(Serialize, Deserialize), serde(crate = "serde_crate", transparent) )] -#[wrapper(BorrowSlice, Index, RangeOps)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, Display, FromStr)] // all-zeros used in coinbase pub struct Txid( #[from] #[from([u8; 32])] - Bytes32, + Bytes32StrRev, ); -impl_sha256d_hashtype!(Txid, "Txid"); impl Txid { pub fn coinbase() -> Self { Self(zero!()) } } +impl FromHex for Txid { + fn from_byte_iter(iter: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + Bytes32StrRev::from_byte_iter(iter).map(Self) + } +} + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -367,11 +373,14 @@ mod test { #[test] fn txid_byteorder() { - let hex = "c9a86c99127f1b2d1ff495c238f13069ac881ec9527905016122d11d85b19b61"; + let hex = "ed9f6388c0360c1861d331a0388d5a54815dd720cc67fa783c348217a0e943ca"; let from_str = Txid::from_str(hex).unwrap(); let from_hex = Txid::from_hex(hex).unwrap(); assert_eq!(from_str, from_hex); assert_eq!(from_str.to_string(), from_str.to_hex()); + assert_eq!(from_str.to_string(), hex); + assert_eq!(format!("{from_str:x}"), hex); + assert_eq!(from_str[0], 0xca); } #[test] From 133cbbe3de452f86de2e3d5357e19131b6bcdb45 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 21:51:46 +0200 Subject: [PATCH 11/20] primitives: impl FromHex for script types --- primitives/src/lib.rs | 11 ++++++++++- primitives/src/script.rs | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index f3c0facb..faf7271e 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -71,7 +71,7 @@ mod types { use std::fmt::{Formatter, LowerHex, UpperHex}; use amplify::confinement::{Confined, U32}; - use amplify::hex::ToHex; + use amplify::hex::{Error, FromHex, ToHex}; use super::LIB_NAME_BITCOIN; use crate::opcodes::*; @@ -108,6 +108,15 @@ mod types { } } + impl FromHex for ScriptBytes { + fn from_hex(s: &str) -> Result { Vec::::from_hex(s).map(Self::from) } + fn from_byte_iter(_: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator + { + unreachable!() + } + } + impl ScriptBytes { /// Adds instructions to push some arbitrary data onto the stack. /// diff --git a/primitives/src/script.rs b/primitives/src/script.rs index 6d724b3a..410e83b5 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -20,6 +20,7 @@ // limitations under the License. use amplify::confinement::Confined; +use amplify::hex::{Error, FromHex}; use crate::opcodes::*; use crate::{ScriptBytes, LIB_NAME_BITCOIN}; @@ -121,6 +122,15 @@ pub struct SigScript( ScriptBytes, ); +impl FromHex for SigScript { + fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } + + fn from_byte_iter(_: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + unreachable!() + } +} + #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Deref, Index, RangeOps, BorrowSlice, LowerHex, UpperHex)] #[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] @@ -196,3 +206,12 @@ impl ScriptPubkey { self.0.push(op_code as u8).expect("script exceeds 4GB"); } } + +impl FromHex for ScriptPubkey { + fn from_hex(s: &str) -> Result { ScriptBytes::from_hex(s).map(Self) } + + fn from_byte_iter(_: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + unreachable!() + } +} From 75595c417782d6f734a626b3406e5bc4744efe37 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 22:44:52 +0200 Subject: [PATCH 12/20] primitives: move ScriptBytes to script module --- primitives/src/lib.rs | 107 +-------------------------------------- primitives/src/script.rs | 104 +++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 110 deletions(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index faf7271e..0a298b4a 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -55,123 +55,20 @@ mod util; pub mod stl; pub use block::{BlockHash, BlockHeader}; -pub use script::{OpCode, ScriptPubkey, SigScript}; +pub use script::{OpCode, ScriptBytes, ScriptPubkey, SigScript}; pub use segwit::*; pub use taproot::*; pub use tx::{ LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxVer, Txid, Vout, Witness, }; -pub use types::{ScriptBytes, VarIntArray}; +pub use types::VarIntArray; pub use util::{Chain, ChainParseError, NonStandardValue}; pub const LIB_NAME_BITCOIN: &str = "Bitcoin"; mod types { - use std::fmt::{Formatter, LowerHex, UpperHex}; - use amplify::confinement::{Confined, U32}; - use amplify::hex::{Error, FromHex, ToHex}; - - use super::LIB_NAME_BITCOIN; - use crate::opcodes::*; pub type VarIntArray = Confined, 0, U32>; - - #[derive( - Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From - )] - #[derive(StrictType, StrictEncode, StrictDecode)] - #[strict_type(lib = LIB_NAME_BITCOIN)] - #[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) - )] - #[wrapper(Deref, Index, RangeOps, BorrowSlice)] - #[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] - pub struct ScriptBytes(VarIntArray); - - impl From> for ScriptBytes { - fn from(value: Vec) -> Self { Self(Confined::try_from(value).expect("u64 >= usize")) } - } - - impl LowerHex for ScriptBytes { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.as_inner().to_hex()) - } - } - - impl UpperHex for ScriptBytes { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0.as_inner().to_hex().to_uppercase()) - } - } - - impl FromHex for ScriptBytes { - fn from_hex(s: &str) -> Result { Vec::::from_hex(s).map(Self::from) } - fn from_byte_iter(_: I) -> Result - where I: Iterator> + ExactSizeIterator + DoubleEndedIterator - { - unreachable!() - } - } - - impl ScriptBytes { - /// Adds instructions to push some arbitrary data onto the stack. - /// - /// ## Panics - /// - /// The method panics if `data` length is greater or equal to - /// 0x100000000. - pub fn push_slice(&mut self, data: &[u8]) { - // Start with a PUSH opcode - match data.len() as u64 { - n if n < OP_PUSHDATA1 as u64 => { - self.push(n as u8); - } - n if n < 0x100 => { - self.push(OP_PUSHDATA1); - self.push(n as u8); - } - n if n < 0x10000 => { - self.push(OP_PUSHDATA2); - self.push((n % 0x100) as u8); - self.push((n / 0x100) as u8); - } - n if n < 0x100000000 => { - self.push(OP_PUSHDATA4); - self.push((n % 0x100) as u8); - self.push(((n / 0x100) % 0x100) as u8); - self.push(((n / 0x10000) % 0x100) as u8); - self.push((n / 0x1000000) as u8); - } - _ => panic!("tried to put a 4bn+ sized object into a script!"), - } - // Then push the raw bytes - self.extend(data); - } - - #[inline] - fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") } - - #[inline] - fn extend(&mut self, data: &[u8]) { - self.0 - .extend(data.iter().copied()) - .expect("script exceeds 4GB") - } - - /// Computes the sum of `len` and the lenght of an appropriate push - /// opcode. - pub fn len_for_slice(len: usize) -> usize { - len + match len { - 0..=0x4b => 1, - 0x4c..=0xff => 2, - 0x100..=0xffff => 3, - // we don't care about oversized, the other fn will panic anyway - _ => 5, - } - } - } } diff --git a/primitives/src/script.rs b/primitives/src/script.rs index 410e83b5..b7371f1f 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -19,11 +19,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fmt::{Formatter, LowerHex, UpperHex}; + use amplify::confinement::Confined; -use amplify::hex::{Error, FromHex}; +use amplify::hex::{Error, FromHex, ToHex}; use crate::opcodes::*; -use crate::{ScriptBytes, LIB_NAME_BITCOIN}; +use crate::{VarIntArray, LIB_NAME_BITCOIN}; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] @@ -202,9 +204,7 @@ impl ScriptPubkey { pub fn is_op_return(&self) -> bool { self[0] == OpCode::Return as u8 } /// Adds a single opcode to the script. - pub fn push_opcode(&mut self, op_code: OpCode) { - self.0.push(op_code as u8).expect("script exceeds 4GB"); - } + pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8) } } impl FromHex for ScriptPubkey { @@ -215,3 +215,97 @@ impl FromHex for ScriptPubkey { unreachable!() } } + +#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +#[wrapper(Deref, Index, RangeOps, BorrowSlice)] +#[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] +pub struct ScriptBytes(VarIntArray); + +impl From> for ScriptBytes { + fn from(value: Vec) -> Self { Self(Confined::try_from(value).expect("u64 >= usize")) } +} + +impl LowerHex for ScriptBytes { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0.as_inner().to_hex()) + } +} + +impl UpperHex for ScriptBytes { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0.as_inner().to_hex().to_uppercase()) + } +} + +impl FromHex for ScriptBytes { + fn from_hex(s: &str) -> Result { Vec::::from_hex(s).map(Self::from) } + fn from_byte_iter(_: I) -> Result + where I: Iterator> + ExactSizeIterator + DoubleEndedIterator { + unreachable!() + } +} + +impl ScriptBytes { + /// Adds instructions to push some arbitrary data onto the stack. + /// + /// ## Panics + /// + /// The method panics if `data` length is greater or equal to + /// 0x100000000. + pub fn push_slice(&mut self, data: &[u8]) { + // Start with a PUSH opcode + match data.len() as u64 { + n if n < OP_PUSHDATA1 as u64 => { + self.push(n as u8); + } + n if n < 0x100 => { + self.push(OP_PUSHDATA1); + self.push(n as u8); + } + n if n < 0x10000 => { + self.push(OP_PUSHDATA2); + self.push((n % 0x100) as u8); + self.push((n / 0x100) as u8); + } + n if n < 0x100000000 => { + self.push(OP_PUSHDATA4); + self.push((n % 0x100) as u8); + self.push(((n / 0x100) % 0x100) as u8); + self.push(((n / 0x10000) % 0x100) as u8); + self.push((n / 0x1000000) as u8); + } + _ => panic!("tried to put a 4bn+ sized object into a script!"), + } + // Then push the raw bytes + self.extend(data); + } + + #[inline] + fn push(&mut self, data: u8) { self.0.push(data).expect("script exceeds 4GB") } + + #[inline] + fn extend(&mut self, data: &[u8]) { + self.0 + .extend(data.iter().copied()) + .expect("script exceeds 4GB") + } + + /// Computes the sum of `len` and the lenght of an appropriate push + /// opcode. + pub fn len_for_slice(len: usize) -> usize { + len + match len { + 0..=0x4b => 1, + 0x4c..=0xff => 2, + 0x100..=0xffff => 3, + // we don't care about oversized, the other fn will panic anyway + _ => 5, + } + } +} From 64649c0e1c7aaa8757ea14b9b0241baa39921dcb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 22:49:57 +0200 Subject: [PATCH 13/20] primitives: custom serde implementation for ScriptBytes --- primitives/src/script.rs | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/primitives/src/script.rs b/primitives/src/script.rs index b7371f1f..d8b1f95a 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -219,11 +219,6 @@ impl FromHex for ScriptPubkey { #[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", transparent) -)] #[wrapper(Deref, Index, RangeOps, BorrowSlice)] #[wrapper_mut(DerefMut, IndexMut, RangeMut, BorrowSliceMut)] pub struct ScriptBytes(VarIntArray); @@ -309,3 +304,36 @@ impl ScriptBytes { } } } + +#[cfg(feature = "serde")] +mod _serde { + use serde::{Deserialize, Serialize}; + use serde_crate::de::Error; + use serde_crate::{Deserializer, Serializer}; + + use super::*; + + impl Serialize for ScriptBytes { + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_hex()) + } else { + serializer.serialize_bytes(self.as_slice()) + } + } + } + + impl<'de> Deserialize<'de> for ScriptBytes { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + if deserializer.is_human_readable() { + String::deserialize(deserializer).and_then(|string| { + Self::from_hex(&string).map_err(|_| D::Error::custom("wrong hex data")) + }) + } else { + Vec::::deserialize(deserializer).map(ScriptBytes::from) + } + } + } +} From 30d4db7103dd9002e718b5c71ed146b639a10553 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 23:37:35 +0200 Subject: [PATCH 14/20] primitives: add ScriptBytes::into_vec method --- primitives/src/script.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/primitives/src/script.rs b/primitives/src/script.rs index d8b1f95a..e7a17aba 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -303,6 +303,8 @@ impl ScriptBytes { _ => 5, } } + + pub fn into_vec(self) -> Vec { self.0.into_inner() } } #[cfg(feature = "serde")] From 2815ea6ffd77ad4bfe5e7a5b63abbf1699c5edc0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 23:37:59 +0200 Subject: [PATCH 15/20] primitives: add Witness default constructors --- primitives/src/tx.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 5d509cca..3c797bea 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -145,14 +145,15 @@ impl SeqNo { pub const fn to_consensus_u32(&self) -> u32 { self.0 } } -#[derive(Wrapper, Clone, Eq, PartialEq, Hash, Debug, From)] +#[derive(Wrapper, Clone, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Deref, Index, RangeOps)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] pub struct Witness(VarIntArray>); impl Witness { + pub fn new() -> Self { default!() } + pub fn from_consensus_stack(witness: impl IntoIterator>) -> Witness { let iter = witness.into_iter().map(|vec| { VarIntArray::try_from(vec).expect("witness stack element length exceeds 2^64 bytes") From 0453d78aebe6af85e9740189fd8a8dda8f2c3322 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 19 Sep 2023 23:38:18 +0200 Subject: [PATCH 16/20] primitives: custom Witness serde implementation --- primitives/src/tx.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 3c797bea..b6cc8ade 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -164,6 +164,35 @@ impl Witness { } } +#[cfg(feature = "serde")] +mod _serde { + use serde::{Deserialize, Serialize}; + use serde_crate::ser::SerializeSeq; + use serde_crate::{Deserializer, Serializer}; + + use super::*; + use crate::ScriptBytes; + + impl Serialize for Witness { + fn serialize(&self, serializer: S) -> Result + where S: Serializer { + let mut ser = serializer.serialize_seq(Some(self.len()))?; + for el in &self.0 { + ser.serialize_element(&ScriptBytes::from(el.to_inner()))?; + } + ser.end() + } + } + + impl<'de> Deserialize<'de> for Witness { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + let data = Vec::::deserialize(deserializer)?; + Ok(Witness::from_consensus_stack(data.into_iter().map(ScriptBytes::into_vec))) + } + } +} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] From 6b2e0dad6593e84f051fa879f3b3e61ba8f0b099 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 20 Sep 2023 00:14:55 +0200 Subject: [PATCH 17/20] primitives: add Outpoint::vout_u32 convenience method --- primitives/src/tx.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index b6cc8ade..9ca880e6 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -72,12 +72,14 @@ impl FromHex for Txid { pub struct Vout(u32); impl Vout { + #[inline] pub fn into_u32(self) -> u32 { self.0 } } impl FromStr for Vout { type Err = ParseIntError; + #[inline] fn from_str(s: &str) -> Result { s.parse().map(Self) } } @@ -92,12 +94,16 @@ pub struct Outpoint { } impl Outpoint { + #[inline] pub fn new(txid: Txid, vout: impl Into) -> Self { Self { txid, vout: vout.into(), } } + + #[inline] + pub fn vout_u32(self) -> u32 { self.vout.into_u32() } } #[derive(Clone, Eq, PartialEq, Debug, Display, From, Error)] From 64477b55072782deed9b3b1ba47644fc0dd3592d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 20 Sep 2023 10:13:33 +0200 Subject: [PATCH 18/20] primitives: improvements to Sats API --- primitives/src/tx.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 9ca880e6..8ba55f41 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -214,10 +214,10 @@ pub struct TxIn { pub witness: Witness, } -#[derive(Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Hash, Debug, From)] +#[derive(Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Hash, Debug, From, Default)] #[wrapper(Add, Sub, Mul, Div, FromStr)] #[wrapper_mut(MathAssign)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( feature = "serde", @@ -237,6 +237,10 @@ impl Sats { 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 fn from_sats(sats: impl Into) -> Self { Self(sats.into()) } + + pub const fn is_zero(&self) -> bool { self.0 == 0 } + pub const fn is_non_zero(&self) -> bool { self.0 != 0 } pub const fn btc_round(&self) -> u64 { if self.0 == 0 { @@ -263,6 +267,10 @@ impl Sats { pub const fn sats(&self) -> u64 { self.0 } + pub fn sats_i64(&self) -> i64 { + i64::try_from(self.0).expect("amount of sats exceeds total bitcoin supply") + } + pub const fn sats_rem(&self) -> u64 { self.0 % Self::BTC.0 } pub fn checked_add(&self, other: impl Into) -> Option { @@ -304,6 +312,10 @@ impl Sats { } } +impl PartialEq for Sats { + fn eq(&self, other: &u64) -> bool { self.0.eq(other) } +} + impl Sum for Sats { fn sum>(iter: I) -> Self { iter.fold(Sats::ZERO, |sum, value| sum.saturating_add(value)) From 13aa032c58c45849708cc03dfb9cafe18657d0a0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 20 Sep 2023 13:05:09 +0200 Subject: [PATCH 19/20] primitives: add Outpoint and Vout convertors to usize --- primitives/src/tx.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 8ba55f41..86b6ae00 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -74,6 +74,8 @@ pub struct Vout(u32); impl Vout { #[inline] pub fn into_u32(self) -> u32 { self.0 } + #[inline] + pub fn into_usize(self) -> usize { self.0 as usize } } impl FromStr for Vout { @@ -104,6 +106,9 @@ impl Outpoint { #[inline] pub fn vout_u32(self) -> u32 { self.vout.into_u32() } + + #[inline] + pub fn vout_usize(self) -> usize { self.vout.into_usize() } } #[derive(Clone, Eq, PartialEq, Debug, Display, From, Error)] From 263d48ebc313b71b34c624a0b6f78e76cfabfd5e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 21 Sep 2023 00:44:07 +0200 Subject: [PATCH 20/20] primitives: add LockTime API --- primitives/src/lib.rs | 2 +- primitives/src/tx.rs | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 0a298b4a..5102b573 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -60,7 +60,7 @@ pub use segwit::*; pub use taproot::*; pub use tx::{ LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxVer, Txid, Vout, - Witness, + Witness, LOCKTIME_THRESHOLD, }; pub use types::VarIntArray; pub use util::{Chain, ChainParseError, NonStandardValue}; diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index 86b6ae00..e495f194 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -19,6 +19,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::cmp::Ordering; use std::fmt::{self, Debug, Display, Formatter}; use std::iter::Sum; use std::num::ParseIntError; @@ -385,6 +386,22 @@ impl TxVer { pub const fn to_consensus_u32(&self) -> i32 { self.0 } } +/// The Threshold for deciding whether a lock time value is a height or a time +/// (see [Bitcoin Core]). +/// +/// `LockTime` values _below_ the threshold are interpreted as block heights, +/// values _above_ (or equal to) the threshold are interpreted as block times +/// (UNIX timestamp, seconds since epoch). +/// +/// Bitcoin is able to safely use this value because a block height greater than +/// 500,000,000 would never occur because it would represent a height in +/// approximately 9500 years. Conversely, block times under 500,000,000 will +/// never happen because they would represent times before 1986 which +/// are, for obvious reasons, not useful within the Bitcoin network. +/// +/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 +pub const LOCKTIME_THRESHOLD: u32 = 500_000_000; + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -395,12 +412,67 @@ impl TxVer { )] pub struct LockTime(u32); +impl PartialOrd for LockTime { + fn partial_cmp(&self, other: &Self) -> Option { + if self.is_height_based() != other.is_height_based() { + None + } else { + Some(self.0.cmp(&other.0)) + } + } +} + impl LockTime { + /// Create zero time lock + #[inline] + pub const fn zero() -> Self { Self(0) } + + /// Creates absolute time lock with the given block height. + /// + /// Block height must be strictly less than `0x1DCD6500`, otherwise + /// `None` is returned. + #[inline] + pub const fn from_height(height: u32) -> Option { + if height < LOCKTIME_THRESHOLD { + Some(Self(height)) + } else { + None + } + } + + /// Creates absolute time lock with the given UNIX timestamp value. + /// + /// Timestamp value must be greater or equal to `0x1DCD6500`, otherwise + /// `None` is returned. + #[inline] + pub const fn from_unix_timestamp(timestamp: u32) -> Option { + if timestamp < LOCKTIME_THRESHOLD { + None + } else { + Some(Self(timestamp)) + } + } + + /// Converts into full u32 representation of `nLockTime` value as it is + /// serialized in bitcoin transaction. #[inline] pub const fn from_consensus_u32(lock_time: u32) -> Self { LockTime(lock_time) } #[inline] pub const fn to_consensus_u32(&self) -> u32 { self.0 } + + #[inline] + pub const fn into_consensus_u32(self) -> u32 { self.0 } + + /// Checks if the absolute timelock provided by the `nLockTime` value + /// specifies height-based lock + #[inline] + pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD } + + /// Checks if the absolute timelock provided by the `nLockTime` value + /// specifies time-based lock + #[inline] + pub const fn is_time_based(self) -> bool { !self.is_height_based() } } #[derive(Clone, Eq, PartialEq, Hash, Debug)]