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

[frost] Make "tweaks" mutate #190

Merged
merged 15 commits into from
Aug 9, 2024
Merged

[frost] Make "tweaks" mutate #190

merged 15 commits into from
Aug 9, 2024

Conversation

LLFourn
Copy link
Owner

@LLFourn LLFourn commented Jul 25, 2024

Prior to this PR tweaks were being stored in a separate field inside FrostKey and were applied at signature combination time. In frostsnap we actually want to be able to do "one-way" tweaks where you forget the original polynomial (at least the first coefficient). This is not an API safety concern but we strictly don't want the original to exist anymore in memory.

The next problem we address is that we don't want you to need the full polynomial for signing. Instead we make the user provide a "paired" secret share which is the secret scalar and index paired with the shared key (first coefficient of polynomial). This is a convenient combination since it allows you to hash the shared key to figure out a tweak and then apply it to both the shared key and the shared secret.

See commits individually. eb6dc14 is the frost only one.

I haven't updated docs yet because I intend to integrate some bip-frost stuff which will change the API again. I haven't updated the examples yet so this is not really mergable.

In case you want a non-zero point generically
Prior to this commit tweaks were being stored in a separate field inside
`FrostKey` and were applied at signature combination time. In frostsnap
we actually want to be able to do "one-way" tweaks where you forget the
original polynomial (at least the first coefficient). This is not an API
safety concern but we strictly don't want the original to exist anymore.

The next problem we address is that we don't want you to need the full
polynomial for signing. Instead we make the user provide a "paired"
secret share which is the secret scalar and index paired with the shared
key (first coefficient of polynomial). This is a convenient combination
since it allows you to hash the shared key to figure out a tweak and
then apply it to both the shared key and the shared secret.
@LLFourn LLFourn requested a review from nickfarrow July 25, 2024 09:28
@LLFourn LLFourn force-pushed the tweak-mutate branch 2 times, most recently from ec8a662 to 5bea2bd Compare July 26, 2024 07:18
@LLFourn
Copy link
Owner Author

LLFourn commented Jul 26, 2024

This is reviewable now. Please excuse poor documentation. I think I'll do a full sweep after I've attempted implementing the bip.

See #189 for why I did multiplicative tweaking.

@irriden
Copy link

irriden commented Jul 26, 2024

Thank you for the PR !

I have rebased my work on top of this PR here, everything ok.

I also tested add and mul tweaks on signet, separately and together, both worked.

The awkward parts were 1) applying tweaks to both SharedKey and PairedSecretShare 2) the function signature difference between homomorphic_add and homomorphic_mul.

Consider these minor nits :)

Thank you again ! Also particularly appreciate recover_secret, this is useful in a LN context.

@LLFourn
Copy link
Owner Author

LLFourn commented Jul 28, 2024

The awkward parts were 1) applying tweaks to both SharedKey and PairedSecretShare

Hmm I was actually thinking you wouldn't have to do this unless in your protocol signers are also verifying signature shares (which I guess in LN context is likely).

The function signature difference between homomorphic_add and homomorphic_mul

Good point I think homomorphic_mul should just own self as well.

@irriden
Copy link

irriden commented Jul 29, 2024

Hmm I was actually thinking you wouldn't have to do this unless in your protocol signers are also verifying signature shares (which I guess in LN context is likely).

What drives this for me at the moment: coordinator_sign_session requires the SharedKey, and I assume that it needs the tweaked SharedKey. Let me know if this assumption is wrong thank you !

EDIT: and the CoordinatorSignSession returned is needed to combine signature shares.

@LLFourn
Copy link
Owner Author

LLFourn commented Jul 29, 2024

@irriden I can relax this a bit but you will need CoordinatorSignSession to verify the signature shares anyway or are you not doing that in this case?

To make arithmetic API simpler at the cost of making the caller call
`.non_zero()` etc
@LLFourn LLFourn force-pushed the tweak-mutate branch 2 times, most recently from 15677b5 to 29962aa Compare July 29, 2024 07:51
@LLFourn
Copy link
Owner Author

LLFourn commented Jul 29, 2024

@irriden see the two last commits. Hopefully the tweaking is more straightforward now. There is no longer two variants of tweakings "xonly" and normal tweaks. To do an xonly tweak you just do a normal homomorphic_add then call non_zero and then into_xonly. This is more steps for the caller but I think more natural and means less code.

I've made it so you don't need a CoordinatorSignSession to create the final signature. I'm guessing you are just checking the final signature for validity?

@irriden
Copy link

irriden commented Jul 29, 2024

I can relax this a bit but you will need CoordinatorSignSession to verify the signature shares anyway or are you not doing that in this case?

Right now I am not verifying signature shares, I am leaving identifiable aborts for later :)

To do an xonly tweak you just do a normal homomorphic_add then call non_zero and then into_xonly. This is more steps for the caller but I think more natural and means less code.

All sounds great thank you !

I've made it so you don't need a CoordinatorSignSession to create the final signature.

Seems to me PartySignSession is created from fn party_sign_session, which is a function that requires the agg_binonce as a parameter, and this agg_binonce can only be produced by a CoordinatorSignSession. Looks like this is also the case for the parties parameter of fn party_sign_session, it can only be produced by CoordinatorSignSession.

So CoordinatorSignSession is still needed to create the final signature isn't it?

EDIT: I do see that these two params could be created explicitly without creating a CoordinatorSignSession.

Do not worry too much about this, I'm happy to ensure tweaks are applied to both SharedKey and PairedSecretShare simultaneously.

I'm guessing you are just checking the final signature for validity?

Yes, just the final signature, not the partial signatures.

So it's clearer how you can avoid a coordinator
@LLFourn
Copy link
Owner Author

LLFourn commented Jul 30, 2024

See 4e22279 where I make a method to make it more discoverable how this is meant to work.

@irriden
Copy link

irriden commented Jul 30, 2024

See 4e22279 where I make a method to make it more discoverable how this is meant to work.

Thank you !

I am going to keep using the CoordinatorSignSession route. My coordinator for now keeps a copy of SharedKey in order to check the aggregate signature at the last step, and I'm happy to provide a tweaked SharedKey to coordinator_sign_session.

Nonetheless seems to me this would be particularly useful in the case where one of the parties is used as the coordinator.

/// Constructor to create a from a vector of points where each item represent a polynomial
/// coefficient.
///
/// Returns `None` if the first coefficient is [`Point::zero`].
Copy link
Owner Author

@LLFourn LLFourn Jul 30, 2024

Choose a reason for hiding this comment

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

This is not true anymore. It can be None is the poly is empty. I think I might make two different from_polys one for Zero and another for NonZero. The Zero one will be infallible.

[edit] maybe just the Zero one and call non_zero to get the non_zero one.

Pair the secret shares using the shared key so you know it's correct.

Also cleaned up the accessing the public key in order to do the implementation.

..and some other misc changes 🤗
As well as make more generic the bincode/serde impls
/// The resulting shared key will be `SharedKey<Normal, Zero>`. It's up to the caller to do the zero check with [`non_zero`]
///
/// [`non_zero`]: Self::non_zero
pub fn from_poly(poly: Vec<Point<Normal, Public, Zero>>) -> Self {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this again because there's nothing special about preventing Zero for a shared key?

I always feel weird seeing it allowed. Why avoid panics when the user passes an empty poly for the shared key? (maybe they just don't know it but have a secret share?)

}

/// The public key the session was started under
pub fn public_key(&self) -> Point<EvenY> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I liked how you re-allowed SharedKey to be Normal, but here the signing session is forced into a BIP340 context. No problem for us but maybe other people would want signing for Normal keys?

Copy link
Owner Author

Choose a reason for hiding this comment

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

There is no way to sign with Normal keys though.

/// encoded as 33 bytes.
///
/// ⚠ Unlike other secp256kfun things this doesn't exactly match the serde/bincode
/// implemenations which will length prefix the list of points.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am curious as to why? Or is this just a note that serde/bincode prefix the length so you'll have to keep that in mind if you mix & match?

Copy link
Owner Author

Choose a reason for hiding this comment

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

This is the first time I'm creating a to_bytes on something that is variable length and I just felt like noting the difference. Maybe the comment is more trouble than it's worth. You definitely shouldn't mix and match.

Copy link
Collaborator

@nickfarrow nickfarrow left a comment

Choose a reason for hiding this comment

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

I noticed you made all the public keys shared_key and then changed back. Looks like they are no longer going with shared_key on the BIP also!

Very much like 🐙 = coordinator

Proptest with all the variations of tweaks looks very robust.

One completely unrelated thing I just noticed is that create_shares_and_pop is kind of confusing, makes it sound like something is getting popped off a vec.

I didn't look too deeply into the secp256kfun casting between point types etc. but the idea of checking the types like this makes sense.

There's quite a few spelling mistakes, not sure whether you care much for that, happy to fix up in commit or another PR. Everything else LGTM!

signature share verification was unnecessarily slow because you get to
regenerate their whole verification share each time. Now you can cache
it.

Also verification shares are only for EvenY keys. We now have a concept
of "share image" which is more general I'm not entirely sure I like but
I'll leave it in for now.
@LLFourn
Copy link
Owner Author

LLFourn commented Aug 8, 2024

I just pushed a commit to make "verification shares" a first class type. Now you use the verification shares to verify signature shares. @irriden you can now create a polynomial from "share images" rather than "verification shares" because verification shares are strictly something you can verify a signature share against (i.e. homomorphically linked to a Point<EvenY>.

One completely unrelated thing I just noticed is that create_shares_and_pop is kind of confusing, makes it sound like something is getting popped off a vec.

heh it will be gone soon I hope.

There's quite a few spelling mistakes, not sure whether you care much for that, happy to fix up in commit or another PR. Everything else LGTM!

Feel free to push doc fixes you catch at anytime. I fixed up a few given that you made me self-conscious about it!

There's no need for the `Frost` type to do signing/verification. It's
needed to start the session but not after.
@LLFourn
Copy link
Owner Author

LLFourn commented Aug 8, 2024

Added 84ac38b which made things make a bit more sense.

@LLFourn LLFourn merged commit 646b969 into master Aug 9, 2024
15 checks passed
@LLFourn LLFourn deleted the tweak-mutate branch August 9, 2024 01:00
@LLFourn LLFourn mentioned this pull request Aug 9, 2024
@irriden
Copy link

irriden commented Aug 23, 2024

Hi @LLFourn,

Thank you for the ping.

For now I remain on 151c6d6.

bip-frost-dkg outputs this:

class DKGOutput(NamedTuple):
    secshare: Optional[bytes]
    threshold_pubkey: bytes
    pubshares: List[bytes]

where pubshares are verification shares.

That's all the info that an individual signer gets. The individual signer doesn't get the "share images" of the other signers.

My solution will be to get rid of the bip-frost-dkg chilldkg reference implementation, and replace it with the secp256kfun version.

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