Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
8 changes: 5 additions & 3 deletions api/clients/v2/disperser_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import (
"sync"
"time"

"github.com/docker/go-units"

"github.com/Layr-Labs/eigenda/api"
disperser_rpc "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
"github.com/Layr-Labs/eigenda/core"
corev2 "github.com/Layr-Labs/eigenda/core/v2"
dispv2 "github.com/Layr-Labs/eigenda/disperser/common/v2"
"github.com/Layr-Labs/eigenda/encoding"
"github.com/Layr-Labs/eigenda/encoding/rs"
"github.com/docker/go-units"
"google.golang.org/grpc"
)

Expand Down Expand Up @@ -297,14 +296,17 @@ func (c *disperserClient) GetPaymentState(ctx context.Context) (*disperser_rpc.G
return nil, fmt.Errorf("error getting signer's account ID: %w", err)
}

signature, err := c.signer.SignPaymentStateRequest()
timestamp := uint64(time.Now().UnixNano())

signature, err := c.signer.SignPaymentStateRequest(timestamp)
if err != nil {
return nil, fmt.Errorf("error signing payment state request: %w", err)
}

request := &disperser_rpc.GetPaymentStateRequest{
AccountId: accountID.Hex(),
Signature: signature,
Timestamp: timestamp,
}
return c.client.GetPaymentState(ctx, request)
}
Expand Down
8 changes: 8 additions & 0 deletions api/docs/disperser_v2.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions api/docs/eigenda-protos.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

258 changes: 135 additions & 123 deletions api/grpc/disperser/v2/disperser_v2.pb.go

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions api/hashing/payment_state_hashing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package hashing

import (
"fmt"
pb "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
)

// HashGetPaymentStateRequestFromFields hashes the given GetPaymentStateRequest from accountId and timestamp
func HashGetPaymentStateRequestFromFields(accountId common.Address, timestamp uint64) ([]byte, error) {
hasher := sha3.NewLegacyKeccak256()

// Hash the accountId
err := hashByteArray(hasher, accountId.Bytes())
if err != nil {
return nil, fmt.Errorf("failed to hash account id: %w", err)
}

// Hash the timestamp
hashUint64(hasher, timestamp)

return hasher.Sum(nil), nil
}

// HashGetPaymentStateRequestFromRequest hashes the given GetPaymentStateRequest from request
func HashGetPaymentStateRequestFromRequest(request *pb.GetPaymentStateRequest) ([]byte, error) {
accountId := common.HexToAddress(request.GetAccountId())
timestamp := request.GetTimestamp()

return HashGetPaymentStateRequestFromFields(accountId, timestamp)
}
3 changes: 3 additions & 0 deletions api/proto/disperser/v2/disperser_v2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ message GetPaymentStateRequest {
string account_id = 1;
// Signature over the account ID
bytes signature = 2;
// Timestamp of the request in nanoseconds since the Unix epoch. If too far out of sync with the server's clock,
// request may be rejected.
uint64 timestamp = 3;
}

// GetPaymentStateReply contains the payment state of an account.
Expand Down
59 changes: 40 additions & 19 deletions core/auth/v2/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package v2_test

import (
"crypto/sha256"
disperser_rpc "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
"github.com/Layr-Labs/eigenda/common/replay"
"math/big"
"testing"
"time"

"github.com/Layr-Labs/eigenda/core"
auth "github.com/Layr-Labs/eigenda/core/auth/v2"
Expand All @@ -16,13 +19,16 @@ import (
)

var (
privateKeyHex = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
privateKeyHex = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
maxPastAge = 5 * time.Minute
maxFutureAge = 5 * time.Minute
fixedTimestamp = uint64(1609459200000000000)
)

func TestAuthentication(t *testing.T) {
signer, err := auth.NewLocalBlobRequestSigner(privateKeyHex)
assert.NoError(t, err)
authenticator := auth.NewAuthenticator()
blobRequestAuthenticator := auth.NewBlobRequestAuthenticator()

accountId, err := signer.GetAccountID()
assert.NoError(t, err)
Expand All @@ -32,15 +38,14 @@ func TestAuthentication(t *testing.T) {
signature, err := signer.SignBlobRequest(header)
assert.NoError(t, err)

err = authenticator.AuthenticateBlobRequest(header, signature)
err = blobRequestAuthenticator.AuthenticateBlobRequest(header, signature)
assert.NoError(t, err)

}

func TestAuthenticationFail(t *testing.T) {
signer, err := auth.NewLocalBlobRequestSigner(privateKeyHex)
assert.NoError(t, err)
authenticator := auth.NewAuthenticator()
blobRequestAuthenticator := auth.NewBlobRequestAuthenticator()

accountId, err := signer.GetAccountID()
assert.NoError(t, err)
Expand All @@ -55,7 +60,7 @@ func TestAuthenticationFail(t *testing.T) {
signature, err := signer.SignBlobRequest(header)
assert.NoError(t, err)

err = authenticator.AuthenticateBlobRequest(header, signature)
err = blobRequestAuthenticator.AuthenticateBlobRequest(header, signature)
assert.Error(t, err)
}

Expand Down Expand Up @@ -119,38 +124,43 @@ func testHeader(t *testing.T, accountID gethcommon.Address) *corev2.BlobHeader {
func TestAuthenticatePaymentStateRequestValid(t *testing.T) {
signer, err := auth.NewLocalBlobRequestSigner(privateKeyHex)
assert.NoError(t, err)
authenticator := auth.NewAuthenticator()
paymentStateAuthenticator := auth.NewPaymentStateAuthenticator(maxPastAge, maxFutureAge)
paymentStateAuthenticator.ReplayGuardian = replay.NewNoOpReplayGuardian()

signature, err := signer.SignPaymentStateRequest()
signature, err := signer.SignPaymentStateRequest(fixedTimestamp)
assert.NoError(t, err)
assert.NotNil(t, signature)

accountId, err := signer.GetAccountID()
assert.NoError(t, err)

err = authenticator.AuthenticatePaymentStateRequest(signature, accountId)
request := mockGetPaymentStateRequest(accountId, signature)

err = paymentStateAuthenticator.AuthenticatePaymentStateRequest(accountId, request)
assert.NoError(t, err)
}

func TestAuthenticatePaymentStateRequestInvalidSignatureLength(t *testing.T) {
authenticator := auth.NewAuthenticator()
paymentStateAuthenticator := auth.NewPaymentStateAuthenticator(maxPastAge, maxFutureAge)
request := mockGetPaymentStateRequest(gethcommon.HexToAddress("0x123"), []byte{1, 2, 3})

err := authenticator.AuthenticatePaymentStateRequest([]byte{1, 2, 3}, gethcommon.HexToAddress("0x123"))
err := paymentStateAuthenticator.AuthenticatePaymentStateRequest(gethcommon.HexToAddress("0x123"), request)
assert.Error(t, err)
assert.Contains(t, err.Error(), "signature length is unexpected")
}

func TestAuthenticatePaymentStateRequestInvalidPublicKey(t *testing.T) {
authenticator := auth.NewAuthenticator()

err := authenticator.AuthenticatePaymentStateRequest(make([]byte, 65), gethcommon.Address{})
paymentStateAuthenticator := auth.NewPaymentStateAuthenticator(maxPastAge, maxFutureAge)
request := mockGetPaymentStateRequest(gethcommon.Address{}, make([]byte, 65))
err := paymentStateAuthenticator.AuthenticatePaymentStateRequest(gethcommon.Address{}, request)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to recover public key from signature")
}

func TestAuthenticatePaymentStateRequestSignatureMismatch(t *testing.T) {
signer, err := auth.NewLocalBlobRequestSigner(privateKeyHex)
assert.NoError(t, err)
authenticator := auth.NewAuthenticator()
paymentStateAuthenticator := auth.NewPaymentStateAuthenticator(maxPastAge, maxFutureAge)

// Create a different signer with wrong private key
wrongSigner, err := auth.NewLocalBlobRequestSigner("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded")
Expand All @@ -160,18 +170,20 @@ func TestAuthenticatePaymentStateRequestSignatureMismatch(t *testing.T) {
accountId, err := signer.GetAccountID()
assert.NoError(t, err)

signature, err := wrongSigner.SignPaymentStateRequest()
signature, err := wrongSigner.SignPaymentStateRequest(uint64(time.Now().UnixNano()))
assert.NoError(t, err)

err = authenticator.AuthenticatePaymentStateRequest(signature, accountId)
request := mockGetPaymentStateRequest(accountId, signature)

err = paymentStateAuthenticator.AuthenticatePaymentStateRequest(accountId, request)
assert.Error(t, err)
assert.Contains(t, err.Error(), "signature doesn't match with provided public key")
}

func TestAuthenticatePaymentStateRequestCorruptedSignature(t *testing.T) {
signer, err := auth.NewLocalBlobRequestSigner(privateKeyHex)
assert.NoError(t, err)
authenticator := auth.NewAuthenticator()
paymentStateAuthenticator := auth.NewPaymentStateAuthenticator(maxPastAge, maxFutureAge)

accountId, err := signer.GetAccountID()
assert.NoError(t, err)
Expand All @@ -182,7 +194,16 @@ func TestAuthenticatePaymentStateRequestCorruptedSignature(t *testing.T) {

// Corrupt the signature
signature[0] ^= 0x01
request := mockGetPaymentStateRequest(accountId, signature)

err = authenticator.AuthenticatePaymentStateRequest(signature, accountId)
err = paymentStateAuthenticator.AuthenticatePaymentStateRequest(accountId, request)
assert.Error(t, err)
}

func mockGetPaymentStateRequest(accountId gethcommon.Address, signature []byte) *disperser_rpc.GetPaymentStateRequest {
return &disperser_rpc.GetPaymentStateRequest{
AccountId: accountId.Hex(),
Signature: signature,
Timestamp: fixedTimestamp,
}
}
42 changes: 36 additions & 6 deletions core/auth/v2/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@ import (
"crypto/sha256"
"errors"
"fmt"
pb "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
"github.com/Layr-Labs/eigenda/api/hashing"
"time"

"github.com/Layr-Labs/eigenda/common/replay"
core "github.com/Layr-Labs/eigenda/core/v2"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)

type authenticator struct{}
type authenticator struct {
ReplayGuardian replay.ReplayGuardian
}

func NewAuthenticator() *authenticator {
return &authenticator{}
// NewBlobRequestAuthenticator creates an authenticator for blob requests.
// ReplayGuardian is not used for blob requests.
func NewBlobRequestAuthenticator() *authenticator {
return &authenticator{
ReplayGuardian: nil, // Not needed for blob requests
}
}

// NewPaymentStateAuthenticator creates an authenticator for payment state requests,
// which requires replay protection.
func NewPaymentStateAuthenticator(maxTimeInPast, maxTimeInFuture time.Duration) *authenticator {
return &authenticator{
ReplayGuardian: replay.NewReplayGuardian(time.Now, maxTimeInPast, maxTimeInFuture),
}
}

var _ core.BlobRequestAuthenticator = &authenticator{}
Expand Down Expand Up @@ -46,16 +64,23 @@ func (*authenticator) AuthenticateBlobRequest(header *core.BlobHeader, signature
}

// AuthenticatePaymentStateRequest verifies the signature of the payment state request
// The signature is signed over the byte representation of the account ID
// The signature is signed over the byte representation of the account ID and requestHash
// See implementation of BlobRequestSigner.SignPaymentStateRequest for more details
func (*authenticator) AuthenticatePaymentStateRequest(sig []byte, accountAddr common.Address) error {
func (a *authenticator) AuthenticatePaymentStateRequest(accountAddr common.Address, request *pb.GetPaymentStateRequest) error {
sig := request.GetSignature()
// Ensure the signature is 65 bytes (Recovery ID is the last byte)
if len(sig) != 65 {
return fmt.Errorf("signature length is unexpected: %d", len(sig))
}

requestHash, err := hashing.HashGetPaymentStateRequestFromRequest(request)
if err != nil {
return fmt.Errorf("failed to hash request: %w", err)
}
accountAddrWithHash := append(accountAddr.Bytes(), requestHash...)
hash := sha256.Sum256(accountAddrWithHash)

// Verify the signature
hash := sha256.Sum256(accountAddr.Bytes())
sigPublicKeyECDSA, err := crypto.SigToPub(hash[:], sig)
if err != nil {
return fmt.Errorf("failed to recover public key from signature: %v", err)
Expand All @@ -67,5 +92,10 @@ func (*authenticator) AuthenticatePaymentStateRequest(sig []byte, accountAddr co
return errors.New("signature doesn't match with provided public key")
}

timestamp := request.GetTimestamp()
if err := a.ReplayGuardian.VerifyRequest(requestHash, time.Unix(0, int64(timestamp))); err != nil {
return fmt.Errorf("failed to verify request: %v", err)
}

return nil
}
13 changes: 10 additions & 3 deletions core/auth/v2/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/ecdsa"
"crypto/sha256"
"fmt"
"github.com/Layr-Labs/eigenda/api/hashing"

core "github.com/Layr-Labs/eigenda/core/v2"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -44,13 +45,19 @@ func (s *LocalBlobRequestSigner) SignBlobRequest(header *core.BlobHeader) ([]byt
return sig, nil
}

func (s *LocalBlobRequestSigner) SignPaymentStateRequest() ([]byte, error) {
func (s *LocalBlobRequestSigner) SignPaymentStateRequest(timestamp uint64) ([]byte, error) {
accountId, err := s.GetAccountID()
if err != nil {
return nil, fmt.Errorf("failed to get account ID: %v", err)
}

hash := sha256.Sum256(accountId.Bytes())
requestHash, err := hashing.HashGetPaymentStateRequestFromFields(accountId, timestamp)
if err != nil {
return nil, fmt.Errorf("failed to hash request: %w", err)
}

accountIdWithHash := append(accountId.Bytes(), requestHash...)
hash := sha256.Sum256(accountIdWithHash)
// Sign the account ID using the private key
sig, err := crypto.Sign(hash[:], s.PrivateKey)
if err != nil {
Expand All @@ -77,7 +84,7 @@ func (s *LocalNoopSigner) SignBlobRequest(header *core.BlobHeader) ([]byte, erro
return nil, fmt.Errorf("noop signer cannot sign blob request")
}

func (s *LocalNoopSigner) SignPaymentStateRequest() ([]byte, error) {
func (s *LocalNoopSigner) SignPaymentStateRequest(timestamp uint64) ([]byte, error) {
return nil, fmt.Errorf("noop signer cannot sign payment state request")
}

Expand Down
Loading
Loading