Repository: rust-bitcoin/rust-bitcoin
Pull request #2392 (2024-01-23 08:18:13)

Add functionality to serialize signatures to a writer
Serializing the ecdsa and taproot `Signature` straight to a writer is a useful thing to be able to do.

Add `to_writer` to both `SerializedSignature`s and also to the `Signature`s (calling through to `SerializedSignature`).

Remove TODO comments from code.

tcharding: I cannot think of a proper function name, needs changing from `to_write` to something more appropriate.
coveralls: ## Pull Request Test Coverage Report for [Build 7622853838](https://coveralls.io/builds/65235633)


* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.01%**) to **84.245%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/65235633/badge)](https://coveralls.io/builds/65235633) |
| :-- | --: |
| Change from base [Build 7616194895](https://coveralls.io/builds/65223220): |  -0.01% |
| Covered Lines: | 19239 |
| Relevant Lines: | 22837 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 4e75c7e4..02aedb92 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -21,6 +21,7 @@ jobs:
         uses: dtolnay/rust-toolchain@stable
       - name: Running test script
         env:
+          DO_COV: true
           AS_DEPENDENCY: false
           DO_DOCS: true
           DO_FEATURE_MATRIX: true
diff --git a/bitcoin/examples/bip32.rs b/bitcoin/examples/bip32.rs
index 9d8efbc7..98642870 100644
--- a/bitcoin/examples/bip32.rs
+++ b/bitcoin/examples/bip32.rs
@@ -3,12 +3,12 @@
 use std::str::FromStr;
 use std::{env, process};
 
-use bitcoin::address::{Address, KnownHrp};
+use bitcoin::address::Address;
 use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv, Xpub};
 use bitcoin::hex::FromHex;
 use bitcoin::secp256k1::ffi::types::AlignedType;
 use bitcoin::secp256k1::Secp256k1;
-use bitcoin::{CompressedPublicKey, NetworkKind};
+use bitcoin::{CompressedPublicKey, Network, NetworkKind};
 
 fn main() {
     // This example derives root xprv from a 32-byte seed,
@@ -50,6 +50,6 @@ fn main() {
     // manually creating indexes this time
     let zero = ChildNumber::from_normal_idx(0).unwrap();
     let public_key = xpub.derive_pub(&secp, &[zero, zero]).unwrap().public_key;
-    let address = Address::p2wpkh(&CompressedPublicKey(public_key), KnownHrp::Mainnet);
+    let address = Address::p2wpkh(&CompressedPublicKey(public_key), Network::Bitcoin);
     println!("First receiving address: {}", address);
 }
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index aeca55ab..d1d8175e 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -180,12 +180,9 @@ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
 }
 
 /// Known bech32 human-readable parts.
-///
-/// This is the human-readable part before the separator (`1`) in a bech32 encoded address e.g.,
-/// the "bc" in "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5".
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[non_exhaustive]
-pub enum KnownHrp {
+enum KnownHrp {
     /// The main Bitcoin network.
     Mainnet,
     /// The test networks, testnet and signet.
@@ -390,9 +387,9 @@ fn p2sh_from_hash(hash: ScriptHash, network: impl Into<NetworkKind>) -> Address
     /// Creates a witness pay to public key address from a public key.
     ///
     /// This is the native segwit address type for an output redeemable with a single signature.
-    pub fn p2wpkh(pk: &CompressedPublicKey, hrp: impl Into<KnownHrp>) -> Self {
+    pub fn p2wpkh(pk: &CompressedPublicKey, network: Network) -> Self {
         let program = WitnessProgram::p2wpkh(pk);
-        Address::from_witness_program(program, hrp)
+        Address::from_witness_program(program, network)
     }
 
     /// Creates a pay to script address that embeds a witness pay to public key.
@@ -405,9 +402,9 @@ pub fn p2shwpkh(pk: &CompressedPublicKey, network: impl Into<NetworkKind>) -> Ad
     }
 
     /// Creates a witness pay to script hash address.
-    pub fn p2wsh(script: &Script, hrp: impl Into<KnownHrp>) -> Address {
+    pub fn p2wsh(script: &Script, network: Network) -> Address {
         let program = WitnessProgram::p2wsh(script);
-        Address::from_witness_program(program, hrp)
+        Address::from_witness_program(program, network)
     }
 
     /// Creates a pay to script address that embeds a witness pay to script hash address.
@@ -424,24 +421,26 @@ pub fn p2tr<C: Verification>(
         secp: &Secp256k1<C>,
         internal_key: UntweakedPublicKey,
         merkle_root: Option<TapNodeHash>,
-        hrp: impl Into<KnownHrp>,
+        network: Network,
     ) -> Address {
         let program = WitnessProgram::p2tr(secp, internal_key, merkle_root);
-        Address::from_witness_program(program, hrp)
+        Address::from_witness_program(program, network)
     }
 
     /// Creates a pay to taproot address from a pre-tweaked output key.
-    pub fn p2tr_tweaked(output_key: TweakedPublicKey, hrp: impl Into<KnownHrp>) -> Address {
+    pub fn p2tr_tweaked(output_key: TweakedPublicKey, network: Network) -> Address {
         let program = WitnessProgram::p2tr_tweaked(output_key);
-        Address::from_witness_program(program, hrp)
+        Address::from_witness_program(program, network)
     }
 
     /// Creates an address from an arbitrary witness program.
     ///
     /// This only exists to support future witness versions. If you are doing normal mainnet things
     /// then you likely do not need this constructor.
-    pub fn from_witness_program(program: WitnessProgram, hrp: impl Into<KnownHrp>) -> Address {
-        let inner = AddressInner::Segwit { program, hrp: hrp.into() };
+    // TODO: This is still arguably wrong, could take a KnownHrp/Hrp instead of Network.
+    pub fn from_witness_program(program: WitnessProgram, network: Network) -> Address {
+        let hrp = KnownHrp::from_network(network);
+        let inner = AddressInner::Segwit { program, hrp };
         Address(inner, PhantomData)
     }
 
@@ -875,7 +874,7 @@ fn test_p2wpkh() {
         let key = "033bc8c83c52df5712229a2f72206d90192366c36428cb0c12b6af98324d97bfbc"
             .parse::<CompressedPublicKey>()
             .unwrap();
-        let addr = Address::p2wpkh(&key, KnownHrp::Mainnet);
+        let addr = Address::p2wpkh(&key, Bitcoin);
         assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
         assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
         roundtrips(&addr, Bitcoin);
@@ -885,7 +884,7 @@ fn test_p2wpkh() {
     fn test_p2wsh() {
         // stolen from Bitcoin transaction 5df912fda4becb1c29e928bec8d64d93e9ba8efa9b5b405bd683c86fd2c65667
         let script = ScriptBuf::from_hex("52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae").unwrap();
-        let addr = Address::p2wsh(&script, KnownHrp::Mainnet);
+        let addr = Address::p2wsh(&script, Bitcoin);
         assert_eq!(
             &addr.to_string(),
             "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"
@@ -924,7 +923,7 @@ fn test_non_existent_segwit_version() {
         );
         let program = WitnessProgram::new(WitnessVersion::V13, &program).expect("valid program");
 
-        let addr = Address::from_witness_program(program, KnownHrp::Mainnet);
+        let addr = Address::from_witness_program(program, Bitcoin);
         roundtrips(&addr, Bitcoin);
     }
 
@@ -1091,7 +1090,7 @@ fn p2tr_from_untweaked() {
         )
         .unwrap();
         let secp = Secp256k1::verification_only();
-        let address = Address::p2tr(&secp, internal_key, None, KnownHrp::Mainnet);
+        let address = Address::p2tr(&secp, internal_key, None, Network::Bitcoin);
         assert_eq!(
             address.to_string(),
             "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr"
@@ -1190,7 +1189,7 @@ fn test_is_related_to_pubkey_p2tr() {
         let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey");
         let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
         let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(xonly_pubkey);
-        let address = Address::p2tr_tweaked(tweaked_pubkey, KnownHrp::Mainnet);
+        let address = Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin);
 
         assert_eq!(
             address,
@@ -1216,7 +1215,7 @@ fn test_is_related_to_xonly_pubkey() {
         let pubkey = PublicKey::from_str(pubkey_string).expect("pubkey");
         let xonly_pubkey = XOnlyPublicKey::from(pubkey.inner);
         let tweaked_pubkey = TweakedPublicKey::dangerous_assume_tweaked(xonly_pubkey);
-        let address = Address::p2tr_tweaked(tweaked_pubkey, KnownHrp::Mainnet);
+        let address = Address::p2tr_tweaked(tweaked_pubkey, Network::Bitcoin);
 
         assert_eq!(
             address,
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index adad9084..fa0fe508 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -773,9 +773,9 @@ pub fn base_size(&self) -> usize {
     #[inline]
     pub fn total_size(&self) -> usize {
         let mut size: usize = 4; // Serialized length of a u32 for the version number.
-	let uses_segwit = self.uses_segwit_serialization();
+	let use_segwit = self.use_segwit_serialization();
 
-        if uses_segwit {
+        if use_segwit {
             size += 2; // 1 byte for the marker and 1 for the flag.
         }
 
@@ -784,7 +784,7 @@ pub fn total_size(&self) -> usize {
             .input
             .iter()
             .map(|input| {
-                if uses_segwit {
+                if use_segwit {
                     input.total_size()
                 } else {
                     input.base_size()
@@ -984,9 +984,11 @@ fn count_sigops(prevout: TxOut, input: &TxIn) -> usize {
     }
 
     /// Returns whether or not to serialize transaction as specified in BIP-144.
-    fn uses_segwit_serialization(&self) -> bool {
-        if self.input.iter().any(|input| !input.witness.is_empty()) {
-            return true;
+    fn use_segwit_serialization(&self) -> bool {
+        for input in &self.input {
+            if !input.witness.is_empty() {
+                return true;
+            }
         }
         // To avoid serialization ambiguity, no inputs means we use BIP141 serialization (see
         // `Transaction` docs for full explanation).
@@ -1173,7 +1175,7 @@ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Er
         len += self.version.consensus_encode(w)?;
 
         // Legacy transaction serialization format only includes inputs and outputs.
-        if !self.uses_segwit_serialization() {
+        if !self.use_segwit_serialization() {
             len += self.input.consensus_encode(w)?;
             len += self.output.consensus_encode(w)?;
         } else {
@@ -2165,14 +2167,14 @@ fn txin_txout_weight() {
         for (is_segwit, tx, expected_weight) in &txs {
             let txin_weight = if *is_segwit { TxIn::segwit_weight } else { TxIn::legacy_weight };
             let tx: Transaction = deserialize(Vec::from_hex(tx).unwrap().as_slice()).unwrap();
-            assert_eq!(*is_segwit, tx.uses_segwit_serialization());
+            assert_eq!(*is_segwit, tx.use_segwit_serialization());
 
             let mut calculated_weight = empty_transaction_weight
                 + tx.input.iter().fold(Weight::ZERO, |sum, i| sum + txin_weight(i))
                 + tx.output.iter().fold(Weight::ZERO, |sum, o| sum + o.weight());
 
             // The empty tx uses segwit serialization but a legacy tx does not.
-            if !tx.uses_segwit_serialization() {
+            if !tx.use_segwit_serialization() {
                 calculated_weight -= Weight::from_wu(2);
             }
 
diff --git a/bitcoin/src/crypto/ecdsa.rs b/bitcoin/src/crypto/ecdsa.rs
index 2ce06002..24940b02 100644
--- a/bitcoin/src/crypto/ecdsa.rs
+++ b/bitcoin/src/crypto/ecdsa.rs
@@ -9,6 +9,7 @@
 
 use hex::FromHex;
 use internals::write_err;
+use io::Write;
 use secp256k1;
 
 use crate::prelude::*;
@@ -58,7 +59,6 @@ pub fn serialize(&self) -> SerializedSignature {
     /// Note: this performs an extra heap allocation, you might prefer the
     /// [`serialize`](Self::serialize) method instead.
     pub fn to_vec(self) -> Vec<u8> {
-        // TODO: add support to serialize to a writer to SerializedSig
         self.signature
             .serialize_der()
             .iter()
@@ -66,6 +66,13 @@ pub fn to_vec(self) -> Vec<u8> {
             .chain(iter::once(self.sighash_type as u8))
             .collect()
     }
+
+    /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) to a `writer`.
+    #[inline]
+    pub fn serialize_to_writer<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
+        let sig = self.serialize();
+        sig.write_to(writer)
+    }
 }
 
 impl fmt::Display for Signature {
@@ -105,6 +112,12 @@ impl SerializedSignature {
     /// Returns an iterator over bytes of the signature.
     #[inline]
     pub fn iter(&self) -> core::slice::Iter<'_, u8> { self.into_iter() }
+
+    /// Writes this serialized signature to a `writer`.
+    #[inline]
+    pub fn write_to<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
+        writer.write_all(self)
+    }
 }
 
 impl core::ops::Deref for SerializedSignature {
@@ -239,3 +252,22 @@ fn from(err: NonStandardSighashTypeError) -> Self { Error::SighashType(err) }
 impl From<hex::HexToBytesError> for Error {
     fn from(err: hex::HexToBytesError) -> Self { Error::Hex(err) }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn write_serialized_signature() {
+        let hex = "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45";
+        let sig = Signature {
+            signature: secp256k1::ecdsa::Signature::from_str(hex).unwrap(),
+            sighash_type: EcdsaSighashType::All,
+        };
+
+        let mut buf = vec![];
+        sig.serialize_to_writer(&mut buf).expect("write failed");
+
+        assert_eq!(sig.to_vec(), buf)
+    }
+}
diff --git a/bitcoin/src/crypto/taproot.rs b/bitcoin/src/crypto/taproot.rs
index c0a1a2d7..b64a8e4d 100644
--- a/bitcoin/src/crypto/taproot.rs
+++ b/bitcoin/src/crypto/taproot.rs
@@ -8,6 +8,7 @@
 use core::fmt;
 
 use internals::write_err;
+use io::Write;
 
 use crate::prelude::*;
 use crate::sighash::{InvalidSighashTypeError, TapSighashType};
@@ -47,7 +48,6 @@ pub fn from_slice(sl: &[u8]) -> Result<Self, SigFromSliceError> {
     ///
     /// Note: this allocates on the heap, prefer [`serialize`](Self::serialize) if vec is not needed.
     pub fn to_vec(self) -> Vec<u8> {
-        // TODO: add support to serialize to a writer to SerializedSig
         let mut ser_sig = self.signature.as_ref().to_vec();
         if self.sighash_type == TapSighashType::Default {
             // default sighash type, don't add extra sighash byte
@@ -57,6 +57,13 @@ pub fn to_vec(self) -> Vec<u8> {
         ser_sig
     }
 
+    /// Serializes the signature to `writer`.
+    #[inline]
+    pub fn serialize_to_writer<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
+        let sig = self.serialize();
+        sig.write_to(writer)
+    }
+
     /// Serializes the signature (without heap allocation)
     ///
     /// This returns a type with an API very similar to that of `Box<[u8]>`.
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 6ea821b8..f7a4c82e 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -59,6 +59,9 @@
 /// Encodes and decodes base64 as bytes or utf8.
 pub extern crate base64;
 
+/// Encodes and decodes the Bech32 forrmat.
+pub extern crate bech32;
+
 /// Rust implementation of cryptographic hash function algorithms.
 pub extern crate hashes;
 
@@ -113,7 +116,7 @@
 #[rustfmt::skip]                // Keep public re-exports separate.
 #[doc(inline)]
 pub use crate::{
-    address::{Address, AddressType, KnownHrp},
+    address::{Address, AddressType},
     amount::{Amount, Denomination, SignedAmount},
     bip158::{FilterHash, FilterHeader},
     bip32::XKeyIdentifier,
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index 3ae3ee6b..96d78577 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -1735,6 +1735,7 @@ fn u256_to_f64() {
 mod verification {
     use super::*;
 
+    // TODO: After we verify div_rem assert x * y / y == x
     #[kani::unwind(5)] // mul_u64 loops over 4 64 bit ints so use one more than 4
     #[kani::proof]
     fn check_mul_u64() {
@@ -1743,13 +1744,4 @@ fn check_mul_u64() {
 
         let _ = x.mul_u64(y);
     }
-
-    #[kani::unwind(5)]   // I can't remember exactly why we need this.
-    #[kani::proof]
-    fn check_div_rem() {
-        let x: U256 = kani::any();
-        let y: U256 = kani::any();
-
-        assert_eq!(x * y / y, x);
-    }
 }
diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs
index 203ac314..32271bf2 100644
--- a/bitcoin/src/taproot/mod.rs
+++ b/bitcoin/src/taproot/mod.rs
@@ -1436,7 +1436,7 @@ mod test {
 
     use super::*;
     use crate::sighash::{TapSighash, TapSighashTag};
-    use crate::{Address, KnownHrp};
+    use crate::{Address, Network};
     extern crate serde_json;
 
     #[cfg(feature = "serde")]
@@ -1828,7 +1828,7 @@ fn process_script_trees(
 
             let tweak = TapTweakHash::from_key_and_tweak(internal_key, merkle_root);
             let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
-            let addr = Address::p2tr(secp, internal_key, merkle_root, KnownHrp::Mainnet);
+            let addr = Address::p2tr(secp, internal_key, merkle_root, Network::Bitcoin);
             let spk = addr.script_pubkey();
 
             assert_eq!(expected_output_key, output_key.to_inner());
diff --git a/bitcoin/src/taproot/serialized_signature.rs b/bitcoin/src/taproot/serialized_signature.rs
index ebf9ccf4..581c139b 100644
--- a/bitcoin/src/taproot/serialized_signature.rs
+++ b/bitcoin/src/taproot/serialized_signature.rs
@@ -10,6 +10,7 @@
 use core::{fmt, ops};
 
 pub use into_iter::IntoIter;
+use io::Write;
 
 use super::{SigFromSliceError, Signature};
 
@@ -166,6 +167,12 @@ pub fn to_signature(&self) -> Result<Signature, SigFromSliceError> {
     /// (this serializes it)
     #[inline]
     pub fn from_signature(sig: &Signature) -> SerializedSignature { sig.serialize() }
+
+    /// Writes this serialized signature to a `writer`.
+    #[inline]
+    pub fn write_to<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
+        writer.write_all(self)
+    }
 }
 
 /// Separate mod to prevent outside code from accidentally breaking invariants.


Test patch:
diff --git a/bitcoin/contrib/test.sh b/bitcoin/contrib/test.sh
index 6124872a..45b5b79f 100755
--- a/bitcoin/contrib/test.sh
+++ b/bitcoin/contrib/test.sh
@@ -4,6 +4,11 @@ set -ex
 
 FEATURES="std rand-std rand serde secp-recovery bitcoinconsensus-std base64 bitcoinconsensus"
 
+if [ "$DO_COV" = true ]
+then
+    export RUSTFLAGS="-C link-dead-code"
+fi
+
 cargo --version
 rustc --version