bip32: infallible derive_pub#2911
bip32: infallible derive_pub#2911storopoli wants to merge 2 commits intorust-bitcoin:masterfrom storopoli:bip32/derive_pub-unfallible
derive_pub#2911Conversation
|
This looks great to me. A minor point on naming is that I notice we have I think we should change the names of the new types to |
Ok will do in this PR.
Should we remove the |
Pull Request Test Coverage Report for Build 9650838641Details
💛 - Coveralls |
Pull Request Test Coverage Report for Build 9650886863Details
💛 - Coveralls |
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
I'm not sure about this.
Suggestions?
There was a problem hiding this comment.
From defiinitely should not panic. Since the conversion is fallible we should implement TryFrom instead if we want a conversion function.
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
Since this type is supposed to maintain the invariant that the top bit is not set, IMO the inner field should be private. Same for the hardened struct.
There was a problem hiding this comment.
Done, but I needed a new() method for the structs that don't check the range of the u32 for within [0, 2^31 - 1].
So that this diff
- let num = ChildNumber::Normal { index: 0xDEADBEEF };
+ let num = ChildNumber::Normal(NormalChildNumber::new(0xDEADBEEF));Because 0xDEADBEEF > 2^31 -1 is true. 🤷🏻♂️
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
From defiinitely should not panic. Since the conversion is fallible we should implement TryFrom instead if we want a conversion function.
Pull Request Test Coverage Report for Build 9659761194Details
💛 - Coveralls |
Kixunil
left a comment
There was a problem hiding this comment.
The panics are definitely wrong and I'd like the naming to be addressed as well.
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
I think there might be a difference between index and child number. It looks to me that child number contains the hardened flag while index doesn't. So index was probably correct here. We're also storing index in the *ChildNumber which is confusing. (Also maybe if integer validity ranges stabilize in the future we can get better optimization changing it.) I don't care to change the layout in this PR though.
There was a problem hiding this comment.
Cc @apoelstra, shall we revert back to from_*_idx?
There was a problem hiding this comment.
Oh, yeah, I guess index is correct then. That is confusing.
Let's expand idx to index though.
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
I'd prefer returning a more specific error but this can be done in a followup PR.
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
Unrelated but I dislike this impl existing...
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
This is not what "prevent by type system" means. There should be no panic and P needs to be AsRef<[NormalChildNumber]> instead.
Though now I'm wondering why we're not using IntoIterator<Item=(Normal)ChildNumber> instead.
There was a problem hiding this comment.
Yes, I agree. I am trying the AsRef<[NormalChildNumber]> route. But I didn't get the implicit conversions correct yet.
I'll try harder 😄.
I like the IntoIterator idea I'll also try that.
bitcoin/src/bip32.rs
Outdated
There was a problem hiding this comment.
So ckd_pub_tweak can still fail due to https://github.com/rust-bitcoin/rust-bitcoin/blob/584ab34315054f145a8368f81bca2a5cb122639f/bitcoin/src/bip32.rs#L832-L838
hence the expect here.
Any suggestions?
There was a problem hiding this comment.
I think it's mathematically unlikely (same as guessing a private key), but I don't remember certainly, so I'll let @apoelstra to answer this. If it's mathematically unlikely we can except but a comment about likelihood would help.
If it's not unlikely we need to use Result.
There was a problem hiding this comment.
Yes because you're guaranteed to have 32 bytes
and ckd_pub_tweak only fails if SecretKey::from_slice is not 32 bytes and within the secp curve order:
/// use secp256k1::SecretKey;
/// let sk = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order");
/// ```
#[inline]
pub fn from_slice(data: &[u8]) -> Result<SecretKey, Error> {
match <[u8; constants::SECRET_KEY_SIZE]>::try_from(data) {
Ok(data) => {
unsafe {
if ffi::secp256k1_ec_seckey_verify(
ffi::secp256k1_context_no_precomp,
data.as_c_ptr(),
) == 0
{
return Err(InvalidSecretKey);
}
}
Ok(SecretKey(data))
}
Err(_) => Err(InvalidSecretKey),
}
}That's why I've put the expect(), this will not fail. I could use unwrap() but that's not recommended inside src/ code (it's fine for examples and tests though).
There was a problem hiding this comment.
I mean not on the curve - essentially someone managing to create a derivation number that tweaks by the inverse of the key. I can't remember if there's hashing or not. If there is it should be impossible. LOL, it's literally in the code you posted - there's HMAC. So it's impossible to trigger it intentionally, therefore the except is valid. I'd still appreciate a cryptographer double checking my claim though. :)
There was a problem hiding this comment.
Yep, you're correct on the crypto front.
But why does ckd_pub_tweak return a Result if it can't fail. I think any expects should appear next to the HMAC. (If ckd_pub_tweak is returning a result because it takes a general child number, then it needs to be inverted with ckd_pub -- the fallible methods should call the infallible ones,not the other way around.)
There was a problem hiding this comment.
Yeah, ckd_pub_tweak should just accept NormalChildNumber and not return Result.
|
Note to myself (TODO) Ok, we can now accept in |
Pull Request Test Coverage Report for Build 9664378557Details
💛 - Coveralls |
That is impossible. At best we could completely revamp the layout to allow (BTW time locks kinda have the same problem but people don't store them in arrays, so it's fine.) |
|
I did a PR right now to @storopoli s branch... |
|
@jaoleal that can be useful in a bunch of cases but it allocates so it'd be nice to see a non-allocating version too. |
|
I`m not certain of what kind of "allocation" do you mean. I wonder if is because of the Its possible to exclude that and retrieve directly from the |
|
Pedantically, it's not
I don't understand this but it's getting late here, so maybe I'm just tired.
Exactly that's why a fallible alternative method is useful. |
Yes, exactly. If we've We can't infallible convert Is this a good tradeoff? Make |
Pull Request Test Coverage Report for Build 9678710862Details
💛 - Coveralls |
|
@storopoli API breaks are expected these days and we prefer better API over unbroken API. I personally believe we should have both methods because some applications will prefer to enforce normal path at parsing and the conversion is complicated. Something like this should work: // note the lack of pub
fn internal_derive_pub<T: TryInto<NormalChildNumber>(&self, path: impl Iterator<Item=T>) -> Result<Xpub, T::Error> {
// current code here, call .try_into()? on each item in the for loop
}
pub fn derive_pub<I: IntoIterator<Item=NormalChildNumber>>(&self, path: I) -> Xpub {
self.internal_derive_pub(path.into_iter()).unwrap_or_else(|never| match never {})
}
pub fn try_derive_pub<I: IntoIterator<Item=ChildNumber>>(&self, path: I) -> Result<Xpub, NormalChildNumberError> {
self.internal_derive_pub(path.into_iter())
}(We could also just expose the internal method and let people use |
pub fn derive_pub<I: IntoIterator<Item=NormalChildNumber>>(&self, path: I) -> Xpub {
self.internal_derive_pub(path.into_iter()).unwrap_or_else(|never| match never {})
}What is the point of using a non-null safe language to do this ? Including the impl TryFrom<DerivationPath> for Vec<NormalChildNumber> {
type Error = Error;
fn try_from(value: DerivationPath) -> Result<Self, Self::Error> {
let mut ret = Vec::new();
for cnum in value.0.iter() {
match *cnum {
ChildNumber::Normal(normal) => {
ret.push(normal);
},
_ => return Err(Error::InvalidChildNumberFormat),
}
}
Ok(ret)
}
}we could improve your proposal of a function for each possibility. Like: // Certain of normalized path
fn derive_pub_pure( v: Vec<NormalChildNumber>) -> Xpub {}
// Uncertain about whats inside
fn try_derive_pub( v: DerivationPath) -> Result<Xpub, Error>{}And use it like let path: Vec<NormalChildNumber> = DerivationPath::from(*).try_into()
derive_pub_pure(path)
///Would work here too
try_derive_pub(path)
///Since a vector of Normals are easily convertible for DerivationPath
Improving is not hard, we can iterate DerivationPath and return a owned vector slice... |
Yes. Don't worry about Right now we have a |
I don't understand what relation this has to non-null. Care to explain?
Sorry, now I see I messed up the types. Check the fixed version.
In your example you're doing an unneeded heap allocation. Let's not do those.
I don't understand, returning an owned vec requires heap allocation. |
The only way that i could think of have certainty of //Here we just get rid of some abstraction since `NormalChildNumber` and `HardenedChildNumber`
//implement the same traits.
pub enum ChildNumber {
/// Non-hardened key
NormalChildNumber(u32),
/// Hardened key
HardenedChildNumber(u32),
}
struct DerivationPath<ChildNumber> (Vec<ChildNumber>);
impl DerivationPath<ChildNumber>{
fn to_normal( path: Self) -> Result<DerivationPath<NormalChildNumber>, Error> {
path.iter().for_each(|cnum| {
if cnum < 2147483647 {
cnum = ChildNumber::NormalChildNumber(cnum);
} else {
return Error
}
});
Self
}
fn to_hard( path: Self) -> Result<DerivationPath<HardenedChildNumber>, Error> {
path.iter().for_each(|cnum| {
if cnum > 2147483647 {
cnum = ChildNumber::HardenedChildNumber(cnum);
} else {
return Error
}
});
Self
}
}
//No more uncertainty
fn derive_pub(p: DerivationPath<NormalChildNumber>) -> Xpub {}Maybe the |
|
@jaoleal that doesn't work and there's no need to do that. The code that's written right now already avoids allocation. We only need to refactor it a bit to make fallibility generic. |
|
I've spoken offline with @jaoleal and he will be taking over this PR. |
|
I`ll reopen the PR tagging the issue when i think better of the @Kixunil Iterator's strategy |
Closes #2752.