Fix wrong newtype APIs

This fixes several API bugs:

* Use `TryFrom` instead of `From` for fallible conversions
* Move byte conversion methods from `impl_array_newtype` to
  `impl_bytes_newtype`
* Add missing trait impls like `AsRef`, `Borrow`, their mutable versions
  and infallible conversions from arrays

Closes #1336
This commit is contained in:
Martin Habovstiak 2022-12-23 01:28:17 +01:00
parent 7fee6239e1
commit 64f7d2549e
5 changed files with 105 additions and 33 deletions

View File

@ -33,6 +33,12 @@ pub struct ChainCode([u8; 32]);
impl_array_newtype!(ChainCode, u8, 32); impl_array_newtype!(ChainCode, u8, 32);
impl_bytes_newtype!(ChainCode, 32); impl_bytes_newtype!(ChainCode, 32);
impl ChainCode {
fn from_hmac(hmac: Hmac<sha512::Hash>) -> Self {
hmac[32..].try_into().expect("half of hmac is guaranteed to be 32 bytes")
}
}
/// A fingerprint /// A fingerprint
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Fingerprint([u8; 4]); pub struct Fingerprint([u8; 4]);
@ -523,7 +529,7 @@ impl ExtendedPrivKey {
parent_fingerprint: Default::default(), parent_fingerprint: Default::default(),
child_number: ChildNumber::from_normal_idx(0)?, child_number: ChildNumber::from_normal_idx(0)?,
private_key: secp256k1::SecretKey::from_slice(&hmac_result[..32])?, private_key: secp256k1::SecretKey::from_slice(&hmac_result[..32])?,
chain_code: ChainCode::from(&hmac_result[32..]), chain_code: ChainCode::from_hmac(hmac_result),
}) })
} }
@ -588,7 +594,7 @@ impl ExtendedPrivKey {
parent_fingerprint: self.fingerprint(secp), parent_fingerprint: self.fingerprint(secp),
child_number: i, child_number: i,
private_key: tweaked, private_key: tweaked,
chain_code: ChainCode::from(&hmac_result[32..]), chain_code: ChainCode::from_hmac(hmac_result),
}) })
} }
@ -611,9 +617,9 @@ impl ExtendedPrivKey {
Ok(ExtendedPrivKey { Ok(ExtendedPrivKey {
network, network,
depth: data[4], depth: data[4],
parent_fingerprint: Fingerprint::from(&data[5..9]), parent_fingerprint: data[5..9].try_into().expect("9 - 5 == 4, which is the Fingerprint length"),
child_number: u32::from_be_bytes(data[9..13].try_into().expect("4 byte slice")).into(), child_number: u32::from_be_bytes(data[9..13].try_into().expect("4 byte slice")).into(),
chain_code: ChainCode::from(&data[13..45]), chain_code: data[13..45].try_into().expect("45 - 13 == 32, which is the ChainCode length"),
private_key: secp256k1::SecretKey::from_slice(&data[46..78])?, private_key: secp256k1::SecretKey::from_slice(&data[46..78])?,
}) })
} }
@ -643,7 +649,7 @@ impl ExtendedPrivKey {
/// Returns the first four bytes of the identifier /// Returns the first four bytes of the identifier
pub fn fingerprint<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>) -> Fingerprint { pub fn fingerprint<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>) -> Fingerprint {
Fingerprint::from(&self.identifier(secp)[0..4]) self.identifier(secp)[0..4].try_into().expect("4 is the fingerprint length")
} }
} }
@ -701,7 +707,7 @@ impl ExtendedPubKey {
let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(hmac_engine); let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(hmac_engine);
let private_key = secp256k1::SecretKey::from_slice(&hmac_result[..32])?; let private_key = secp256k1::SecretKey::from_slice(&hmac_result[..32])?;
let chain_code = ChainCode::from(&hmac_result[32..]); let chain_code = ChainCode::from_hmac(hmac_result);
Ok((private_key, chain_code)) Ok((private_key, chain_code))
} }
} }
@ -743,9 +749,9 @@ impl ExtendedPubKey {
return Err(Error::UnknownVersion(ver)); return Err(Error::UnknownVersion(ver));
}, },
depth: data[4], depth: data[4],
parent_fingerprint: Fingerprint::from(&data[5..9]), parent_fingerprint: data[5..9].try_into().expect("9 - 5 == 4, which is the Fingerprint length"),
child_number: u32::from_be_bytes(data[9..13].try_into().expect("4 byte slice")).into(), child_number: u32::from_be_bytes(data[9..13].try_into().expect("4 byte slice")).into(),
chain_code: ChainCode::from(&data[13..45]), chain_code: data[13..45].try_into().expect("45 - 13 == 32, which is the ChainCode length"),
public_key: secp256k1::PublicKey::from_slice(&data[45..78])?, public_key: secp256k1::PublicKey::from_slice(&data[45..78])?,
}) })
} }
@ -775,7 +781,7 @@ impl ExtendedPubKey {
} }
/// Returns the first four bytes of the identifier /// Returns the first four bytes of the identifier
pub fn fingerprint(&self) -> Fingerprint { Fingerprint::from(&self.identifier()[0..4]) } pub fn fingerprint(&self) -> Fingerprint { self.identifier()[0..4].try_into().expect("4 is the fingerprint length") }
} }
impl fmt::Display for ExtendedPrivKey { impl fmt::Display for ExtendedPrivKey {
@ -1108,10 +1114,10 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
pub fn encode_fingerprint_chaincode() { pub fn encode_fingerprint_chaincode() {
use serde_json; use serde_json;
let fp = Fingerprint::from(&[1u8, 2, 3, 42][..]); let fp = Fingerprint::from([1u8, 2, 3, 42]);
#[rustfmt::skip] #[rustfmt::skip]
let cc = ChainCode::from( let cc = ChainCode::from(
&[1u8,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2][..] [1u8,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]
); );
serde_round_trip!(fp); serde_round_trip!(fp);
@ -1156,7 +1162,7 @@ mod tests {
parent_fingerprint: Default::default(), parent_fingerprint: Default::default(),
child_number: ChildNumber::Normal { index: 0 }, child_number: ChildNumber::Normal { index: 0 },
private_key: sk, private_key: sk,
chain_code: ChainCode::from(&[0u8; 32][..]) chain_code: ChainCode::from([0u8; 32])
}; };
println!("{}", xpriv); println!("{}", xpriv);

View File

@ -115,6 +115,23 @@ mod test_macros {
/// - hashes::hex::FromHex /// - hashes::hex::FromHex
macro_rules! impl_bytes_newtype { macro_rules! impl_bytes_newtype {
($t:ident, $len:literal) => { ($t:ident, $len:literal) => {
impl $t {
/// Returns a reference the underlying bytes.
#[inline]
pub fn as_bytes(&self) -> &[u8; $len] { &self.0 }
/// Returns the underlying bytes.
#[inline]
pub fn to_bytes(self) -> [u8; $len] {
// We rely on `Copy` being implemented for $t so conversion
// methods use the correct Rust naming conventions.
fn check_copy<T: Copy>() {}
check_copy::<$t>();
self.0
}
}
impl core::fmt::LowerHex for $t { impl core::fmt::LowerHex for $t {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for &ch in self.0.iter() { for &ch in self.0.iter() {

View File

@ -149,7 +149,7 @@ impl PartiallySignedTransaction {
} }
let derivation = DerivationPath::from(path); let derivation = DerivationPath::from(path);
// Keys, according to BIP-174, must be unique // Keys, according to BIP-174, must be unique
if xpub_map.insert(xpub, (Fingerprint::from(&fingerprint[..]), derivation)).is_some() { if xpub_map.insert(xpub, (Fingerprint::from(fingerprint), derivation)).is_some() {
return Err(encode::Error::ParseFailed("Repeated global xpub key")) return Err(encode::Error::ParseFailed("Repeated global xpub key"))
} }
} else { } else {

View File

@ -6,6 +6,8 @@
//! bytes from/as PSBT key-value pairs. //! bytes from/as PSBT key-value pairs.
//! //!
use core::convert::TryInto;
use crate::prelude::*; use crate::prelude::*;
use crate::io; use crate::io;
@ -150,7 +152,7 @@ impl Deserialize for KeySource {
return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()) return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
} }
let fprint: Fingerprint = Fingerprint::from(&bytes[0..4]); let fprint: Fingerprint = bytes[0..4].try_into().expect("4 is the fingerprint length");
let mut dpath: Vec<ChildNumber> = Default::default(); let mut dpath: Vec<ChildNumber> = Default::default();
let mut d = &bytes[4..]; let mut d = &bytes[4..];

View File

@ -27,29 +27,76 @@ macro_rules! impl_array_newtype {
/// Returns whether the object, as an array, is empty. Always false. /// Returns whether the object, as an array, is empty. Always false.
#[inline] #[inline]
pub fn is_empty(&self) -> bool { false } pub fn is_empty(&self) -> bool { false }
}
/// Returns a reference the underlying bytes. impl<'a> core::convert::From<[$ty; $len]> for $thing {
#[inline] fn from(data: [$ty; $len]) -> Self {
pub fn as_bytes(&self) -> &[$ty; $len] { &self.0 } $thing(data)
/// Returns the underlying bytes.
#[inline]
pub fn to_bytes(self) -> [$ty; $len] {
// We rely on `Copy` being implemented for $thing so conversion
// methods use the correct Rust naming conventions.
fn check_copy<T: Copy>() {}
check_copy::<$thing>();
self.0
} }
} }
impl<'a> core::convert::From<&'a [$ty]> for $thing { impl<'a> core::convert::From<&'a [$ty; $len]> for $thing {
fn from(data: &'a [$ty]) -> $thing { fn from(data: &'a [$ty; $len]) -> Self {
assert_eq!(data.len(), $len); $thing(*data)
let mut ret = [0; $len]; }
ret.copy_from_slice(&data[..]); }
$thing(ret)
impl<'a> core::convert::TryFrom<&'a [$ty]> for $thing {
type Error = core::array::TryFromSliceError;
fn try_from(data: &'a [$ty]) -> Result<Self, Self::Error> {
use core::convert::TryInto;
Ok($thing(data.try_into()?))
}
}
impl AsRef<[$ty; $len]> for $thing {
fn as_ref(&self) -> &[$ty; $len] {
&self.0
}
}
impl AsMut<[$ty; $len]> for $thing {
fn as_mut(&mut self) -> &mut [$ty; $len] {
&mut self.0
}
}
impl AsRef<[$ty]> for $thing {
fn as_ref(&self) -> &[$ty] {
&self.0
}
}
impl AsMut<[$ty]> for $thing {
fn as_mut(&mut self) -> &mut [$ty] {
&mut self.0
}
}
impl core::borrow::Borrow<[$ty; $len]> for $thing {
fn borrow(&self) -> &[$ty; $len] {
&self.0
}
}
impl core::borrow::BorrowMut<[$ty; $len]> for $thing {
fn borrow_mut(&mut self) -> &mut [$ty; $len] {
&mut self.0
}
}
// The following two are valid because `[T; N]: Borrow<[T]>`
impl core::borrow::Borrow<[$ty]> for $thing {
fn borrow(&self) -> &[$ty] {
&self.0
}
}
impl core::borrow::BorrowMut<[$ty]> for $thing {
fn borrow_mut(&mut self) -> &mut [$ty] {
&mut self.0
} }
} }