From 87911096baadce0db514f81df348e7e1f33d3311 Mon Sep 17 00:00:00 2001 From: vnprc Date: Tue, 10 Sep 2024 22:56:25 -0400 Subject: [PATCH 1/6] fix: return u32 from existing Id::TryFrom and add lossless u64 versions --- crates/cdk/src/nuts/nut02.rs | 43 +++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/cdk/src/nuts/nut02.rs b/crates/cdk/src/nuts/nut02.rs index 0de24c98..57393980 100644 --- a/crates/cdk/src/nuts/nut02.rs +++ b/crates/cdk/src/nuts/nut02.rs @@ -114,14 +114,33 @@ impl Id { } } -impl TryFrom for u64 { +impl TryFrom for u32 { type Error = Error; fn try_from(value: Id) -> Result { let hex_bytes: [u8; 8] = value.to_bytes().try_into().map_err(|_| Error::Length)?; let int = u64::from_be_bytes(hex_bytes); - Ok(int % (2_u64.pow(31) - 1)) + let result = (int % (2_u64.pow(31) - 1)) as u32; + Ok(result) + } +} + +impl TryFrom for Id { + type Error = Error; + fn try_from(value: u64) -> Result { + let bytes = value.to_be_bytes(); + Self::from_bytes(&bytes) + } +} + +impl TryFrom for u64 { + type Error = Error; + + fn try_from(value: Id) -> Result { + let bytes = value.to_bytes(); + let byte_array: [u8; 8] = bytes.try_into().map_err(|_| Error::Length)?; + Ok(u64::from_be_bytes(byte_array)) } } @@ -490,10 +509,28 @@ mod test { fn test_to_int() { let id = Id::from_str("009a1f293253e41e").unwrap(); - let id_int = u64::try_from(id).unwrap(); + let id_int = u32::try_from(id).unwrap(); assert_eq!(864559728, id_int) } + #[test] + fn test_to_u64_and_back() { + let id = Id::from_str("009a1f293253e41e").unwrap(); + + let id_long = u64::try_from(id).unwrap(); + assert_eq!(43381408211919902, id_long); + + let new_id = Id::try_from(id_long).unwrap(); + assert_eq!(id, new_id); + } + + #[test] + fn test_id_from_invalid_byte_length() { + let three_bytes = [0x01, 0x02, 0x03]; + let result = Id::from_bytes(&three_bytes); + assert!(result.is_err(), "Expected an invalid byte length error"); + } + #[test] fn test_keyset_bytes() { let id = Id::from_str("009a1f293253e41e").unwrap(); From 67fc241d3095bfbb3cde09cd870f925d9936e1bb Mon Sep 17 00:00:00 2001 From: vnprc Date: Wed, 13 Nov 2024 10:33:35 -0500 Subject: [PATCH 2/6] remove TryFrom for Id and it's inverse --- crates/cdk/src/nuts/nut02.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/cdk/src/nuts/nut02.rs b/crates/cdk/src/nuts/nut02.rs index 57393980..c01c9b36 100644 --- a/crates/cdk/src/nuts/nut02.rs +++ b/crates/cdk/src/nuts/nut02.rs @@ -126,24 +126,6 @@ impl TryFrom for u32 { } } -impl TryFrom for Id { - type Error = Error; - fn try_from(value: u64) -> Result { - let bytes = value.to_be_bytes(); - Self::from_bytes(&bytes) - } -} - -impl TryFrom for u64 { - type Error = Error; - - fn try_from(value: Id) -> Result { - let bytes = value.to_bytes(); - let byte_array: [u8; 8] = bytes.try_into().map_err(|_| Error::Length)?; - Ok(u64::from_be_bytes(byte_array)) - } -} - impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&format!("{}{}", self.version, hex::encode(self.id))) From 2cc861837656212a39f9180cda231e7061a3ed68 Mon Sep 17 00:00:00 2001 From: vnprc Date: Wed, 13 Nov 2024 10:56:51 -0500 Subject: [PATCH 3/6] fix: remove unit test and fix nut13::derive_path_from_keyset_id --- crates/cdk/src/nuts/nut02.rs | 11 ----------- crates/cdk/src/nuts/nut13.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/cdk/src/nuts/nut02.rs b/crates/cdk/src/nuts/nut02.rs index c01c9b36..934be23f 100644 --- a/crates/cdk/src/nuts/nut02.rs +++ b/crates/cdk/src/nuts/nut02.rs @@ -495,17 +495,6 @@ mod test { assert_eq!(864559728, id_int) } - #[test] - fn test_to_u64_and_back() { - let id = Id::from_str("009a1f293253e41e").unwrap(); - - let id_long = u64::try_from(id).unwrap(); - assert_eq!(43381408211919902, id_long); - - let new_id = Id::try_from(id_long).unwrap(); - assert_eq!(id, new_id); - } - #[test] fn test_id_from_invalid_byte_length() { let three_bytes = [0x01, 0x02, 0x03]; diff --git a/crates/cdk/src/nuts/nut13.rs b/crates/cdk/src/nuts/nut13.rs index 4383ba13..9cef31ca 100644 --- a/crates/cdk/src/nuts/nut13.rs +++ b/crates/cdk/src/nuts/nut13.rs @@ -170,7 +170,7 @@ impl PreMintSecrets { } fn derive_path_from_keyset_id(id: Id) -> Result { - let index = (u64::try_from(id)? % (2u64.pow(31) - 1)) as u32; + let index = u32::try_from(id)? % (2u32.pow(31) - 1); let keyset_child_number = ChildNumber::from_hardened_idx(index)?; Ok(DerivationPath::from(vec![ ChildNumber::from_hardened_idx(129372)?, From f3ae4f4862ef42196fa24324750655175763cd7d Mon Sep 17 00:00:00 2001 From: vnprc Date: Wed, 13 Nov 2024 11:59:48 -0500 Subject: [PATCH 4/6] test: derive_path_from_keyset_id --- crates/cdk/src/nuts/nut13.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/cdk/src/nuts/nut13.rs b/crates/cdk/src/nuts/nut13.rs index 9cef31ca..df7706b7 100644 --- a/crates/cdk/src/nuts/nut13.rs +++ b/crates/cdk/src/nuts/nut13.rs @@ -170,7 +170,8 @@ impl PreMintSecrets { } fn derive_path_from_keyset_id(id: Id) -> Result { - let index = u32::try_from(id)? % (2u32.pow(31) - 1); + let index = u32::try_from(id)?; + let keyset_child_number = ChildNumber::from_hardened_idx(index)?; Ok(DerivationPath::from(vec![ ChildNumber::from_hardened_idx(129372)?, @@ -184,6 +185,7 @@ mod tests { use std::str::FromStr; use bip39::Mnemonic; + use bitcoin::bip32::DerivationPath; use bitcoin::Network; use super::*; @@ -232,4 +234,23 @@ mod tests { assert_eq!(r, SecretKey::from_hex(test_r).unwrap()) } } + + #[test] + fn test_derive_path_from_keyset_id() { + let test_cases = [ + ("009a1f293253e41e", "m/129372'/0'/864559728'"), + ("0000000000000000", "m/129372'/0'/0'"), + ("00ffffffffffffff", "m/129372'/0'/33554431'"), + ]; + + for (id_hex, expected_path) in test_cases { + let id = Id::from_str(id_hex).unwrap(); + let path = derive_path_from_keyset_id(id).unwrap(); + assert_eq!( + DerivationPath::from_str(expected_path).unwrap(), + path, + "Path derivation failed for ID {id_hex}" + ); + } + } } From 507cadba201701e15514e2dedd9003ef7df50c32 Mon Sep 17 00:00:00 2001 From: vnprc Date: Thu, 14 Nov 2024 17:18:57 -0500 Subject: [PATCH 5/6] fix: convert Id::TryFrom to Id::From --- crates/cdk/src/nuts/nut02.rs | 21 ++++++++++++++------- crates/cdk/src/nuts/nut13.rs | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/cdk/src/nuts/nut02.rs b/crates/cdk/src/nuts/nut02.rs index 934be23f..9f36a4a2 100644 --- a/crates/cdk/src/nuts/nut02.rs +++ b/crates/cdk/src/nuts/nut02.rs @@ -112,17 +112,24 @@ impl Id { id: bytes[1..].try_into()?, }) } + + /// [`Id`] as bytes + pub fn as_bytes(&self) -> [u8; Self::BYTELEN + 1] { + let mut bytes = [0u8; Self::BYTELEN + 1]; + bytes[0] = self.version.to_byte(); + bytes[1..].copy_from_slice(&self.id); + bytes + } } -impl TryFrom for u32 { - type Error = Error; - fn try_from(value: Id) -> Result { - let hex_bytes: [u8; 8] = value.to_bytes().try_into().map_err(|_| Error::Length)?; +// Used to generate a compressed unique identifier as part of the NUT13 spec +impl From for u32 { + fn from(value: Id) -> Self { + let hex_bytes: [u8; 8] = value.as_bytes(); let int = u64::from_be_bytes(hex_bytes); - let result = (int % (2_u64.pow(31) - 1)) as u32; - Ok(result) + (int % (2_u64.pow(31) - 1)) as u32 } } @@ -491,7 +498,7 @@ mod test { fn test_to_int() { let id = Id::from_str("009a1f293253e41e").unwrap(); - let id_int = u32::try_from(id).unwrap(); + let id_int = u32::from(id); assert_eq!(864559728, id_int) } diff --git a/crates/cdk/src/nuts/nut13.rs b/crates/cdk/src/nuts/nut13.rs index df7706b7..84913197 100644 --- a/crates/cdk/src/nuts/nut13.rs +++ b/crates/cdk/src/nuts/nut13.rs @@ -170,7 +170,7 @@ impl PreMintSecrets { } fn derive_path_from_keyset_id(id: Id) -> Result { - let index = u32::try_from(id)?; + let index = u32::from(id); let keyset_child_number = ChildNumber::from_hardened_idx(index)?; Ok(DerivationPath::from(vec![ From bb4b49e3ca178ffd9e908d08538e2757e89d1099 Mon Sep 17 00:00:00 2001 From: vnprc Date: Thu, 14 Nov 2024 17:40:23 -0500 Subject: [PATCH 6/6] docs: comment calling out From for u32 as a one-way function --- crates/cdk/src/nuts/nut02.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cdk/src/nuts/nut02.rs b/crates/cdk/src/nuts/nut02.rs index 9f36a4a2..5d63c56b 100644 --- a/crates/cdk/src/nuts/nut02.rs +++ b/crates/cdk/src/nuts/nut02.rs @@ -123,6 +123,7 @@ impl Id { } // Used to generate a compressed unique identifier as part of the NUT13 spec +// This is a one-way function impl From for u32 { fn from(value: Id) -> Self { let hex_bytes: [u8; 8] = value.as_bytes();