Skip to content
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

Create a blob verification utility #1066

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions api/clients/v2/verification/blob_verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package verification

import (
"context"
"fmt"

"github.com/Layr-Labs/eigenda/common"

disperser "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
verifierBindings "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDABlobVerifier"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
)

// BlobVerifier is responsible for making eth calls against the BlobVerifier contract to ensure cryptographic and
Copy link
Contributor

Choose a reason for hiding this comment

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

add link to BlobVerifier contract?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean like this? I don't like that this sort of link is instantly out of date, but I don't know of a better way to link here

function verifyBlobV2FromSignedBatch(

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes did mean like this. We can also just link to the contract itself, in the master branch, so that it doesn't get out of date instantly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added 8bba429a

// structural integrity of V2 certificates
type BlobVerifier struct {
// go binding around the EigenDABlobVerifier ethereum contract
blobVerifierCaller *verifierBindings.ContractEigenDABlobVerifierCaller
}

// NewBlobVerifier constructs a BlobVerifier
func NewBlobVerifier(
ethClient *common.EthClient, // the eth client, which should already be set up
blobVerifierAddress string, // the hex address of the EigenDABlobVerifier contract
) (*BlobVerifier, error) {

verifierCaller, err := verifierBindings.NewContractEigenDABlobVerifierCaller(
gethcommon.HexToAddress(blobVerifierAddress),
*ethClient)

if err != nil {
return nil, fmt.Errorf("bind to verifier contract at %s: %s", blobVerifierAddress, err)
}

return &BlobVerifier{
blobVerifierCaller: verifierCaller,
}, nil
}

// VerifyBlobV2FromSignedBatch calls the verifyBlobV2FromSignedBatch view function on the EigenDABlobVerifier contract
//
// This method returns nil if the blob is successfully verified. Otherwise, it returns an error.
samlaf marked this conversation as resolved.
Show resolved Hide resolved
//
// It is the responsibility of the caller to configure a timeout on the ctx, if a timeout is required.
func (v *BlobVerifier) VerifyBlobV2FromSignedBatch(
ctx context.Context,
// The signed batch that contains the blob being verified. This is obtained from the disperser, and is used
// to verify that the described blob actually exists in a valid batch.
signedBatch *disperser.SignedBatch,
// Contains all necessary information about the blob, so that it can be verified.
blobVerificationProof *disperser.BlobVerificationInfo,
) error {
convertedSignedBatch, err := verifierBindings.ConvertSignedBatch(signedBatch)
if err != nil {
return fmt.Errorf("convert signed batch: %s", err)
}

convertedBlobVerificationProof, err := verifierBindings.ConvertVerificationProof(blobVerificationProof)
if err != nil {
return fmt.Errorf("convert blob verification proof: %s", err)
}

err = v.blobVerifierCaller.VerifyBlobV2FromSignedBatch(
&bind.CallOpts{Context: ctx},
Copy link

Choose a reason for hiding this comment

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

knit - is there any utility in allowing a configurable user timeout for simulation calls?

Copy link

Choose a reason for hiding this comment

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

if there's not a default timeout used by the binding wrapper then we may wanna instate given this could result in the connection infinitely hanging

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a comment clarifying that the caller is responsible for any timeout c752e5b1

*convertedSignedBatch,
*convertedBlobVerificationProof)

if err != nil {
return fmt.Errorf("verify blob v2 from signed batch: %s", err)
}

return nil
}
227 changes: 227 additions & 0 deletions contracts/bindings/EigenDABlobVerifier/conversion_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package contractEigenDABlobVerifier

import (
"fmt"
"math"
"math/big"

"github.com/Layr-Labs/eigenda/api/grpc/common"
commonv2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2"
disperserv2 "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
"github.com/Layr-Labs/eigenda/core"
"github.com/consensys/gnark-crypto/ecc/bn254"
)

func ConvertSignedBatch(inputBatch *disperserv2.SignedBatch) (*SignedBatch, error) {
convertedBatchHeader, err := convertBatchHeader(inputBatch.GetHeader())
if err != nil {
return nil, fmt.Errorf("convert batch header: %s", err)
}

convertedAttestation, err := convertAttestation(inputBatch.GetAttestation())
if err != nil {
return nil, fmt.Errorf("convert attestation: %s", err)
}

outputSignedBatch := &SignedBatch{
BatchHeader: *convertedBatchHeader,
Attestation: *convertedAttestation,
}

return outputSignedBatch, nil
}

func convertBatchHeader(inputHeader *commonv2.BatchHeader) (*BatchHeaderV2, error) {
var outputBatchRoot [32]byte

inputBatchRoot := inputHeader.GetBatchRoot()
if len(inputBatchRoot) != 32 {
return nil, fmt.Errorf("BatchRoot must be 32 bytes (length was %d)", len(inputBatchRoot))
}
copy(outputBatchRoot[:], inputBatchRoot[:])

inputReferenceBlockNumber := inputHeader.GetReferenceBlockNumber()
if inputReferenceBlockNumber > math.MaxUint32 {
return nil, fmt.Errorf(
"ReferenceBlockNumber overflow: value was %d, but max allowable value is %d",
inputReferenceBlockNumber,
math.MaxUint32)
}

convertedHeader := &BatchHeaderV2{
BatchRoot: outputBatchRoot,
ReferenceBlockNumber: uint32(inputReferenceBlockNumber),
}

return convertedHeader, nil
}

func convertAttestation(inputAttestation *disperserv2.Attestation) (*Attestation, error) {
nonSignerPubkeys, err := repeatedBytesToG1Points(inputAttestation.GetNonSignerPubkeys())
if err != nil {
return nil, fmt.Errorf("convert non signer pubkeys to g1 points: %s", err)
}

quorumApks, err := repeatedBytesToG1Points(inputAttestation.GetQuorumApks())
if err != nil {
return nil, fmt.Errorf("convert quorum apks to g1 points: %s", err)
}

sigma, err := bytesToBN254G1Point(inputAttestation.GetSigma())
if err != nil {
return nil, fmt.Errorf("convert sigma to g1 point: %s", err)
}

apkG2, err := bytesToBN254G2Point(inputAttestation.GetApkG2())
if err != nil {
return nil, fmt.Errorf("convert apk g2 to g2 point: %s", err)
}

convertedAttestation := &Attestation{
NonSignerPubkeys: nonSignerPubkeys,
QuorumApks: quorumApks,
Sigma: *sigma,
ApkG2: *apkG2,
QuorumNumbers: inputAttestation.GetQuorumNumbers(),
}

return convertedAttestation, nil
}

func ConvertVerificationProof(inputVerificationInfo *disperserv2.BlobVerificationInfo) (*BlobVerificationProofV2, error) {
convertedBlobCertificate, err := convertBlobCertificate(inputVerificationInfo.GetBlobCertificate())

if err != nil {
return nil, fmt.Errorf("convert blob certificate: %s", err)
}

return &BlobVerificationProofV2{
BlobCertificate: *convertedBlobCertificate,
BlobIndex: inputVerificationInfo.GetBlobIndex(),
InclusionProof: inputVerificationInfo.GetInclusionProof(),
}, nil
}

func convertBlobCertificate(inputCertificate *commonv2.BlobCertificate) (*BlobCertificate, error) {
convertedBlobHeader, err := convertBlobHeader(inputCertificate.GetBlobHeader())
if err != nil {
return nil, fmt.Errorf("convert blob header: %s", err)
}

return &BlobCertificate{
BlobHeader: *convertedBlobHeader,
RelayKeys: inputCertificate.GetRelays(),
}, nil
}

func convertBlobHeader(inputHeader *commonv2.BlobHeader) (*BlobHeaderV2, error) {
inputVersion := inputHeader.GetVersion()
if inputVersion > math.MaxUint16 {
return nil, fmt.Errorf(
"version overflow: value was %d, but max allowable value is %d",
inputVersion,
math.MaxUint16)
}

var quorumNumbers []byte
for _, quorumNumber := range inputHeader.GetQuorumNumbers() {
if quorumNumber > math.MaxUint8 {
return nil, fmt.Errorf(
"quorum number overflow: value was %d, but max allowable value is %d",
quorumNumber,
uint8(math.MaxUint8))
}

quorumNumbers = append(quorumNumbers, byte(quorumNumber))
}

convertedBlobCommitment, err := convertBlobCommitment(inputHeader.GetCommitment())
if err != nil {
return nil, fmt.Errorf("convert blob commitment: %s", err)
}

paymentHeaderHash, err := core.ConvertToPaymentMetadata(inputHeader.GetPaymentHeader()).Hash()
if err != nil {
return nil, fmt.Errorf("hash payment header: %s", err)
}

return &BlobHeaderV2{
Version: uint16(inputVersion),
QuorumNumbers: quorumNumbers,
Commitment: *convertedBlobCommitment,
PaymentHeaderHash: paymentHeaderHash,
}, nil
}

func convertBlobCommitment(inputCommitment *common.BlobCommitment) (*BlobCommitment, error) {
convertedCommitment, err := bytesToBN254G1Point(inputCommitment.GetCommitment())
if err != nil {
return nil, fmt.Errorf("convert commitment to g1 point: %s", err)
}

convertedLengthCommitment, err := bytesToBN254G2Point(inputCommitment.GetLengthCommitment())
if err != nil {
return nil, fmt.Errorf("convert length commitment to g2 point: %s", err)
}

convertedLengthProof, err := bytesToBN254G2Point(inputCommitment.GetLengthProof())
if err != nil {
return nil, fmt.Errorf("convert length proof to g2 point: %s", err)
}

return &BlobCommitment{
Commitment: *convertedCommitment,
LengthCommitment: *convertedLengthCommitment,
LengthProof: *convertedLengthProof,
DataLength: inputCommitment.GetLength(),
}, nil
}

func bytesToBN254G1Point(bytes []byte) (*BN254G1Point, error) {
var g1Point bn254.G1Affine
_, err := g1Point.SetBytes(bytes)

if err != nil {
return nil, fmt.Errorf("deserialize g1 point: %s", err)
}

return &BN254G1Point{
X: g1Point.X.BigInt(new(big.Int)),
Y: g1Point.Y.BigInt(new(big.Int)),
}, nil
}

func bytesToBN254G2Point(bytes []byte) (*BN254G2Point, error) {
var g2Point bn254.G2Affine
_, err := g2Point.SetBytes(bytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add a comment to say, this makes sure bytes is in the subgroup
https://pkg.go.dev/github.com/consensys/gnark-crypto/ecc/bn254#G2Affine.SetBytes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done f31e4a6b


if err != nil {
return nil, fmt.Errorf("deserialize g2 point: %s", err)
}

var x, y [2]*big.Int
x[0] = g2Point.X.A0.BigInt(new(big.Int))
Copy link
Contributor

@ian-shim ian-shim Jan 8, 2025

Choose a reason for hiding this comment

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

Has this been tested?
I'm pretty sure the ordering should be reversed, i.e. X = {X.A1, X.A0}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Has this been tested?

Not yet, integration tests are forthcoming

Good catch, thank you. Fixed

x[1] = g2Point.X.A1.BigInt(new(big.Int))

y[0] = g2Point.Y.A0.BigInt(new(big.Int))
y[1] = g2Point.Y.A1.BigInt(new(big.Int))

return &BN254G2Point{
X: x,
Y: y,
}, nil
}

func repeatedBytesToG1Points(repeatedBytes [][]byte) ([]BN254G1Point, error) {
var outputPoints []BN254G1Point
for _, bytes := range repeatedBytes {
g1Point, err := bytesToBN254G1Point(bytes)
if err != nil {
return nil, fmt.Errorf("deserialize g1 point: %s", err)
}

outputPoints = append(outputPoints, *g1Point)
}

return outputPoints, nil
}
Loading