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

Shamir Secret Sharing over GF(2^8) #7891

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open

Shamir Secret Sharing over GF(2^8) #7891

wants to merge 14 commits into from

Conversation

andrewjstone
Copy link
Contributor

@andrewjstone andrewjstone commented Mar 28, 2025

Please see the doc comments for details.

@andrewjstone andrewjstone changed the title WIP: Shamir Secret Sharing over GF(2^8) Shamir Secret Sharing over GF(2^8) Mar 31, 2025
@andrewjstone andrewjstone marked this pull request as ready for review March 31, 2025 22:41
/// implementations.
impl Mul for Gf256 {
type Output = Self;
fn mul(mut self, mut rhs: Self) -> Self::Output {
Copy link
Contributor

Choose a reason for hiding this comment

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

these both being mut seems strange, especially since the trait doesn't look to have it https://doc.rust-lang.org/stable/core/ops/trait.Mul.html . I know this is rust but are we going to run into surprises with the mut?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The trait takes the parameters by value, so this shouldn't cause any problems. They'll get copied before being passed as arguments. I could create temporary locals inside the method I suppose. That may make the code a touch clearer.

Copy link
Contributor

Choose a reason for hiding this comment

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

I completely failed the by value part. I think I was too focused on comparing against the other code which does use temporaries.

/// implementations.
impl Mul for Gf256 {
type Output = Self;
fn mul(mut self, mut rhs: Self) -> Self::Output {
Copy link
Contributor

Choose a reason for hiding this comment

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

I completely failed the by value part. I think I was too focused on comparing against the other code which does use temporaries.

Copy link

@inickles inickles left a comment

Choose a reason for hiding this comment

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

Primarily just had one comment / question about the API for shamir, the other comments are minor.

// Can't divide by zero
for i in 1..=255u8 {
let a = Gf256::new(i);
assert_eq!((a * a.invert()).ct_eq(&ONE).unwrap_u8(), 1);
Copy link

Choose a reason for hiding this comment

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

I know the unwrap_u8 isn't your code, but just thought I'd mention that I find the wording of that from sublte is unfortunate because it makes it seem like it could panic. I think your choice of into_u8 is much better. into_inner is also what I'd expect, though I also like into_u8 being that it's more idiomatic and better describes what you get.

/// implementations.
impl Mul for Gf256 {
type Output = Self;
fn mul(mut self, mut rhs: Self) -> Self::Output {
Copy link

Choose a reason for hiding this comment

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

I verified this implementation matches the link you provided (https://en.wikipedia.org/wiki/Finite_field_arithmetic#Rijndael's_(AES)_finite_field), though I think readability could be improved by using if statements instead of using wrapping_sub. Ex:

if rhs.0 & 1 == 1 {
    product ^= self.0;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I 100% agree with the readability point, this was done intentionally to avoid data dependent code when dealing with secrets. I'm attempting to make this as constant time as possible in rust, barring the compiler harming me, which is extremely likely!

}

/// Interpolate the points for each polynomial to find the value `y = f(x)`
/// and then concatenate them and return the corrseponding [`Share`].
Copy link

Choose a reason for hiding this comment

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

Suggested change
/// and then concatenate them and return the corrseponding [`Share`].
/// and then concatenate them and return the corresponding [`Share`].

///
/// The secret is the concatenation of the y-coordinates at x=0.
pub fn compute_secret(
shares: &[Share],
Copy link

Choose a reason for hiding this comment

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

Because it's part of the public API, it looks to me like shares should be behind a secrecry::Secret, considering this it all that's needed to construct the secret, where that only might be the case depending on the caller. Without enforcing that I think it adds potential for "holding it wrong" here. And so I could see an argument for making the underlying data in a public Share be wrapped in a secrecry::Secret.

This of course would have some big implications on the API, and I could imagine the ergonomic implications might have you cursing my name. I wanted to bounce this off you before thinking through all that further.

Thoughts?

Copy link
Contributor Author

@andrewjstone andrewjstone Apr 3, 2025

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 necessary. Shares themselves implement ZeroizeOnDrop and this API really shouldn't mandate how the user maintains them as it can indeed introduce serious ergonomic issues. Although not shown in any tests here, Shares are sent across nodes in messages, and must be collated over time. We'd need a mutable secret to store them, and we don't really want to force the collection in which they are stored on a caller. They can store them as values in maps, Vecs, etc... They can put their collection type l in a Secret, or not, but we don't really care about that here. In order to allow this work, we also need to update Secrecy because in the version we are on you can't have a mutable secret and can't build up shares retrieved over the network into a single collection. Also, I'm pretty sure this API would have to become generic over the collections to take a Secret of arbitrary inner type, which I"m definitely loathe to do.

Now, with that said, I actually think this API probably should be changed, except not to take a secret. It should instead take an Iterator<Item = &Share> to allow storage in various collection types without having to collect into a type that can be passed as a slice.

We could also wrap each individual Share in a Secret, but that just means for every damn math function we are constantly calling expose_secret, which will make things harder to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, with that said, I actually think this API probably should be changed, except not to take a secret. It should instead take an Iterator<Item = &Share> to allow storage in various collection types without having to collect into a type that can be passed as a slice.

Actually doing this may be a mistake also, as they'd still have to be collected to build a ValidatedShares type. I think the best thing to do is to build on top of this library by using it and then make changes over time if they seem reasonable.

andrewjstone added a commit that referenced this pull request Apr 5, 2025
This code builds upon #7859, which itself builds upon #7891. Those
need to be merged in order first.

A `Node` is the sans-io entity driving the trust quorum protocol. This
PR starts the work of creating the `Node` type and coordinating an
initial configuration.

There's a property based test that generates an initial `ReconfigureMsg`
that is then used to call `Node::coordinate_reconfiguration`, which will
internally setup a `CoordinatorState` and create a bunch of messages
to be sent. We verify that a new `PersistentState` is returned and that
messages are sent.

This needs a bit more documentation and testing, so it's still WIP.
andrewjstone added a commit that referenced this pull request Apr 5, 2025
This code builds upon #7859, which itself builds upon #7891. Those
need to be merged in order first.

A `Node` is the sans-io entity driving the trust quorum protocol. This
PR starts the work of creating the `Node` type and coordinating an
initial configuration.

There's a property based test that generates an initial `ReconfigureMsg`
that is then used to call `Node::coordinate_reconfiguration`, which will
internally setup a `CoordinatorState` and create a bunch of messages
to be sent. We verify that a new `PersistentState` is returned and that
messages are sent.

This needs a bit more documentation and testing, so it's still WIP.
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.

4 participants