Repository: LNP-BP/client_side_validation (language: rust)

Issue:
Make bitcoin crate dependency optional in strict encoding library and everywhere else
This will require rewriting strict encoding for primitive data types instead of using bitcoin consensus encoding for them.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 58b364f3..105b6d15 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,6 +10,19 @@ env:
   CARGO_TERM_COLOR: always
 
 jobs:
+  default:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install rust stable
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: stable
+          override: true
+      - name: Default build
+        uses: actions-rs/cargo@v1
+        with:
+          command: check
   features:
     runs-on: ubuntu-latest
     strategy:
@@ -19,6 +32,7 @@ jobs:
           - rand
           - crypto
           - serde
+          - bitcoin
           - all
     steps:
       - uses: actions/checkout@v2
diff --git a/Cargo.lock b/Cargo.lock
index 5b7023bf..860db626 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -628,6 +628,7 @@ version = "1.5.0-beta.1"
 dependencies = [
  "amplify",
  "bitcoin",
+ "bitcoin_hashes",
  "chrono",
  "ed25519-dalek",
  "grin_secp256k1zkp",
diff --git a/Cargo.toml b/Cargo.toml
index 199b2e1d..729a5801 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -23,10 +23,11 @@ single_use_seals = { version = "0.5.0-rc.1", path = "./single_use_seals" }
 
 [features]
 default = ["crypto"]
-all = ["serde", "rand"]
+all = ["serde", "rand", "bitcoin"]
 rand = ["commit_verify/rand"]
 serde = ["commit_verify/serde"]
-crypto = ["strict_encoding/miniscript", "strict_encoding/crypto"]
+bitcoin = ["strict_encoding/bitcoin", "strict_encoding/miniscript"]
+crypto = ["strict_encoding/crypto"]
 
 [workspace]
 members = [".", "strict_encoding", "commit_verify", "single_use_seals", "strict_encoding/derive"]
diff --git a/commit_verify/src/multi_commit.rs b/commit_verify/src/multi_commit.rs
index 308980e2..e69f6caf 100644
--- a/commit_verify/src/multi_commit.rs
+++ b/commit_verify/src/multi_commit.rs
@@ -297,9 +297,11 @@ impl ConsensusCommit for MultiCommitBlock {
 #[cfg(test)]
 mod test {
     use super::*;
+    #[cfg(feature = "rand")]
     use amplify::Wrapper;
     use bitcoin_hashes::{Hash, HashEngine};
 
+    #[cfg(feature = "rand")]
     fn entropy_tagged_engine() -> sha256::HashEngine {
         let tag_hash = sha256::Hash::hash("LNPBP4:entropy".as_bytes());
         let mut engine = Message::engine();
@@ -309,10 +311,14 @@ mod test {
     }
 
     #[test]
-    fn test_lnpbp4_tags() {
+    #[cfg(feature = "rand")]
+    fn test_entropy_tag() {
         let midstate = sha256::Midstate::from_inner(MIDSTATE_ENTROPY);
         assert_eq!(midstate, entropy_tagged_engine().midstate());
+    }
 
+    #[test]
+    fn test_lnpbp4_tag() {
         let midstate = sha256::Midstate::from_inner(MIDSTATE_LNPBP4);
         let tag_hash = sha256::Hash::hash("LNPBP4".as_bytes());
         let mut engine = Message::engine();
diff --git a/strict_encoding/Cargo.toml b/strict_encoding/Cargo.toml
index 6e3e4f64..8aa12dde 100644
--- a/strict_encoding/Cargo.toml
+++ b/strict_encoding/Cargo.toml
@@ -20,9 +20,10 @@ crate-type = ["rlib", "staticlib"]
 [dependencies]
 amplify = { version = "3.5.1", features = ["proc_attr"] }
 strict_encoding_derive = { version = "1.1", path = "./derive", optional = true }
+bitcoin_hashes = "0.9.6" # We need this separately since bitcoin is an optional dependency
 # TODO #1: Replace with a dedicated bitcoin encoding crate once it will be
 #          ready (0.27 release presumably)
-bitcoin = "0.26"
+bitcoin = { version = "0.26", optional = true }
 miniscript = { version = "5", optional = true }
 ed25519-dalek = { version = "1", optional = true }
 grin_secp256k1zkp = { version = "0.7", optional = true }
@@ -33,6 +34,6 @@ rand = "0.7"
 
 [features]
 default = ["chrono", "derive"]
-all = ["miniscript", "crypto", "chrono", "derive"]
+all = ["miniscript", "crypto", "chrono", "derive", "bitcoin"]
 crypto = ["ed25519-dalek", "grin_secp256k1zkp"]
 derive = ["strict_encoding_derive"]
diff --git a/strict_encoding/src/crypto.rs b/strict_encoding/src/crypto.rs
index 8167b937..9889d109 100644
--- a/strict_encoding/src/crypto.rs
+++ b/strict_encoding/src/crypto.rs
@@ -158,10 +158,9 @@ impl StrictDecode for secp256k1zkp::pedersen::RangeProof {
 #[cfg(test)]
 mod test {
     use super::*;
-    use bitcoin::secp256k1::PublicKey;
-    use std::str::FromStr;
 
     #[test]
+    #[cfg(feature = "ed25519-dalek")]
     fn ed25519() {
         let keypair = ed25519_dalek::Keypair::generate(&mut rand::thread_rng());
 
@@ -174,6 +173,7 @@ mod test {
     }
 
     #[test]
+    #[cfg(feature = "ed25519-dalek")]
     fn x25519() {
         use ed25519_dalek::Signer;
 
@@ -190,7 +190,11 @@ mod test {
     }
 
     #[test]
+    #[cfg(all(feature = "grin_secp256k1zkp", feature = "bitcoin"))]
     fn pedersen() {
+        use bitcoin::secp256k1::PublicKey;
+        use std::str::FromStr;
+
         let pk = PublicKey::from_str("02d1780dd0e08f4d873f94faf49d878d909a1174291d3fcac3e02a6c45e7eda744").unwrap();
         let secp = secp256k1zkp::Secp256k1::new();
         let pedersen = secp256k1zkp::pedersen::Commitment::from_pubkey(
@@ -210,6 +214,7 @@ mod test {
     }
 
     #[test]
+    #[cfg(feature = "grin_secp256k1zkp")]
     fn bulletproof() {
         let secp = secp256k1zkp::Secp256k1::new();
         let blind = secp256k1zkp::SecretKey::new(
@@ -234,22 +239,24 @@ mod test {
         );
     }
 
-    /*
+    /* TODO: #25 Uncomment this test once `grin_secp256k1zkp::Error` impl `Ord`
     #[test]
+    #[cfg(feature = "grin_secp256k1zkp")]
     fn error_encoding() {
-        test_enum_u8_exhaustive!(crate => secp256k1zkp::Error;
-            secp256k1zkp::Error::IncapableContext => 0,
-            secp256k1zkp::Error::IncorrectSignature => 1,
-            secp256k1zkp::Error::InvalidMessage => 2,
-            secp256k1zkp::Error::InvalidPublicKey => 3,
-            secp256k1zkp::Error::InvalidCommit => 4,
-            secp256k1zkp::Error::InvalidSignature => 5,
-            secp256k1zkp::Error::InvalidSecretKey => 6,
-            secp256k1zkp::Error::InvalidRecoveryId => 7,
-            secp256k1zkp::Error::IncorrectCommitSum => 8,
-            secp256k1zkp::Error::InvalidRangeProof => 9,
-            secp256k1zkp::Error::PartialSigFailure => 10
+        test_encoding_enum_u8_exhaustive!(crate => secp256k1zkp::Error;
+            secp256k1zkp::Error::IncapableContext => 0u8,
+            secp256k1zkp::Error::IncorrectSignature => 1u8,
+            secp256k1zkp::Error::InvalidMessage => 2u8,
+            secp256k1zkp::Error::InvalidPublicKey => 3u8,
+            secp256k1zkp::Error::InvalidCommit => 4u8,
+            secp256k1zkp::Error::InvalidSignature => 5u8,
+            secp256k1zkp::Error::InvalidSecretKey => 6u8,
+            secp256k1zkp::Error::InvalidRecoveryId => 7u8,
+            secp256k1zkp::Error::IncorrectCommitSum => 8u8,
+            secp256k1zkp::Error::InvalidRangeProof => 9u8,
+            secp256k1zkp::Error::PartialSigFailure => 10u8
         )
+        .unwrap()
     }
      */
 }
diff --git a/strict_encoding/src/lib.rs b/strict_encoding/src/lib.rs
index 8013d926..1d923b4a 100644
--- a/strict_encoding/src/lib.rs
+++ b/strict_encoding/src/lib.rs
@@ -66,6 +66,7 @@ mod macros;
 pub mod test_helpers;
 
 mod amplify_types;
+#[cfg(feature = "bitcoin")]
 mod bitcoin;
 mod collections;
 #[cfg(feature = "crypto")]
@@ -82,12 +83,13 @@ pub use strategies::Strategy;
 /// Re-exporting extended read and write functions from bitcoin consensus
 /// module so others may use semantic convenience
 /// `strict_encode::ReadExt`
+#[cfg(feature = "bitcoin")]
 pub use ::bitcoin::consensus::encode::{ReadExt, WriteExt};
 
 use amplify::IoError;
-use core::ops::Range;
 use std::fmt;
 use std::io;
+use std::ops::Range;
 use std::string::FromUtf8Error;
 
 /// Binary encoding according to the strict rules that usually apply to
diff --git a/strict_encoding/src/net.rs b/strict_encoding/src/net.rs
index 9e4534ee..71a09f8d 100644
--- a/strict_encoding/src/net.rs
+++ b/strict_encoding/src/net.rs
@@ -737,9 +737,7 @@ impl Strategy for SocketAddrV6 {
 mod test {
     use super::*;
     use crate::strict_deserialize;
-    use bitcoin::secp256k1::PublicKey;
     use std::convert::TryInto;
-    use std::str::FromStr;
 
     fn gen_ipv4_addrs() -> Vec<Ipv4Addr> {
         let vars = [0u8, 1, 32, 48, 64, 127, 168, 192, 254, 255];
@@ -1106,7 +1104,11 @@ mod test {
     }
 
     #[test]
+    #[cfg(feature = "bitcoin")]
     fn uniform_raw_roundtrip_other() {
+        use bitcoin::secp256k1::PublicKey;
+        use std::str::FromStr;
+
         let lk = PublicKey::from_str("02d1780dd0e08f4d873f94faf49d878d909a1174291d3fcac3e02a6c45e7eda744").unwrap();
         let addr = lk.serialize();
 
@@ -1146,7 +1148,7 @@ mod test {
 
     #[test]
     fn uniform_raw_conversion() {
-        use bitcoin::hashes::hex::ToHex;
+        use bitcoin_hashes::hex::ToHex;
         use std::str::FromStr;
 
         let ips = vec![
diff --git a/strict_encoding/src/primitives.rs b/strict_encoding/src/primitives.rs
index 04ad0631..22ee22e8 100644
--- a/strict_encoding/src/primitives.rs
+++ b/strict_encoding/src/primitives.rs
@@ -17,7 +17,7 @@
 use core::time::Duration;
 use std::io;
 
-use super::{strategies, Error, Strategy, StrictDecode, StrictEncode};
+use super::{Error, StrictDecode, StrictEncode};
 
 impl StrictEncode for bool {
     fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
@@ -35,29 +35,140 @@ impl StrictDecode for bool {
     }
 }
 
-impl Strategy for u8 {
-    type Strategy = strategies::BitcoinConsensus;
+impl StrictEncode for u8 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&[*self][..])?;
+        Ok(1)
+    }
+}
+
+impl StrictDecode for u8 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 1];
+        d.read_exact(&mut ret)?;
+        Ok(ret[0])
+    }
+}
+
+impl StrictEncode for i8 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(1)
+    }
+}
+
+impl StrictDecode for i8 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 1];
+        d.read_exact(&mut ret)?;
+        Ok(ret[0] as i8)
+    }
+}
+
+impl StrictEncode for u16 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(2)
+    }
+}
+
+impl StrictDecode for u16 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 2];
+        d.read_exact(&mut ret)?;
+        Ok(u16::from_le_bytes(ret))
+    }
+}
+
+impl StrictEncode for i16 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(2)
+    }
+}
+
+impl StrictDecode for i16 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 2];
+        d.read_exact(&mut ret)?;
+        Ok(i16::from_le_bytes(ret))
+    }
 }
-impl Strategy for u16 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictEncode for u32 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(4)
+    }
 }
-impl Strategy for u32 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictDecode for u32 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 4];
+        d.read_exact(&mut ret)?;
+        Ok(u32::from_le_bytes(ret))
+    }
 }
-impl Strategy for u64 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictEncode for i32 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(4)
+    }
 }
-impl Strategy for i8 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictDecode for i32 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 4];
+        d.read_exact(&mut ret)?;
+        Ok(i32::from_le_bytes(ret))
+    }
 }
-impl Strategy for i16 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictEncode for u64 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(8)
+    }
+}
+
+impl StrictDecode for u64 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 8];
+        d.read_exact(&mut ret)?;
+        Ok(u64::from_le_bytes(ret))
+    }
 }
-impl Strategy for i32 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictEncode for i64 {
+    #[inline]
+    fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
+        e.write_all(&self.to_le_bytes())?;
+        Ok(8)
+    }
 }
-impl Strategy for i64 {
-    type Strategy = strategies::BitcoinConsensus;
+
+impl StrictDecode for i64 {
+    #[inline]
+    fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
+        let mut ret = [0u8; 8];
+        d.read_exact(&mut ret)?;
+        Ok(i64::from_le_bytes(ret))
+    }
 }
 
 impl StrictEncode for u128 {
@@ -92,7 +203,7 @@ impl StrictDecode for i128 {
 
 impl StrictEncode for usize {
     fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
-        if *self > core::u16::MAX as usize {
+        if *self > u16::MAX as usize {
             return Err(Error::ExceedMaxItems(*self));
         }
         let size = *self as u16;
diff --git a/strict_encoding/src/strategies.rs b/strict_encoding/src/strategies.rs
index 4775ea25..f42ce186 100644
--- a/strict_encoding/src/strategies.rs
+++ b/strict_encoding/src/strategies.rs
@@ -22,12 +22,13 @@ use std::io;
 use super::net;
 use super::{Error, StrictDecode, StrictEncode};
 
-/// Encodes/decodes data as a [`bitcoin::hashes::Hash`]-based (wrapper) type,
-/// i.e. as a fixed-size byte string of [`bitcoin::hashes::Hash::LEN`] length.
+/// Encodes/decodes data as a [`bitcoin_hashes::Hash`]-based (wrapper) type,
+/// i.e. as a fixed-size byte string of [`bitcoin_hashes::Hash::LEN`] length.
 pub struct HashFixedBytes;
 
 /// Encodes/decodes data in the same way as they are encoded/decoded according
 /// to bitcoin consensus rules from Bitcoin Core
+#[cfg(feature = "bitcoin")]
 pub struct BitcoinConsensus;
 
 /// Encodes/decodes data as a wrapped type, i.e. according to the rules of
@@ -96,9 +97,8 @@ where
 
 impl<H> StrictEncode for amplify::Holder<H, HashFixedBytes>
 where
-    H: bitcoin::hashes::Hash,
+    H: bitcoin_hashes::Hash,
 {
-    // TODO #5: Verify byte order for hash encodings
     #[inline]
     fn strict_encode<E: io::Write>(&self, mut e: E) -> Result<usize, Error> {
         e.write_all(&self.as_inner()[..])?;
@@ -108,7 +108,7 @@ where
 
 impl<H> StrictDecode for amplify::Holder<H, HashFixedBytes>
 where
-    H: bitcoin::hashes::Hash,
+    H: bitcoin_hashes::Hash,
 {
     #[inline]
     fn strict_decode<D: io::Read>(mut d: D) -> Result<Self, Error> {
@@ -121,6 +121,7 @@ where
     }
 }
 
+#[cfg(feature = "bitcoin")]
 impl<B> StrictEncode for amplify::Holder<B, BitcoinConsensus>
 where
     B: bitcoin::consensus::Encodable,
@@ -131,6 +132,7 @@ where
     }
 }
 
+#[cfg(feature = "bitcoin")]
 impl<B> StrictDecode for amplify::Holder<B, BitcoinConsensus>
 where
     B: bitcoin::consensus::Decodable,
@@ -164,6 +166,7 @@ where
     }
 }
 
+#[cfg(feature = "bitcoin")]
 impl From<bitcoin::hashes::Error> for Error {
     #[inline]
     fn from(_: bitcoin::hashes::Error) -> Self {
@@ -171,6 +174,7 @@ impl From<bitcoin::hashes::Error> for Error {
     }
 }
 
+#[cfg(feature = "bitcoin")]
 impl From<bitcoin::consensus::encode::Error> for Error {
     #[inline]
     fn from(e: bitcoin::consensus::encode::Error) -> Self {


Test patch: