diff --git a/Cargo.lock b/Cargo.lock index 91e82ad7..712b3e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,8 +4,8 @@ version = 3 [[package]] name = "amplify" -version = "4.0.2" -source = "git+https://github.com/rust-amplify/rust-amplify?branch=feat/array-reverse#dd1debe29309af7ebced4d33e7120283c3145f00" +version = "4.1.1" +source = "git+https://github.com/rust-amplify/rust-amplify?branch=feat/array-reverse#cb9186baded3635bc85ce6d4b4fe512ba7201932" dependencies = [ "amplify_apfloat", "amplify_derive 3.0.1", @@ -121,9 +121,9 @@ checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base85" @@ -142,16 +142,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest", ] [[package]] @@ -220,9 +219,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" @@ -232,9 +231,12 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -309,7 +311,6 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", - "subtle", ] [[package]] @@ -378,15 +379,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "mnemonic" @@ -418,18 +419,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -492,29 +493,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.174" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.174" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -546,9 +547,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -588,9 +589,9 @@ dependencies = [ [[package]] name = "strict_types" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a6654c43ed02891b249730856ad6b866fd85d1008aad51b30bd68812798427" +checksum = "c41eb7f3d2da6b413204be9605fd27de10a440875695246cf5e000eecf6d08ed" dependencies = [ "amplify", "baid58", @@ -611,12 +612,6 @@ dependencies = [ "serde_str_helpers", ] -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - [[package]] name = "syn" version = "1.0.109" @@ -630,9 +625,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -641,22 +636,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -670,15 +665,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unsafe-libyaml" @@ -719,7 +714,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", "wasm-bindgen-shared", ] @@ -741,7 +736,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/primitives/src/consensus.rs b/primitives/src/consensus.rs new file mode 100644 index 00000000..9052cf5f --- /dev/null +++ b/primitives/src/consensus.rs @@ -0,0 +1,699 @@ +// 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::io::{self, Cursor, Read, Write}; + +use amplify::confinement::{Confined, U32}; +use amplify::{confinement, IoError, RawArray, Wrapper}; + +use crate::{ + LockTime, Outpoint, Sats, ScriptBytes, ScriptPubkey, SeqNo, SigScript, Tx, TxIn, TxOut, TxVer, + Txid, VarInt, Vout, Witness, +}; + +pub type VarIntArray = Confined, 0, U32>; + +pub trait VarIntSize { + fn var_int_size(&self) -> VarInt; +} + +impl VarIntSize for VarIntArray { + fn var_int_size(&self) -> VarInt { VarInt::with(self.len()) } +} + +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(inner)] +pub enum ConsensusDecodeError { + #[from] + #[from(io::Error)] + Io(IoError), + + #[from] + #[from(confinement::Error)] + Data(ConsensusDataError), +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(doc_comments)] +pub enum ConsensusDataError { + /// consensus data are followed by some excessive bytes. + DataNotConsumed, + + /// not a minimally-encoded variable integer. + NonMinimalVarInt, + + #[from] + #[display(inner)] + Confined(confinement::Error), + + /// unsupported Segwit flag {0}. + UnsupportedSegwitFlag(u8), +} + +pub trait ConsensusEncode { + fn consensus_encode(&self, writer: &mut impl Write) -> Result; + fn consensus_serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.consensus_encode(&mut buf) + .expect("in-memory writing can't fail"); + buf + } +} + +pub trait ConsensusDecode +where Self: Sized +{ + fn consensus_decode(reader: &mut impl Read) -> Result; + fn consensus_deserialize(bytes: impl AsRef<[u8]>) -> Result { + let mut cursor = Cursor::new(bytes.as_ref()); + let me = Self::consensus_decode(&mut cursor)?; + if cursor.position() as usize != bytes.as_ref().len() { + return Err(ConsensusDataError::DataNotConsumed.into()); + } + Ok(me) + } +} + +impl ConsensusEncode for Tx { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + let mut counter = self.version.consensus_encode(writer)?; + if self.is_segwit() && !self.inputs.is_empty() { + 0x00_u8.consensus_encode(writer)?; + 0x01_u8.consensus_encode(writer)?; + counter += 2; + } + counter += self.inputs.consensus_encode(writer)?; + counter += self.outputs.consensus_encode(writer)?; + if self.is_segwit() { + for input in self.inputs() { + counter += input.witness.consensus_encode(writer)?; + } + } + counter += self.lock_time.consensus_encode(writer)?; + Ok(counter) + } +} + +impl ConsensusDecode for Tx { + fn consensus_decode(reader: &mut impl Read) -> Result { + let version = TxVer::consensus_decode(reader)?; + let prefix = VarInt::consensus_decode(reader)?; + + let segwit = prefix == 0u8; + let mut inputs = if segwit { + // SegWit + let flag = u8::consensus_decode(reader)?; + if flag != 0x01 { + Err(ConsensusDataError::UnsupportedSegwitFlag(flag))? + } + VarIntArray::::consensus_decode(reader)? + } else { + // our prefix is the number of inputs + let mut inputs = Vec::with_capacity(prefix.to_usize()); + for _ in 0..prefix.to_u64() { + inputs.push(TxIn::consensus_decode(reader)?); + } + VarIntArray::try_from(inputs)? + }; + + let outputs = VarIntArray::consensus_decode(reader)?; + if segwit { + for input in &mut inputs { + input.witness = Witness::consensus_decode(reader)?; + } + } + let lock_time = LockTime::consensus_decode(reader)?; + + Ok(Tx { + version, + inputs, + outputs, + lock_time, + }) + } +} + +impl ConsensusEncode for TxVer { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.to_consensus_i32().consensus_encode(writer) + } +} + +impl ConsensusDecode for TxVer { + fn consensus_decode(reader: &mut impl Read) -> Result { + i32::consensus_decode(reader).map(Self::from_consensus_i32) + } +} + +impl ConsensusEncode for TxIn { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + let mut counter = self.prev_output.consensus_encode(writer)?; + counter += self.sig_script.consensus_encode(writer)?; + counter += self.sequence.consensus_encode(writer)?; + Ok(counter) + } +} + +impl ConsensusDecode for TxIn { + fn consensus_decode(reader: &mut impl Read) -> Result { + let prev_output = Outpoint::consensus_decode(reader)?; + let sig_script = SigScript::consensus_decode(reader)?; + let sequence = SeqNo::consensus_decode(reader)?; + Ok(TxIn { + prev_output, + sig_script, + sequence, + witness: none!(), + }) + } +} + +impl ConsensusEncode for TxOut { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + let mut counter = self.value.consensus_encode(writer)?; + counter += self.script_pubkey.consensus_encode(writer)?; + Ok(counter) + } +} + +impl ConsensusDecode for TxOut { + fn consensus_decode(reader: &mut impl Read) -> Result { + let value = Sats::consensus_decode(reader)?; + let script_pubkey = ScriptPubkey::consensus_decode(reader)?; + Ok(TxOut { + value, + script_pubkey, + }) + } +} + +impl ConsensusEncode for Outpoint { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + let mut counter = self.txid.consensus_encode(writer)?; + counter += self.vout.consensus_encode(writer)?; + Ok(counter) + } +} + +impl ConsensusDecode for Outpoint { + fn consensus_decode(reader: &mut impl Read) -> Result { + let txid = Txid::consensus_decode(reader)?; + let vout = Vout::consensus_decode(reader)?; + Ok(Outpoint { txid, vout }) + } +} + +impl ConsensusEncode for Txid { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_raw_array())?; + Ok(32) + } +} + +impl ConsensusDecode for Txid { + fn consensus_decode(reader: &mut impl Read) -> Result { + <[u8; 32]>::consensus_decode(reader).map(Self::from) + } +} + +impl ConsensusEncode for Vout { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.into_u32().consensus_encode(writer) + } +} + +impl ConsensusDecode for Vout { + fn consensus_decode(reader: &mut impl Read) -> Result { + u32::consensus_decode(reader).map(Self::from) + } +} + +impl ConsensusEncode for SeqNo { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.to_consensus_u32().consensus_encode(writer) + } +} + +impl ConsensusDecode for SeqNo { + fn consensus_decode(reader: &mut impl Read) -> Result { + u32::consensus_decode(reader).map(Self::from_consensus_u32) + } +} + +impl ConsensusEncode for LockTime { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.to_consensus_u32().consensus_encode(writer) + } +} + +impl ConsensusDecode for LockTime { + fn consensus_decode(reader: &mut impl Read) -> Result { + u32::consensus_decode(reader).map(Self::from_consensus_u32) + } +} + +impl ConsensusEncode for ScriptBytes { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_var_int_array().consensus_encode(writer) + } +} + +impl ConsensusDecode for ScriptBytes { + fn consensus_decode(reader: &mut impl Read) -> Result { + VarIntArray::consensus_decode(reader).map(Self::from_inner) + } +} + +impl ConsensusEncode for ScriptPubkey { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_script_bytes().consensus_encode(writer) + } +} + +impl ConsensusDecode for ScriptPubkey { + fn consensus_decode(reader: &mut impl Read) -> Result { + ScriptBytes::consensus_decode(reader).map(Self::from_inner) + } +} + +impl ConsensusEncode for SigScript { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_script_bytes().consensus_encode(writer) + } +} + +impl ConsensusDecode for SigScript { + fn consensus_decode(reader: &mut impl Read) -> Result { + ScriptBytes::consensus_decode(reader).map(Self::from_inner) + } +} + +impl ConsensusEncode for Witness { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.as_var_int_array().consensus_encode(writer) + } +} + +impl ConsensusDecode for Witness { + fn consensus_decode(reader: &mut impl Read) -> Result { + VarIntArray::consensus_decode(reader).map(Self::from_inner) + } +} + +impl ConsensusEncode for Sats { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + self.0.consensus_encode(writer) + } +} + +impl ConsensusDecode for Sats { + fn consensus_decode(reader: &mut impl Read) -> Result { + u64::consensus_decode(reader).map(Self) + } +} + +impl ConsensusEncode for VarInt { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + match self.0 { + 0..=0xFC => { + (self.0 as u8).consensus_encode(writer)?; + Ok(1) + } + 0xFD..=0xFFFF => { + 0xFDu8.consensus_encode(writer)?; + (self.0 as u16).consensus_encode(writer)?; + Ok(3) + } + 0x10000..=0xFFFFFFFF => { + 0xFEu8.consensus_encode(writer)?; + (self.0 as u32).consensus_encode(writer)?; + Ok(5) + } + _ => { + 0xFFu8.consensus_encode(writer)?; + self.0.consensus_encode(writer)?; + Ok(9) + } + } + } +} + +impl ConsensusDecode for VarInt { + fn consensus_decode(reader: &mut impl Read) -> Result { + let n = u8::consensus_decode(reader)?; + match n { + 0xFF => { + let x = u64::consensus_decode(reader)?; + if x < 0x100000000 { + Err(ConsensusDataError::NonMinimalVarInt)? + } else { + Ok(VarInt::new(x)) + } + } + 0xFE => { + let x = u32::consensus_decode(reader)?; + if x < 0x10000 { + Err(ConsensusDataError::NonMinimalVarInt)? + } else { + Ok(VarInt::new(x as u64)) + } + } + 0xFD => { + let x = u16::consensus_decode(reader)?; + if x < 0xFD { + Err(ConsensusDataError::NonMinimalVarInt)? + } else { + Ok(VarInt::with(x)) + } + } + n => Ok(VarInt::with(n)), + } + } +} + +impl ConsensusEncode for VarIntArray { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + let mut counter = self.var_int_size().consensus_encode(writer)?; + for item in self { + counter += item.consensus_encode(writer)?; + } + Ok(counter) + } +} + +impl ConsensusDecode for VarIntArray { + fn consensus_decode(reader: &mut impl Read) -> Result { + let len = VarInt::consensus_decode(reader)?; + let mut arr = Vec::new(); + for _ in 0..len.0 { + arr.push(T::consensus_decode(reader)?); + } + VarIntArray::try_from(arr).map_err(ConsensusDecodeError::from) + } +} + +impl ConsensusEncode for u8 { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&[*self])?; + Ok(1) + } +} + +impl ConsensusDecode for u8 { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; (Self::BITS / 8) as usize]; + reader.read_exact(&mut buf)?; + Ok(Self::from_le_bytes(buf)) + } +} + +impl ConsensusEncode for u16 { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_le_bytes())?; + Ok(2) + } +} + +impl ConsensusDecode for u16 { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; (Self::BITS / 8) as usize]; + reader.read_exact(&mut buf)?; + Ok(Self::from_le_bytes(buf)) + } +} + +impl ConsensusEncode for u32 { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_le_bytes())?; + Ok(4) + } +} + +impl ConsensusDecode for u32 { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; (Self::BITS / 8) as usize]; + reader.read_exact(&mut buf)?; + Ok(Self::from_le_bytes(buf)) + } +} + +impl ConsensusEncode for i32 { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_le_bytes())?; + Ok(4) + } +} + +impl ConsensusDecode for i32 { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; (Self::BITS / 8) as usize]; + reader.read_exact(&mut buf)?; + Ok(Self::from_le_bytes(buf)) + } +} + +impl ConsensusEncode for u64 { + fn consensus_encode(&self, writer: &mut impl Write) -> Result { + writer.write_all(&self.to_le_bytes())?; + Ok(8) + } +} + +impl ConsensusDecode for u64 { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; (Self::BITS / 8) as usize]; + reader.read_exact(&mut buf)?; + Ok(Self::from_le_bytes(buf)) + } +} + +impl ConsensusDecode for [u8; 32] { + fn consensus_decode(reader: &mut impl Read) -> Result { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + Ok(buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn serialize(t: &impl ConsensusEncode) -> Vec { + let mut vec = Vec::new(); + t.consensus_encode(&mut vec).unwrap(); + vec + } + + fn deserialize(d: impl AsRef<[u8]>) -> Result { + T::consensus_deserialize(d) + } + + fn deserialize_partial( + d: impl AsRef<[u8]>, + ) -> Result { + let mut cursor = Cursor::new(d.as_ref()); + Ok(T::consensus_decode(&mut cursor).map_err(|err| match err { + ConsensusDecodeError::Data(e) => e, + ConsensusDecodeError::Io(_) => unreachable!(), + })?) + } + + #[test] + fn serialize_int_test() { + // u8 + assert_eq!(serialize(&1u8), vec![1u8]); + assert_eq!(serialize(&0u8), vec![0u8]); + assert_eq!(serialize(&255u8), vec![255u8]); + // u16 + assert_eq!(serialize(&1u16), vec![1u8, 0]); + assert_eq!(serialize(&256u16), vec![0u8, 1]); + assert_eq!(serialize(&5000u16), vec![136u8, 19]); + // u32 + assert_eq!(serialize(&1u32), vec![1u8, 0, 0, 0]); + assert_eq!(serialize(&256u32), vec![0u8, 1, 0, 0]); + assert_eq!(serialize(&5000u32), vec![136u8, 19, 0, 0]); + assert_eq!(serialize(&500000u32), vec![32u8, 161, 7, 0]); + assert_eq!(serialize(&168430090u32), vec![10u8, 10, 10, 10]); + // i32 + assert_eq!(serialize(&-1i32), vec![255u8, 255, 255, 255]); + assert_eq!(serialize(&-256i32), vec![0u8, 255, 255, 255]); + assert_eq!(serialize(&-5000i32), vec![120u8, 236, 255, 255]); + assert_eq!(serialize(&-500000i32), vec![224u8, 94, 248, 255]); + assert_eq!(serialize(&-168430090i32), vec![246u8, 245, 245, 245]); + assert_eq!(serialize(&1i32), vec![1u8, 0, 0, 0]); + assert_eq!(serialize(&256i32), vec![0u8, 1, 0, 0]); + assert_eq!(serialize(&5000i32), vec![136u8, 19, 0, 0]); + assert_eq!(serialize(&500000i32), vec![32u8, 161, 7, 0]); + assert_eq!(serialize(&168430090i32), vec![10u8, 10, 10, 10]); + // u64 + assert_eq!(serialize(&1u64), vec![1u8, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!(serialize(&256u64), vec![0u8, 1, 0, 0, 0, 0, 0, 0]); + assert_eq!(serialize(&5000u64), vec![136u8, 19, 0, 0, 0, 0, 0, 0]); + assert_eq!(serialize(&500000u64), vec![32u8, 161, 7, 0, 0, 0, 0, 0]); + assert_eq!(serialize(&723401728380766730u64), vec![10u8, 10, 10, 10, 10, 10, 10, 10]); + } + + #[test] + fn serialize_varint_test() { + assert_eq!(serialize(&VarInt(10)), vec![10u8]); + assert_eq!(serialize(&VarInt(0xFC)), vec![0xFCu8]); + assert_eq!(serialize(&VarInt(0xFD)), vec![0xFDu8, 0xFD, 0]); + assert_eq!(serialize(&VarInt(0xFFF)), vec![0xFDu8, 0xFF, 0xF]); + assert_eq!(serialize(&VarInt(0xF0F0F0F)), vec![0xFEu8, 0xF, 0xF, 0xF, 0xF]); + assert_eq!(serialize(&VarInt(0xF0F0F0F0F0E0)), vec![ + 0xFFu8, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0, 0 + ]); + assert_eq!( + test_varint_encode(0xFF, &0x100000000_u64.to_le_bytes()).unwrap(), + VarInt(0x100000000) + ); + assert_eq!(test_varint_encode(0xFE, &0x10000_u64.to_le_bytes()).unwrap(), VarInt(0x10000)); + assert_eq!(test_varint_encode(0xFD, &0xFD_u64.to_le_bytes()).unwrap(), VarInt(0xFD)); + + // Test that length calc is working correctly + test_varint_len(VarInt(0), 1); + test_varint_len(VarInt(0xFC), 1); + test_varint_len(VarInt(0xFD), 3); + test_varint_len(VarInt(0xFFFF), 3); + test_varint_len(VarInt(0x10000), 5); + test_varint_len(VarInt(0xFFFFFFFF), 5); + test_varint_len(VarInt(0xFFFFFFFF + 1), 9); + test_varint_len(VarInt(u64::MAX), 9); + } + + fn test_varint_len(varint: VarInt, expected: usize) { + let mut encoder = vec![]; + assert_eq!(varint.consensus_encode(&mut encoder).unwrap(), expected); + assert_eq!(varint.len(), expected); + } + + fn test_varint_encode(n: u8, x: &[u8]) -> Result { + let mut input = [0u8; 9]; + input[0] = n; + input[1..x.len() + 1].copy_from_slice(x); + deserialize_partial::(&input) + } + + #[test] + fn deserialize_nonminimal_vec() { + // Check the edges for variant int + assert_eq!( + test_varint_encode(0xFF, &(0x100000000_u64 - 1).to_le_bytes()).unwrap_err(), + ConsensusDataError::NonMinimalVarInt + ); + assert_eq!( + test_varint_encode(0xFE, &(0x10000_u64 - 1).to_le_bytes()).unwrap_err(), + ConsensusDataError::NonMinimalVarInt + ); + assert_eq!( + test_varint_encode(0xFD, &(0xFD_u64 - 1).to_le_bytes()).unwrap_err(), + ConsensusDataError::NonMinimalVarInt + ); + + assert_eq!( + deserialize::>(&[0xfd, 0x00, 0x00]).unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + assert_eq!( + deserialize::>(&[0xfd, 0xfc, 0x00]).unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + assert_eq!( + deserialize::>(&[0xfd, 0xfc, 0x00]).unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + assert_eq!( + deserialize::>(&[0xfe, 0xff, 0x00, 0x00, 0x00]).unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + assert_eq!( + deserialize::>(&[0xfe, 0xff, 0xff, 0x00, 0x00]).unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + assert_eq!( + deserialize::>(&[0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + .unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + assert_eq!( + deserialize::>(&[0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00]) + .unwrap_err(), + ConsensusDataError::NonMinimalVarInt.into() + ); + + let mut vec_256 = vec![0; 259]; + vec_256[0] = 0xfd; + vec_256[1] = 0x00; + vec_256[2] = 0x01; + assert!(deserialize::>(&vec_256).is_ok()); + + let mut vec_253 = vec![0; 256]; + vec_253[0] = 0xfd; + vec_253[1] = 0xfd; + vec_253[2] = 0x00; + assert!(deserialize::>(&vec_253).is_ok()); + } + + #[test] + fn deserialize_int_test() { + // u8 + assert_eq!(deserialize(&[58u8]).ok(), Some(58u8)); + + // u16 + assert_eq!(deserialize(&[0x01u8, 0x02]).ok(), Some(0x0201u16)); + assert_eq!(deserialize(&[0xABu8, 0xCD]).ok(), Some(0xCDABu16)); + assert_eq!(deserialize(&[0xA0u8, 0x0D]).ok(), Some(0xDA0u16)); + let failure16: Result = deserialize(&[1u8]); + assert!(failure16.is_err()); + + // u32 + assert_eq!(deserialize(&[0xABu8, 0xCD, 0, 0]).ok(), Some(0xCDABu32)); + assert_eq!(deserialize(&[0xA0u8, 0x0D, 0xAB, 0xCD]).ok(), Some(0xCDAB0DA0u32)); + + let failure32: Result = deserialize(&[1u8, 2, 3]); + assert!(failure32.is_err()); + + // i32 + assert_eq!(deserialize(&[0xABu8, 0xCD, 0, 0]).ok(), Some(0xCDABi32)); + assert_eq!(deserialize(&[0xA0u8, 0x0D, 0xAB, 0x2D]).ok(), Some(0x2DAB0DA0i32)); + + assert_eq!(deserialize(&[0, 0, 0, 0]).ok(), Some(-0_i32)); + assert_eq!(deserialize(&[0, 0, 0, 0]).ok(), Some(0_i32)); + + assert_eq!(deserialize(&[0xFF, 0xFF, 0xFF, 0xFF]).ok(), Some(-1_i32)); + assert_eq!(deserialize(&[0xFE, 0xFF, 0xFF, 0xFF]).ok(), Some(-2_i32)); + assert_eq!(deserialize(&[0x01, 0xFF, 0xFF, 0xFF]).ok(), Some(-255_i32)); + assert_eq!(deserialize(&[0x02, 0xFF, 0xFF, 0xFF]).ok(), Some(-254_i32)); + + let failurei32: Result = deserialize(&[1u8, 2, 3]); + assert!(failurei32.is_err()); + + // u64 + assert_eq!(deserialize(&[0xABu8, 0xCD, 0, 0, 0, 0, 0, 0]).ok(), Some(0xCDABu64)); + assert_eq!( + deserialize(&[0xA0u8, 0x0D, 0xAB, 0xCD, 0x99, 0, 0, 0x99]).ok(), + Some(0x99000099CDAB0DA0u64) + ); + let failure64: Result = deserialize(&[1u8, 2, 3, 4, 5, 6, 7]); + assert!(failure64.is_err()); + } +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 5102b573..a5c3af32 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -41,6 +41,7 @@ extern crate commit_verify; #[macro_use] extern crate serde_crate as serde; +extern crate core; /// Re-export of `secp256k1` crate. pub extern crate secp256k1; @@ -51,24 +52,24 @@ mod segwit; mod taproot; mod tx; mod util; +mod weights; #[cfg(feature = "stl")] pub mod stl; +mod consensus; pub use block::{BlockHash, BlockHeader}; +pub use consensus::{ + ConsensusDataError, ConsensusDecode, ConsensusDecodeError, ConsensusEncode, VarIntArray, + VarIntSize, +}; 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, LOCKTIME_THRESHOLD, + LockTime, Outpoint, OutpointParseError, Sats, SeqNo, Tx, TxIn, TxOut, TxParseError, TxVer, + Txid, Vout, Witness, Wtxid, LOCKTIME_THRESHOLD, }; -pub use types::VarIntArray; -pub use util::{Chain, ChainParseError, NonStandardValue}; +pub use util::{Chain, ChainParseError, NonStandardValue, VarInt}; +pub use weights::{VBytes, Weight, WeightUnits}; pub const LIB_NAME_BITCOIN: &str = "Bitcoin"; - -mod types { - use amplify::confinement::{Confined, U32}; - - pub type VarIntArray = Confined, 0, U32>; -} diff --git a/primitives/src/script.rs b/primitives/src/script.rs index e7a17aba..b7d25f6f 100644 --- a/primitives/src/script.rs +++ b/primitives/src/script.rs @@ -133,6 +133,11 @@ impl FromHex for SigScript { } } +impl SigScript { + pub fn empty() -> Self { SigScript::default() } + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } +} + #[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)] @@ -205,6 +210,8 @@ impl ScriptPubkey { /// Adds a single opcode to the script. pub fn push_opcode(&mut self, op_code: OpCode) { self.0.push(op_code as u8) } + + pub fn as_script_bytes(&self) -> &ScriptBytes { &self.0 } } impl FromHex for ScriptPubkey { @@ -305,6 +312,8 @@ impl ScriptBytes { } pub fn into_vec(self) -> Vec { self.0.into_inner() } + + pub(crate) fn as_var_int_array(&self) -> &VarIntArray { &self.0 } } #[cfg(feature = "serde")] diff --git a/primitives/src/taproot.rs b/primitives/src/taproot.rs index fffa9304..b4dfc785 100644 --- a/primitives/src/taproot.rs +++ b/primitives/src/taproot.rs @@ -53,6 +53,10 @@ pub const MIDSTATE_TAPTWEAK: [u8; 8] = *b"TapTweak"; pub const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; // f504a425d7f8783b1363868ae3e556586eee945dbc7888dd02a6e2c31873fe9f +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] +#[display("invalid public key")] +pub struct InvalidPubkey; + #[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, LowerHex, Display, FromStr)] #[wrapper_mut(DerefMut)] @@ -66,6 +70,14 @@ pub const MIDSTATE_TAPSIGHASH: [u8; 10] = *b"TapSighash"; pub struct InternalPk(XOnlyPublicKey); impl InternalPk { + pub fn from_byte_array(data: [u8; 32]) -> Result { + XOnlyPublicKey::from_slice(data.as_ref()) + .map(Self) + .map_err(|_| InvalidPubkey) + } + + pub fn to_byte_array(&self) -> [u8; 32] { self.0.serialize() } + pub fn to_output_key(&self, merkle_root: Option) -> XOnlyPublicKey { let mut engine = Sha256::from_tag(MIDSTATE_TAPTWEAK); // always hash the key diff --git a/primitives/src/tx.rs b/primitives/src/tx.rs index e495f194..dc457ff4 100644 --- a/primitives/src/tx.rs +++ b/primitives/src/tx.rs @@ -19,17 +19,23 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::slice; use std::cmp::Ordering; -use std::fmt::{self, Debug, Display, Formatter}; +use std::fmt::{self, Debug, Display, Formatter, LowerHex}; use std::iter::Sum; use std::num::ParseIntError; use std::str::FromStr; +use std::vec; -use amplify::hex::FromHex; -use amplify::{hex, Bytes32StrRev, Wrapper}; +use amplify::hex::{self, FromHex, ToHex}; +use amplify::{Bytes32StrRev, RawArray, Wrapper}; +use commit_verify::{DigestExt, Sha256}; use super::{VarIntArray, LIB_NAME_BITCOIN}; -use crate::{NonStandardValue, ScriptPubkey, SigScript}; +use crate::{ + ConsensusDecode, ConsensusDecodeError, ConsensusEncode, NonStandardValue, ScriptPubkey, + SigScript, +}; #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] @@ -50,7 +56,10 @@ pub struct Txid( ); impl Txid { - pub fn coinbase() -> Self { Self(zero!()) } + #[inline] + pub const fn coinbase() -> Self { Self(Bytes32StrRev::zero()) } + #[inline] + pub fn is_coinbase(&self) -> bool { self.to_raw_array() == [0u8; 32] } } impl FromHex for Txid { @@ -60,6 +69,30 @@ impl FromHex for Txid { } } +#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_BITCOIN)] +#[derive(CommitEncode)] +#[commit_encode(strategy = strict)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", transparent) +)] +#[wrapper(BorrowSlice, Index, RangeOps, Debug, LowerHex, UpperHex, Display, FromStr)] +pub struct Wtxid( + #[from] + #[from([u8; 32])] + Bytes32StrRev, +); + +impl FromHex for Wtxid { + 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)] @@ -73,10 +106,15 @@ impl FromHex for Txid { pub struct Vout(u32); impl Vout { + pub const fn from_u32(u: u32) -> Self { Vout(u) } #[inline] - pub fn into_u32(self) -> u32 { self.0 } + pub const fn into_u32(self) -> u32 { self.0 } #[inline] - pub fn into_usize(self) -> usize { self.0 as usize } + pub const fn into_usize(self) -> usize { self.0 as usize } + #[inline] + pub const fn to_u32(&self) -> u32 { self.0 } + #[inline] + pub const fn to_usize(&self) -> usize { self.0 as usize } } impl FromStr for Vout { @@ -105,11 +143,22 @@ impl Outpoint { } } + #[inline] + pub const fn coinbase() -> Self { + Self { + txid: Txid::coinbase(), + vout: Vout::from_u32(0), + } + } + #[inline] pub fn vout_u32(self) -> u32 { self.vout.into_u32() } #[inline] pub fn vout_usize(self) -> usize { self.vout.into_usize() } + + #[inline] + pub fn is_coinbase(&self) -> bool { self.txid.is_coinbase() && self.vout.into_u32() == 0 } } #[derive(Clone, Eq, PartialEq, Debug, Display, From, Error)] @@ -163,9 +212,20 @@ impl SeqNo { #[strict_type(lib = LIB_NAME_BITCOIN)] pub struct Witness(VarIntArray>); +impl IntoIterator for Witness { + type Item = VarIntArray; + type IntoIter = vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} + impl Witness { pub fn new() -> Self { default!() } + pub fn elements(&self) -> impl Iterator { + self.0.iter().map(|el| el.as_slice()) + } + 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") @@ -174,6 +234,8 @@ impl Witness { VarIntArray::try_from_iter(iter).expect("witness stack size exceeds 2^64 bytes"); Witness(stack) } + + pub(crate) fn as_var_int_array(&self) -> &VarIntArray> { &self.0 } } #[cfg(feature = "serde")] @@ -220,7 +282,9 @@ pub struct TxIn { pub witness: Witness, } -#[derive(Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Hash, Debug, From, Default)] +#[derive( + Wrapper, WrapperMut, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, From, Default +)] #[wrapper(Add, Sub, Mul, Div, FromStr)] #[wrapper_mut(MathAssign)] #[derive(StrictType, StrictEncode, StrictDecode)] @@ -351,6 +415,15 @@ pub struct TxOut { pub script_pubkey: ScriptPubkey, } +impl TxOut { + pub fn new(script_pubkey: impl Into, value: impl Into) -> Self { + TxOut { + script_pubkey: script_pubkey.into(), + value: value.into(), + } + } +} + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] @@ -383,7 +456,7 @@ impl TxVer { pub const fn is_standard(self) -> bool { self.0 <= TxVer::V2.0 } #[inline] - pub const fn to_consensus_u32(&self) -> i32 { self.0 } + pub const fn to_consensus_i32(&self) -> i32 { self.0 } } /// The Threshold for deciding whether a lock time value is a height or a time @@ -423,8 +496,12 @@ impl PartialOrd for LockTime { } impl LockTime { + /// Zero time lock + pub const ZERO: Self = Self(0); + /// Create zero time lock #[inline] + #[deprecated(since = "0.10.8", note = "use LockTime::ZERO")] pub const fn zero() -> Self { Self(0) } /// Creates absolute time lock with the given block height. @@ -475,7 +552,7 @@ impl LockTime { pub const fn is_time_based(self) -> bool { !self.is_height_based() } } -#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_BITCOIN)] #[cfg_attr( @@ -483,6 +560,7 @@ impl LockTime { derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] +#[display(LowerHex)] pub struct Tx { pub version: TxVer, pub inputs: VarIntArray, @@ -490,6 +568,97 @@ pub struct Tx { pub lock_time: LockTime, } +impl LowerHex for Tx { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(&self.consensus_serialize().to_hex()) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] +#[display(inner)] +pub enum TxParseError { + #[from] + Hex(hex::Error), + #[from] + Consensus(ConsensusDecodeError), +} + +impl FromStr for Tx { + type Err = TxParseError; + + fn from_str(s: &str) -> Result { + let data = Vec::::from_hex(s)?; + Tx::consensus_deserialize(&data).map_err(TxParseError::from) + } +} + +impl Tx { + #[inline] + pub fn inputs(&self) -> slice::Iter { self.inputs.iter() } + + #[inline] + pub fn outputs(&self) -> slice::Iter { self.outputs.iter() } + + #[inline] + pub fn is_segwit(&self) -> bool { self.inputs().any(|txin| !txin.witness.is_empty()) } + + #[inline] + pub fn to_unsigned_tx(&self) -> Tx { + let mut tx = self.clone(); + for input in &mut tx.inputs { + input.sig_script = SigScript::empty(); + input.witness = empty!(); + } + tx + } + + /// Computes a "normalized TXID" which does not include any signatures. + /// + /// This gives a way to identify a transaction that is "the same" as + /// another in the sense of having same inputs and outputs. + pub fn ntxid(&self) -> [u8; 32] { self.to_unsigned_tx().txid().to_raw_array() } + + /// Computes the [`Txid`]. + /// + /// Hashes the transaction **excluding** the segwit data (i.e. the marker, + /// flag bytes, and the witness fields themselves). For non-segwit + /// transactions which do not have any segwit data, this will be equal + /// to [`Tx::wtxid()`]. + pub fn txid(&self) -> Txid { + let mut enc = Sha256::default(); + self.version + .consensus_encode(&mut enc) + .expect("engines don't error"); + self.inputs + .consensus_encode(&mut enc) + .expect("engines don't error"); + self.outputs + .consensus_encode(&mut enc) + .expect("engines don't error"); + self.lock_time + .consensus_encode(&mut enc) + .expect("engines don't error"); + let mut double = Sha256::default(); + double.input_raw(&enc.finish()); + Txid::from_raw_array(double.finish()) + } + + /// Computes the segwit version of the transaction id. + /// + /// Hashes the transaction **including** all segwit data (i.e. the marker, + /// flag bytes, and the witness fields themselves). For non-segwit + /// transactions which do not have any segwit data, this will be equal + /// to [`Transaction::txid()`]. + pub fn wtxid(&self) -> Wtxid { + let mut enc = Sha256::default(); + self.consensus_encode(&mut enc) + .expect("engines don't error"); + let mut double = Sha256::default(); + double.input_raw(&enc.finish()); + Wtxid::from_raw_array(double.finish()) + } +} + #[cfg(test)] mod test { use amplify::hex::{FromHex, ToHex}; @@ -549,4 +718,104 @@ mod test { assert_eq!(Sats(110_000_000).sats(), 110_000_000); assert_eq!(Sats(110_000_000).sats_rem(), 10_000_000); } + + #[test] + fn nonsegwit_transaction() { + let tx = + "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c49\ + 3046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7\ + f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506e\ + fdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b\ + 3839e2bbf32d826a1e222031fd888ac00000000"; + let realtx = Tx::from_str(tx).unwrap(); + + assert_eq!(&realtx.to_string(), tx); + assert_eq!(&realtx.to_hex(), tx); + assert_eq!(&format!("{realtx:x}"), tx); + + // All these tests aren't really needed because if they fail, the hash check at + // the end will also fail. But these will show you where the failure is + // so I'll leave them in. + assert_eq!(realtx.version, TxVer::V1); + assert_eq!(realtx.inputs.len(), 1); + // In particular this one is easy to get backward -- in bitcoin hashes are + // encoded as little-endian 256-bit numbers rather than as data strings. + assert_eq!( + format!("{:x}", realtx.inputs[0].prev_output.txid), + "ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string() + ); + assert_eq!(realtx.inputs[0].prev_output.vout, Vout::from_u32(1)); + assert_eq!(realtx.outputs.len(), 1); + assert_eq!(realtx.lock_time, LockTime::ZERO); + + assert_eq!( + format!("{:x}", realtx.txid()), + "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string() + ); + assert_eq!( + format!("{:x}", realtx.wtxid()), + "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string() + ); + /* TODO: Enable once weight calculation is there + assert_eq!(realtx.weight().to_wu() as usize, tx_bytes.len() * WITNESS_SCALE_FACTOR); + assert_eq!(realtx.total_size(), tx_bytes.len()); + assert_eq!(realtx.vsize(), tx_bytes.len()); + assert_eq!(realtx.base_size(), tx_bytes.len()); + */ + } + + #[test] + fn segwit_transaction() { + let tx = + "02000000000101595895ea20179de87052b4046dfe6fd515860505d6511a9004cf12a1f93cac7c01000000\ + 00ffffffff01deb807000000000017a9140f3444e271620c736808aa7b33e370bd87cb5a078702483045022\ + 100fb60dad8df4af2841adc0346638c16d0b8035f5e3f3753b88db122e70c79f9370220756e6633b17fd271\ + 0e626347d28d60b0a2d6cbb41de51740644b9fb3ba7751040121028fa937ca8cba2197a37c007176ed89410\ + 55d3bcb8627d085e94553e62f057dcc00000000"; + let realtx = Tx::from_str(tx).unwrap(); + + assert_eq!(&realtx.to_string(), tx); + assert_eq!(&realtx.to_hex(), tx); + assert_eq!(&format!("{realtx:x}"), tx); + + // All these tests aren't really needed because if they fail, the hash check at + // the end will also fail. But these will show you where the failure is + // so I'll leave them in. + assert_eq!(realtx.version, TxVer::V2); + assert_eq!(realtx.inputs.len(), 1); + // In particular this one is easy to get backward -- in bitcoin hashes are + // encoded as little-endian 256-bit numbers rather than as data strings. + assert_eq!( + format!("{:x}", realtx.inputs[0].prev_output.txid), + "7cac3cf9a112cf04901a51d605058615d56ffe6d04b45270e89d1720ea955859".to_string() + ); + assert_eq!(realtx.inputs[0].prev_output.vout, Vout::from_u32(1)); + assert_eq!(realtx.outputs.len(), 1); + assert_eq!(realtx.lock_time, LockTime::ZERO); + + assert_eq!( + format!("{:x}", realtx.txid()), + "f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206".to_string() + ); + assert_eq!( + format!("{:x}", realtx.wtxid()), + "80b7d8a82d5d5bf92905b06f2014dd699e03837ca172e3a59d51426ebbe3e7f5".to_string() + ); + + /* TODO: Enable once weight calculation is there + const EXPECTED_WEIGHT: Weight = Weight::from_wu(442); + assert_eq!(realtx.weight(), EXPECTED_WEIGHT); + assert_eq!(realtx.total_size(), tx_bytes.len()); + assert_eq!(realtx.vsize(), 111); + + let expected_strippedsize = (442 - realtx.total_size()) / 3; + assert_eq!(realtx.base_size(), expected_strippedsize); + + // Construct a transaction without the witness data. + let mut tx_without_witness = realtx; + tx_without_witness.input.iter_mut().for_each(|input| input.witness.clear()); + assert_eq!(tx_without_witness.total_size(), tx_without_witness.total_size()); + assert_eq!(tx_without_witness.total_size(), expected_strippedsize); + */ + } } diff --git a/primitives/src/util.rs b/primitives/src/util.rs index d7c70642..cef8139c 100644 --- a/primitives/src/util.rs +++ b/primitives/src/util.rs @@ -92,3 +92,39 @@ impl Chain { } } } + +/// A variable-length unsigned integer. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct VarInt(pub u64); + +#[allow(clippy::len_without_is_empty)] // VarInt has no concept of 'is_empty'. +impl VarInt { + pub const fn new(u: u64) -> Self { VarInt(u) } + + pub fn with(u: impl Into) -> Self { VarInt(u.into() as u64) } + + /// Gets the length of this VarInt when encoded. + /// + /// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1), + /// and 9 otherwise. + #[inline] + pub const fn len(&self) -> usize { + match self.0 { + 0..=0xFC => 1, + 0xFD..=0xFFFF => 3, + 0x10000..=0xFFFFFFFF => 5, + _ => 9, + } + } + + pub const fn to_u64(&self) -> u64 { self.0 } + pub const fn into_u64(self) -> u64 { self.0 } + pub fn to_usize(&self) -> usize { + usize::try_from(self.0).expect("transaction too large for a non-64 bit platform") + } + pub fn into_usize(self) -> usize { self.to_usize() } +} + +impl + Copy> PartialEq for VarInt { + fn eq(&self, other: &U) -> bool { self.0.eq(&(*other).into()) } +} diff --git a/primitives/src/weights.rs b/primitives/src/weights.rs new file mode 100644 index 00000000..e9436e78 --- /dev/null +++ b/primitives/src/weights.rs @@ -0,0 +1,144 @@ +// 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::iter::Sum; +use std::ops::{Add, AddAssign}; + +use crate::{ScriptPubkey, SigScript, Tx, TxIn, TxOut, VarIntSize, Witness}; + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[display("{0} vbytes")] +pub struct VBytes(u32); + +impl Add for VBytes { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { Self(self.0 + rhs.0) } +} + +impl AddAssign for VBytes { + fn add_assign(&mut self, rhs: Self) { self.0.add_assign(rhs.0) } +} + +impl Sum for VBytes { + fn sum>(iter: I) -> Self { Self(iter.map(Self::into_u32).sum()) } +} + +impl VBytes { + pub fn to_u32(&self) -> u32 { self.0 } + pub fn into_u32(self) -> u32 { self.0 } +} + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[display("{0} WU")] +pub struct WeightUnits(u32); + +impl Add for WeightUnits { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { Self(self.0 + rhs.0) } +} + +impl AddAssign for WeightUnits { + fn add_assign(&mut self, rhs: Self) { self.0.add_assign(rhs.0) } +} + +impl Sum for WeightUnits { + fn sum>(iter: I) -> Self { Self(iter.map(Self::into_u32).sum()) } +} + +impl From for VBytes { + fn from(wu: WeightUnits) -> Self { Self((wu.0 as f32 / 4.0).ceil() as u32) } +} + +impl WeightUnits { + pub fn no_discount(bytes: usize) -> Self { WeightUnits(bytes as u32 * 4) } + pub fn witness_discount(bytes: usize) -> Self { WeightUnits(bytes as u32) } + pub fn to_u32(&self) -> u32 { self.0 } + pub fn into_u32(self) -> u32 { self.0 } +} + +pub trait Weight { + fn weight_units(&self) -> WeightUnits; + + #[inline] + fn vbytes(&self) -> VBytes { VBytes::from(self.weight_units()) } +} + +impl Weight for Tx { + fn weight_units(&self) -> WeightUnits { + let bytes = 4 // version + + self.inputs.var_int_size().len() + + self.outputs.var_int_size().len() + + 4; // lock time + + let mut weight = WeightUnits::no_discount(bytes) + + self.inputs().map(TxIn::weight_units).sum() + + self.outputs().map(TxOut::weight_units).sum(); + if self.is_segwit() { + weight += WeightUnits::witness_discount(2); // marker and flag bytes + weight += self + .inputs() + .map(|txin| &txin.witness) + .map(Witness::weight_units) + .sum(); + } + weight + } +} + +impl Weight for TxIn { + fn weight_units(&self) -> WeightUnits { + WeightUnits::no_discount( + 32 // txid + + 4 // vout + + 4, // nseq + ) + self.sig_script.weight_units() + } +} + +impl Weight for TxOut { + fn weight_units(&self) -> WeightUnits { + WeightUnits::no_discount(8) // value + + self.script_pubkey.weight_units() + } +} + +impl Weight for ScriptPubkey { + fn weight_units(&self) -> WeightUnits { + WeightUnits::no_discount(self.var_int_size().len() + self.len()) + } +} + +impl Weight for SigScript { + fn weight_units(&self) -> WeightUnits { + WeightUnits::no_discount(self.var_int_size().len() + self.len()) + } +} + +impl Weight for Witness { + fn weight_units(&self) -> WeightUnits { + WeightUnits::witness_discount( + self.var_int_size().len() + + self.iter() + .map(|item| item.var_int_size().len() + item.len()) + .sum::(), + ) + } +}