axelarnetwork / tofn

A threshold cryptography library in Rust
Apache License 2.0
112 stars 23 forks source link

refactor!: allow happy/sad paths within a round to expect different combos of bcast and/or p2p messages #132

Closed ggutoski closed 3 years ago

ggutoski commented 3 years ago

This PR is a significant refactor and breaking change to the tofn SDK implementer API. The crate API surface and binary message format have changed slightly but these changes should not break existing downstream code.

How it was

Previously we required each round to specify at compile time the combination of bcast and/or p2ps messages it expects to receive from other parties. A consequence is that each round must expect that combo of message types regardless of whether it's in happy/sad path. This restriction is a problem because some rounds might, for example, require bcast-only in happy path but p2ps-only in sad path. Previously the only workaround is to send empty dummy bcast messages and/or bundle all p2ps into a single bcast, both of which are Very Bad™.

An additional annoyance with the previous design is copied code across different modules no_messages, bcast_only, p2ps_only, bcast_and_p2ps.

How it is now

There is now a single Executer trait that all rounds must implement. The new executer method has the following argument changes:

It is now the job of the protocol implementer to check in each round that the needed messages are present. (The previous SDK design sought to avoid this boilerplate.)

Each share declares its expected message types

How does the SDK know which types of messages to expect from a given share in a given round? ie. How does expecting_more_msgs_this_round() work? Due to nondeterministic delivery of messages, we cannot simply deduce the answer from observation. (eg. If I receive only a bcast from party X does that mean that party X intends to send only bcasts this round, or is X also sending p2ps and I just haven't received any yet?)

When share X ends a round it must specify:

From this the SDK auto-magically deduces X's intentions and bundles a declaration into the binary payload of every outgoing message from X. On the receiving side, share Y learns what types of messages to expect from X after receiving any message from X. If X sends inconsistent declarations then X is declared as a faulter by Y.

An annoying special case is total_share_count == 1 and p2ps-only. In this case the party will send zero messages and so the SDK cannot learn what to expect. I've left this special case as a TODO for the future. My suggested solution is to send an empty dummy bcast message with a special new declaration to be used only in this special case. This declaration says, "This is a p2ps-only round so you should not see any messages from me."

Changes to P2ps

As mentioned above, P2ps is now a VecMap of Option<HoleVecMap>. What used to be P2ps is now called FullP2ps. FullP2ps is still used in happy path cases where we want to remember the p2ps we received from previous rounds. To conclude: there are now 3 p2ps-related structs:

I added methods to convert among these structs. The hierarchy is FillP2ps > P2ps > FullP2ps where x > y indicates that x can be converted to y.

I expect protocol implementers should have no use for FillP2ps. Current module hierarchy means we have no way to restrict visibility of collection types. We can discuss this if there's concern.

ggutoski commented 3 years ago

fixes #94