From 94c17a344f8e4806ca9497627fbe333bf8ca3f3b Mon Sep 17 00:00:00 2001 From: Robert Raynor <35671663+mooselumph@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:29:44 +0000 Subject: [PATCH 1/4] Breakout encoding functions --- encoding/encoding.go | 4 + encoding/kzg/prover/parametrized_prover.go | 124 ++++++++++++++++----- encoding/kzg/prover/prover.go | 62 +++++++++++ encoding/kzg/prover/prover_cpu.go | 9 +- encoding/mock/encoder.go | 12 ++ 5 files changed, 181 insertions(+), 30 deletions(-) diff --git a/encoding/encoding.go b/encoding/encoding.go index 5acd0539a..3a050bfa8 100644 --- a/encoding/encoding.go +++ b/encoding/encoding.go @@ -11,6 +11,10 @@ type Prover interface { // for any number M such that M*params.ChunkLength > BlobCommitments.Length, then any set of M chunks will be sufficient to // reconstruct the blob. EncodeAndProve(data []byte, params EncodingParams) (BlobCommitments, []*Frame, error) + + GetCommitments(data []byte) (BlobCommitments, error) + + GetFrames(data []byte, params EncodingParams) ([]*Frame, error) } type Verifier interface { diff --git a/encoding/kzg/prover/parametrized_prover.go b/encoding/kzg/prover/parametrized_prover.go index af0486384..316b513bc 100644 --- a/encoding/kzg/prover/parametrized_prover.go +++ b/encoding/kzg/prover/parametrized_prover.go @@ -67,25 +67,58 @@ func (g *ParametrizedProver) Encode(inputFr []fr.Element) (*bn254.G1Affine, *bn2 encodeStart := time.Now() - rsChan := make(chan RsEncodeResult, 1) - lengthCommitmentChan := make(chan LengthCommitmentResult, 1) - lengthProofChan := make(chan LengthProofResult, 1) - commitmentChan := make(chan CommitmentResult, 1) - proofChan := make(chan ProofsResult, 1) + type commitments struct { + commitment *bn254.G1Affine + lengthCommitment *bn254.G2Affine + lengthProof *bn254.G2Affine + Error error + } + + commitmentsChan := make(chan commitments, 1) // inputFr is untouched // compute chunks go func() { - start := time.Now() - frames, indices, err := g.Encoder.Encode(inputFr) - rsChan <- RsEncodeResult{ - Frames: frames, - Indices: indices, - Err: err, - Duration: time.Since(start), + commitment, lengthCommitment, lengthProof, err := g.GetCommitments(inputFr) + + commitmentsChan <- commitments{ + commitment: commitment, + lengthCommitment: lengthCommitment, + lengthProof: lengthProof, + Error: err, } }() + frames, indices, err := g.GetFrames(inputFr) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + commitmentResult := <-commitmentsChan + if commitmentResult.Error != nil { + return nil, nil, nil, nil, nil, commitmentResult.Error + } + + totalProcessingTime := time.Since(encodeStart) + + if g.Verbose { + log.Printf("Total encoding took %v\n", totalProcessingTime) + } + return commitmentResult.commitment, commitmentResult.lengthCommitment, commitmentResult.lengthProof, frames, indices, nil +} + +func (g *ParametrizedProver) GetCommitments(inputFr []fr.Element) (*bn254.G1Affine, *bn254.G2Affine, *bn254.G2Affine, error) { + + if len(inputFr) > int(g.KzgConfig.SRSNumberToLoad) { + return nil, nil, nil, fmt.Errorf("poly Coeff length %v is greater than Loaded SRS points %v", len(inputFr), int(g.KzgConfig.SRSNumberToLoad)) + } + + encodeStart := time.Now() + + lengthCommitmentChan := make(chan LengthCommitmentResult, 1) + lengthProofChan := make(chan LengthProofResult, 1) + commitmentChan := make(chan CommitmentResult, 1) + // compute commit for the full poly go func() { start := time.Now() @@ -117,6 +150,52 @@ func (g *ParametrizedProver) Encode(inputFr []fr.Element) (*bn254.G1Affine, *bn2 } }() + lengthProofResult := <-lengthProofChan + lengthCommitmentResult := <-lengthCommitmentChan + commitmentResult := <-commitmentChan + + if lengthProofResult.Err != nil || lengthCommitmentResult.Err != nil || + commitmentResult.Err != nil { + return nil, nil, nil, multierror.Append(lengthProofResult.Err, lengthCommitmentResult.Err, commitmentResult.Err) + } + totalProcessingTime := time.Since(encodeStart) + + log.Printf("\n\t\tCommiting %-v\n\t\tLengthCommit %-v\n\t\tlengthProof %-v\n\t\tMetaInfo. order %-v shift %v\n", + commitmentResult.Duration, + lengthCommitmentResult.Duration, + lengthProofResult.Duration, + g.SRSOrder, + g.SRSOrder-uint64(len(inputFr)), + ) + + if g.Verbose { + log.Printf("Total encoding took %v\n", totalProcessingTime) + } + return &commitmentResult.Commitment, &lengthCommitmentResult.LengthCommitment, &lengthProofResult.LengthProof, nil +} + +func (g *ParametrizedProver) GetFrames(inputFr []fr.Element) ([]encoding.Frame, []uint32, error) { + + if len(inputFr) > int(g.KzgConfig.SRSNumberToLoad) { + return nil, nil, fmt.Errorf("poly Coeff length %v is greater than Loaded SRS points %v", len(inputFr), int(g.KzgConfig.SRSNumberToLoad)) + } + + proofChan := make(chan ProofsResult, 1) + rsChan := make(chan RsEncodeResult, 1) + + // inputFr is untouched + // compute chunks + go func() { + start := time.Now() + frames, indices, err := g.Encoder.Encode(inputFr) + rsChan <- RsEncodeResult{ + Frames: frames, + Indices: indices, + Err: err, + Duration: time.Since(start), + } + }() + go func() { start := time.Now() // compute proofs @@ -138,24 +217,15 @@ func (g *ParametrizedProver) Encode(inputFr []fr.Element) (*bn254.G1Affine, *bn2 } }() - lengthProofResult := <-lengthProofChan - lengthCommitmentResult := <-lengthCommitmentChan - commitmentResult := <-commitmentChan rsResult := <-rsChan proofsResult := <-proofChan - if lengthProofResult.Err != nil || lengthCommitmentResult.Err != nil || - commitmentResult.Err != nil || rsResult.Err != nil || - proofsResult.Err != nil { - return nil, nil, nil, nil, nil, multierror.Append(lengthProofResult.Err, lengthCommitmentResult.Err, commitmentResult.Err, rsResult.Err, proofsResult.Err) + if rsResult.Err != nil || proofsResult.Err != nil { + return nil, nil, multierror.Append(rsResult.Err, proofsResult.Err) } - totalProcessingTime := time.Since(encodeStart) - log.Printf("\n\t\tRS encode %-v\n\t\tCommiting %-v\n\t\tLengthCommit %-v\n\t\tlengthProof %-v\n\t\tmultiProof %-v\n\t\tMetaInfo. order %-v shift %v\n", + log.Printf("\n\t\tRS encode %-v\n\t\tmultiProof %-v\n\t\tMetaInfo. order %-v shift %v\n", rsResult.Duration, - commitmentResult.Duration, - lengthCommitmentResult.Duration, - lengthProofResult.Duration, proofsResult.Duration, g.SRSOrder, g.SRSOrder-uint64(len(inputFr)), @@ -170,8 +240,6 @@ func (g *ParametrizedProver) Encode(inputFr []fr.Element) (*bn254.G1Affine, *bn2 } } - if g.Verbose { - log.Printf("Total encoding took %v\n", totalProcessingTime) - } - return &commitmentResult.Commitment, &lengthCommitmentResult.LengthCommitment, &lengthProofResult.LengthProof, kzgFrames, rsResult.Indices, nil + return kzgFrames, rsResult.Indices, nil + } diff --git a/encoding/kzg/prover/prover.go b/encoding/kzg/prover/prover.go index 0c5198311..64786a16e 100644 --- a/encoding/kzg/prover/prover.go +++ b/encoding/kzg/prover/prover.go @@ -171,6 +171,68 @@ func (e *Prover) EncodeAndProve(data []byte, params encoding.EncodingParams) (en return commitments, chunks, nil } +func (e *Prover) GetFrames(data []byte, params encoding.EncodingParams) ([]*encoding.Frame, error) { + + symbols, err := rs.ToFrArray(data) + if err != nil { + return nil, err + } + + enc, err := e.GetKzgEncoder(params) + if err != nil { + return nil, err + } + + kzgFrames, _, err := enc.GetFrames(symbols) + if err != nil { + return nil, err + } + + chunks := make([]*encoding.Frame, len(kzgFrames)) + for ind, frame := range kzgFrames { + + chunks[ind] = &encoding.Frame{ + Coeffs: frame.Coeffs, + Proof: frame.Proof, + } + } + + return chunks, nil +} + +func (e *Prover) GetCommitments(data []byte) (encoding.BlobCommitments, error) { + + symbols, err := rs.ToFrArray(data) + if err != nil { + return encoding.BlobCommitments{}, err + } + + params := encoding.EncodingParams{ + NumChunks: 2, + ChunkLength: 2, + } + + enc, err := e.GetKzgEncoder(params) + if err != nil { + return encoding.BlobCommitments{}, err + } + + commit, lengthCommit, lengthProof, err := enc.GetCommitments(symbols) + if err != nil { + return encoding.BlobCommitments{}, err + } + + length := uint(len(symbols)) + commitments := encoding.BlobCommitments{ + Commitment: (*encoding.G1Commitment)(commit), + LengthCommitment: (*encoding.G2Commitment)(lengthCommit), + LengthProof: (*encoding.G2Commitment)(lengthProof), + Length: length, + } + + return commitments, nil +} + func (g *Prover) GetKzgEncoder(params encoding.EncodingParams) (*ParametrizedProver, error) { g.mu.Lock() defer g.mu.Unlock() diff --git a/encoding/kzg/prover/prover_cpu.go b/encoding/kzg/prover/prover_cpu.go index 62f9989f7..3ac9298a2 100644 --- a/encoding/kzg/prover/prover_cpu.go +++ b/encoding/kzg/prover/prover_cpu.go @@ -21,8 +21,13 @@ import ( func (g *Prover) newProver(params encoding.EncodingParams) (*ParametrizedProver, error) { - // Check that the parameters are valid with respect to the SRS. - if params.ChunkLength*params.NumChunks >= g.SRSOrder { + // Check that the parameters are valid with respect to the SRS. The precomputed terms of the amortized KZG + // prover use up to order params.ChunkLen*params.NumChunks-1 for the SRS, so we must have + // params.ChunkLen*params.NumChunks-1 <= g.SRSOrder. The condition below could technically + // be relaxed to params.ChunkLen*params.NumChunks > g.SRSOrder+1, but because all of the paramters are + // powers of 2, the stricter condition is equivalent. + + if params.ChunkLength*params.NumChunks > g.SRSOrder { return nil, fmt.Errorf("the supplied encoding parameters are not valid with respect to the SRS. ChunkLength: %d, NumChunks: %d, SRSOrder: %d", params.ChunkLength, params.NumChunks, g.SRSOrder) } diff --git a/encoding/mock/encoder.go b/encoding/mock/encoder.go index 3f643fb74..d7ca70823 100644 --- a/encoding/mock/encoder.go +++ b/encoding/mock/encoder.go @@ -23,6 +23,18 @@ func (e *MockEncoder) EncodeAndProve(data []byte, params encoding.EncodingParams return args.Get(0).(encoding.BlobCommitments), args.Get(1).([]*encoding.Frame), args.Error(2) } +func (e *MockEncoder) GetCommitments(data []byte) (encoding.BlobCommitments, error) { + args := e.Called(data) + time.Sleep(e.Delay) + return args.Get(0).(encoding.BlobCommitments), args.Error(1) +} + +func (e *MockEncoder) GetFrames(data []byte, params encoding.EncodingParams) ([]*encoding.Frame, error) { + args := e.Called(data, params) + time.Sleep(e.Delay) + return args.Get(0).([]*encoding.Frame), args.Error(1) +} + func (e *MockEncoder) VerifyFrames(chunks []*encoding.Frame, indices []encoding.ChunkNumber, commitments encoding.BlobCommitments, params encoding.EncodingParams) error { args := e.Called(chunks, indices, commitments, params) time.Sleep(e.Delay) From ded07b12df9ae3b64606ae0ab850df788971bcd8 Mon Sep 17 00:00:00 2001 From: Robert Raynor <35671663+mooselumph@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:30:00 +0000 Subject: [PATCH 2/4] Add core/v2 --- core/v2/aggregation.go | 366 ++++++++++++++++++++++++++++++++++++ core/v2/aggregation_test.go | 301 +++++++++++++++++++++++++++++ core/v2/assignment.go | 124 ++++++++++++ core/v2/assignment_test.go | 220 ++++++++++++++++++++++ core/v2/core_test.go | 266 ++++++++++++++++++++++++++ core/v2/errors.go | 7 + core/v2/types.go | 89 +++++++++ core/v2/utils.go | 26 +++ core/v2/validator.go | 199 ++++++++++++++++++++ 9 files changed, 1598 insertions(+) create mode 100644 core/v2/aggregation.go create mode 100644 core/v2/aggregation_test.go create mode 100644 core/v2/assignment.go create mode 100644 core/v2/assignment_test.go create mode 100644 core/v2/core_test.go create mode 100644 core/v2/errors.go create mode 100644 core/v2/types.go create mode 100644 core/v2/utils.go create mode 100644 core/v2/validator.go diff --git a/core/v2/aggregation.go b/core/v2/aggregation.go new file mode 100644 index 000000000..6a17e95f3 --- /dev/null +++ b/core/v2/aggregation.go @@ -0,0 +1,366 @@ +package corev2 + +import ( + "bytes" + "context" + "encoding/hex" + "errors" + "fmt" + "math/big" + "slices" + "sort" + + "github.com/Layr-Labs/eigenda/chainio" + "github.com/Layr-Labs/eigenda/crypto/ecc/bn254" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + lru "github.com/hashicorp/golang-lru/v2" +) + +const percentMultiplier = 100 + +const maxNumOperatorAddresses = 300 + +var ( + ErrPubKeysNotEqual = errors.New("public keys are not equal") + ErrInsufficientEthSigs = errors.New("insufficient eth signatures") + ErrAggPubKeyNotValid = errors.New("aggregated public key is not valid") + ErrAggSigNotValid = errors.New("aggregated signature is not valid") +) + +type SigningMessage struct { + Signature *bn254.Signature + Operator OperatorID + BatchHeaderHash [32]byte + // Undefined if this value <= 0. + AttestationLatencyMs float64 + Err error +} + +// QuorumResult contains the quorum ID and the amount signed for the quorum +type QuorumResult struct { + QuorumID QuorumID + // PercentSigned is percentage of the total stake for the quorum that signed for a particular batch. + PercentSigned uint8 +} + +// QuorumAttestation contains the results of aggregating signatures from a set of operators by quorums +// It also returns map of all signers across all quorums +type QuorumAttestation struct { + // QuorumAggPubKeys contains the aggregated public keys for all of the operators each quorum, + // including those that did not sign + QuorumAggPubKey map[QuorumID]*bn254.G1Point + // SignersAggPubKey is the aggregated public key for all of the operators that signed the message by each quorum + SignersAggPubKey map[QuorumID]*bn254.G2Point + // AggSignature is the aggregated signature for all of the operators that signed the message for each quorum, mirroring the + // SignersAggPubKey. + AggSignature map[QuorumID]*bn254.Signature + // QuorumResults contains the quorum ID and the amount signed for each quorum + QuorumResults map[QuorumID]*QuorumResult + // SignerMap contains the operator IDs that signed the message + SignerMap map[OperatorID]bool +} + +// SignatureAggregation contains the results of aggregating signatures from a set of operators across multiple quorums +type SignatureAggregation struct { + // NonSigners contains the public keys of the operators that did not sign the message + NonSigners []*bn254.G1Point + // QuorumAggPubKeys contains the aggregated public keys for all of the operators each quorum, + // Including those that did not sign + QuorumAggPubKeys map[QuorumID]*bn254.G1Point + // AggPubKey is the aggregated public key for all of the operators that signed the message, + // further aggregated across the quorums; operators signing for multiple quorums will be included in + // the aggregation multiple times + AggPubKey *bn254.G2Point + // AggSignature is the aggregated signature for all of the operators that signed the message, mirroring the + // AggPubKey. + AggSignature *bn254.Signature + // QuorumResults contains the quorum ID and the amount signed for each quorum + QuorumResults map[QuorumID]*QuorumResult +} + +// SignatureAggregator is an interface for aggregating the signatures returned by DA nodes so that they can be verified by the DA contract +type SignatureAggregator interface { + // ReceiveSignatures blocks until it receives a response for each operator in the operator state via messageChan, and then returns the attestation result by quorum. + ReceiveSignatures(ctx context.Context, state *chainio.IndexedOperatorState, message [32]byte, messageChan chan SigningMessage) (*QuorumAttestation, error) + // AggregateSignatures takes attestation result by quorum and aggregates the signatures across them. + // If the aggregated signature is invalid, an error is returned. + AggregateSignatures(ctx context.Context, ics chainio.IndexedChainState, referenceBlockNumber uint, quorumAttestation *QuorumAttestation, quorumIDs []QuorumID) (*SignatureAggregation, error) +} + +type StdSignatureAggregator struct { + Logger logging.Logger + ChainReader chainio.Reader + // OperatorAddresses contains the ethereum addresses of the operators corresponding to their operator IDs + OperatorAddresses *lru.Cache[OperatorID, gethcommon.Address] +} + +func NewStdSignatureAggregator(logger logging.Logger, reader chainio.Reader) (*StdSignatureAggregator, error) { + operatorAddrs, err := lru.New[OperatorID, gethcommon.Address](maxNumOperatorAddresses) + if err != nil { + return nil, err + } + + return &StdSignatureAggregator{ + Logger: logger.With("component", "SignatureAggregator"), + ChainReader: reader, + OperatorAddresses: operatorAddrs, + }, nil +} + +var _ SignatureAggregator = (*StdSignatureAggregator)(nil) + +func (a *StdSignatureAggregator) ReceiveSignatures(ctx context.Context, state *chainio.IndexedOperatorState, message [32]byte, messageChan chan SigningMessage) (*QuorumAttestation, error) { + quorumIDs := make([]QuorumID, 0, len(state.AggKeys)) + for quorumID := range state.Operators { + quorumIDs = append(quorumIDs, quorumID) + } + slices.Sort(quorumIDs) + + if len(quorumIDs) == 0 { + return nil, errors.New("the number of quorums must be greater than zero") + } + + // Ensure all quorums are found in state + for _, id := range quorumIDs { + _, found := state.Operators[id] + if !found { + return nil, errors.New("quorum not found") + } + } + + stakeSigned := make(map[QuorumID]*big.Int, len(quorumIDs)) + for _, quorumID := range quorumIDs { + stakeSigned[quorumID] = big.NewInt(0) + } + aggSigs := make(map[QuorumID]*bn254.Signature, len(quorumIDs)) + aggPubKeys := make(map[QuorumID]*bn254.G2Point, len(quorumIDs)) + signerMap := make(map[OperatorID]bool) + + // Aggregate Signatures + numOperators := len(state.IndexedOperators) + + for numReply := 0; numReply < numOperators; numReply++ { + var err error + r := <-messageChan + operatorIDHex := chainio.GetOperatorHex(r.Operator) + operatorAddr, ok := a.OperatorAddresses.Get(r.Operator) + if !ok && a.ChainReader != nil { + operatorAddr, err = a.ChainReader.OperatorIDToAddress(ctx, r.Operator) + if err != nil { + a.Logger.Error("failed to get operator address from registry", "operatorID", operatorIDHex) + operatorAddr = gethcommon.Address{} + } else { + a.OperatorAddresses.Add(r.Operator, operatorAddr) + } + } else if !ok { + operatorAddr = gethcommon.Address{} + } + + socket := "" + if op, ok := state.IndexedOperators[r.Operator]; ok { + socket = op.Socket + } + batchHeaderHashHex := hex.EncodeToString(r.BatchHeaderHash[:]) + if r.Err != nil { + a.Logger.Warn("error returned from messageChan", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket, "batchHeaderHash", batchHeaderHashHex, "attestationLatencyMs", r.AttestationLatencyMs, "err", r.Err) + continue + } + + op, found := state.IndexedOperators[r.Operator] + if !found { + a.Logger.Error("Operator not found in state", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket) + continue + } + + // Verify Signature + sig := r.Signature + ok = sig.Verify(op.PubkeyG2, message) + if !ok { + a.Logger.Error("signature is not valid", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket, "pubkey", hexutil.Encode(op.PubkeyG2.Serialize())) + continue + } + + operatorQuorums := make([]uint8, 0, len(quorumIDs)) + for _, quorumID := range quorumIDs { + // Get stake amounts for operator + ops := state.Operators[quorumID] + opInfo, ok := ops[r.Operator] + // If operator is not in quorum, skip + if !ok { + continue + } + operatorQuorums = append(operatorQuorums, quorumID) + + signerMap[r.Operator] = true + + // Add to stake signed + stakeSigned[quorumID].Add(stakeSigned[quorumID], opInfo.Stake) + + // Add to agg signature + if aggSigs[quorumID] == nil { + aggSigs[quorumID] = &bn254.Signature{sig.Clone()} + aggPubKeys[quorumID] = op.PubkeyG2.Clone() + } else { + aggSigs[quorumID].Add(sig.G1Point) + aggPubKeys[quorumID].Add(op.PubkeyG2) + } + } + a.Logger.Info("received signature from operator", "operatorID", operatorIDHex, "operatorAddress", operatorAddr, "socket", socket, "quorumIDs", fmt.Sprint(operatorQuorums), "batchHeaderHash", batchHeaderHashHex, "attestationLatencyMs", r.AttestationLatencyMs) + } + + // Aggregate Non signer Pubkey Id + nonSignerKeys := make([]*bn254.G1Point, 0) + nonSignerOperatorIds := make([]OperatorID, 0) + + for id, op := range state.IndexedOperators { + _, found := signerMap[id] + if !found { + nonSignerKeys = append(nonSignerKeys, op.PubkeyG1) + nonSignerOperatorIds = append(nonSignerOperatorIds, id) + } + } + + quorumAggPubKeys := make(map[QuorumID]*bn254.G1Point, len(quorumIDs)) + + // Validate the amount signed and aggregate signatures for each quorum + quorumResults := make(map[QuorumID]*QuorumResult) + + for _, quorumID := range quorumIDs { + // Check that quorum has sufficient stake + percent := GetSignedPercentage(state.OperatorState, quorumID, stakeSigned[quorumID]) + quorumResults[quorumID] = &QuorumResult{ + QuorumID: quorumID, + PercentSigned: percent, + } + + if percent == 0 { + a.Logger.Warn("no stake signed for quorum", "quorumID", quorumID) + continue + } + + // Verify that the aggregated public key for the quorum matches the on-chain quorum aggregate public key sans non-signers of the quorum + quorumAggKey := state.AggKeys[quorumID] + quorumAggPubKeys[quorumID] = quorumAggKey + + signersAggKey := quorumAggKey.Clone() + for opInd, nsk := range nonSignerKeys { + ops := state.Operators[quorumID] + if _, ok := ops[nonSignerOperatorIds[opInd]]; ok { + signersAggKey.Sub(nsk) + } + } + + if aggPubKeys[quorumID] == nil { + return nil, ErrAggPubKeyNotValid + } + + ok, err := signersAggKey.VerifyEquivalence(aggPubKeys[quorumID]) + if err != nil { + return nil, err + } + if !ok { + return nil, ErrPubKeysNotEqual + } + + // Verify the aggregated signature for the quorum + ok = aggSigs[quorumID].Verify(aggPubKeys[quorumID], message) + if !ok { + return nil, ErrAggSigNotValid + } + } + + return &QuorumAttestation{ + QuorumAggPubKey: quorumAggPubKeys, + SignersAggPubKey: aggPubKeys, + AggSignature: aggSigs, + QuorumResults: quorumResults, + SignerMap: signerMap, + }, nil +} + +func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, ics chainio.IndexedChainState, referenceBlockNumber uint, quorumAttestation *QuorumAttestation, quorumIDs []QuorumID) (*SignatureAggregation, error) { + // Aggregate the aggregated signatures. We reuse the first aggregated signature as the accumulator + var aggSig *bn254.Signature + for _, quorumID := range quorumIDs { + sig := quorumAttestation.AggSignature[quorumID] + if aggSig == nil { + aggSig = &bn254.Signature{sig.G1Point.Clone()} + } else { + aggSig.Add(sig.G1Point) + } + } + + // Aggregate the aggregated public keys. We reuse the first aggregated public key as the accumulator + var aggPubKey *bn254.G2Point + for _, quorumID := range quorumIDs { + apk := quorumAttestation.SignersAggPubKey[quorumID] + if aggPubKey == nil { + aggPubKey = apk.Clone() + } else { + aggPubKey.Add(apk) + } + } + + nonSignerKeys := make([]*bn254.G1Point, 0) + indexedOperatorState, err := ics.GetIndexedOperatorState(ctx, referenceBlockNumber, quorumIDs) + if err != nil { + return nil, err + } + for id, op := range indexedOperatorState.IndexedOperators { + _, found := quorumAttestation.SignerMap[id] + if !found { + nonSignerKeys = append(nonSignerKeys, op.PubkeyG1) + } + } + + // sort non signer keys according to how it's checked onchain + // ref: https://github.com/Layr-Labs/eigenlayer-middleware/blob/m2-mainnet/src/BLSSignatureChecker.sol#L99 + sort.Slice(nonSignerKeys, func(i, j int) bool { + hash1 := nonSignerKeys[i].Hash() + hash2 := nonSignerKeys[j].Hash() + // sort in accending order + return bytes.Compare(hash1[:], hash2[:]) == -1 + }) + + quorumAggKeys := make(map[QuorumID]*bn254.G1Point, len(quorumIDs)) + for _, quorumID := range quorumIDs { + quorumAggKeys[quorumID] = quorumAttestation.QuorumAggPubKey[quorumID] + } + + quorumResults := make(map[QuorumID]*QuorumResult, len(quorumIDs)) + for _, quorumID := range quorumIDs { + quorumResults[quorumID] = quorumAttestation.QuorumResults[quorumID] + } + + return &SignatureAggregation{ + NonSigners: nonSignerKeys, + QuorumAggPubKeys: quorumAggKeys, + AggPubKey: aggPubKey, + AggSignature: aggSig, + QuorumResults: quorumResults, + }, nil + +} + +func GetStakeThreshold(state *chainio.OperatorState, quorum QuorumID, quorumThreshold uint8) *big.Int { + + // Get stake threshold + quorumThresholdBig := new(big.Int).SetUint64(uint64(quorumThreshold)) + stakeThreshold := new(big.Int) + stakeThreshold.Mul(quorumThresholdBig, state.Totals[quorum].Stake) + stakeThreshold = RoundUpDivideBig(stakeThreshold, new(big.Int).SetUint64(percentMultiplier)) + + return stakeThreshold +} + +func GetSignedPercentage(state *chainio.OperatorState, quorum QuorumID, stakeAmount *big.Int) uint8 { + + stakeAmount = stakeAmount.Mul(stakeAmount, new(big.Int).SetUint64(percentMultiplier)) + quorumThresholdBig := stakeAmount.Div(stakeAmount, state.Totals[quorum].Stake) + + quorumThreshold := uint8(quorumThresholdBig.Uint64()) + + return quorumThreshold +} diff --git a/core/v2/aggregation_test.go b/core/v2/aggregation_test.go new file mode 100644 index 000000000..aeb2d5a41 --- /dev/null +++ b/core/v2/aggregation_test.go @@ -0,0 +1,301 @@ +package corev2_test + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/Layr-Labs/eigenda/chainio/mock" + corev2 "github.com/Layr-Labs/eigenda/core/v2" + "github.com/Layr-Labs/eigenda/crypto/ecc/bn254" + "github.com/stretchr/testify/assert" +) + +func simulateOperators(state mock.PrivateOperatorState, message [32]byte, update chan corev2.SigningMessage, advCount uint) { + + count := 0 + + // Simulate the operators signing the message. + // In real life, the ordering will be random, but we simulate the signing in a fixed order + // to simulate stakes deterministically + for i := 0; i < len(state.PrivateOperators); i++ { + id := mock.MakeOperatorId(i) + op := state.PrivateOperators[id] + sig := op.KeyPair.SignMessage(message) + if count < len(state.IndexedOperators)-int(advCount) { + update <- corev2.SigningMessage{ + Signature: sig, + Operator: id, + Err: nil, + } + } else { + update <- corev2.SigningMessage{ + Signature: nil, + Operator: id, + Err: errors.New("adversary"), + } + } + + count += 1 + } +} + +func TestAggregateSignaturesStatus(t *testing.T) { + + tests := []struct { + name string + quorums []corev2.QuorumResult + adversaryCount uint + expectedErr error + meetsQuorum []bool + }{ + { + name: "Succeeds when all operators sign at quorum threshold 100", + quorums: []corev2.QuorumResult{ + { + QuorumID: 0, + PercentSigned: 100, + }, + }, + adversaryCount: 0, + expectedErr: nil, + meetsQuorum: []bool{true}, + }, + { + name: "Succeeds when 5/6 operators sign at quorum threshold 70", + quorums: []corev2.QuorumResult{ + { + QuorumID: 0, + PercentSigned: 70, + }, + }, + adversaryCount: 1, + expectedErr: nil, + meetsQuorum: []bool{true}, + }, + { + name: "Fails when 4/6 operators sign at quorum threshold 90", + quorums: []corev2.QuorumResult{ + { + QuorumID: 0, + PercentSigned: 90, + }, + }, + adversaryCount: 2, + expectedErr: nil, + meetsQuorum: []bool{false}, + }, + { + name: "Fails when 5/6 operators sign at quorum threshold 80 for 2 quorums", + quorums: []corev2.QuorumResult{ + { + QuorumID: 0, + PercentSigned: 80, + }, + { + QuorumID: 1, + PercentSigned: 80, + }, + }, + adversaryCount: 1, + expectedErr: nil, + meetsQuorum: []bool{false, true}, + }, + { + name: "Succeeds when 5/6 operators sign at quorum threshold 70 and 100", + quorums: []corev2.QuorumResult{ + { + QuorumID: 0, + PercentSigned: 70, + }, + { + QuorumID: 1, + PercentSigned: 100, + }, + }, + adversaryCount: 1, + expectedErr: nil, + meetsQuorum: []bool{true, true}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + state := dat.GetTotalOperatorStateWithQuorums(context.Background(), 0, []corev2.QuorumID{0, 1}) + assert.NotNil(t, state) + + update := make(chan corev2.SigningMessage) + message := [32]byte{1, 2, 3, 4, 5, 6} + + go simulateOperators(*state, message, update, tt.adversaryCount) + + quorumIDs := make([]corev2.QuorumID, len(tt.quorums)) + for ind, quorum := range tt.quorums { + quorumIDs[ind] = quorum.QuorumID + } + + numOpr := 0 + for _, quorum := range tt.quorums { + if len(dat.Stakes[quorum.QuorumID]) > numOpr { + numOpr = len(dat.Stakes[quorum.QuorumID]) + } + } + + aq, err := agg.ReceiveSignatures(context.Background(), state.IndexedOperatorState, message, update) + assert.NoError(t, err) + assert.Len(t, aq.SignerMap, numOpr-int(tt.adversaryCount)) + assert.Len(t, aq.AggSignature, 2) + assert.Len(t, aq.QuorumAggPubKey, 2) + assert.Len(t, aq.SignersAggPubKey, 2) + assert.Len(t, aq.QuorumResults, 2) + for i, q := range tt.quorums { + assert.NotNil(t, aq.AggSignature[q.QuorumID]) + assert.NotNil(t, aq.QuorumAggPubKey[q.QuorumID]) + assert.NotNil(t, aq.SignersAggPubKey[q.QuorumID]) + if tt.meetsQuorum[i] { + assert.GreaterOrEqual(t, aq.QuorumResults[q.QuorumID].PercentSigned, q.PercentSigned) + } else { + assert.Less(t, aq.QuorumResults[q.QuorumID].PercentSigned, q.PercentSigned) + } + } + + sigAgg, err := agg.AggregateSignatures(context.Background(), dat, 0, aq, quorumIDs) + assert.NoError(t, err) + + for i, quorum := range tt.quorums { + if tt.meetsQuorum[i] { + assert.GreaterOrEqual(t, sigAgg.QuorumResults[quorum.QuorumID].PercentSigned, quorum.PercentSigned) + } else { + assert.Less(t, sigAgg.QuorumResults[quorum.QuorumID].PercentSigned, quorum.PercentSigned) + } + } + }) + } + +} + +func TestSortNonsigners(t *testing.T) { + state := dat.GetTotalOperatorState(context.Background(), 0) + + update := make(chan corev2.SigningMessage) + message := [32]byte{1, 2, 3, 4, 5, 6} + + go simulateOperators(*state, message, update, 4) + + quorums := []corev2.QuorumID{0} + + aq, err := agg.ReceiveSignatures(context.Background(), state.IndexedOperatorState, message, update) + assert.NoError(t, err) + sigAgg, err := agg.AggregateSignatures(context.Background(), dat, 0, aq, quorums) + assert.NoError(t, err) + + for i := range sigAgg.NonSigners { + if i == 0 { + continue + } + prevHash := sigAgg.NonSigners[i-1].Hash() + currHash := sigAgg.NonSigners[i].Hash() + prevHashInt := new(big.Int).SetBytes(prevHash[:]) + currHashInt := new(big.Int).SetBytes(currHash[:]) + assert.Equal(t, currHashInt.Cmp(prevHashInt), 1) + } +} + +func TestFilterQuorums(t *testing.T) { + allQuorums := []corev2.QuorumID{0, 1} + state := dat.GetTotalOperatorStateWithQuorums(context.Background(), 0, allQuorums) + + update := make(chan corev2.SigningMessage) + message := [32]byte{1, 2, 3, 4, 5, 6} + advCount := 4 + go simulateOperators(*state, message, update, uint(advCount)) + + numOpr := 0 + for _, quorum := range allQuorums { + if len(dat.Stakes[quorum]) > numOpr { + numOpr = len(dat.Stakes[quorum]) + } + } + + aq, err := agg.ReceiveSignatures(context.Background(), state.IndexedOperatorState, message, update) + assert.NoError(t, err) + assert.Len(t, aq.SignerMap, numOpr-advCount) + assert.Equal(t, aq.SignerMap, map[corev2.OperatorID]bool{ + mock.MakeOperatorId(0): true, + mock.MakeOperatorId(1): true, + }) + assert.Contains(t, aq.AggSignature, corev2.QuorumID(0)) + assert.Contains(t, aq.AggSignature, corev2.QuorumID(1)) + assert.Equal(t, aq.QuorumAggPubKey, map[corev2.QuorumID]*bn254.G1Point{ + corev2.QuorumID(0): state.IndexedOperatorState.AggKeys[0], + corev2.QuorumID(1): state.IndexedOperatorState.AggKeys[1], + }) + aggSignerPubKey0 := state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(0)].PubkeyG2.Clone() + aggSignerPubKey0.Add(state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(1)].PubkeyG2) + aggSignerPubKey1 := state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(0)].PubkeyG2.Clone() + aggSignerPubKey1.Add(state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(1)].PubkeyG2) + assert.Contains(t, aq.SignersAggPubKey, corev2.QuorumID(0)) + assert.Equal(t, aq.SignersAggPubKey[corev2.QuorumID(0)], aggSignerPubKey0) + assert.Contains(t, aq.SignersAggPubKey, corev2.QuorumID(1)) + assert.Equal(t, aq.SignersAggPubKey[corev2.QuorumID(1)], aggSignerPubKey1) + assert.Equal(t, aq.QuorumResults[corev2.QuorumID(0)].PercentSigned, uint8(14)) + assert.Equal(t, aq.QuorumResults[corev2.QuorumID(1)].PercentSigned, uint8(50)) + + // Only consider quorum 0 + quorums := []corev2.QuorumID{0} + sigAgg, err := agg.AggregateSignatures(context.Background(), dat, 0, aq, quorums) + assert.NoError(t, err) + assert.Len(t, sigAgg.NonSigners, 4) + assert.ElementsMatch(t, sigAgg.NonSigners, []*bn254.G1Point{ + state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(2)].PubkeyG1, + state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(3)].PubkeyG1, + state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(4)].PubkeyG1, + state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(5)].PubkeyG1, + }) + assert.Len(t, sigAgg.QuorumAggPubKeys, 1) + assert.Contains(t, sigAgg.QuorumAggPubKeys, corev2.QuorumID(0)) + assert.Equal(t, sigAgg.QuorumAggPubKeys[0], state.IndexedOperatorState.AggKeys[0]) + + assert.Equal(t, sigAgg.AggPubKey, aggSignerPubKey0) + expectedAggSignerKey := sigAgg.QuorumAggPubKeys[0].Clone() + for _, nsk := range sigAgg.NonSigners { + expectedAggSignerKey.Sub(nsk) + } + ok, err := expectedAggSignerKey.VerifyEquivalence(sigAgg.AggPubKey) + assert.NoError(t, err) + assert.True(t, ok) + ok = sigAgg.AggSignature.Verify(sigAgg.AggPubKey, message) + assert.True(t, ok) + assert.Len(t, sigAgg.QuorumResults, 1) + assert.Contains(t, sigAgg.QuorumResults, corev2.QuorumID(0)) + assert.Equal(t, sigAgg.QuorumResults[0].QuorumID, corev2.QuorumID(0)) + assert.Equal(t, sigAgg.QuorumResults[0].PercentSigned, corev2.QuorumID(14)) + + // Only consider quorum 1 + quorums = []corev2.QuorumID{1} + sigAgg, err = agg.AggregateSignatures(context.Background(), dat, 0, aq, quorums) + assert.NoError(t, err) + assert.Len(t, sigAgg.NonSigners, 1) + assert.ElementsMatch(t, sigAgg.NonSigners, []*bn254.G1Point{ + state.IndexedOperatorState.IndexedOperators[mock.MakeOperatorId(2)].PubkeyG1, + }) + assert.Len(t, sigAgg.QuorumAggPubKeys, 1) + assert.Contains(t, sigAgg.QuorumAggPubKeys, corev2.QuorumID(1)) + assert.Equal(t, sigAgg.QuorumAggPubKeys[1], state.IndexedOperatorState.AggKeys[1]) + + assert.Equal(t, sigAgg.AggPubKey, aggSignerPubKey1) + expectedAggSignerKey = sigAgg.QuorumAggPubKeys[1].Clone() + for _, nsk := range sigAgg.NonSigners { + expectedAggSignerKey.Sub(nsk) + } + ok, err = expectedAggSignerKey.VerifyEquivalence(sigAgg.AggPubKey) + assert.NoError(t, err) + assert.True(t, ok) + ok = sigAgg.AggSignature.Verify(sigAgg.AggPubKey, message) + assert.True(t, ok) + assert.Len(t, sigAgg.QuorumResults, 1) + assert.Contains(t, sigAgg.QuorumResults, corev2.QuorumID(1)) + assert.Equal(t, sigAgg.QuorumResults[1].QuorumID, corev2.QuorumID(1)) + assert.Equal(t, sigAgg.QuorumResults[1].PercentSigned, corev2.QuorumID(50)) +} diff --git a/core/v2/assignment.go b/core/v2/assignment.go new file mode 100644 index 000000000..e62059039 --- /dev/null +++ b/core/v2/assignment.go @@ -0,0 +1,124 @@ +package corev2 + +import ( + "fmt" + "math/big" + "sort" + + "github.com/Layr-Labs/eigenda/chainio" + "github.com/Layr-Labs/eigenda/core" +) + +func GetAssignments(state *chainio.OperatorState, blobVersion byte, quorum uint8) (map[core.OperatorID]Assignment, error) { + + params, ok := ParametersMap[blobVersion] + if !ok { + return nil, fmt.Errorf("blob version %d not found", blobVersion) + } + + ops, ok := state.Operators[quorum] + if !ok { + return nil, fmt.Errorf("no operators found for quorum %d", quorum) + } + + if len(ops) > int(params.MaxNumOperators()) { + return nil, fmt.Errorf("too many operators for blob version %d", blobVersion) + } + + n := big.NewInt(int64(len(ops))) + m := big.NewInt(int64(params.NumChunks)) + + type assignment struct { + id core.OperatorID + index uint32 + chunks uint32 + stake *big.Int + } + + chunkAssignments := make([]assignment, 0, len(ops)) + for ID, r := range state.Operators[quorum] { + + num := new(big.Int).Mul(r.Stake, new(big.Int).Sub(m, n)) + denom := state.Totals[quorum].Stake + + chunks := RoundUpDivideBig(num, denom) + + chunkAssignments = append(chunkAssignments, assignment{id: ID, index: uint32(r.Index), chunks: uint32(chunks.Uint64()), stake: r.Stake}) + } + + // Sort chunk decreasing by stake or operator ID in case of a tie + sort.Slice(chunkAssignments, func(i, j int) bool { + if chunkAssignments[i].stake.Cmp(chunkAssignments[j].stake) == 0 { + return chunkAssignments[i].index < chunkAssignments[j].index + } + return chunkAssignments[i].stake.Cmp(chunkAssignments[j].stake) == 1 + }) + + mp := 0 + for _, a := range chunkAssignments { + mp += int(a.chunks) + } + + delta := int(params.NumChunks) - mp + if delta < 0 { + return nil, fmt.Errorf("total chunks %d exceeds maximum %d", mp, params.NumChunks) + } + + assignments := make(map[core.OperatorID]Assignment, len(chunkAssignments)) + index := uint32(0) + for i, a := range chunkAssignments { + if i < delta { + a.chunks++ + } + + assignment := Assignment{ + StartIndex: index, + NumChunks: a.chunks, + } + + assignments[a.id] = assignment + index += a.chunks + } + + return assignments, nil + +} + +func GetAssignment(state *chainio.OperatorState, blobVersion byte, quorum QuorumID, id core.OperatorID) (Assignment, error) { + + assignments, err := GetAssignments(state, blobVersion, quorum) + if err != nil { + return Assignment{}, err + } + + assignment, ok := assignments[id] + if !ok { + return Assignment{}, ErrNotFound + } + + return assignment, nil +} + +func GetChunkLength(blobVersion byte, blobLength uint32) (uint32, error) { + + if blobLength == 0 { + return 0, fmt.Errorf("blob length must be greater than 0") + } + + // Check that the blob length is a power of 2 + if blobLength&(blobLength-1) != 0 { + return 0, fmt.Errorf("blob length %d is not a power of 2", blobLength) + } + + if _, ok := ParametersMap[blobVersion]; !ok { + return 0, fmt.Errorf("blob version %d not found", blobVersion) + } + + chunkLength := blobLength * ParametersMap[blobVersion].CodingRate / ParametersMap[blobVersion].NumChunks + if chunkLength == 0 { + chunkLength = 1 + } + + return chunkLength, nil + +} diff --git a/core/v2/assignment_test.go b/core/v2/assignment_test.go new file mode 100644 index 000000000..f2c9baac2 --- /dev/null +++ b/core/v2/assignment_test.go @@ -0,0 +1,220 @@ +package corev2_test + +import ( + "context" + "math/rand" + "testing" + + "github.com/Layr-Labs/eigenda/chainio" + "github.com/Layr-Labs/eigenda/chainio/mock" + corev2 "github.com/Layr-Labs/eigenda/core/v2" + "github.com/stretchr/testify/assert" +) + +const ( + maxNumOperators = 3537 +) + +func TestOperatorAssignmentsV2(t *testing.T) { + + state := dat.GetTotalOperatorState(context.Background(), 0) + operatorState := state.OperatorState + + blobVersion := byte(0) + + assignments, err := corev2.GetAssignments(operatorState, blobVersion, 0) + assert.NoError(t, err) + expectedAssignments := map[chainio.OperatorID]corev2.Assignment{ + mock.MakeOperatorId(0): { + StartIndex: 7802, + NumChunks: 390, + }, + mock.MakeOperatorId(1): { + StartIndex: 7022, + NumChunks: 780, + }, + mock.MakeOperatorId(2): { + StartIndex: 5852, + NumChunks: 1170, + }, + mock.MakeOperatorId(3): { + StartIndex: 4291, + NumChunks: 1561, + }, + mock.MakeOperatorId(4): { + StartIndex: 2340, + NumChunks: 1951, + }, + mock.MakeOperatorId(5): { + StartIndex: 0, + NumChunks: 2340, + }, + } + + for operatorID, assignment := range assignments { + + assert.Equal(t, assignment, expectedAssignments[operatorID]) + + assignment, err := corev2.GetAssignment(operatorState, blobVersion, 0, operatorID) + assert.NoError(t, err) + + assert.Equal(t, assignment, expectedAssignments[operatorID]) + + } + +} + +func TestMaxNumOperators(t *testing.T) { + + assert.Equal(t, corev2.ParametersMap[0].MaxNumOperators(), uint32(maxNumOperators)) + +} + +func TestAssignmentWithTooManyOperators(t *testing.T) { + + numOperators := maxNumOperators + 1 + + stakes := map[chainio.QuorumID]map[chainio.OperatorID]int{ + 0: {}, + } + for i := 0; i < numOperators; i++ { + stakes[0][mock.MakeOperatorId(i)] = rand.Intn(100) + 1 + } + + dat, err := mock.NewChainDataMock(stakes) + if err != nil { + t.Fatal(err) + } + + state := dat.GetTotalOperatorState(context.Background(), 0) + + assert.Equal(t, len(state.Operators[0]), numOperators) + + blobVersion := byte(0) + + _, err = corev2.GetAssignments(state.OperatorState, blobVersion, 0) + assert.Error(t, err) + +} + +func FuzzOperatorAssignmentsV2(f *testing.F) { + + // Add distributions to fuzz + + for i := 1; i < 100; i++ { + f.Add(i) + } + + for i := 0; i < 100; i++ { + f.Add(rand.Intn(2048) + 100) + } + + for i := 0; i < 5; i++ { + f.Add(maxNumOperators) + } + + f.Fuzz(func(t *testing.T, numOperators int) { + + // Generate a random slice of integers of length n + + stakes := map[chainio.QuorumID]map[chainio.OperatorID]int{ + 0: {}, + } + for i := 0; i < numOperators; i++ { + stakes[0][mock.MakeOperatorId(i)] = rand.Intn(100) + 1 + } + + dat, err := mock.NewChainDataMock(stakes) + if err != nil { + t.Fatal(err) + } + + state := dat.GetTotalOperatorState(context.Background(), 0) + + blobVersion := byte(0) + + assignments, err := corev2.GetAssignments(state.OperatorState, blobVersion, 0) + assert.NoError(t, err) + + // Check that the total number of chunks is correct + totalChunks := uint32(0) + for _, assignment := range assignments { + totalChunks += assignment.NumChunks + } + assert.Equal(t, totalChunks, corev2.ParametersMap[blobVersion].NumChunks) + + // Check that each operator's assignment satisfies the security requirement + for operatorID, assignment := range assignments { + + totalStake := uint32(state.Totals[0].Stake.Uint64()) + myStake := uint32(state.Operators[0][operatorID].Stake.Uint64()) + + LHS := assignment.NumChunks * totalStake * corev2.ParametersMap[blobVersion].CodingRate * uint32(corev2.ParametersMap[blobVersion].ReconstructionThreshold*100) + RHS := 100 * myStake * corev2.ParametersMap[blobVersion].NumChunks + + assert.GreaterOrEqual(t, LHS, RHS) + + } + + }) + +} + +func TestChunkLength(t *testing.T) { + + blobVersion := byte(0) + + pairs := []struct { + blobLength uint32 + chunkLength uint32 + }{ + {512, 1}, + {1024, 1}, + {2048, 2}, + {4096, 4}, + {8192, 8}, + } + + for _, pair := range pairs { + + chunkLength, err := corev2.GetChunkLength(blobVersion, pair.blobLength) + + assert.NoError(t, err) + + assert.Equal(t, pair.chunkLength, chunkLength) + } + +} + +func TestInvalidChunkLength(t *testing.T) { + + blobVersion := byte(0) + + invalidLengths := []uint32{ + 0, + 3, + 5, + 6, + 7, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 31, + 63, + 127, + 255, + 511, + 1023, + } + + for _, length := range invalidLengths { + + _, err := corev2.GetChunkLength(blobVersion, length) + assert.Error(t, err) + } + +} diff --git a/core/v2/core_test.go b/core/v2/core_test.go new file mode 100644 index 000000000..39fa267d8 --- /dev/null +++ b/core/v2/core_test.go @@ -0,0 +1,266 @@ +package corev2_test + +import ( + "context" + "fmt" + "math/rand" + "os" + "runtime" + "testing" + + "github.com/Layr-Labs/eigenda/chainio" + "github.com/Layr-Labs/eigenda/chainio/mock" + "github.com/Layr-Labs/eigenda/common" + "github.com/Layr-Labs/eigenda/core" + corev2 "github.com/Layr-Labs/eigenda/core/v2" + "github.com/Layr-Labs/eigenda/encoding" + "github.com/Layr-Labs/eigenda/encoding/kzg" + "github.com/Layr-Labs/eigenda/encoding/kzg/prover" + "github.com/Layr-Labs/eigenda/encoding/kzg/verifier" + "github.com/Layr-Labs/eigenda/encoding/utils/codec" + "github.com/Layr-Labs/eigensdk-go/logging" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/gammazero/workerpool" + "github.com/hashicorp/go-multierror" + "github.com/stretchr/testify/assert" +) + +var ( + dat *mock.ChainDataMock + agg corev2.SignatureAggregator + + p encoding.Prover + v encoding.Verifier + + GETTYSBURG_ADDRESS_BYTES = []byte("Fourscore and seven years ago our fathers brought forth, on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived, and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting-place for those who here gave their lives, that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we cannot dedicate, we cannot consecrate—we cannot hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they here gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom, and that government of the people, by the people, for the people, shall not perish from the earth.") +) + +func TestMain(m *testing.M) { + var err error + dat, err = mock.MakeChainDataMock(map[uint8]int{ + 0: 6, + 1: 3, + }) + if err != nil { + panic(err) + } + logger := logging.NewNoopLogger() + reader := &mock.MockReader{} + reader.On("OperatorIDToAddress").Return(gethcommon.Address{}, nil) + agg, err = corev2.NewStdSignatureAggregator(logger, reader) + if err != nil { + panic(err) + } + + p, v, err = makeTestComponents() + if err != nil { + panic("failed to start localstack container") + } + + code := m.Run() + os.Exit(code) +} + +// makeTestComponents makes a prover and verifier currently using the only supported backend. +func makeTestComponents() (encoding.Prover, encoding.Verifier, error) { + config := &kzg.KzgConfig{ + G1Path: "../inabox/resources/kzg/g1.point.300000", + G2Path: "../inabox/resources/kzg/g2.point.300000", + CacheDir: "../inabox/resources/kzg/SRSTables", + SRSOrder: 8192, + SRSNumberToLoad: 8192, + NumWorker: uint64(runtime.GOMAXPROCS(0)), + } + + p, err := prover.NewProver(config, true) + if err != nil { + return nil, nil, err + } + + v, err := verifier.NewVerifier(config, true) + if err != nil { + return nil, nil, err + } + + return p, v, nil +} + +func makeTestBlob(t *testing.T, p encoding.Prover, version uint8, refBlockNumber uint64, length int, quorums []corev2.QuorumID) (corev2.BlobHeader, []byte) { + + data := make([]byte, length*31) + _, err := rand.Read(data) + if err != nil { + t.Fatal(err) + } + + data = codec.ConvertByPaddingEmptyByte(data) + + commitments, err := p.GetCommitments(data) + if err != nil { + t.Fatal(err) + } + + header := corev2.BlobHeader{ + Version: version, + QuorumNumbers: quorums, + BlobCommitments: commitments, + ReferenceBlockNumber: refBlockNumber, + } + + return header, data + +} + +// prepareBatch takes in multiple blob, encodes them, generates the associated assignments, and the batch header. +// These are the products that a disperser will need in order to disperse data to the DA nodes. +func prepareBlobs(t *testing.T, operatorCount uint, headers []corev2.BlobHeader, blobs [][]byte) (map[corev2.OperatorID][]*corev2.BlobShard, chainio.IndexedChainState) { + + cst, err := mock.MakeChainDataMock(map[uint8]int{ + 0: int(operatorCount), + 1: int(operatorCount), + 2: int(operatorCount), + }) + assert.NoError(t, err) + + blobsMap := make([]map[corev2.QuorumID]map[corev2.OperatorID][]*encoding.Frame, 0, len(headers)) + + for z, header := range headers { + + blob := blobs[z] + + params, err := header.GetEncodingParams() + if err != nil { + t.Fatal(err) + } + + chunks, err := p.GetFrames(blob, params) + if err != nil { + t.Fatal(err) + } + + state, err := cst.GetOperatorState(context.Background(), uint(header.ReferenceBlockNumber), header.QuorumNumbers) + if err != nil { + t.Fatal(err) + } + + blobMap := make(map[corev2.QuorumID]map[corev2.OperatorID][]*encoding.Frame) + + for _, quorum := range header.QuorumNumbers { + + assignments, err := corev2.GetAssignments(state, header.Version, quorum) + if err != nil { + t.Fatal(err) + } + + blobMap[quorum] = make(map[corev2.OperatorID][]*encoding.Frame) + + for opID, assignment := range assignments { + + blobMap[quorum][opID] = chunks[assignment.StartIndex : assignment.StartIndex+assignment.NumChunks] + + } + + } + + blobsMap = append(blobsMap, blobMap) + } + + // Invert the blobsMap + inverseMap := make(map[corev2.OperatorID][]*corev2.BlobShard) + for blobIndex, blobMap := range blobsMap { + for quorum, operatorMap := range blobMap { + for operatorID, frames := range operatorMap { + + if _, ok := inverseMap[operatorID]; !ok { + inverseMap[operatorID] = make([]*corev2.BlobShard, 0) + } + if len(inverseMap[operatorID]) < blobIndex+1 { + inverseMap[operatorID] = append(inverseMap[operatorID], &corev2.BlobShard{ + BlobHeader: headers[blobIndex], + Chunks: make(map[corev2.QuorumID][]*encoding.Frame), + }) + } + if len(frames) == 0 { + continue + } + inverseMap[operatorID][blobIndex].Chunks[quorum] = append(inverseMap[operatorID][blobIndex].Chunks[quorum], frames...) + + } + } + } + + return inverseMap, cst + +} + +// checkBatchByUniversalVerifier runs the verification logic for each DA node in the current OperatorState, and returns an error if any of +// the DA nodes' validation checks fails +func checkBatchByUniversalVerifier(cst chainio.IndexedChainState, packagedBlobs map[corev2.OperatorID][]*corev2.BlobShard, pool common.WorkerPool) error { + + ctx := context.Background() + + quorums := []core.QuorumID{0, 1} + state, _ := cst.GetIndexedOperatorState(context.Background(), 0, quorums) + // numBlob := len(encodedBlobs) + + var errList *multierror.Error + + for id := range state.IndexedOperators { + + val := corev2.NewShardValidator(v, cst, id) + + blobs := packagedBlobs[id] + + err := val.ValidateBlobs(ctx, blobs, pool) + if err != nil { + errList = multierror.Append(errList, err) + } + } + + return errList.ErrorOrNil() + +} + +func TestValidationSucceeds(t *testing.T) { + + // operatorCounts := []uint{1, 2, 4, 10, 30} + + // numBlob := 3 // must be greater than 0 + // blobLengths := []int{1, 32, 128} + + operatorCounts := []uint{4} + + numBlob := 1 // must be greater than 0 + blobLengths := []int{1, 2} + + quorumNumbers := []corev2.QuorumID{0, 1} + + bn := uint64(0) + + version := uint8(0) + + pool := workerpool.New(1) + + for _, operatorCount := range operatorCounts { + + // batch can only be tested per operatorCount, because the assignment would be wrong otherwise + headers := make([]corev2.BlobHeader, 0) + blobs := make([][]byte, 0) + for _, blobLength := range blobLengths { + for i := 0; i < numBlob; i++ { + header, data := makeTestBlob(t, p, version, bn, blobLength, quorumNumbers) + headers = append(headers, header) + blobs = append(blobs, data) + } + } + + packagedBlobs, cst := prepareBlobs(t, operatorCount, headers, blobs) + + t.Run(fmt.Sprintf("universal verifier operatorCount=%v over %v blobs", operatorCount, len(blobs)), func(t *testing.T) { + err := checkBatchByUniversalVerifier(cst, packagedBlobs, pool) + assert.NoError(t, err) + }) + + } + +} diff --git a/core/v2/errors.go b/core/v2/errors.go new file mode 100644 index 000000000..b48e1e146 --- /dev/null +++ b/core/v2/errors.go @@ -0,0 +1,7 @@ +package corev2 + +import "errors" + +var ( + ErrNotFound = errors.New("not found") +) diff --git a/core/v2/types.go b/core/v2/types.go new file mode 100644 index 000000000..c4418e3ba --- /dev/null +++ b/core/v2/types.go @@ -0,0 +1,89 @@ +package corev2 + +import ( + "math" + + "github.com/Layr-Labs/eigenda/encoding" +) + +var ( + ParametersMap = map[uint8]BlobVersionParameters{ + 0: {CodingRate: 8, ReconstructionThreshold: 0.22, NumChunks: 8192}, + } +) + +type QuorumID = uint8 + +type OperatorID = [32]byte + +// Assignment contains information about the set of chunks that a specific node will receive +type Assignment struct { + StartIndex uint32 + NumChunks uint32 +} + +// GetIndices generates the list of ChunkIndices associated with a given assignment +func (c *Assignment) GetIndices() []uint32 { + indices := make([]uint32, c.NumChunks) + for ind := range indices { + indices[ind] = c.StartIndex + uint32(ind) + } + return indices +} + +// BlobHeader contains all metadata related to a blob including commitments and parameters for encoding +type BlobHeader struct { + Version uint8 + + encoding.BlobCommitments + + // QuorumInfos contains the quorum specific parameters for the blob + QuorumNumbers []uint8 + + // ReferenceBlockNumber is the block number of the block at which the operator state will be referenced + ReferenceBlockNumber uint64 +} + +func (b *BlobHeader) GetEncodingParams() (encoding.EncodingParams, error) { + + params := ParametersMap[b.Version] + + length, err := GetChunkLength(b.Version, uint32(b.Length)) + if err != nil { + return encoding.EncodingParams{}, err + } + + return encoding.EncodingParams{ + NumChunks: uint64(params.NumChunks), + ChunkLength: uint64(length), + }, nil + +} + +type PaymentHeader struct { + // BlobKey is the hash of the blob header + BlobKey [32]byte + + // AccountID is the account that is paying for the blob to be stored. AccountID is hexadecimal representation of the ECDSA public key + AccountID string + + // Cumulative Payment + CumulativePayment uint64 + + BinIndex uint64 + + // AuthenticationData is the signature of the blob header by the account ID + AuthenticationData []byte `json:"authentication_data"` +} + +type BlobVersionParameters struct { + CodingRate uint32 + ReconstructionThreshold float64 + NumChunks uint32 +} + +func (p BlobVersionParameters) MaxNumOperators() uint32 { + + return uint32(math.Floor(float64(p.NumChunks) * (1 - 1/(p.ReconstructionThreshold*float64(p.CodingRate))))) + +} diff --git a/core/v2/utils.go b/core/v2/utils.go new file mode 100644 index 000000000..7cbff05fc --- /dev/null +++ b/core/v2/utils.go @@ -0,0 +1,26 @@ +package corev2 + +import ( + "math" + "math/big" + + "golang.org/x/exp/constraints" +) + +func RoundUpDivideBig(a, b *big.Int) *big.Int { + + one := new(big.Int).SetUint64(1) + num := new(big.Int).Sub(new(big.Int).Add(a, b), one) // a + b - 1 + res := new(big.Int).Div(num, b) // (a + b - 1) / b + return res + +} + +func RoundUpDivide[T constraints.Integer](a, b T) T { + return (a + b - 1) / b +} + +func NextPowerOf2[T constraints.Integer](d T) T { + nextPower := math.Ceil(math.Log2(float64(d))) + return T(math.Pow(2.0, nextPower)) +} diff --git a/core/v2/validator.go b/core/v2/validator.go new file mode 100644 index 000000000..6d603d5a8 --- /dev/null +++ b/core/v2/validator.go @@ -0,0 +1,199 @@ +package corev2 + +import ( + "context" + "errors" + "fmt" + + "github.com/Layr-Labs/eigenda/chainio" + "github.com/Layr-Labs/eigenda/common" + "github.com/Layr-Labs/eigenda/encoding" +) + +var ( + ErrChunkLengthMismatch = errors.New("chunk length mismatch") + ErrBlobQuorumSkip = errors.New("blob skipped for a quorum before verification") +) + +type BlobShard struct { + BlobHeader + Chunks map[QuorumID][]*encoding.Frame +} + +// shardValidator implements the validation logic that a DA node should apply to its received data +type ShardValidator struct { + verifier encoding.Verifier + chainState chainio.ChainState + operatorID OperatorID +} + +func NewShardValidator(v encoding.Verifier, cst chainio.ChainState, operatorID OperatorID) *ShardValidator { + return &ShardValidator{ + verifier: v, + chainState: cst, + operatorID: operatorID, + } +} + +func (v *ShardValidator) validateBlobQuorum(quorum QuorumID, blob *BlobShard, operatorState *chainio.OperatorState) ([]*encoding.Frame, *Assignment, error) { + + // Check if the operator is a member of the quorum + if _, ok := operatorState.Operators[quorum]; !ok { + return nil, nil, fmt.Errorf("%w: operator %s is not a member of quorum %d", ErrBlobQuorumSkip, chainio.GetOperatorHex(v.operatorID), quorum) + } + + // Get the assignments for the quorum + assignment, err := GetAssignment(operatorState, blob.Version, quorum, v.operatorID) + if err != nil { + return nil, nil, err + } + + // Validate the number of chunks + if assignment.NumChunks == 0 { + return nil, nil, fmt.Errorf("%w: operator %s has no chunks in quorum %d", ErrBlobQuorumSkip, chainio.GetOperatorHex(v.operatorID), quorum) + } + if assignment.NumChunks != uint32(len(blob.Chunks[quorum])) { + return nil, nil, fmt.Errorf("number of chunks (%d) does not match assignment (%d) for quorum %d", len(blob.Chunks[quorum]), assignment.NumChunks, quorum) + } + + // Validate the chunkLength against the confirmation and adversary threshold parameters + chunkLength, err := GetChunkLength(blob.Version, uint32(blob.BlobHeader.Length)) + if err != nil { + return nil, nil, fmt.Errorf("invalid chunk length: %w", err) + } + + // Get the chunk length + chunks := blob.Chunks[quorum] + for _, chunk := range chunks { + if uint32(chunk.Length()) != chunkLength { + return nil, nil, fmt.Errorf("%w: chunk length (%d) does not match quorum header (%d) for quorum %d", ErrChunkLengthMismatch, chunk.Length(), chunkLength, quorum) + } + } + + return chunks, &assignment, nil +} + +func (v *ShardValidator) ValidateBlobs(ctx context.Context, blobs []*BlobShard, pool common.WorkerPool) error { + var err error + subBatchMap := make(map[encoding.EncodingParams]*encoding.SubBatch) + blobCommitmentList := make([]encoding.BlobCommitments, len(blobs)) + + for k, blob := range blobs { + if len(blob.Chunks) != len(blob.BlobHeader.QuorumNumbers) { + return fmt.Errorf("number of bundles (%d) does not match number of quorums (%d)", len(blob.Chunks), len(blob.BlobHeader.QuorumNumbers)) + } + + state, err := v.chainState.GetOperatorState(ctx, uint(blob.ReferenceBlockNumber), blob.BlobHeader.QuorumNumbers) + if err != nil { + return err + } + + // Saved for the blob length validation + blobCommitmentList[k] = blob.BlobHeader.BlobCommitments + + // for each quorum + for _, quorum := range blob.BlobHeader.QuorumNumbers { + chunks, assignment, err := v.validateBlobQuorum(quorum, blob, state) + if err != nil { + return err + } + // TODO: Define params for the blob + params, err := blob.GetEncodingParams() + if err != nil { + return err + } + + if errors.Is(err, ErrBlobQuorumSkip) { + continue + } else if err != nil { + return err + } else { + // Check the received chunks against the commitment + blobIndex := 0 + subBatch, ok := subBatchMap[params] + if ok { + blobIndex = subBatch.NumBlobs + } + + indices := assignment.GetIndices() + samples := make([]encoding.Sample, len(chunks)) + for ind := range chunks { + samples[ind] = encoding.Sample{ + Commitment: blob.BlobHeader.BlobCommitments.Commitment, + Chunk: chunks[ind], + AssignmentIndex: uint(indices[ind]), + BlobIndex: blobIndex, + } + } + + // update subBatch + if !ok { + subBatchMap[params] = &encoding.SubBatch{ + Samples: samples, + NumBlobs: 1, + } + } else { + subBatch.Samples = append(subBatch.Samples, samples...) + subBatch.NumBlobs += 1 + } + } + } + } + + // Parallelize the universal verification for each subBatch + numResult := len(subBatchMap) + len(blobCommitmentList) + // create a channel to accept results, we don't use stop + out := make(chan error, numResult) + + // parallelize subBatch verification + for params, subBatch := range subBatchMap { + params := params + subBatch := subBatch + pool.Submit(func() { + v.universalVerifyWorker(params, subBatch, out) + }) + } + + // parallelize length proof verification + for _, blobCommitments := range blobCommitmentList { + blobCommitments := blobCommitments + pool.Submit(func() { + v.VerifyBlobLengthWorker(blobCommitments, out) + }) + } + // check if commitments are equivalent + err = v.verifier.VerifyCommitEquivalenceBatch(blobCommitmentList) + if err != nil { + return err + } + + for i := 0; i < numResult; i++ { + err := <-out + if err != nil { + return err + } + } + + return nil +} + +func (v *ShardValidator) universalVerifyWorker(params encoding.EncodingParams, subBatch *encoding.SubBatch, out chan error) { + + err := v.verifier.UniversalVerifySubBatch(params, subBatch.Samples, subBatch.NumBlobs) + if err != nil { + out <- err + return + } + + out <- nil +} + +func (v *ShardValidator) VerifyBlobLengthWorker(blobCommitments encoding.BlobCommitments, out chan error) { + err := v.verifier.VerifyBlobLength(blobCommitments) + if err != nil { + out <- err + return + } + + out <- nil +} From 8cb55fe206133e8075e11901446f285c4e29cf85 Mon Sep 17 00:00:00 2001 From: Robert Raynor <35671663+mooselumph@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:39:24 +0000 Subject: [PATCH 3/4] Update core types --- core/v2/core_test.go | 26 ++++++++++++++------------ core/v2/types.go | 21 ++++++++++++++------- core/v2/validator.go | 2 +- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/core/v2/core_test.go b/core/v2/core_test.go index 39fa267d8..5b3d153f8 100644 --- a/core/v2/core_test.go +++ b/core/v2/core_test.go @@ -64,9 +64,9 @@ func TestMain(m *testing.M) { // makeTestComponents makes a prover and verifier currently using the only supported backend. func makeTestComponents() (encoding.Prover, encoding.Verifier, error) { config := &kzg.KzgConfig{ - G1Path: "../inabox/resources/kzg/g1.point.300000", - G2Path: "../inabox/resources/kzg/g2.point.300000", - CacheDir: "../inabox/resources/kzg/SRSTables", + G1Path: "../../inabox/resources/kzg/g1.point.300000", + G2Path: "../../inabox/resources/kzg/g2.point.300000", + CacheDir: "../../inabox/resources/kzg/SRSTables", SRSOrder: 8192, SRSNumberToLoad: 8192, NumWorker: uint64(runtime.GOMAXPROCS(0)), @@ -85,7 +85,7 @@ func makeTestComponents() (encoding.Prover, encoding.Verifier, error) { return p, v, nil } -func makeTestBlob(t *testing.T, p encoding.Prover, version uint8, refBlockNumber uint64, length int, quorums []corev2.QuorumID) (corev2.BlobHeader, []byte) { +func makeTestBlob(t *testing.T, p encoding.Prover, version uint8, refBlockNumber uint64, length int, quorums []corev2.QuorumID) (corev2.BlobCertificate, []byte) { data := make([]byte, length*31) _, err := rand.Read(data) @@ -100,10 +100,12 @@ func makeTestBlob(t *testing.T, p encoding.Prover, version uint8, refBlockNumber t.Fatal(err) } - header := corev2.BlobHeader{ - Version: version, - QuorumNumbers: quorums, - BlobCommitments: commitments, + header := corev2.BlobCertificate{ + BlobHeader: corev2.BlobHeader{ + Version: version, + QuorumNumbers: quorums, + BlobCommitments: commitments, + }, ReferenceBlockNumber: refBlockNumber, } @@ -113,7 +115,7 @@ func makeTestBlob(t *testing.T, p encoding.Prover, version uint8, refBlockNumber // prepareBatch takes in multiple blob, encodes them, generates the associated assignments, and the batch header. // These are the products that a disperser will need in order to disperse data to the DA nodes. -func prepareBlobs(t *testing.T, operatorCount uint, headers []corev2.BlobHeader, blobs [][]byte) (map[corev2.OperatorID][]*corev2.BlobShard, chainio.IndexedChainState) { +func prepareBlobs(t *testing.T, operatorCount uint, headers []corev2.BlobCertificate, blobs [][]byte) (map[corev2.OperatorID][]*corev2.BlobShard, chainio.IndexedChainState) { cst, err := mock.MakeChainDataMock(map[uint8]int{ 0: int(operatorCount), @@ -176,8 +178,8 @@ func prepareBlobs(t *testing.T, operatorCount uint, headers []corev2.BlobHeader, } if len(inverseMap[operatorID]) < blobIndex+1 { inverseMap[operatorID] = append(inverseMap[operatorID], &corev2.BlobShard{ - BlobHeader: headers[blobIndex], - Chunks: make(map[corev2.QuorumID][]*encoding.Frame), + BlobCertificate: headers[blobIndex], + Chunks: make(map[corev2.QuorumID][]*encoding.Frame), }) } if len(frames) == 0 { @@ -244,7 +246,7 @@ func TestValidationSucceeds(t *testing.T) { for _, operatorCount := range operatorCounts { // batch can only be tested per operatorCount, because the assignment would be wrong otherwise - headers := make([]corev2.BlobHeader, 0) + headers := make([]corev2.BlobCertificate, 0) blobs := make([][]byte, 0) for _, blobLength := range blobLengths { for i := 0; i < numBlob; i++ { diff --git a/core/v2/types.go b/core/v2/types.go index c4418e3ba..d314471f7 100644 --- a/core/v2/types.go +++ b/core/v2/types.go @@ -40,8 +40,11 @@ type BlobHeader struct { // QuorumInfos contains the quorum specific parameters for the blob QuorumNumbers []uint8 - // ReferenceBlockNumber is the block number of the block at which the operator state will be referenced - ReferenceBlockNumber uint64 + // PaymentHeader contains the payment information for the blob + PaymentHeader + + // AuthenticationData is the signature of the blob header by the account ID + AuthenticationData []byte `json:"authentication_data"` } func (b *BlobHeader) GetEncodingParams() (encoding.EncodingParams, error) { @@ -61,9 +64,6 @@ func (b *BlobHeader) GetEncodingParams() (encoding.EncodingParams, error) { } type PaymentHeader struct { - // BlobKey is the hash of the blob header - BlobKey [32]byte - // AccountID is the account that is paying for the blob to be stored. AccountID is hexadecimal representation of the ECDSA public key AccountID string @@ -71,9 +71,16 @@ type PaymentHeader struct { CumulativePayment uint64 BinIndex uint64 +} - // AuthenticationData is the signature of the blob header by the account ID - AuthenticationData []byte `json:"authentication_data"` +type BlobCertificate struct { + BlobHeader + + // ReferenceBlockNumber is the block number of the block at which the operator state will be referenced + ReferenceBlockNumber uint64 + + // RelayKeys + RelayKeys []uint16 } type BlobVersionParameters struct { diff --git a/core/v2/validator.go b/core/v2/validator.go index 6d603d5a8..2ba9b865d 100644 --- a/core/v2/validator.go +++ b/core/v2/validator.go @@ -16,7 +16,7 @@ var ( ) type BlobShard struct { - BlobHeader + BlobCertificate Chunks map[QuorumID][]*encoding.Frame } From 26c6684437ceaaf833d1df2793929eab4b9d6129 Mon Sep 17 00:00:00 2001 From: Robert Raynor <35671663+mooselumph@users.noreply.github.com> Date: Tue, 15 Oct 2024 22:57:18 +0000 Subject: [PATCH 4/4] Lint fixes --- core/v2/aggregation.go | 4 ++-- core/v2/core_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/v2/aggregation.go b/core/v2/aggregation.go index 6a17e95f3..50a3af5f3 100644 --- a/core/v2/aggregation.go +++ b/core/v2/aggregation.go @@ -200,7 +200,7 @@ func (a *StdSignatureAggregator) ReceiveSignatures(ctx context.Context, state *c // Add to agg signature if aggSigs[quorumID] == nil { - aggSigs[quorumID] = &bn254.Signature{sig.Clone()} + aggSigs[quorumID] = &bn254.Signature{G1Point: sig.Clone()} aggPubKeys[quorumID] = op.PubkeyG2.Clone() } else { aggSigs[quorumID].Add(sig.G1Point) @@ -286,7 +286,7 @@ func (a *StdSignatureAggregator) AggregateSignatures(ctx context.Context, ics ch for _, quorumID := range quorumIDs { sig := quorumAttestation.AggSignature[quorumID] if aggSig == nil { - aggSig = &bn254.Signature{sig.G1Point.Clone()} + aggSig = &bn254.Signature{G1Point: sig.G1Point.Clone()} } else { aggSig.Add(sig.G1Point) } diff --git a/core/v2/core_test.go b/core/v2/core_test.go index 5b3d153f8..68522e32d 100644 --- a/core/v2/core_test.go +++ b/core/v2/core_test.go @@ -2,8 +2,8 @@ package corev2_test import ( "context" + "crypto/rand" "fmt" - "math/rand" "os" "runtime" "testing"