Skip to content

Implement an AVID protocol for batched AVSS#954

Open
jonas-lj wants to merge 108 commits into
mainfrom
jonas/avid_1
Open

Implement an AVID protocol for batched AVSS#954
jonas-lj wants to merge 108 commits into
mainfrom
jonas/avid_1

Conversation

@jonas-lj

@jonas-lj jonas-lj commented May 1, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@jonas-lj jonas-lj marked this pull request as ready for review May 4, 2026 19:05
@jonas-lj jonas-lj requested a review from benr-ml May 4, 2026 19:05

/// BCS-serialized length of a `SharesForNode` for a node of the given weight at the given
/// batch size.
fn bcs_serialized_size(weight: usize, batch_size: usize) -> usize {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a bit of a hack: The erasure code pads zeros to fit the block size, so when decoding, we need to truncate before decrypting. This function computes the expected size of a parties' shares given its weight.

This is safer than relying on the dealer to give the expected length and more efficient than saving this along with the shards, but suggestions are welcome.

jonas-lj added a commit that referenced this pull request May 5, 2026
Splits the support-module changes out of #954 so that PR can focus on
the AVID/AVSS protocol itself. No protocol changes here:

- Cargo.toml: drop serde-big-array (unused), add reed-solomon-erasure
  for the RS code.
- ecies_v1.rs: small additions used by the AVSS complaint flow, with
  a regression test.
- nodes.rs: add `collect_to_nodes` helper.
- reed_solomon.rs: extend the RS wrapper used for AVID dispersal,
  with doc comments on encode/decode.
Comment thread fastcrypto-tbls/src/ecies_v1.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/reed_solomon.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/reed_solomon.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
.collect_vec();

// The encryption used, counter-mode, is length-preserving, so the length of the ciphertext is equal to the length of the plaintext.
let expected_length = SharesForNode::bcs_serialized_size(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"Expected length" recomputed from weight + batch_size depends silently on SharesForNode's BCS layout. If the layout ever changes, the AVID consistency check breaks without a compile error. Consider putting the expected length in CommonMessage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yea, I thought about that. That's both extra data (manageable) but also an attack vector for the dealer. I've added a unit test which checks that the computation is always correct but I agree, it is indeed a bit of hack.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've already put a todo to consider other options, so I'll leave it as is for now.

@zhouwfang zhouwfang May 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair to defer — just leaving an option for when you revisit. The wire-bloat / dealer-supplied-length concerns only apply when the length is on the wire. Computing this from a fresh SharesForNode of the right shape (weight * ShareBatch of batch_size default scalars) via bcs::to_bytes(...).len(), cached at Receiver::new, removes the duplicated layout knowledge — a future field added to ShareBatch would automatically update both the wire serialization and this computation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's a good point, but then it assumes that the length of a SharesForNode is constant for any inputs (given weight and batch_size) which is true today, but is still an assumption to make.

Comment thread fastcrypto-tbls/src/threshold_schnorr/reed_solomon.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/reed_solomon.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/reed_solomon.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/reed_solomon.rs Outdated
}

#[cfg(test)]
mod tests {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider adding tests for the three newly-added validation paths?

@jonas-lj jonas-lj force-pushed the jonas/avid_1 branch 10 times, most recently from 3ea8534 to 8ef6f6b Compare May 22, 2026 08:45
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/ecies_v1.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
.collect_vec();

// The encryption used, counter-mode, is length-preserving, so the length of the ciphertext is equal to the length of the plaintext.
let expected_length = SharesForNode::bcs_serialized_size(

@zhouwfang zhouwfang May 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair to defer — just leaving an option for when you revisit. The wire-bloat / dealer-supplied-length concerns only apply when the length is on the wire. Computing this from a fresh SharesForNode of the right shape (weight * ShareBatch of batch_size default scalars) via bcs::to_bytes(...).len(), cached at Receiver::new, removes the duplicated layout knowledge — a future field added to ShareBatch would automatically update both the wire serialization and this computation.


/// RS-encode `ciphertext`, rebuild the per-recipient Merkle tree, and check its root matches
/// the dealer's `expected_root`. Errors with [InvalidMessage] on mismatch.
fn check_avid_consistency(

@zhouwfang zhouwfang May 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It does a full RS-encode of W shards + Merkle tree rebuild per call. With W = 700 and per-dealer recovery, this is the dominant recurring cost on the recovery path. Worth a follow-up to see whether the per-accuser tree can be cached across handle_* paths for the same accuser, since both currently re-derive it.

Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
@jonas-lj jonas-lj force-pushed the jonas/avid_1 branch 2 times, most recently from 9e76919 to acef21d Compare June 2, 2026 11:37
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
jonas-lj added 3 commits June 3, 2026 11:41
…mers

Move the confirmer set out of every IndirectMessage and into a
VerifiedConfirmers the caller constructs (confirmer ids + the
common_message_hash they attested to). echo now takes it, checks the
t+f weight quorum and that the confirmers attested to the same common
message, and the dispersal/confirmer partition is checked as a single
multiset equality. Make IndirectMessage::verify private and drop the
unused pending_recipients. Document the load-bearing H(E_i) ciphertext
check and the RevealComplaint.accuser_id unforgeability.
Replace the `broadcast_hash: Option<Digest>` overload with explicit
steps: check_ciphertext_hash (hard error on a swapped ciphertext) and
decrypt_and_verify_shares (returns the underlying error). The optimistic
path now surfaces the real error instead of collapsing every failure
into InvalidMessage; verify_and_decrypt takes a plain Digest and turns a
share-verification failure into a RevealComplaint.
…Message

Align the message type names with the module's optimistic/pessimistic
phase vocabulary.

@benr-ml benr-ml left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The names and documentation need a cleanup - consider using

  • Avss for the first round / optimistic message; Avid for the second round.
  • DealerCommonAvssMessage, DealerCommonAvidMessage; DealerAvssMessage, DealerAvidMessage; AvidEchoMessage, and so on
  • Let's move documentation as steps in the impl of dealer and receiver (we have that partially there, and partially in the top; let's move that to the impl, with concrete order 1, 2, 3..., and clear assumptions about the caller / things needs to be done (e.g., check sig, persist message, etc)


/// Threshold parameters for the AVSS protocol.
#[derive(Copy, Clone, Debug)]
pub struct Parameters {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should avss use this as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AVSS doesn't use the f parameter (only t) right now, but if should. So yes, but probably in a follow up pr

Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
if self
.nodes
.total_weight_of(verified_confirmers.confirmers.iter())?
< self.params.t + self.params.f

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2f, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think W - f actually?

Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss.rs Outdated
jonas-lj added 13 commits June 3, 2026 16:35
… docs

- create_optimistic_messages and echo now return BTreeMap<PartyId, _>
  keyed by recipient instead of Vec; update tests and bench accordingly.
- Rename for clarity: VerifiedMessage -> VerifiedPessimisticMessage,
  CommonMessage -> AvssCommonMessage (+ Verified variant),
  broadcast_hash -> dispersal_hash, the `common` fields -> `avss_common`,
  and common_message_hash -> avss_common_message_hash.
- Module doc trimmed to an overview; the per-step protocol detail,
  signing targets, complaint semantics, and retention guidance now live
  on the functions/types that produce them. Protocol steps renumbered
  1..8 consistently.
- Fix the benchmark for these changes (it had also drifted from earlier
  refactors): 3-arg echo with a VerifiedConfirmers, BTreeMap-keyed
  echoes, and make the benchmarked recipient the straggler so it runs.
The new `avid` module disperses opaque byte payloads (one per recipient)
over the weighted node set: RS-encode, Merkle-commit, echo, reconstruct,
blame. It binds each dispersal to a caller-supplied `context` digest.

batch_avss now layers on top of it: PessimisticMessage/Echo/BlameComplaint
are re-exports of the avid types, create_pessimistic_messages serializes
the ciphertexts as payloads bound to H(v), and the receiver keeps only the
AVSS-specific checks (confirmer partition, H(E_i) match against v, decrypt).
This removes ~280 lines of RS/Merkle plumbing from batch_avss.

Tests and bench updated; behavior unchanged.
…test

- create_pessimistic_messages now returns BTreeMap<PartyId, PessimisticMessage>
  directly from the AVID layer, dropping the redundant dispersal_hash (already
  pinned in every message) and the Vec flattening.
- Add cheating_dealer_complaint unit test in avid.rs covering complaint
  create + verify end-to-end.
- AVID disperse/verify_dispersal take a dst: &[u8] context instead of a Digest.
- Rename enough_weight -> sufficient_weight, decode_or_complain param -> id.
- echo requires W - f confirmer weight.
- avid.rs: fix stale context -> dst references in dispersal_hash docs.
- echo: document why confirmers need W - f weight.
- decode_ciphertext/handle_blame: note the length-preserving-ciphertext
  assumption behind expected_len.
- verify_and_decrypt: document that dispersal_hash is unauthenticated and must
  come from the VerifiedPessimisticMessage.
- VerifiedConfirmers: spell out the caller-asserted signature-verification
  safety contract.
Building the (W, W-2f) coder inverts a k*k matrix (O(k^3)), and it was being
rebuilt on every avid() call -- twice per decode_ciphertext/handle_blame. This
dominated process_message (seconds at W=1500).

Give Dealer/Receiver an owned avid::Avid field that builds the coder once and
reuses it. Avid owns its coder and shares the node set with its parent via
Arc<Nodes>, so there's a single node set, no per-call rebuilds, and no lifetimes
threaded through the API.
Move the new AVID-based implementation into a new batch_avss_avid module
(plus its benchmark) and restore the original batch_avss module verbatim,
along with the complaint module it depends on.

- presigning stays wired to the original batch_avss output types
- e2e tests and benchmarks exercise the new batch_avss_avid implementation,
  converting its (structurally identical) outputs via From impls so they can
  feed presigning
…ow, drop dead code, avoid shard clones

- avss: reject ciphertexts with the wrong recipient count and index complaint
  encryptions fallibly, so a Byzantine dealer cannot panic responders
- avid: guard W - 2f against underflow in Avid::new
- ecies_v1: remove unused EncryptedPart struct and PhantomData import
- avid: move per-sender shard chunks into dispersal messages instead of cloning
/// One recipient's slice of a dispersal.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DispersalEntry {
pub recipient_root: merkle::Node,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't that included/can be computed from in the merkle proof?

}

/// 1. Disperse one payload per recipient. Returns the `dispersal_hash` (the signing target
/// for the dispersal layer) and one [Dispersal] per party in `nodes`. The `dst`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is the dst needed? binding to the protocol is done by the signature, no?

Comment thread fastcrypto-tbls/src/threshold_schnorr/avid.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/avid.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/avid.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/avid.rs
pub fn verify_echo(
&self,
echo: Echo,
dispersal: &VerifiedDispersal,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when do i need to verify echos if i already have VerifiedDispersal? shouldn't this be just dispersal_hash?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But you still need echos (and verifying them) even though you verified the dispersal message, since that just checks the hash. Or do you mean that using the entire VerifiedDispersal is overkill since you just need the hash?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh, I saw your comment in the other file - it's an issue in case you don't have the dispersal message. But how do you verify the merkle proofs then?

@benr-ml benr-ml Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

just need the hash - me receiving echos is independent of me sending them to others


/// 3b. Collect the shards from a quorum of [VerifiedEcho]s, keyed by sender. Rejects duplicate
/// senders and requires `≥ W − 2f` weight.
pub fn collect_shards(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when do we need to call collect_shards and not to decode_or_complain?

Comment thread fastcrypto-tbls/src/ecies_v1.rs
Comment thread fastcrypto-tbls/src/ecies_v1.rs
Comment thread fastcrypto-tbls/src/ecies_v1.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss_avid.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss_avid.rs
Comment thread fastcrypto-tbls/src/threshold_schnorr/batch_avss_avid.rs
verified_common: VerifiedAvssCommonMessage,
verified_confirmers: &VerifiedConfirmers,
) -> FastCryptoResult<(VerifiedPessimisticMessage, BTreeMap<PartyId, Echo>, Vote)> {
// Require `W − f` weight of confirmers so that at least `W − 2f` of them are honest — the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit - what we want here is t+f

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But we should use t for the rs quorom then, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we need t+f for the first cert and 2f for the second

pub fn verify_echo(
&self,
echo: Echo,
verified_message: &VerifiedPessimisticMessage,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

as before, how will i have VerifiedPessimisticMessage in case the sender didn't send me anything

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(this is one of the reasons i prefer to skip pushed echos and rely on polling after seeing the cert and the hashes)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So if you don't get anything, you should wait for the cert of Votes and use the hash from there?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

yes, otherwise we need to pass with the echo the first cert + a signature by the dealer (otherwise how can the receiver indeed verify them?)

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