Skip to content

Add handling for Auxiliary info#402

Merged
yacovm merged 1 commit into
mainfrom
reconfig-6
Jun 19, 2026
Merged

Add handling for Auxiliary info#402
yacovm merged 1 commit into
mainfrom
reconfig-6

Conversation

@yacovm

@yacovm yacovm commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Introduce an AuxiliaryInfoApp interface abstraction that lets an application
piggyback application-specific data on Simplex epoch changes (e.g.
threshold distributed key generation), and gate the epoch transition on
that data being final. Final doesn't mean finalized as in Simplex,
but rather that the data is "good enough" to be used for the next epoch.

Metadata & encoding:

  • Add AuxiliaryInfo (Info, PrevAuxInfoSeq, ApplicationID) to block
    metadata and an AppID type, with Canoto (de)serialization.
  • Each block's PrevAuxInfoSeq back-points to the most recent block with a
    non-empty Info, skipping empty-Info blocks. collectAuxiliaryInfo walks
    these pointers to rebuild the epoch's aux info history.

The digest of the last AuxInfo in a final history is signed by the node,
rather than the entire history of non-empty Aux Info.
This is done for flexibility, as some applications like publicly verifiable DKG
have commutative histories.

State machine:

  • AuxiliaryInfoApp drives the flow: GenerateAuxInfo contributes to the
    history until IsFinalAuxInfoHistory reports it ready; only then are
    next-epoch approvals collected. IsLegalAuxInfoAppend validates a
    proposed append on the verify path. Each method also receives the
    next epoch's validator set (NodeBLSMappings).
  • The builder carries forward / extends aux info, and the verifier
    reconstructs the expected AuxiliaryInfo (ApplicationID, PrevAuxInfoSeq)
    and enforces it via the block-digest comparison.

Approvals:

  • Approvals now bind to the auxiliary info: the signed payload is
    (NextPChainReferenceHeight, auxInfoDigest), where auxInfoDigest is the
    sha256 of the last aux info element in the history.
  • The ApprovalStore is now keyed by (NodeID, PChainHeight, AuxInfoSeqDigest)
    so approvals for different digests coexist, and sanitizeApprovals only
    aggregates approvals matching the candidate height and digest.

Tests:

  • Verify approvals with real ECDSA signatures over the signed payload
    (the test signature verifier now actually checks signatures and
    (NextPChainReferenceHeight, auxInfoDigest), where auxInfoDigest is the
    sha256 of the final aux info history. assembleApprovalToBeSigned
    replaces the previous height-only payload.
    The switch to ECDSA is because this way we make sure we really sign
    the correct digest and not an empty one because of a bug.
  • The ApprovalStore is keyed by (NodeID, PChainHeight, AuxInfoSeqDigest)
    so approvals for different digests coexist, and sanitizeApprovals only
    aggregates approvals matching the candidate height and digest.

@yacovm yacovm marked this pull request as draft June 9, 2026 21:28
@yacovm yacovm force-pushed the reconfig-6 branch 7 times, most recently from 98a5139 to cdff0f1 Compare June 11, 2026 17:50
@yacovm yacovm marked this pull request as ready for review June 11, 2026 17:51
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/approvals.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go
Comment thread msm/msm.go
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go
parentAuxInfo := parentBlock.Metadata.AuxiliaryInfo
if parentAuxInfo != nil {
auxInfo = &AuxiliaryInfo{
VersionID: parentAuxInfo.VersionID,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

so here we use the version ID from the parent, but in collectAuxillaryInfo we check against the oldest version id.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do we have any tests for upgrading the version in the middle of an epoch, or is this not testable until we implement AuxiliaryInfoGenVerifier

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

also if the struct implementing the interface already has the DefaultVersionID function, why do we need to call it, then pass it back to the other functions?

we are basically doing

version := GetDefaultVersion()
version = oldestVersion ? oldestVersion : version
IsLegalAuxInfoAppend(version) 

It seems like we will need to associate the version & epoch in the struct implementing the interface anyways, so why pass it in everywhere?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

collectAuxiliaryInfo checks if we have auxiliary information in this epoch.

it returns the default version ID if we don't have auxiliary information in the epoch.
In case we end up upgrading the auxiliary information app, we'll also change the default version ID (.DefaultVersionID()) and then it will return that.

The idea is to fallback to the older version ID if we already have a block in the epoch with a version ID.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do we have any tests for upgrading the version in the middle of an epoch, or is this not testable until we implement AuxiliaryInfoGenVerifier

makes sense, I still think we should have a test where the version ID varies between blocks in an epoch if possible

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment thread msm/msm.go Outdated
Comment thread msm/msm.go
Comment thread msm/msm.go
Comment thread msm/approvals.go Outdated
Comment thread msm/msm.go Outdated
Comment thread msm/msm.go
Comment thread msm/msm.go
Comment thread msm/msm.go Outdated
Comment thread msm/fuzz_test.go
Comment thread msm/util_test.go
Comment thread msm/msm.go
parentAuxInfo := parentBlock.Metadata.AuxiliaryInfo
if parentAuxInfo != nil {
auxInfo = &AuxiliaryInfo{
VersionID: parentAuxInfo.VersionID,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do we have any tests for upgrading the version in the middle of an epoch, or is this not testable until we implement AuxiliaryInfoGenVerifier

makes sense, I still think we should have a test where the version ID varies between blocks in an epoch if possible

Introduce an AuxiliaryInfoApp interface abstraction that lets an application
piggyback application-specific data on Simplex epoch changes (e.g.
threshold distributed key generation), and gate the epoch transition on
that data being final. Final doesn't mean finalized as in Simplex,
but rather that the data is "good enough" to be used for the next epoch.

  Metadata & encoding:
  - Add AuxiliaryInfo (Info, PrevAuxInfoSeq, ApplicationID) to block
    metadata and an AppID type, with Canoto (de)serialization.
  - Each block's PrevAuxInfoSeq back-points to the most recent block with a
    non-empty Info, skipping empty-Info blocks. collectAuxiliaryInfo walks
    these pointers to rebuild the epoch's aux info history.

  The digest of the last AuxInfo in a final history is signed by the node,
  rather than the entire history of non-empty Aux Info.
  This is done for flexibility, as some applications like publicly verifiable DKG
  have commutative histories.

  State machine:
  - AuxiliaryInfoApp drives the flow: GenerateAuxInfo contributes to the
    history until IsFinalAuxInfoHistory reports it ready; only then are
    next-epoch approvals collected. IsLegalAuxInfoAppend validates a
    proposed append on the verify path. Each method also receives the
    next epoch's validator set (NodeBLSMappings).
  - The builder carries forward / extends aux info, and the verifier
    reconstructs the expected AuxiliaryInfo (ApplicationID, PrevAuxInfoSeq)
    and enforces it via the block-digest comparison.

  Approvals:
  - Approvals now bind to the auxiliary info: the signed payload is
    (NextPChainReferenceHeight, auxInfoDigest), where auxInfoDigest is the
    sha256 of the last aux info element in the history.
  - The ApprovalStore is now keyed by (NodeID, PChainHeight, AuxInfoSeqDigest)
    so approvals for different digests coexist, and sanitizeApprovals only
    aggregates approvals matching the candidate height and digest.

  Tests:
  - Verify approvals with real ECDSA signatures over the signed payload
    (the test signature verifier now actually checks signatures and
    (NextPChainReferenceHeight, auxInfoDigest), where auxInfoDigest is the
    sha256 of the final aux info history. assembleApprovalToBeSigned
    replaces the previous height-only payload.
    The switch to ECDSA is because this way we make sure we really sign
    the correct digest and not an empty one because of a bug.
  - The ApprovalStore is keyed by (NodeID, PChainHeight, AuxInfoSeqDigest)
    so approvals for different digests coexist, and sanitizeApprovals only
    aggregates approvals matching the candidate height and digest.

Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
@yacovm yacovm merged commit d7e5210 into main Jun 19, 2026
6 of 7 checks passed
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.

2 participants