-
Notifications
You must be signed in to change notification settings - Fork 191
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
Disperser auth #984
Disperser auth #984
Changes from 56 commits
0936f5f
d36424e
342d500
fd2ab69
e2e3fef
4373095
df38980
d54c7f7
083d67d
0a5c91a
8e9c16b
f348550
01b7ece
e09a77c
290c93c
5ba4b7b
e094371
db0885a
da2ca6c
b04fcfa
29e7e7d
fb1ebfb
6ca7c9b
3348883
e5966f0
f28a5d4
f0c6f4e
6a00256
27d5b8a
bbd82ec
157c2ea
d793501
b81e430
7327db1
8cfa971
306746e
b41d729
d709520
2e44b02
aebda81
260bf79
dbd0d24
5dbb178
dcd3e06
11cf45a
3580bed
f6b008f
1493ded
feab7a1
52f2354
7c5cc93
c83db79
e5cb69c
20216c4
3c325ef
02ed38a
80c7e6d
2b6cbb2
a9155a0
987b26c
26ec866
cd1bfc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package mock | ||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
"github.com/Layr-Labs/eigenda/api/clients/v2" | ||
v2 "github.com/Layr-Labs/eigenda/api/grpc/node/v2" | ||
"github.com/Layr-Labs/eigenda/node/auth" | ||
) | ||
|
||
var _ clients.DispersalRequestSigner = &staticRequestSigner{} | ||
|
||
// StaticRequestSigner is a DispersalRequestSigner that signs requests with a static key (i.e. it doesn't use AWS KMS). | ||
// Useful for testing. | ||
type staticRequestSigner struct { | ||
key *ecdsa.PrivateKey | ||
} | ||
|
||
func NewStaticRequestSigner(key *ecdsa.PrivateKey) clients.DispersalRequestSigner { | ||
return &staticRequestSigner{ | ||
key: key, | ||
} | ||
} | ||
|
||
func (s *staticRequestSigner) SignStoreChunksRequest( | ||
ctx context.Context, | ||
request *v2.StoreChunksRequest) ([]byte, error) { | ||
|
||
return auth.SignStoreChunksRequest(s.key, request) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package clients | |
import ( | ||
"context" | ||
"fmt" | ||
"github.com/Layr-Labs/eigenda/node/auth" | ||
"sync" | ||
|
||
commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2" | ||
|
@@ -24,21 +25,23 @@ type NodeClient interface { | |
} | ||
|
||
type nodeClient struct { | ||
config *NodeClientConfig | ||
initOnce sync.Once | ||
conn *grpc.ClientConn | ||
config *NodeClientConfig | ||
initOnce sync.Once | ||
conn *grpc.ClientConn | ||
requestSigner DispersalRequestSigner | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A slightly different structuring of it: shall we pass in an interface, not a concrete implementation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused about this comment.
|
||
|
||
dispersalClient nodegrpc.DispersalClient | ||
} | ||
|
||
var _ NodeClient = (*nodeClient)(nil) | ||
|
||
func NewNodeClient(config *NodeClientConfig) (*nodeClient, error) { | ||
func NewNodeClient(config *NodeClientConfig, requestSigner DispersalRequestSigner) (NodeClient, error) { | ||
if config == nil || config.Hostname == "" || config.Port == "" { | ||
return nil, fmt.Errorf("invalid config: %v", config) | ||
} | ||
return &nodeClient{ | ||
config: config, | ||
config: config, | ||
requestSigner: requestSigner, | ||
}, nil | ||
} | ||
|
||
|
@@ -60,16 +63,28 @@ func (c *nodeClient) StoreChunks(ctx context.Context, batch *corev2.Batch) (*cor | |
} | ||
} | ||
|
||
// Call the gRPC method to store chunks | ||
response, err := c.dispersalClient.StoreChunks(ctx, &nodegrpc.StoreChunksRequest{ | ||
request := &nodegrpc.StoreChunksRequest{ | ||
Batch: &commonpb.Batch{ | ||
Header: &commonpb.BatchHeader{ | ||
BatchRoot: batch.BatchHeader.BatchRoot[:], | ||
ReferenceBlockNumber: batch.BatchHeader.ReferenceBlockNumber, | ||
}, | ||
BlobCertificates: blobCerts, | ||
}, | ||
}) | ||
DisperserID: auth.EigenLabsDisperserID, // this will need to be updated when dispersers are decentralized | ||
} | ||
|
||
if c.requestSigner != nil { | ||
// Sign the request to store chunks | ||
signature, err := c.requestSigner.SignStoreChunksRequest(ctx, request) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to sign store chunks request: %v", err) | ||
} | ||
request.Signature = signature | ||
} | ||
|
||
// Call the gRPC method to store chunks | ||
response, err := c.dispersalClient.StoreChunks(ctx, request) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package clients | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we can update the file name as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, forgot to rename the file. 😅 Fixed. |
||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
"fmt" | ||
grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" | ||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/node/auth" | ||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/kms" | ||
) | ||
|
||
// DispersalRequestSigner encapsulates the logic for signing GetChunks requests. | ||
type DispersalRequestSigner interface { | ||
// SignStoreChunksRequest signs a StoreChunksRequest. Does not modify the request | ||
// (i.e. it does not insert the signature). | ||
SignStoreChunksRequest(ctx context.Context, request *grpc.StoreChunksRequest) ([]byte, error) | ||
} | ||
|
||
var _ DispersalRequestSigner = &requestSigner{} | ||
|
||
type requestSigner struct { | ||
keyID string | ||
publicKey *ecdsa.PublicKey | ||
keyManager *kms.Client | ||
} | ||
|
||
// NewDispersalRequestSigner creates a new DispersalRequestSigner. | ||
func NewDispersalRequestSigner( | ||
ctx context.Context, | ||
region string, | ||
endpoint string, | ||
keyID string) (DispersalRequestSigner, error) { | ||
|
||
keyManager := kms.New(kms.Options{ | ||
Region: region, | ||
BaseEndpoint: aws.String(endpoint), | ||
}) | ||
|
||
key, err := common.LoadPublicKeyKMS(ctx, keyManager, keyID) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get ecdsa public key: %w", err) | ||
} | ||
|
||
return &requestSigner{ | ||
keyID: keyID, | ||
publicKey: key, | ||
keyManager: keyManager, | ||
}, nil | ||
} | ||
|
||
func (s *requestSigner) SignStoreChunksRequest(ctx context.Context, request *grpc.StoreChunksRequest) ([]byte, error) { | ||
hash := auth.HashStoreChunksRequest(request) | ||
|
||
signature, err := common.SignKMS(ctx, s.keyManager, s.keyID, s.publicKey, hash) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to sign request: %w", err) | ||
} | ||
|
||
return signature, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package clients | ||
|
||
import ( | ||
"context" | ||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/common/testutils/random" | ||
"github.com/Layr-Labs/eigenda/inabox/deploy" | ||
"github.com/Layr-Labs/eigenda/node/auth" | ||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/kms" | ||
"github.com/aws/aws-sdk-go-v2/service/kms/types" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ory/dockertest/v3" | ||
"github.com/stretchr/testify/require" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"testing" | ||
) | ||
|
||
var ( | ||
dockertestPool *dockertest.Pool | ||
dockertestResource *dockertest.Resource | ||
) | ||
|
||
const ( | ||
localstackPort = "4570" | ||
localstackHost = "http://0.0.0.0:4570" | ||
region = "us-east-1" | ||
) | ||
|
||
func setup(t *testing.T) { | ||
deployLocalStack := !(os.Getenv("DEPLOY_LOCALSTACK") == "false") | ||
|
||
_, b, _, _ := runtime.Caller(0) | ||
rootPath := filepath.Join(filepath.Dir(b), "../../..") | ||
changeDirectory(filepath.Join(rootPath, "inabox")) | ||
|
||
if deployLocalStack { | ||
var err error | ||
dockertestPool, dockertestResource, err = deploy.StartDockertestWithLocalstackContainer(localstackPort) | ||
require.NoError(t, err) | ||
} | ||
} | ||
|
||
func changeDirectory(path string) { | ||
err := os.Chdir(path) | ||
if err != nil { | ||
|
||
currentDirectory, err := os.Getwd() | ||
if err != nil { | ||
log.Printf("Failed to get current directory. Error: %s", err) | ||
} | ||
|
||
log.Panicf("Failed to change directories. CWD: %s, Error: %s", currentDirectory, err) | ||
} | ||
|
||
newDir, err := os.Getwd() | ||
if err != nil { | ||
log.Panicf("Failed to get working directory. Error: %s", err) | ||
} | ||
log.Printf("Current Working Directory: %s\n", newDir) | ||
} | ||
|
||
func teardown() { | ||
deployLocalStack := !(os.Getenv("DEPLOY_LOCALSTACK") == "false") | ||
|
||
if deployLocalStack { | ||
deploy.PurgeDockertestResources(dockertestPool, dockertestResource) | ||
} | ||
} | ||
|
||
func TestRequestSigning(t *testing.T) { | ||
rand := random.NewTestRandom(t) | ||
setup(t) | ||
defer teardown() | ||
|
||
keyManager := kms.New(kms.Options{ | ||
Region: region, | ||
BaseEndpoint: aws.String(localstackHost), | ||
}) | ||
|
||
for i := 0; i < 10; i++ { | ||
createKeyOutput, err := keyManager.CreateKey(context.Background(), &kms.CreateKeyInput{ | ||
KeySpec: types.KeySpecEccSecgP256k1, | ||
KeyUsage: types.KeyUsageTypeSignVerify, | ||
}) | ||
require.NoError(t, err) | ||
|
||
keyID := *createKeyOutput.KeyMetadata.KeyId | ||
|
||
key, err := common.LoadPublicKeyKMS(context.Background(), keyManager, keyID) | ||
require.NoError(t, err) | ||
|
||
publicAddress := crypto.PubkeyToAddress(*key) | ||
|
||
for j := 0; j < 10; j++ { | ||
request := auth.RandomStoreChunksRequest(rand) | ||
request.Signature = nil | ||
|
||
signer, err := NewDispersalRequestSigner(context.Background(), region, localstackHost, keyID) | ||
require.NoError(t, err) | ||
|
||
// Test a valid signature. | ||
signature, err := signer.SignStoreChunksRequest(context.Background(), request) | ||
require.NoError(t, err) | ||
|
||
require.Nil(t, request.Signature) | ||
request.Signature = signature | ||
err = auth.VerifyStoreChunksRequest(publicAddress, request) | ||
require.NoError(t, err) | ||
|
||
// Changing a byte in the middle of the signature should make the verification fail | ||
badSignature := make([]byte, len(signature)) | ||
copy(badSignature, signature) | ||
badSignature[10] = badSignature[10] + 1 | ||
request.Signature = badSignature | ||
err = auth.VerifyStoreChunksRequest(publicAddress, request) | ||
require.Error(t, err) | ||
|
||
// Changing a byte in the middle of the request should make the verification fail | ||
request.DisperserID = request.DisperserID + 1 | ||
request.Signature = signature | ||
err = auth.VerifyStoreChunksRequest(publicAddress, request) | ||
require.Error(t, err) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem to me this is a good dependency direction for api/ to depend on node/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this dependency is no longer present