-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support bech32 encoding without a checksum #66
Support bech32 encoding without a checksum #66
Conversation
src/lib.rs
Outdated
@@ -134,6 +134,7 @@ pub struct Bech32Writer<'a> { | |||
formatter: &'a mut fmt::Write, | |||
chk: u32, | |||
variant: Variant, | |||
checksum: Checksum, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand the point of a struct that appears ~entirely dedicated to tracking the checksum having a Checksum::Disabled
flag? Wouldn't it make more sense to use WriteBase32
directly or with a thinner wrapper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a similar wrapper for without a checksum, though there's a small amount of code duplication that way.
e0494d7
to
2cfe657
Compare
@tcharding @apoelstra Any chance either of you could be a second reviewer on this? |
Sure thing. Can you squash the first two commits please because the second one is patching the changes in the first. Not super important but I'd put patch 3 at the front since it is a trivial improvement. Thanks. |
Hi @jkczyz, thanks for your contribution. This PR does the job but its not pretty. How urgent is this? Is it something you need to use today or is it just something you did for fun? The reason its not pretty is that it adds functionality that is obviously just plastered on top. By that I mean the whole |
Somewhat urgent as it will hold up our work on Offers (lightningdevkit/rust-lightning#1597). I have a draft that uses this through
I don't actually need to use |
As a stop-gap you could just drop the last 6 characters/u8s from a normal bech32 encoding. |
2cfe657
to
665fac0
Compare
All done. |
For encoding that would work, but we would still fail to decode a bech32 offer since it doesn't have a checksum. |
src/lib.rs
Outdated
} | ||
Ok(()) | ||
}; | ||
Ok(write()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little uncertain about this. Seems to maintain the same behavior, but it is odd that an fmt::Error
is always wrapped with Ok
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the existing API is really weird.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you constructing a closure and then immediately calling it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mainly as a simple way to maintain the API behavior. i.e., wrapping a Err(fmt::Error)
returned in each of the three places using ?
with an Ok
. Without the closure, it would be:
if let Err(e) = fmt.write_str(&hrp) { return Ok(Err(e)); }
if let Err(e) = fmt.write_char(SEP) { return Ok(Err(e)); }
for b in data.as_ref() {
if let Err(e) = fmt.write_char(b.to_char()) { return Ok(Err(e)); }
}
Ok(Ok(()))
Happy to update the error type is your prefer. It probably should be an enum that is either bech32::Error
or fmt::Error
. Or maybe adding a variant to bech32::Error
for fmt::Error
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole Result<Result>
thing is weird to me -- I think we should add fmt::Error
to the main Error
enum and then flatten this return type.
I wouldn't worry about maintaining the API here. We want to overhaul this API anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flatted the Result
. Let me know if you prefer a different name for the variant or if you want to drop the wrapped fmt::Error
as it currently doesn't provide any additional information.
665fac0
to
9dc60b4
Compare
This simplifies the return type of encode_to_fmt, which used a nested Result type.
9dc60b4
to
3e95975
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 573d7f4
Can't right now, give me a day or so. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sticking with this! Functionality looks correct to me. Perhaps add the unit test I used if you like it. I'm not super happy with the function naming but perhaps we can leave tat till another day, like others have said already this crate is about to get an overhaul anyways. If this solves your usecase I'm happy to ack it.
tACK 573d7f4 |
Addressed all feedback. Can squash fixups whenever you're ready. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK f768f03
Looks good to me. Yes, would prefer squashing all the fixups. |
BOLT 12 Offers uses bech32 encoding without a checksum since QR codes already have a checksum. Add functions encode_without_checksum and decode_without_checksum to support this use case. Also, remove overall length check in decode since it is unnecessary.
f768f03
to
86edca9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 86edca9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally had the time to review it. The API has problems but it was the case before, so not a problem with this particular PR. It just needs to be cleaned up before 1.0.
@@ -213,7 +216,7 @@ impl<'a> WriteBase32 for Bech32Writer<'a> { | |||
|
|||
impl<'a> Drop for Bech32Writer<'a> { | |||
fn drop(&mut self) { | |||
self.inner_finalize() | |||
self.write_checksum() | |||
.expect("Unhandled error writing the checksum on drop.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FTR std
ignores errors instead of panicking in drop and it is considered the right thing to do because panicking in drop is a footgun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lol, oops! I tried to look for double-panics but got a bit confused and somehow missed this completely obvious one.
But I think we should remove this Drop
business entirely so it's fine for now. I've never heard of a wrapper struct that takes a &mut
ref and then has finalization logic on Drop
...I suspect this was an API experiment that never failed enough to be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDK, it's not too different from BufWriter
behavior. But I guess compiler warning would be better than drop. I was thinking of abusing must_use
but then we couldn't implement any methods taking &mut self
and it'd be fragile anyway.
let hrp = match check_hrp(hrp)? { | ||
Case::Upper => Cow::Owned(hrp.to_lowercase()), | ||
Case::Lower | Case::None => Cow::Borrowed(hrp), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible to o this without Cow
which should be more efficient:
let hrp_owned; // yes, this is allowed in Rust!
let hrp = match check_hrp(hrp)? {
Case::Upper => {
hrp_owned = hrp.to_lowercase();
hrp_owned.as_str()
},
Case::Lower | Case::None => hrp,
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a neat lifetime-organizing trick!
/// * No length limits are enforced for the data part | ||
pub fn encode_without_checksum_to_fmt<T: AsRef<[u5]>>( | ||
fmt: &mut fmt::Write, | ||
hrp: &str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be better to have Hrp
newtype to make the reurn type fmt::Result
allowing use in Display
implementations.
@@ -622,6 +668,14 @@ pub enum Error { | |||
InvalidPadding, | |||
/// The whole string must be of one case | |||
MixedCase, | |||
/// Writing UTF-8 data failed | |||
WriteFailure(fmt::Error), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the right way of using fmt::Error
. It's just a marker that something failed and you're supposed to retrieve the error fro underlying stream. In case of std::io
it's done for you and in case of String
there's no error so unwrapping is the correct thing to do.
This library abuses the fmt::Write
API to signal display failure but Display
must not fail for any other reason than formatter failing.
From the docs:
Formatting implementations should ensure that they propagate errors from the Formatter (e.g., when calling write!). However, they should never return errors spuriously. That is, a formatting implementation must and may only return an error if the passed-in Formatter returns an error. This is because, contrary to what the function signature might suggest, string formatting is an infallible operation. This function only returns a result because writing to the underlying stream might fail and it must provide a way to propagate the fact that an error has occurred back up the stack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find! This will definitely inform our API overhaul.
For sake of planning, what timeframe should I expect for seeing this in a release? Do let me know if I can help in review if other PRs need to be merged first. Just want to set appropriate expectations given this will need to get into a |
We just dropped a If we are going to release then #68 and #72 would likely be nice to consider first. cc @apoelstra |
We can do a much faster release of this than six months. It's still fine if rust-bitcoin depends on an old version -- |
But having said that yes, I'd like to do a bunch more work on this crate before releasing .. so it could be a month or more. |
Gotcha. I think had trouble with multiple versions but can't recall the exact error. Will try again once there's a new |
Could the code be changed to be non-breaking? Minor version bump wouldn't be so painful and we could do API overhaul later. |
Are you suggesting doing that in |
We wouldn't need to bump anything in Edit: and yes, release minor version of |
Ah, so since |
Opened #77. |
BOLT 12 Offers uses bech32 encoding without a checksum since QR codes already have a checksum. Add functions
encode_without_checksum
anddecode_without_checksum
to support this use case.Also, remove overall length check in decode since it is unnecessary.