-
Notifications
You must be signed in to change notification settings - Fork 65
Support bech32 encoding without a checksum #66
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
Conversation
src/lib.rs
Outdated
| 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.
| 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.
| /// 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_checksumanddecode_without_checksumto support this use case.Also, remove overall length check in decode since it is unnecessary.