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

Introduce FieldVec and improve Polynomial and InvalidResidueError #204

Merged
merged 8 commits into from
Sep 30, 2024

Conversation

apoelstra
Copy link
Member

This introduces a new internal type FieldVec which is basically a backing array for checksum residues with some fiddly logic to work in a noalloc context.

Unlike ArrayVec or other similar types, this one's semantics are "an unbounded vector if you have an allocator, a bounded vector otherwise". If you go outside of the bounds without an allocator the code will panic, but our use of it ensures that this is never exposed to a user.

It also assumes that it's holding a Default type which lets us avoid unsafety (and dismisses the potential performance cost because this array is never expected to have more than 10 or 20 elements, even when it is "unbounded").

Using this backing, it improves Polynomial and removes the alloc bound from it; using this, it puts Polynomial into a new InvalidResidueError type, which will allow users to extract the necessary information from a checksum error to run the error correction algorithm. (The resulting change to an error type makes this an API-breaking change, though I don't think that anything else here is API-breaking.)

The next PR will actually implement correction :).

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

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

My review does not cover the math.

Comment on lines 23 to 41
/// Panics if [`Self::has_data`] is false, with an informative panic message.
/// Provide access to the underlying [`FieldVec`].
pub fn into_inner(self) -> FieldVec<F> { self.inner }
Copy link
Member

Choose a reason for hiding this comment

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

Docs and code don't match, this doesn't panic, did you mean to call assert_has_data()

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I think the comment is just out of date. I moved all the panics into functions on FieldVec so there's no need for anything in Polynomial to directly panic.

/// Provide access to the underlying [`FieldVec`].
pub fn into_inner(self) -> FieldVec<F> { self.inner }

/// Provide access to the underlying [`FieldVec`].
Copy link
Member

Choose a reason for hiding this comment

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

Doing super picky review because we should be very close to 1.0'ing this crate.

Suggested change
/// Provide access to the underlying [`FieldVec`].
/// Provides access to the underlying [`FieldVec`].

src/primitives/polynomial.rs Show resolved Hide resolved
src/primitives/polynomial.rs Show resolved Hide resolved
src/lib.rs Show resolved Hide resolved

impl<F: Field> ops::Sub<&Polynomial<F>> for Polynomial<F> {
type Output = Polynomial<F>;
fn sub(self, other: &Polynomial<F>) -> Polynomial<F> { self + other }
Copy link
Member

Choose a reason for hiding this comment

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

This is finite field math so add and sub are the same, right? Perhaps add a comment for less cryptographically trained devs.

Copy link
Member Author

Choose a reason for hiding this comment

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

In this case I probably should actually pass through to the sub impl for F (which is the same as the add impl in all cases, because I have a binary field, but this stuff should all still work even with non-binary fields).

@apoelstra
Copy link
Member Author

Clicked "resolve" on the conversations we agreed were not actionable.

Force-pushed to address the other comments.

Copy link
Member Author

@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.

Successfully ran local tests on 7a28bac.

@apoelstra
Copy link
Member Author

Second force-push was to fix a bug converting residues to polynomials which I noticed when working on the next PR.

Copy link
Member Author

@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.

Successfully ran local tests on 8ee88fc.

@apoelstra apoelstra force-pushed the 2024-09--fieldvec branch 5 times, most recently from 8d1b8c8 to 3b74657 Compare September 21, 2024 18:17
Copy link
Member Author

@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.

Successfully ran local tests on 8ee88fc.

@apoelstra
Copy link
Member Author

Ok, I have working error correction code now based on this PR :). I will stop iterating on it until somebody reviews it.

Copy link
Member Author

@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.

Successfully ran local tests on 8ee88fc.

@tcharding
Copy link
Member

Is a further non-math review useful by me, I can do it if so. I don't want to bother the PR with petty concerns if not.

@apoelstra
Copy link
Member Author

I think it'd be valuable, yeah, if you're willing to take the time.

@tcharding
Copy link
Member

I'll make a coffee :)

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

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

There were a couple of other super trivial docs things I saw but we'll likely need to go over the docs with a fine tooth comb before we try to do 1.0 so I didn't bother mentioning them.

/// Parameterized by the field type `F` which can be anything, but for most methods
/// to be enabled needs `Default` and `Clone`. (Both are implied by `Field`.)
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct FieldVec<F> {
Copy link
Member

Choose a reason for hiding this comment

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

Why go to trouble of mentioning that this should not be exposed in the public API and make this type pub? Code seems to build with pub(crate). Is it because of upcoming work?

Copy link
Member Author

Choose a reason for hiding this comment

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

pub(crate) would make this visible to the entire crate. pub makes it visible only to the parent module (who in turn re-exports it privately to make it visible to other submodules).

I could do pub(super) if it makes you happier.

Copy link
Member

Choose a reason for hiding this comment

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

Oh with private field module, I've never thought to do that before. Thanks for the explanation.

iter::successors(Some(F::ONE), |gen| Some(elem.clone() * gen)).take(n + 1).collect()
}

/// Multiply the elements of two vectors together, pointwise.
Copy link
Member

Choose a reason for hiding this comment

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

Needs panics docs.

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, I'll add them. Though all these docs are private of course.

Copy link
Member

Choose a reason for hiding this comment

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

Should have had two coffees, didn't notice the whole field module being private yesterday.

}
}

/// Multiply the elements of two vectors together, pointwise.
Copy link
Member

Choose a reason for hiding this comment

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

And panic docs here I suppose as well.

impl<F: Default> FieldVec<F> {
/// Pushes an item onto the end of the vector.
pub fn push(&mut self, item: F) {
self.len += 1;
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps its slightly cleaner to put this increment at the end of the function, saves all the + 1s below and saves reader from having to check there are no return paths where the increment was done and an item not added.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, did you write this to be uniform with pop?

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, agreed, it'd be cleaner to move the increment. Will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

@tcharding actually, the increment is before the assert_has_data assertion so that the panic message will be informative if you try to push too much data. Otherwise there'll just be an "index out of bounds" panic in the no-alloc case.

Copy link
Member

Choose a reason for hiding this comment

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

Interesting, I missed that. Thanks.

Comment on lines +175 to +202
#[cfg(not(feature = "alloc"))]
{
self.inner_a[self.len - 1] = item;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#[cfg(not(feature = "alloc"))]
{
self.inner_a[self.len - 1] = item;
}
#[cfg(not(feature = "alloc"))]
self.inner_a[self.len - 1] = item;

Or self.inner_a[self.len] = item if you use my other suggestion.

Copy link
Member Author

Choose a reason for hiding this comment

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

Did you try compiling this suggestion?

Copy link
Member

Choose a reason for hiding this comment

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

error[E0658]: attributes on expressions are experimental

Worst review suggestion ever :)

@apoelstra apoelstra force-pushed the 2024-09--fieldvec branch 2 times, most recently from 61f0810 to 28c97e6 Compare September 22, 2024 14:54
@apoelstra
Copy link
Member Author

Improved doccomments and added a bunch of unit tests. Also added missing Index<Range<usize>> impl on fieldvec.

This will allow `Polynomial` to work without an allocator (at least for
"small" checksums. Here a "small" checksum is one of length at most 6
(which covers bech32 and bech32m).

The descriptor checksum (8 characters), codex32 (13 characters) and "long
codex32" (15 characters) will not work with no-alloc.

I would like to fix this but it results in large types, especially a large
InvalidResidueError type (see last commit of this PR), so probably it will
need to be feature-gated or something. For now we just punt on it.

This PR introduces the `primitives::correction` module but only puts a
single constant into it for now.
We can now use `PrintImpl` even without an allocator. This is of limited
use of course, but it paves the ground for use to do error correction
without an allocator, which is interesting.
We are going to want to extend the ChecksumError::InvalidResidue error
variant to allow error correction, and in order to do so, we need to know
the actual residue computed from a string, not just that the residue failed
to match the target.

Uses the `Polynomial` type; see the previous commits for some caveats
about this type when alloc is disabled. (Prior to this PR, you just
couldn't use Polynomial at all without alloc.)

As an initial application of the new error, avoid re-validating
a bech32 address to handle the distinction between bech32m and
bech32. Instead, if bech32m validation fails, check if the
"bad" residue actually matches the bech32 target. If so, accept.

On my system this reduces the `bech32_parse_address` benchmark
from 610ns to 455ns, more than a 33% speedup.
This is just a helper trait to describe all the errors in the library
from which we can get an "invalid residue". The residues contain all the
information needed for error correction.
Two polynomials are equal even if they have a different number of
leading zeros.

Putting this into its own commit rather than folding it into one of the
FieldVec commits, because this bug was present even before the FieldVec
stuff.

Also remove the `Hash` impl because it's unneeded and we would be
expected to keep it in sync with Eq.
Copy link
Member Author

@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.

Successfully ran local tests on c209ce8.

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

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

ACK c209ce8

Copy link
Member

@clarkmoody clarkmoody left a comment

Choose a reason for hiding this comment

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

ACK c209ce8

Tests running. No further code comments.

@apoelstra apoelstra merged commit 3f98190 into rust-bitcoin:master Sep 30, 2024
12 checks passed
@apoelstra apoelstra deleted the 2024-09--fieldvec branch September 30, 2024 18:07
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