From 1b777cf1086cd9ec5b54099b511d245abb517c8f Mon Sep 17 00:00:00 2001
From: Jean-Pierre De Jesus DIAZ <me@jeandudey.tech>
Date: Thu, 1 Aug 2024 14:22:54 +0200
Subject: [PATCH] SFT-3908: Switch to faster-hex from hex crate.

---
 Cargo.toml                              |  2 +-
 codecs/src/nostr.rs                     |  8 ++-
 firmware/Cargo.toml                     |  6 +--
 firmware/src/bin/foundation-firmware.rs | 11 ++--
 test-vectors/Cargo.toml                 | 14 ++---
 test-vectors/src/bip32.rs               |  2 +-
 test-vectors/src/lib.rs                 | 68 ++++---------------------
 test-vectors/src/psbt.rs                |  2 +-
 ur/Cargo.toml                           |  2 +-
 ur/src/fountain/encoder.rs              | 13 +++--
 urtypes/Cargo.toml                      |  2 +-
 urtypes/src/registry/passport.rs        | 23 ++++++---
 urtypes/src/supply_chain_validation.rs  | 11 ++--
 urtypes/tests/hdkey.rs                  |  5 +-
 14 files changed, 70 insertions(+), 99 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 821eeec..ca2555b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,8 +28,8 @@ bs58 = "0.5"
 clap = { version = "4", features = ["cargo"] }
 crc = "3"
 criterion = { version = "0.4" }
+faster-hex = { version = "0.9", default-features = false }
 heapless = { version = "0.8", default-features = false }
-hex = { version = "0.4.2", default-features = false }
 itertools = { version = "0.10", default-features = false }
 libfuzzer-sys = "0.4"
 minicbor = { version = "0.24", features = ["derive"] }
diff --git a/codecs/src/nostr.rs b/codecs/src/nostr.rs
index 83d0ef4..036b990 100644
--- a/codecs/src/nostr.rs
+++ b/codecs/src/nostr.rs
@@ -98,7 +98,9 @@ pub mod tests {
         let vectors = NIP19Vector::new();
 
         for vector in vectors.iter().filter(|t| t.kind == NPUB) {
-            let encoded = encode_npub(&vector.bytes);
+            let mut public_key = [0; 32];
+            public_key.copy_from_slice(&vector.bytes);
+            let encoded = encode_npub(&public_key);
             assert_eq!(&encoded, &*vector.encoded);
         }
     }
@@ -108,7 +110,9 @@ pub mod tests {
         let vectors = NIP19Vector::new();
 
         for vector in vectors.iter().filter(|t| t.kind == NSEC) {
-            let encoded = encode_nsec(&vector.bytes);
+            let mut private_key = [0; 32];
+            private_key.copy_from_slice(&vector.bytes);
+            let encoded = encode_nsec(&private_key);
             assert_eq!(&encoded, &*vector.encoded);
         }
     }
diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml
index 12aedff..8f795f0 100644
--- a/firmware/Cargo.toml
+++ b/firmware/Cargo.toml
@@ -18,14 +18,14 @@ required-features = ["binary"]
 
 [features]
 default = ["std", "binary"]
-std = ["anyhow/std", "hex?/std", "nom/std", "secp256k1/std"]
-binary = ["anyhow", "clap", "hex", "secp256k1/global-context", "std"]
+std = ["anyhow/std", "faster-hex?/std", "nom/std", "secp256k1/std"]
+binary = ["anyhow", "clap", "faster-hex", "secp256k1/global-context", "std"]
 
 [dependencies]
 bitcoin_hashes = { workspace = true }
 clap = { workspace = true, optional = true }
 heapless = { workspace = true }
-hex = { workspace = true, optional = true }
+faster-hex = { workspace = true, optional = true }
 nom = { workspace = true }
 secp256k1 = { workspace = true }
 anyhow = { workspace = true, optional = true }
diff --git a/firmware/src/bin/foundation-firmware.rs b/firmware/src/bin/foundation-firmware.rs
index 7c1c2c6..94770d2 100644
--- a/firmware/src/bin/foundation-firmware.rs
+++ b/firmware/src/bin/foundation-firmware.rs
@@ -4,6 +4,7 @@
 use anyhow::{anyhow, bail, Context, Result};
 use bitcoin_hashes::{sha256, sha256d, Hash, HashEngine};
 use clap::{command, value_parser, Arg, ArgAction};
+use faster_hex::hex_string;
 use foundation_firmware::{header, Header, Information, HEADER_LEN};
 use nom::Finish;
 use secp256k1::{global::SECP256K1, PublicKey};
@@ -56,7 +57,7 @@ fn main() -> Result<()> {
             println!(
                 "{:>17}: {}",
                 "User Public Key",
-                hex::encode(public_key.serialize_uncompressed())
+                hex_string(&public_key.serialize_uncompressed())
             );
         }
 
@@ -86,9 +87,9 @@ fn print_header(header: &Header) {
     println!("{:>17}: {}", "Version", header.information.version);
     println!("{:>17}: {} bytes", "Length", header.information.length);
     println!("{:>17}: {}", "Key", header.signature.public_key1);
-    println!("{:>17}: {}", "Signature", hex::encode(signature1));
+    println!("{:>17}: {}", "Signature", hex_string(&signature1));
     println!("{:>17}: {}", "Key", header.signature.public_key2);
-    println!("{:>17}: {}", "Signature", hex::encode(signature2));
+    println!("{:>17}: {}", "Signature", hex_string(&signature2));
 }
 
 fn verify_signature(
@@ -127,12 +128,12 @@ fn verify_signature(
     println!(
         "{:>17}: {}",
         "Validation Hash",
-        hex::encode(validation_hash.to_byte_array())
+        hex_string(validation_hash.as_byte_array())
     );
     println!(
         "{:>17}: {}",
         "Single Hash",
-        hex::encode(single_hash.to_byte_array())
+        hex_string(single_hash.as_byte_array())
     );
     println!();
 
diff --git a/test-vectors/Cargo.toml b/test-vectors/Cargo.toml
index 114da96..bde41d7 100644
--- a/test-vectors/Cargo.toml
+++ b/test-vectors/Cargo.toml
@@ -12,18 +12,18 @@ edition = "2021"
 
 [features]
 default = ["std"]
-std = ["hex/std", "serde/std"]
-bip32 = ["bs58", "hex/serde"]
+std = ["faster-hex/std", "serde/std"]
+bip32 = ["bs58", "faster-hex/serde"]
 firmware = []
-nostr = ["hex/serde"]
-psbt = ["hex/serde"]
-seedqr = ["bip39/serde", "hex/serde"]
-blockchain-commons = ["bitcoin/serde", "hex/serde"]
+nostr = ["faster-hex/serde"]
+psbt = ["faster-hex/serde"]
+seedqr = ["bip39/serde", "faster-hex/serde"]
+blockchain-commons = ["bitcoin/serde", "faster-hex/serde"]
 
 [dependencies]
 bip39 = { workspace = true, optional = true }
 bitcoin = { workspace = true, optional = true, features = ["std"] }
 bs58 = { workspace = true, optional = true }
-hex = { workspace = true, optional = true }
+faster-hex = { workspace = true, optional = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
diff --git a/test-vectors/src/bip32.rs b/test-vectors/src/bip32.rs
index 89418f4..8895331 100644
--- a/test-vectors/src/bip32.rs
+++ b/test-vectors/src/bip32.rs
@@ -18,7 +18,7 @@ impl TestVectors {
 #[serde(rename_all = "kebab-case")]
 pub struct TestVector {
     pub name: String,
-    #[serde(rename = "seed-hex", with = "hex")]
+    #[serde(rename = "seed-hex", with = "faster_hex::nopfx_ignorecase")]
     pub seed: Vec<u8>,
     pub chains: Vec<Chain>,
 }
diff --git a/test-vectors/src/lib.rs b/test-vectors/src/lib.rs
index 02534f2..8a0849d 100644
--- a/test-vectors/src/lib.rs
+++ b/test-vectors/src/lib.rs
@@ -6,8 +6,8 @@
 pub struct NIP19Vector {
     pub name: String,
     pub kind: String,
-    #[serde(with = "hex")]
-    pub bytes: [u8; 32],
+    #[serde(with = "faster_hex::nopfx_ignorecase")]
+    pub bytes: Vec<u8>,
     pub encoded: String,
 }
 
@@ -26,7 +26,7 @@ pub struct SeedQRVector {
     pub seed: bip39::Mnemonic,
     pub as_digits: String,
     pub as_compact_bits: String,
-    #[serde(with = "hex")]
+    #[serde(with = "faster_hex::nopfx_ignorecase")]
     pub as_compact_bytes: Vec<u8>,
 }
 
@@ -51,7 +51,7 @@ mod blockchain_commons {
 
     #[derive(Debug, Clone, Deserialize)]
     pub enum UR {
-        #[serde(rename = "bytes", with = "hex")]
+        #[serde(rename = "bytes", with = "faster_hex::nopfx_ignorecase")]
         Bytes(Vec<u8>),
         #[serde(rename = "address")]
         Address(AddressVector),
@@ -59,7 +59,7 @@ mod blockchain_commons {
         ECKey(ECKeyVector),
         #[serde(rename = "hdkey")]
         HDKey(HDKeyVector),
-        #[serde(rename = "psbt", with = "hex")]
+        #[serde(rename = "psbt", with = "faster_hex::nopfx_ignorecase")]
         Psbt(Vec<u8>),
         #[serde(rename = "seed")]
         Seed(SeedVector),
@@ -92,7 +92,7 @@ mod blockchain_commons {
     #[serde(rename_all = "kebab-case")]
     pub enum AddressVector {
         Bitcoin(bitcoin::Address<bitcoin::address::NetworkUnchecked>),
-        #[serde(with = "prefix_hex")]
+        #[serde(with = "faster_hex::withpfx_ignorecase")]
         Ethereum(Vec<u8>),
     }
 
@@ -100,7 +100,7 @@ mod blockchain_commons {
     #[serde(rename_all = "kebab-case")]
     pub struct ECKeyVector {
         pub is_private: bool,
-        #[serde(with = "hex")]
+        #[serde(with = "faster_hex::nopfx_ignorecase")]
         pub data: Vec<u8>,
     }
 
@@ -119,7 +119,7 @@ mod blockchain_commons {
     #[derive(Debug, Clone, Deserialize)]
     #[serde(rename_all = "kebab-case")]
     pub struct SeedVector {
-        #[serde(with = "hex")]
+        #[serde(with = "faster_hex::nopfx_ignorecase")]
         pub payload: Vec<u8>,
         pub creation_date: u64,
     }
@@ -128,7 +128,7 @@ mod blockchain_commons {
     #[serde(rename_all = "kebab-case")]
     pub struct URVector {
         pub name: String,
-        #[serde(with = "hex")]
+        #[serde(with = "faster_hex::nopfx_ignorecase")]
         pub as_cbor: Vec<u8>,
         pub as_ur: String,
         pub ur: UR,
@@ -152,56 +152,6 @@ mod blockchain_commons {
             vectors
         }
     }
-
-    mod prefix_hex {
-        use std::{fmt::Display, fmt::Formatter, marker::PhantomData};
-
-        use hex::FromHex;
-        use serde::{
-            de::{Error, Visitor},
-            Deserializer,
-        };
-
-        pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
-        where
-            D: Deserializer<'de>,
-            T: FromHex,
-            <T as FromHex>::Error: Display,
-        {
-            struct HexStrVisitor<T>(PhantomData<T>);
-            impl<'de, T> Visitor<'de> for HexStrVisitor<T>
-            where
-                T: FromHex,
-                <T as FromHex>::Error: Display,
-            {
-                type Value = T;
-
-                fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
-                    f.write_str("hex encoded string with 0x prefix")
-                }
-
-                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
-                where
-                    E: Error,
-                {
-                    let v = v
-                        .strip_prefix("0x")
-                        .ok_or_else(|| Error::custom("invalid prefix"))?;
-
-                    FromHex::from_hex(v).map_err(Error::custom)
-                }
-
-                fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
-                where
-                    E: Error,
-                {
-                    self.visit_str(&v)
-                }
-            }
-
-            deserializer.deserialize_str(HexStrVisitor(PhantomData))
-        }
-    }
 }
 
 #[cfg(feature = "blockchain-commons")]
diff --git a/test-vectors/src/psbt.rs b/test-vectors/src/psbt.rs
index 8bc46dd..07baf9e 100644
--- a/test-vectors/src/psbt.rs
+++ b/test-vectors/src/psbt.rs
@@ -17,7 +17,7 @@ impl TestVectors {
 #[derive(Debug, serde::Deserialize)]
 pub struct TestVector {
     pub description: String,
-    #[serde(with = "hex", rename = "as-hex")]
+    #[serde(with = "faster_hex::nopfx_ignorecase", rename = "as-hex")]
     pub data: Vec<u8>,
 }
 
diff --git a/ur/Cargo.toml b/ur/Cargo.toml
index dd6aabd..b37a8df 100644
--- a/ur/Cargo.toml
+++ b/ur/Cargo.toml
@@ -33,4 +33,4 @@ phf = { workspace = true }
 rand_xoshiro = { workspace = true }
 
 [dev-dependencies]
-hex = { workspace = true, features = ["alloc"] }
+faster-hex = { workspace = true, features = ["alloc"] }
diff --git a/ur/src/fountain/encoder.rs b/ur/src/fountain/encoder.rs
index fc84372..5a54780 100644
--- a/ur/src/fountain/encoder.rs
+++ b/ur/src/fountain/encoder.rs
@@ -235,7 +235,7 @@ pub mod tests {
         );
         for &expected_fragment in EXPECTED_FRAGMENTS.iter() {
             let part = encoder.next_part();
-            assert_eq!(hex::encode(part.data), expected_fragment);
+            assert_eq!(faster_hex::hex_string(part.data), expected_fragment);
         }
     }
 
@@ -270,7 +270,11 @@ pub mod tests {
 
         for (i, data) in EXPECTED_DATA
             .iter()
-            .map(|v| hex::decode(v).unwrap())
+            .map(|v| {
+                let mut tmp = vec![0; v.len() / 2];
+                faster_hex::hex_decode(v.as_bytes(), &mut tmp).unwrap();
+                tmp
+            })
             .enumerate()
         {
             let sequence = u32::try_from(i).unwrap();
@@ -334,7 +338,10 @@ pub mod tests {
             u32::try_from(SEQUENCE_COUNT).unwrap()
         );
 
-        for expected_cbor in EXPECTED_PARTS_CBOR.iter().map(|v| hex::decode(v).unwrap()) {
+        for expected_cbor_hex in EXPECTED_PARTS_CBOR.iter() {
+            let mut expected_cbor = vec![0; expected_cbor_hex.len() / 2];
+            faster_hex::hex_decode(expected_cbor_hex.as_bytes(), &mut expected_cbor).unwrap();
+
             let mut cbor = alloc::vec::Vec::new();
             minicbor::encode(encoder.next_part(), &mut cbor).unwrap();
 
diff --git a/urtypes/Cargo.toml b/urtypes/Cargo.toml
index ea9a222..2995c12 100644
--- a/urtypes/Cargo.toml
+++ b/urtypes/Cargo.toml
@@ -23,7 +23,7 @@ alloc = ["minicbor/alloc"]
 bitcoin = { workspace = true, optional = true }
 foundation-arena = { workspace = true }
 heapless = { workspace = true }
-hex = { workspace = true }
+faster-hex = { workspace = true }
 minicbor = { workspace = true }
 uuid = { workspace = true }
 
diff --git a/urtypes/src/registry/passport.rs b/urtypes/src/registry/passport.rs
index e865ac5..8ec10c6 100644
--- a/urtypes/src/registry/passport.rs
+++ b/urtypes/src/registry/passport.rs
@@ -268,6 +268,8 @@ impl<'a, C> Encode<C> for PassportResponse<'a> {
 
 #[cfg(test)]
 mod tests {
+    use faster_hex::hex_decode;
+
     use super::*;
 
     #[test]
@@ -275,12 +277,15 @@ mod tests {
         let mut id = [0; 32];
         let mut signature = [0; 64];
 
-        hex::decode_to_slice(
-            "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c",
+        hex_decode(
+            "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c".as_bytes(),
             &mut id,
         )
         .unwrap();
-        hex::decode_to_slice("7d0a8468ed220400c0b8e6f335baa7e070ce880a37e2ac5995b9a97b809026de626da636ac7365249bb974c719edf543b52ed286646f437dc7f810cc2068375c", &mut signature).unwrap();
+        hex_decode(
+            "7d0a8468ed220400c0b8e6f335baa7e070ce880a37e2ac5995b9a97b809026de626da636ac7365249bb974c719edf543b52ed286646f437dc7f810cc2068375c".as_bytes(),
+            &mut signature,
+        ).unwrap();
 
         let request = PassportRequest {
             transaction_id: Default::default(),
@@ -300,12 +305,15 @@ mod tests {
         let mut id = [0; 32];
         let mut signature = [0; 64];
 
-        hex::decode_to_slice(
-            "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c",
+        hex_decode(
+            "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c".as_bytes(),
             &mut id,
         )
         .unwrap();
-        hex::decode_to_slice("7d0a8468ed220400c0b8e6f335baa7e070ce880a37e2ac5995b9a97b809026de626da636ac7365249bb974c719edf543b52ed286646f437dc7f810cc2068375c", &mut signature).unwrap();
+        hex_decode(
+            "7d0a8468ed220400c0b8e6f335baa7e070ce880a37e2ac5995b9a97b809026de626da636ac7365249bb974c719edf543b52ed286646f437dc7f810cc2068375c".as_bytes(),
+            &mut signature,
+        ).unwrap();
 
         let response = PassportResponse {
             transaction_id: Default::default(),
@@ -328,7 +336,8 @@ mod tests {
     #[test]
     fn test_request_decode() {
         const TEST_VECTOR: &str = "a201d8255083816f6064ff4046b93a85687f8f608202d902c6a30178403338633663303561633639613166623737626366333736333330383835326364336530383066653165343630346361613831623534383236653264613062643202788037393035306564663562386636343937663936626661633031333363396365663561303764613363343835613432373466646666396433616637346632393833636566386432303337663164626636613435356431356530666236346162313665333664643336353062363533323265333239303138313639633631356636610378603045022079050ec39f5bc28f64c297c3b96bc3bac380133cc29cc3af5a07c39a3c485a4274c3bdc3bfc29d3ac3b74f29c283022100c38ec3b8c392037f1dc2bf6a455d15c3a0c3bb64c2ab16c3a36dc393650b65322e32c2901816c29c615f6a";
-        let cbor = hex::decode(TEST_VECTOR).unwrap();
+        let mut cbor = vec![0; TEST_VECTOR.len() / 2];
+        hex_decode(TEST_VECTOR.as_bytes(), &mut cbor).unwrap();
         minicbor::decode::<'_, PassportRequest>(&cbor).unwrap();
     }
 }
diff --git a/urtypes/src/supply_chain_validation.rs b/urtypes/src/supply_chain_validation.rs
index 0d41ffb..6fa6dde 100644
--- a/urtypes/src/supply_chain_validation.rs
+++ b/urtypes/src/supply_chain_validation.rs
@@ -29,6 +29,7 @@
 
 use core::str;
 
+use faster_hex::{hex_decode, hex_encode};
 use minicbor::data::{Tag, Type};
 use minicbor::decode::Error;
 use minicbor::encode::Write;
@@ -58,13 +59,13 @@ impl<'b, C> Decode<'b, C> for Challenge {
                 match d.u32()? {
                     1 => {
                         let mut buf = [0; 32];
-                        hex::decode_to_slice(d.str()?, &mut buf)
+                        hex_decode(d.str()?.as_bytes(), &mut buf)
                             .map_err(|_| Error::message("invalid hex string (id)"))?;
                         id = Some(buf);
                     }
                     2 => {
                         let mut buf = [0; 64];
-                        hex::decode_to_slice(d.str()?, &mut buf)
+                        hex_decode(d.str()?.as_bytes(), &mut buf)
                             .map_err(|_| Error::message("invalid hex string (signature)"))?;
                         signature = Some(buf);
                     }
@@ -101,10 +102,8 @@ impl<C> Encode<C> for Challenge {
         let mut signature = [0; 128];
 
         // unreachable errors.
-        hex::encode_to_slice(self.id, &mut id).unwrap();
-        hex::encode_to_slice(self.signature, &mut signature).unwrap();
-        let id = str::from_utf8(&id).unwrap();
-        let signature = str::from_utf8(&signature).unwrap();
+        let id = hex_encode(&self.id, &mut id).unwrap();
+        let signature = hex_encode(&self.signature, &mut signature).unwrap();
 
         e.map(2)?;
         e.u8(1)?.str(id)?;
diff --git a/urtypes/tests/hdkey.rs b/urtypes/tests/hdkey.rs
index 298444d..b415551 100644
--- a/urtypes/tests/hdkey.rs
+++ b/urtypes/tests/hdkey.rs
@@ -1,6 +1,7 @@
 // SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
 // SPDX-License-Identifier: GPL-3.0-or-later
 
+use faster_hex::hex_string;
 use foundation_test_vectors::{HDKeyVector, URVector, UR};
 use foundation_urtypes::registry::{HDKeyRef, KeypathRef};
 
@@ -26,8 +27,8 @@ fn test_roundtrip_ref() {
         };
 
         let cbor = minicbor::to_vec(&hdkey).unwrap();
-        println!("our cbor: {}", hex::encode(&cbor));
-        println!("test vector cbor: {}", hex::encode(&vector.as_cbor));
+        println!("our cbor: {}", hex_string(&cbor));
+        println!("test vector cbor: {}", hex_string(&vector.as_cbor));
         assert_eq!(cbor, vector.as_cbor);
     }
 }