Skip to content
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

Use bech32 to calculate descriptor checksums #608

Merged
merged 2 commits into from
Oct 12, 2023

Conversation

tcharding
Copy link
Member

@tcharding tcharding commented Oct 4, 2023

This is just the last patch, the rest are in #609 now.

The checksum::poly_mod function implements BCH codes to calculate a checksum (appended to descriptors). We recently released a version of BCH codes in bech32. We can implement the bech32::Checksum trait for BIP-380 and use the primitives::checksum module, removing the custom poly_mod function.

@apoelstra
Copy link
Member

Up to 5018673 is great and could be PR'd separately if you want to move more quickly.

@apoelstra
Copy link
Member

As for 9ca3380:

  • We should pull apart the Engine and replace the cls/clscount stuff with an iterator adaptor from chars to Fes.
  • Osd can be DescriptorChecksum or something. It doesn't have to be short; it shouldn't ever be used by users.
  • I believe your expects are all correct; you are construting a 3-digit base 3 number, which can be at most 222 (or 26 in decimal) which fits into a Fe32.
  • Your "comment why we do this" TODO is because you're manually computing a checksum and manually futzing with the target checksum. Just use Engine::input_target_residue and then you can drop the code that inputs 0s and basically all the rest of this function. Also you can just replace the whole checksum_chars function with the Checksummed iterator adaptor from rust-bech32.

@tcharding
Copy link
Member Author

Sick! Thanks man.

@tcharding
Copy link
Member Author

I learned a new expression 'futzing with', not the first one I've learned from you @apoelstra, I love it.

for ch in Checksummed::new_unchecked(&s) {
f.write_char(ch)?;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 22ee854:

We definitely want to keep the wrapped_f stuff. This new code is both longer and now allocates.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I balked at this before I pushed it. Will add it back in.

///
/// [BIP-380]: <https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki>
#[inline]
pub fn new(descriptor: &'a str) -> Result<Checksummed<'a>, Error> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 22ee854:

Rather than taking a string this should take a D: Display and a fmt::Formatter.

let checksummed = Checksummed::new(desc_str)?;
if !checksummed.eq(s.chars()) {
return Err(Error::BadDescriptor("invalid checksum".to_owned()));
}
Copy link
Member

@apoelstra apoelstra Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 22ee854:

It would be faster to feed the checksum into the checksum iterator and compare the residue against the target. Then in the happy case we only need to iterate once. Actually, the speedup here would be minimal, since you are only checksumming once, and s.chars() is extremely fast to iterate on.

In the unhappy case, we should probably return a better error variant that has the expected and received checksum in it. It's fine if this is slow and allocating.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing.

@tcharding
Copy link
Member Author

Alright, I think I got a little trigger happy before to use my new found iterator skills from the recent bech32 work. Instead this PR now just does the minimum required to remove the poly_mod function and use bech32::primitives::checksum instead.

@tcharding
Copy link
Member Author

I was futzing with the whole descriptor module before ;)

@tcharding tcharding force-pushed the 10-04-use-new-bech32 branch 2 times, most recently from 045d645 to ad7d5dc Compare October 9, 2023 03:24
@apoelstra
Copy link
Member

Ok, ACK this. It's a good start.

apoelstra added a commit that referenced this pull request Oct 9, 2023
5018673 Remove magic number (Tobin C. Harding)
535cd17 Improve docs in the checksum module (Tobin C. Harding)
fec700a Add bip 380 checksum test vectors (Tobin C. Harding)
73f4892 Add output descriptor bip referenece (Tobin C. Harding)

Pull request description:

  The first 4 patches from #608, no changes.

  Clean up and add some test vector unit tests.

ACKs for top commit:
  apoelstra:
    ACK 5018673

Tree-SHA512: f50f64bf9a1fc28eebca809379e02580cab96e7e41228aab6045441eb71702bef1b1979e497a6dcb1e1bce082965e5c93e78dba6e8fbd78c7a0ae2e3c8035660
@tcharding
Copy link
Member Author

Rebased to pick up changes in #609 and use newly release bech32 v0.10.0-beta, no other changes.

@tcharding tcharding marked this pull request as ready for review October 9, 2023 18:10
apoelstra
apoelstra previously approved these changes Oct 9, 2023
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK c5c08fe

@apoelstra
Copy link
Member

@sanket1729 I'll let you take a look at this before merging. It basically gets rid of some of the magic constants and uses rust-bech32 facilities to do the checksum. But this doesn't represent a "real" use of the new rust-bech32 API, which would pretty-much reduce the checksum.rs module to just encoding/decoding strings as u5s and then handing off to rust-bech32 APIs.

@tcharding
Copy link
Member Author

I was going to attack it in steps:

  • Move all the Formatter stuff in descriptor/*.rs to use a macro write_desciptor!.
  • Then in the macro work out how to convert the input string to a stream of Fe32s like you suggest.

The reason to do two steps is that I don't think we can have a general "input multiple strings" to the checksum engine because cls/clscount is state that has to be kept between successive input strings. But i noticed that we never do more than input a single descriptor.

@apoelstra
Copy link
Member

I don't like the idea of moving so much logic into a macro.

If we need to retain state then we can keep the existing Engine structure with cls/clscount. If we don't, we should change this to be an iterator adaptor.

But either way, once we have a stream of Fe32s, we should be able to just put them into a rust-bech32 checksum::Engine and everything should be easy.

@tcharding
Copy link
Member Author

tcharding commented Oct 9, 2023

I don't like the idea of moving so much logic into a macro.

The logic is just the more or less duplicate lines

        use fmt::Write;
        let mut wrapped_f = checksum::Formatter::new(f);
        write!(wrapped_f, "{}", self.ms)?;
        wrapped_f.write_checksum_if_not_alt()

Other descriptors have a match statement when calling write but they are essentially the same. Its totally just an abstraction thing, as it is it appears sane to call write multiple times, and the current implementation supports this. But its an unnecessary generalisation I believe because we only ever call write once, to write a single descriptor. I want to replace those 4 lines with write_descriptor!(f, "{}", self.ms)?;

If we need to retain state then we can keep the existing Engine structure with cls/clscount. If we don't, we should change this to be an iterator adaptor.

But either way, once we have a stream of Fe32s, we should be able to just put them into a rust-bech32 checksum::Engine and everything should be easy.

Another complication is we cannot use bech32 iterators to encode because of the # symbol. That is what led me to create the Checksummed type. Anyways, I'll have another play with it.

@apoelstra
Copy link
Member

The logic is just the more or less duplicate lines

Oh, yeah, this seems fine to me.

Another complication is we cannot use bech32 iterators to encode because of the # symbol.

Yeah, that sucks. But I think your solution is good.

@tcharding
Copy link
Member Author

tcharding commented Oct 10, 2023

I had another look at this, I'm not sure we can do much better even using some sort of iterator because of how we use checksum::Formatter. I'm pretty sure that our usage of fmt::Write::write_str is doing an allocation under the hood. If I'm right then changing our usage is then an optimization problem not a "use bech32" problem.

@tcharding
Copy link
Member Author

tcharding commented Oct 10, 2023

I think this could be considered for merge as is, I wrote up #610 to discuss potential improvements.

@apoelstra
Copy link
Member

I'm not sure we can do much better even using some sort of iterator

Yeah, you may be right.

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK c44ce81

Copy link
Member

@sanket1729 sanket1729 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK c44ce81. Did not review the use of bech32 carefully. Any breakage there should have caused existing test cases to fail.

@sanket1729
Copy link
Member

We can also safely merge this despite the red crosses as CI is fixed in master.

The `checksum::poly_mod` function implements BCH codes to calculate a
checksum (appended to descriptors). We recently released a version of
BCH codes in `bech32`. We can implement the `bech32::Checksum` trait for
BIP-380 and use the `primitives::checksum` module, removing the custom
`poly_mod` function.
Currently we have a bunch of places in the code where we create a
checksum formatter wrapper then write a descriptor to it then write the
checksum. This can all be wrapped up in a macro, has to be a macro
because the number of args is variable.

This reduces the line count with no real loss of clarity
`write_descriptor!` is pretty self explanatory.
@tcharding
Copy link
Member Author

tcharding commented Oct 11, 2023

You were correct @sanket1729, I rebased it for good measure, no changes.

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK fe1a6be

@apoelstra apoelstra merged commit 29b612c into rust-bitcoin:master Oct 12, 2023
16 checks passed
@tcharding tcharding deleted the 10-04-use-new-bech32 branch October 12, 2023 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants