-
Notifications
You must be signed in to change notification settings - Fork 840
Simplex QuorumCertificate and BLS aggregator #4091
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
Merged
Merged
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
76629dd
Add simplex QC and BLS Aggregator + tests
samliok de08c8c
merge conflicts
samliok f4fd1ee
consistent error
samliok 1b4f583
rename
samliok b4f082c
use pointers
samliok 1df2a5b
use codec.marshalling
samliok e092d2e
remove register type
samliok 041faeb
inline check
samliok d9c800e
Merge branch 'master' into simplex-quorum
samliok 63eb798
use canoto
samliok 4a423ba
duplicate signers, fixed byte array, dont copy bls verifier
samliok 2a037aa
lint
samliok 5ad5888
ensure converting to id doesn't panic
samliok b2811dc
Merge branch 'master' into simplex-quorum
samliok a4607d3
add bitset, use set and nits
samliok bff95df
update invalid bytes test
samliok 9081e23
table test for aggregate
samliok c184eee
nit
samliok 976b2d8
Merge branch 'master' into simplex-quorum
samliok d932d79
nit
samliok 2105fb6
nit
samliok 0094c69
order of signers
samliok 9b1c075
move test to table test
samliok 996dd0c
nit
samliok 901cb33
add test for filter nodes, and nits
samliok 18539eb
Merge branch 'master' into simplex-quorum
samliok c877428
nit
samliok File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. | ||
| // See the file LICENSE for licensing terms. | ||
|
|
||
| package simplex | ||
|
|
||
| import ( | ||
| "encoding/asn1" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/ava-labs/simplex" | ||
|
|
||
| "github.com/ava-labs/avalanchego/ids" | ||
| "github.com/ava-labs/avalanchego/utils/crypto/bls" | ||
| ) | ||
|
|
||
| var ( | ||
| _ simplex.QuorumCertificate = (*QC)(nil) | ||
| _ simplex.QCDeserializer = QCDeserializer{} | ||
| _ simplex.SignatureAggregator = (*SignatureAggregator)(nil) | ||
|
|
||
| // QC errors | ||
| errFailedToParseQC = errors.New("failed to parse quorum certificate") | ||
| errUnexpectedSigners = errors.New("unexpected number of signers in quorum certificate") | ||
| errSignatureAggregation = errors.New("signature aggregation failed") | ||
yacovm marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| errEncodingMessageToSign = errors.New("failed to encode message to sign") | ||
| ) | ||
|
|
||
| // QC represents a quorum certificate in the Simplex consensus protocol. | ||
| type QC struct { | ||
| verifier BLSVerifier | ||
samliok marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| sig bls.Signature | ||
| signers []simplex.NodeID | ||
| } | ||
|
|
||
| // Signers returns the list of signers for the quorum certificate. | ||
| func (qc *QC) Signers() []simplex.NodeID { | ||
| return qc.signers | ||
| } | ||
|
|
||
| // Verify checks if the quorum certificate is valid by verifying the aggregated signature against the signers' public keys. | ||
| func (qc *QC) Verify(msg []byte) error { | ||
| pks := make([]*bls.PublicKey, 0, len(qc.signers)) | ||
| if len(qc.signers) != simplex.Quorum(len(qc.verifier.nodeID2PK)) { | ||
samliok marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return fmt.Errorf("%w: expected %d signers but got %d", errUnexpectedSigners, simplex.Quorum(len(qc.verifier.nodeID2PK)), len(qc.signers)) | ||
| } | ||
|
|
||
| // ensure all signers are in the membership set | ||
| for _, signer := range qc.signers { | ||
| pk, exists := qc.verifier.nodeID2PK[ids.NodeID(signer)] | ||
| if !exists { | ||
| return fmt.Errorf("%w: %x", errSignerNotFound, signer) | ||
| } | ||
|
|
||
| pks = append(pks, pk) | ||
| } | ||
|
|
||
| // aggregate the public keys | ||
| aggPK, err := bls.AggregatePublicKeys(pks) | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %w", errSignatureAggregation, err) | ||
| } | ||
|
|
||
| message2Verify, err := encodeMessageToSign(msg, qc.verifier.chainID, qc.verifier.networkID) | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %w", errEncodingMessageToSign, err) | ||
| } | ||
|
|
||
| if !bls.Verify(aggPK, &qc.sig, message2Verify) { | ||
| return errSignatureVerificationFailed | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // asn1QC is the ASN.1 structure for the quorum certificate. | ||
samliok marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // It contains the signers' public keys and the aggregated signature. | ||
| // The signers are represented as byte slices of their IDs. | ||
| type asn1QC struct { | ||
| Signers [][]byte | ||
| Signature []byte | ||
| } | ||
|
|
||
| func (qc *QC) MarshalASN1() ([]byte, error) { | ||
| sigBytes := bls.SignatureToBytes(&qc.sig) | ||
|
|
||
| signersBytes := make([][]byte, len(qc.signers)) | ||
| for i, signer := range qc.signers { | ||
| s := signer // avoid aliasing | ||
| signersBytes[i] = s[:] | ||
| } | ||
| asn1Data := asn1QC{ | ||
| Signers: signersBytes, | ||
| Signature: sigBytes, | ||
| } | ||
| return asn1.Marshal(asn1Data) | ||
| } | ||
|
|
||
| func (qc *QC) UnmarshalASN1(data []byte) error { | ||
| var decoded asn1QC | ||
| _, err := asn1.Unmarshal(data, &decoded) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| qc.signers = make([]simplex.NodeID, len(decoded.Signers)) | ||
| for i, signerBytes := range decoded.Signers { | ||
| if len(signerBytes) != ids.ShortIDLen { // TODO: so long as simplex is in a separate repo, we should decouple these ids as much as possible | ||
| return errors.New("invalid signer length") | ||
| } | ||
| qc.signers[i] = simplex.NodeID(signerBytes) | ||
| } | ||
| sig, err := bls.SignatureFromBytes(decoded.Signature) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| qc.sig = *sig | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // Bytes serializes the quorum certificate into bytes. | ||
| func (qc *QC) Bytes() []byte { | ||
| bytes, err := qc.MarshalASN1() | ||
| if err != nil { | ||
| panic(fmt.Errorf("failed to marshal QC: %w", err)) | ||
| } | ||
| return bytes | ||
| } | ||
|
|
||
| type QCDeserializer BLSVerifier | ||
|
|
||
| // DeserializeQuorumCertificate deserializes a quorum certificate from bytes. | ||
| func (d QCDeserializer) DeserializeQuorumCertificate(bytes []byte) (simplex.QuorumCertificate, error) { | ||
| var qc QC | ||
| if err := qc.UnmarshalASN1(bytes); err != nil { | ||
| return nil, fmt.Errorf("%w: %w", errFailedToParseQC, err) | ||
| } | ||
| qc.verifier = BLSVerifier(d) | ||
|
|
||
| return &qc, nil | ||
| } | ||
|
|
||
| // SignatureAggregator aggregates signatures into a quorum certificate. | ||
| type SignatureAggregator BLSVerifier | ||
|
|
||
| // Aggregate aggregates the provided signatures into a quorum certificate. | ||
| // It requires at least a quorum of signatures to succeed. | ||
| // If any signature is from a signer not in the membership set, it returns an error. | ||
| func (a SignatureAggregator) Aggregate(signatures []simplex.Signature) (simplex.QuorumCertificate, error) { | ||
| quorumSize := simplex.Quorum(len(a.nodeID2PK)) | ||
| if len(signatures) < quorumSize { | ||
| return nil, fmt.Errorf("%w: expected %d signatures but got %d", errUnexpectedSigners, quorumSize, len(signatures)) | ||
| } | ||
|
|
||
| signatures = signatures[:quorumSize] | ||
|
|
||
| signers := make([]simplex.NodeID, 0, quorumSize) | ||
| sigs := make([]*bls.Signature, 0, quorumSize) | ||
| for _, signature := range signatures { | ||
| signer := signature.Signer | ||
| _, exists := a.nodeID2PK[ids.NodeID(signer)] | ||
| if !exists { | ||
| return nil, fmt.Errorf("%w: %x", errSignerNotFound, signer) | ||
| } | ||
| signers = append(signers, signer) | ||
| sig, err := bls.SignatureFromBytes(signature.Value) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w: %w", errFailedToParseSignature, err) | ||
| } | ||
| sigs = append(sigs, sig) | ||
| } | ||
|
|
||
| aggregatedSig, err := bls.AggregateSignatures(sigs) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("%w: %w", errSignatureAggregation, err) | ||
| } | ||
|
|
||
| return &QC{ | ||
| verifier: BLSVerifier(a), | ||
| signers: signers, | ||
| sig: *aggregatedSig, | ||
| }, nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.