From 0936f5f5ed0c100ab5751e3d856595770438212b Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 08:55:01 -0600 Subject: [PATCH 01/50] Enable TLS connections between the disperser and the DA nodes. Signed-off-by: Cody Littley --- .gitignore | 3 ++ disperser/auth/generate-cert.sh | 47 ++++++++++++++++++++++++++ disperser/auth/generate-private-key.sh | 5 +++ 3 files changed, 55 insertions(+) create mode 100755 disperser/auth/generate-cert.sh create mode 100755 disperser/auth/generate-private-key.sh diff --git a/.gitignore b/.gitignore index 40fe5f34c5..b5043399c1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ lightnode/docker/args.sh .vscode icicle/* + +# Just to make sure somebody doesn't accidentally commit the disperser's private TLS key. +eigenda-disperser-private.key diff --git a/disperser/auth/generate-cert.sh b/disperser/auth/generate-cert.sh new file mode 100755 index 0000000000..a273cb6b75 --- /dev/null +++ b/disperser/auth/generate-cert.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +if [ -z "${1}" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Used to pass options to the 'openssl req' command. +# It expects a human in the loop, but it's preferable to automate it. +options() { + # Country Name (2 letter code) [AU]: + echo 'US' + # State or Province Name (full name) [Some-State]: + echo 'Washington' + # Locality Name (eg, city) []: + echo 'Seattle' + # Organization Name (eg, company) [Internet Widgits Pty Ltd]: + echo 'Eigen Labs' + # Organizational Unit Name (eg, section) []: + echo 'EigenDA' + # Common Name (e.g. server FQDN or YOUR name) []: + echo 'disperser' + # Email Address []: + echo '.' + # A challenge password []: + echo '.' + # An optional company name []: + echo '.' +} + +# Generate a new certificate signing request. +options | \ + openssl req -new \ + -key "${1}" \ + -noenc \ + -out cert.csr + +# Self sign the certificate. +openssl x509 -req \ + -days 365 \ + -in cert.csr \ + -signkey "${1}" -out eigenda-disperser-public.crt + +# Clean up the certificate signing request. +rm cert.csr + +cowsay "This certificate will expire in one year. Ensure that a new one is made available to node operators before then." \ No newline at end of file diff --git a/disperser/auth/generate-private-key.sh b/disperser/auth/generate-private-key.sh new file mode 100755 index 0000000000..e5e2f9c5ea --- /dev/null +++ b/disperser/auth/generate-private-key.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# This script generates a new private key for the disperser service. + +openssl genrsa -out eigenda-disperser-private.key 4096 \ No newline at end of file From d36424e6ece1505ec714f1dc00ce285ab42d1990 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 09:15:37 -0600 Subject: [PATCH 02/50] Incremental progress. Signed-off-by: Cody Littley --- disperser/auth/generate-cert.sh | 14 +++++++++++++- disperser/auth/next-year.py | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100755 disperser/auth/next-year.py diff --git a/disperser/auth/generate-cert.sh b/disperser/auth/generate-cert.sh index a273cb6b75..7ea6ac4c98 100755 --- a/disperser/auth/generate-cert.sh +++ b/disperser/auth/generate-cert.sh @@ -5,6 +5,15 @@ if [ -z "${1}" ]; then exit 1 fi +if ! command -v cowsay 2>&1 >/dev/null +then + echo "cowsay is not installed. Please install it ('brew install cowsay' or 'apt-get install cowsay')." + exit 1 +fi + +# The location where this script can be found. +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + # Used to pass options to the 'openssl req' command. # It expects a human in the loop, but it's preferable to automate it. options() { @@ -44,4 +53,7 @@ openssl x509 -req \ # Clean up the certificate signing request. rm cert.csr -cowsay "This certificate will expire in one year. Ensure that a new one is made available to node operators before then." \ No newline at end of file +NEXT_YEAR=$("${SCRIPT_DIR}"'/next-year.py') + +cowsay "This certificate will expire on ${NEXT_YEAR}. Ensure that a new one is made available to node operators before then." +cowsay "This certificate will expire on ${NEXT_YEAR}." >> eigenda-disperser-public.crt diff --git a/disperser/auth/next-year.py b/disperser/auth/next-year.py new file mode 100755 index 0000000000..89ad8d7d40 --- /dev/null +++ b/disperser/auth/next-year.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +# This script prints the date one year from now. This can't be done in a shell script since the date +# command differs substantially between Linux and macOS. + +import datetime + +now = datetime.datetime.now() +nextYear = now.replace(year=now.year + 1) +print(nextYear.strftime('%Y-%m-%d')) From 342d5002e0e598781b6c6b7c618f78bf8ffd850b Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 09:23:44 -0600 Subject: [PATCH 03/50] Moar cowsay Signed-off-by: Cody Littley --- disperser/auth/generate-cert.sh | 7 ++-- disperser/auth/test.crt | 40 ++++++++++++++++++++++ disperser/auth/test.key | 60 +++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 disperser/auth/test.crt create mode 100644 disperser/auth/test.key diff --git a/disperser/auth/generate-cert.sh b/disperser/auth/generate-cert.sh index 7ea6ac4c98..e8afb944c2 100755 --- a/disperser/auth/generate-cert.sh +++ b/disperser/auth/generate-cert.sh @@ -53,7 +53,10 @@ openssl x509 -req \ # Clean up the certificate signing request. rm cert.csr +# Document the expiration date of the certificate. NEXT_YEAR=$("${SCRIPT_DIR}"'/next-year.py') -cowsay "This certificate will expire on ${NEXT_YEAR}. Ensure that a new one is made available to node operators before then." -cowsay "This certificate will expire on ${NEXT_YEAR}." >> eigenda-disperser-public.crt +EXPIRATION_MESSAGE=$(cowsay "This certificate expires on ${NEXT_YEAR}.") +echo -e "${EXPIRATION_MESSAGE}\n$(cat eigenda-disperser-public.crt)" > eigenda-disperser-public.crt + +cowsay "This certificate will expire on ${NEXT_YEAR}. Ensure that a new one is made available to node operators before then." \ No newline at end of file diff --git a/disperser/auth/test.crt b/disperser/auth/test.crt new file mode 100644 index 0000000000..2f6a5681e2 --- /dev/null +++ b/disperser/auth/test.crt @@ -0,0 +1,40 @@ + _________________________________________ +< This certificate expires on 2025-12-04. > + ----------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIUdFJ0JieaGN6/fYtgyd37CYyL6uEwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM +B1NlYXR0bGUxEzARBgNVBAoMCkVpZ2VuIExhYnMxEDAOBgNVBAsMB0VpZ2VuREEx +EjAQBgNVBAMMCWRpc3BlcnNlcjAeFw0yNDEyMDQxNTIyMTRaFw0yNTEyMDQxNTIy +MTRaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH +DAdTZWF0dGxlMRMwEQYDVQQKDApFaWdlbiBMYWJzMRAwDgYDVQQLDAdFaWdlbkRB +MRIwEAYDVQQDDAlkaXNwZXJzZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDmkgwhq3n+B2K3Z16ZJ8SKuw6mgsIMUmRwrnkP9PORZujeiMe9wowf1dhy +P9kR8eYnGGksx4ZV4yUjZ4XRlPMP1pAj/jXTTexkbwnZ99RqmJjNFZYtFUVT+qAE +gJtBPRwH7sZlbGVy/HqrQMEPBS2Hg6YSPcfndbiwhnsJsHak59Pkg1tQLPmXwQq8 +nHkuSrp4LHuyOGltxuI33lke+4j3Zz51o1fqHOiANBDMy6UVSTpuT+7nzdrBpQF9 +na32/PO9LpMUoIH+IcRsbT70zIaOsqCqMk2hw8sfjdKapYPt+oMw1bsGKLMfMDsy +cdmUpMFhcLtkwxaUMMaxc5LM/e442NDO78wkA9BpvXFSshMIb6F0otE4caRlI9fG +GkwHlx5+QE3hQZXgMy1FjEjKl63YAnrq2R0x+uheqTLwTvjCqMp3gZUHjCdnawrz +JEj3lp3rU+6/QeV7LxAaNdo0Liq37qvesGgNOicdSuQ6MNZccLDx6h2rywhpjmhi +jrXL7GgH6bBhUTllNyF2PMsuBrSuyggUJ7TR/wM+zXIFcRwb4eWs5sFztTl+PuhI +lFjIBE3UpHf8XFdmSsnXTg7AjVYjqr/gXFn2/cdZGeLPDnBLMm/MbCgR4McO/udu +feRJM2QYd0gVxjXxYc4NJS2SNANuT9c0XTlh01bN8wULsvjJOQIDAQABoyEwHzAd +BgNVHQ4EFgQUUG2BAXYevOBW66TfOFnT2WIyg14wDQYJKoZIhvcNAQELBQADggIB +AA64nu3LzKgB6tMMzLYLJjTCbhkEJS6JOqhjH+m/jVztjEiSQCNmPtklq5Cgyl3U +VvE5oz/bAJVL0wJIFBD1t0F2UCCYq6RtFfDMAr4+kO2E6l3g/5rcq/hEcpLprrVU +mutWLioOX/0bp8ivrJB5PdSt/jrXfjeX+9vhOhE7ZkuWJhvafNEJ+eL/mpfexbbe +wzkHKPiJHZyeyuq9tShGO7bxK3YkFTqNxV/DAJp/CCtHkdOQtVHUK72MURuzld06 +rYY1FQcknD6dD8M/ePbgaWD6fwFc+zbj4D/Wfh4eHKIZ4y+hdCMO4rgcD0v5tssm +PH7IdT61bTuOUqA/zpT1mlH9VqCB/HPnkpAHGz5JgpFAequZ/xivQWszld2gjGep +jp9SXqPybBsUsMyYRphvqzsJaCGG+x9PUF3pvZ/VK+3FQYHjVUwqEcyFPf/5ht3A +VF8DXKNykg3+S0bpX9SNQt8mVo3DxsMIZpnGL6293fPW36qmkZKLquBGpLKK8sWE +2sVmM6SzWqCenV2PVVIj3hu9xap52vvU+81P1xiS7MgwavbfehY8oK0hByRq3ax/ +Fb6uEb2CRVRv9BHKuhoiTCug096c8iMBAMB6u4G4kDMijoKry+UeEJmzrKHKiVP9 +A3iWFWshXRyPkFOu1x+Y21963kmwsItbFrHBBJbOnKp2 +-----END CERTIFICATE----- diff --git a/disperser/auth/test.key b/disperser/auth/test.key new file mode 100644 index 0000000000..649526fc1d --- /dev/null +++ b/disperser/auth/test.key @@ -0,0 +1,60 @@ + _______________________________________ +< this key is for testing purposes only > + --------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +-----BEGIN PRIVATE KEY----- +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 +esM9YhtyGK3Pc3Z4huJmGrpIoBqtSA== +-----END PRIVATE KEY----- From fd2ab69fe29b8bbae9db3cc20e228483f8810903 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 09:24:44 -0600 Subject: [PATCH 04/50] formatting Signed-off-by: Cody Littley --- disperser/auth/generate-cert.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/disperser/auth/generate-cert.sh b/disperser/auth/generate-cert.sh index e8afb944c2..1cbce6b9e7 100755 --- a/disperser/auth/generate-cert.sh +++ b/disperser/auth/generate-cert.sh @@ -55,8 +55,6 @@ rm cert.csr # Document the expiration date of the certificate. NEXT_YEAR=$("${SCRIPT_DIR}"'/next-year.py') - EXPIRATION_MESSAGE=$(cowsay "This certificate expires on ${NEXT_YEAR}.") echo -e "${EXPIRATION_MESSAGE}\n$(cat eigenda-disperser-public.crt)" > eigenda-disperser-public.crt - -cowsay "This certificate will expire on ${NEXT_YEAR}. Ensure that a new one is made available to node operators before then." \ No newline at end of file +cowsay "This certificate expires on ${NEXT_YEAR}. Ensure that a new one is made available to node operators before then." \ No newline at end of file From e2e3fefe8f39207c6dcbbae19a478bb66b1f56a9 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 10:58:15 -0600 Subject: [PATCH 05/50] Started working on unit test. Signed-off-by: Cody Littley --- disperser/auth/auth_test.go | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 disperser/auth/auth_test.go diff --git a/disperser/auth/auth_test.go b/disperser/auth/auth_test.go new file mode 100644 index 0000000000..5017b8449a --- /dev/null +++ b/disperser/auth/auth_test.go @@ -0,0 +1,72 @@ +package auth + +import ( + "context" + "fmt" + "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "net" + "testing" +) + +var _ v2.DispersalServer = (*mockDispersalServer)(nil) + +type mockDispersalServer struct { + v2.DispersalServer +} + +func (s *mockDispersalServer) StoreChunks(context.Context, *v2.StoreChunksRequest) (*v2.StoreChunksReply, error) { + fmt.Printf("called StoreChunks\n") + return nil, nil +} + +func (s *mockDispersalServer) NodeInfo(context.Context, *v2.NodeInfoRequest) (*v2.NodeInfoReply, error) { + fmt.Printf("called NodeInfo\n") + return nil, nil +} + +func buildClient(t *testing.T) v2.DispersalClient { + addr := "0.0.0.0:50051" + + options := make([]grpc.DialOption, 0) + //options = append(options, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) + options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials())) + + conn, err := grpc.NewClient(addr, options...) + require.NoError(t, err) + + return v2.NewDispersalClient(conn) +} + +func buildServer(t *testing.T) (v2.DispersalServer, *grpc.Server) { + dispersalServer := &mockDispersalServer{} + + options := make([]grpc.ServerOption, 0) + server := grpc.NewServer(options...) + v2.RegisterDispersalServer(server, dispersalServer) + + addr := "0.0.0.0:50051" + listener, err := net.Listen("tcp", addr) + require.NoError(t, err) + + go func() { + err = server.Serve(listener) + require.NoError(t, err) + }() + + return dispersalServer, server +} + +func TestServerWithTLS(t *testing.T) { + dispersalServer, server := buildServer(t) + defer server.Stop() + require.NotNil(t, dispersalServer) // TODO remove + + client := buildClient(t) + + response, err := client.NodeInfo(context.Background(), &v2.NodeInfoRequest{}) + require.NoError(t, err) + require.NotNil(t, response) +} From 43730956f7a5c3b81d642d35c35e13642f34b1b1 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 13:11:57 -0600 Subject: [PATCH 06/50] Incremental progress. Signed-off-by: Cody Littley --- disperser/auth/auth_test.go | 48 +++++++++++++++++++++---- disperser/auth/generate-cert.sh | 14 +++++++- disperser/auth/test-disperser.crt | 40 +++++++++++++++++++++ disperser/auth/test-disperser.key | 52 +++++++++++++++++++++++++++ disperser/auth/test-node.crt | 40 +++++++++++++++++++++ disperser/auth/test-node.key | 52 +++++++++++++++++++++++++++ disperser/auth/test.crt | 40 --------------------- disperser/auth/test.key | 60 ------------------------------- 8 files changed, 238 insertions(+), 108 deletions(-) create mode 100644 disperser/auth/test-disperser.crt create mode 100644 disperser/auth/test-disperser.key create mode 100644 disperser/auth/test-node.crt create mode 100644 disperser/auth/test-node.key delete mode 100644 disperser/auth/test.crt delete mode 100644 disperser/auth/test.key diff --git a/disperser/auth/auth_test.go b/disperser/auth/auth_test.go index 5017b8449a..96de6b1e7b 100644 --- a/disperser/auth/auth_test.go +++ b/disperser/auth/auth_test.go @@ -2,15 +2,21 @@ package auth import ( "context" + "crypto/tls" + "crypto/x509" "fmt" "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/stretchr/testify/require" "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials" "net" + "os" "testing" ) +// The purpose of these tests are to verify that TLS key generation works as expected. +// TODO recreate keys each time a test is run + var _ v2.DispersalServer = (*mockDispersalServer)(nil) type mockDispersalServer struct { @@ -30,11 +36,23 @@ func (s *mockDispersalServer) NodeInfo(context.Context, *v2.NodeInfoRequest) (*v func buildClient(t *testing.T) v2.DispersalClient { addr := "0.0.0.0:50051" - options := make([]grpc.DialOption, 0) - //options = append(options, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))) - options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials())) + cert, err := tls.LoadX509KeyPair("./test-disperser.crt", "./test-disperser.key") + require.NoError(t, err) - conn, err := grpc.NewClient(addr, options...) + nodeCert, err := os.ReadFile("./test-node.crt") + require.NoError(t, err) + certPool := x509.NewCertPool() + ok := certPool.AppendCertsFromPEM(nodeCert) + require.True(t, ok) + + creds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: certPool, + //ServerName: "0.0.0.0", + //InsecureSkipVerify: true, + }) + + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds)) require.NoError(t, err) return v2.NewDispersalClient(conn) @@ -43,8 +61,24 @@ func buildClient(t *testing.T) v2.DispersalClient { func buildServer(t *testing.T) (v2.DispersalServer, *grpc.Server) { dispersalServer := &mockDispersalServer{} - options := make([]grpc.ServerOption, 0) - server := grpc.NewServer(options...) + cert, err := tls.LoadX509KeyPair("./test-node.crt", "./test-node.key") + require.NoError(t, err) + + disperserCert, err := os.ReadFile("./test-disperser.crt") + require.NoError(t, err) + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(disperserCert) + ok := certPool.AppendCertsFromPEM(disperserCert) + require.True(t, ok) + + creds := credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, // TODO commenting this makes things pass + //ServerName: "0.0.0.0", + }) + + server := grpc.NewServer(grpc.Creds(creds)) v2.RegisterDispersalServer(server, dispersalServer) addr := "0.0.0.0:50051" diff --git a/disperser/auth/generate-cert.sh b/disperser/auth/generate-cert.sh index 1cbce6b9e7..cb45ce53bb 100755 --- a/disperser/auth/generate-cert.sh +++ b/disperser/auth/generate-cert.sh @@ -28,7 +28,7 @@ options() { # Organizational Unit Name (eg, section) []: echo 'EigenDA' # Common Name (e.g. server FQDN or YOUR name) []: - echo 'disperser' + echo '.' # Email Address []: echo '.' # A challenge password []: @@ -42,14 +42,26 @@ options | \ openssl req -new \ -key "${1}" \ -noenc \ + -addext "subjectAltName = IP:0.0.0.0" \ -out cert.csr +if [ $? -ne 0 ]; then + echo "Failed to generate certificate signing request." + exit 1 +fi + # Self sign the certificate. openssl x509 -req \ -days 365 \ -in cert.csr \ + -copy_extensions=copyall \ -signkey "${1}" -out eigenda-disperser-public.crt +if [ $? -ne 0 ]; then + echo "Failed to generate certificate." + exit 1 +fi + # Clean up the certificate signing request. rm cert.csr diff --git a/disperser/auth/test-disperser.crt b/disperser/auth/test-disperser.crt new file mode 100644 index 0000000000..af43313b90 --- /dev/null +++ b/disperser/auth/test-disperser.crt @@ -0,0 +1,40 @@ + _________________________________________ +< This certificate expires on 2025-12-04. > + ----------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIUK6diFhVTWqRt5ehQFZSwRkXxsO8wDQYJKoZIhvcNAQEL +BQAwWzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM +B1NlYXR0bGUxEzARBgNVBAoMCkVpZ2VuIExhYnMxEDAOBgNVBAsMB0VpZ2VuREEw +HhcNMjQxMjA0MTkwNTQ0WhcNMjUxMjA0MTkwNTQ0WjBbMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECgwK +RWlnZW4gTGFiczEQMA4GA1UECwwHRWlnZW5EQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBANPr2kpaJnVCha5VG8sfVm3ZVTSKJvX1/fKmSNjeQdRSRNPK +gIVWhQFwBL6ZYI9uQug/NUEvJyMQ8CsUOCEzRE9TKV41t30shf7SR900CEuFskHW +NXKiLmJUjtCc+K1d3+5zya99A+gwsO0KzADROUk/KcduAlJ/yZ4b0ZZF+rvZ7v36 ++efrJeJ6jARrEiY29CKc5oPAbSxQFTII/3XWENZR9igE+HzfC1DXRMqM35gbzpGC +mLoG+tEzNkysct4hHV6xjpK5tEEFmU7haMIZGbNVhncJT15gCF76zEZTMe2AZte3 +D/KkmfYcjjiLnKyF0jMEKk23h26gUKG3qik2st8hPmYmnipwfWJb433FuhFDKBXF +wMY82k+b0TpX8Pk6wmAWSsDz02xE1KETSTvJzQ6/s6uc0O5nrfj0e9M6wnkWb8EG +su35DarlvQFMFu45eYhGgvgEfedMGwiptpmul+VMCJtTxXQQy8ZHZgfLkXK0XJm8 +TiExj6ae8q+V3DDMDAKGwRBNlj+Enw8wZMQLgS/4QGhvVZWiWXnjtxUkZIjO/TBX +dd0Y5uTqTr8ynNg5JE/nywuwr9Us9DZdZm11AzLurrZ5zAt3yvzjFC3zSXv6xfVL ++X1kNV/Pje2EuPMr3LMxOmtY+jfixZEZXCDyk0u9nhfdJicMTLeg/uBFow3LAgMB +AAGjMjAwMA8GA1UdEQQIMAaHBAAAAAAwHQYDVR0OBBYEFNplMkMMZQckbsO1UNga +kHj2IqqEMA0GCSqGSIb3DQEBCwUAA4ICAQByff61bLfXbC80j7n7fQki4oU2XcXM +bT7W3Dc/s8IWbRKC5KGmXn4qkOeqxOSnXWIQavzebou6gbqJe7s3ulA8shDPIGNL +XwETMul1ikJEAnFMGrlWTYvBr2FYI+wcK/tDryyRRyUVV23GLbCHTDCb9tAG94mA +7TFHGEqDl+KCaxvedcqjdQJ96kHoVTd+wx0f//wCsUMEtnGfHtmclWBiuGYs/LIq +mlSrehc4PVazNw0AJHb01YN3OtWqfA5rE+7HEpVz8jc0JkJ1o4F/Ui9HcZ+UWVF7 +qWolCSLEe9/XQAbPd7PTdwFkRIMRvcfw8Aj3MdmnI6sdy7dmkcWgOfLe2HtXUv4G +DLlT6Zy6R3Wh7L+vcOI2SBF5W9JAiwrqlhFcSq+F+r8CyxtE8wkCGfgZYaf4gZu/ +/D32hxSyMo2WkEEmOKVcJ5pX2jnE3AL4yGwEkrLOAJxRqFm0t/xi/cdO8z0o1tbk +qawyZz2YX2VIXXTxlepcitBh/s1Y1JaETLbeNg7FSYtVLkJQcCHJxR51c1r7326z +htyovlK2foJYjfilfaWqVe2UkxUuhy77bX5N5mucZgOm/zdg/5LjJEBPgCFP3y9c +jZEG7k9RNSvPIR8bsVYy7lD9SlgnxBrZkwu4nl8AU1iKk4lvl+l6t3Uktdue9kIn +wV8xikDOZ/3kKw== +-----END CERTIFICATE----- diff --git a/disperser/auth/test-disperser.key b/disperser/auth/test-disperser.key new file mode 100644 index 0000000000..b754feb3b1 --- /dev/null +++ b/disperser/auth/test-disperser.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +Hcp7KaKkfbbscYe86aArwdI/3e5ucg== +-----END PRIVATE KEY----- diff --git a/disperser/auth/test-node.crt b/disperser/auth/test-node.crt new file mode 100644 index 0000000000..dfa7238ccf --- /dev/null +++ b/disperser/auth/test-node.crt @@ -0,0 +1,40 @@ + _________________________________________ +< This certificate expires on 2025-12-04. > + ----------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIURmsPi7n6NYchkG9fkrYPnj+HJQYwDQYJKoZIhvcNAQEL +BQAwWzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM +B1NlYXR0bGUxEzARBgNVBAoMCkVpZ2VuIExhYnMxEDAOBgNVBAsMB0VpZ2VuREEw +HhcNMjQxMjA0MTkwNjI1WhcNMjUxMjA0MTkwNjI1WjBbMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECgwK +RWlnZW4gTGFiczEQMA4GA1UECwwHRWlnZW5EQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBALJnA78QyKdEdTQ/SVG4qlUfhXhi8vKq4oFBiCBT2zsU7fra +mIkp99LB1obPjx11uArfbFYE/36G/SzTOT/eVWvDnAbW7vxz7kd3ehzpaQvkwFZz +K6mJOsUScMRhmU4+91QVeYThOv6lmCsA8jEBfnkC19sflI1Aios8NAGGLmcFPXpk +/fzmSUBhlFgw/AKb2px3sgVOECRz1hQBlLc9/XmCLJEVmWfp4Hh+fRTSfXmLdXvC +WkzVz91kBKY56gkI1KDZnP4+hIxb7nSBmiFcUdOlMeWHqX7b5rKxNvs2SQUff9d2 +jy/hGiiCW9xwh28SCT9QSnK1mmGm5qCp2SFEpCcq/3ohf3YEAHHGKKDHUXY5DJJq +VUB1UdLpgdwx1iVm5BRWwjRbfmxcf1AgZcwUAwASU6li2HyHbi5rD4lsrDGfNS/M +0NXJG8tEG+OmfAJrRNTeV3pkXMoDS2bfWpvCjjBSrPrfIBl0xed/omRy6FYAual9 +IxGr1QrSkYj+Zv4puNlQBPWan9oM6xeuU+1LE3gJWRcIKE23/UAYh+kKmkv10hG3 +AyosvBlorB9SNi3l3CbNNXS8XiqFO6pGbZya5eFB1x7yHHrb3JrOer2iHe2Hhd+Y +5U4XgDXDQ+jhmv6PTxO/Ej/LLlyVLIRXNz2U+mVDmHzJU9bEbGKadxXIdo67AgMB +AAGjMjAwMA8GA1UdEQQIMAaHBAAAAAAwHQYDVR0OBBYEFD8lGujebczd7MIwTGHd +tHNCVhkFMA0GCSqGSIb3DQEBCwUAA4ICAQCWXU86yDoie4O4bcYDodadEmeh1dlS +a9uCQIRuFxrR8L4AsZ5+F0zQ4sLGUxhkaYK0lESsLiirEnGYMZfSnF+CwMglSsdE +gRj5tUSutoC9dBMTYp+7DXNZR9Ixx2qHOPufj4zX5+zMrElhHNSIFBwxzGfhpSxW +dXFvXJmj6gZB5JAqxj1EVl1VDdXlj/JEZcKJ25ImXh1VEnHp97sxLaVB21DlwkYC +agQGHCHizMmUbrHQw22TzTvE1+6ALmcaMbk8d3Q1tJmqh+6lPxIae/q46+rPCH2k +/LP3Okgbc1UVd4OplSvRmlV6k2+H39vAOieau8tGINvkuS2lxptu2T6W5DaZUwhA +1x5A1bDUWv8Uei/DH8T27782loLPt/RbrvW65117PgNv4MgWpFqPwFyIrka/bsw9 ++afgq9CpC1/KsT4hXr5Bl2IT5v9d6ggk1cEccQRzvfpWDGlfbgsLOH9L1+qvEt68 +vdV2l9TyVI7vTIMMoEuv9Ymgu4jMzEx4rNmwx29Bb3I3KF6c3uLyO8MOpuENMWE+ +Ul41FVgQuUfHahJ+LbaeN/CIwCO0R1twkf7ES4NPzWxOre47T71a40LO11dexAXl +DnDpVhfNCbVQom0W6WipbFMWATGBTHPys0lekmh6O1dspxxWxqZoHv5ZJNQugvi0 +PEsl3ch0zDd1Rg== +-----END CERTIFICATE----- diff --git a/disperser/auth/test-node.key b/disperser/auth/test-node.key new file mode 100644 index 0000000000..f788818e35 --- /dev/null +++ b/disperser/auth/test-node.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +d/czU7IvbTeqQE0aHPnFNaWJfgKs2Fs= +-----END PRIVATE KEY----- diff --git a/disperser/auth/test.crt b/disperser/auth/test.crt deleted file mode 100644 index 2f6a5681e2..0000000000 --- a/disperser/auth/test.crt +++ /dev/null @@ -1,40 +0,0 @@ - _________________________________________ -< This certificate expires on 2025-12-04. > - ----------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || ------BEGIN CERTIFICATE----- -MIIFjTCCA3WgAwIBAgIUdFJ0JieaGN6/fYtgyd37CYyL6uEwDQYJKoZIhvcNAQEL -BQAwbzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM -B1NlYXR0bGUxEzARBgNVBAoMCkVpZ2VuIExhYnMxEDAOBgNVBAsMB0VpZ2VuREEx -EjAQBgNVBAMMCWRpc3BlcnNlcjAeFw0yNDEyMDQxNTIyMTRaFw0yNTEyMDQxNTIy -MTRaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH -DAdTZWF0dGxlMRMwEQYDVQQKDApFaWdlbiBMYWJzMRAwDgYDVQQLDAdFaWdlbkRB -MRIwEAYDVQQDDAlkaXNwZXJzZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQDmkgwhq3n+B2K3Z16ZJ8SKuw6mgsIMUmRwrnkP9PORZujeiMe9wowf1dhy -P9kR8eYnGGksx4ZV4yUjZ4XRlPMP1pAj/jXTTexkbwnZ99RqmJjNFZYtFUVT+qAE -gJtBPRwH7sZlbGVy/HqrQMEPBS2Hg6YSPcfndbiwhnsJsHak59Pkg1tQLPmXwQq8 -nHkuSrp4LHuyOGltxuI33lke+4j3Zz51o1fqHOiANBDMy6UVSTpuT+7nzdrBpQF9 -na32/PO9LpMUoIH+IcRsbT70zIaOsqCqMk2hw8sfjdKapYPt+oMw1bsGKLMfMDsy -cdmUpMFhcLtkwxaUMMaxc5LM/e442NDO78wkA9BpvXFSshMIb6F0otE4caRlI9fG -GkwHlx5+QE3hQZXgMy1FjEjKl63YAnrq2R0x+uheqTLwTvjCqMp3gZUHjCdnawrz -JEj3lp3rU+6/QeV7LxAaNdo0Liq37qvesGgNOicdSuQ6MNZccLDx6h2rywhpjmhi -jrXL7GgH6bBhUTllNyF2PMsuBrSuyggUJ7TR/wM+zXIFcRwb4eWs5sFztTl+PuhI -lFjIBE3UpHf8XFdmSsnXTg7AjVYjqr/gXFn2/cdZGeLPDnBLMm/MbCgR4McO/udu -feRJM2QYd0gVxjXxYc4NJS2SNANuT9c0XTlh01bN8wULsvjJOQIDAQABoyEwHzAd -BgNVHQ4EFgQUUG2BAXYevOBW66TfOFnT2WIyg14wDQYJKoZIhvcNAQELBQADggIB -AA64nu3LzKgB6tMMzLYLJjTCbhkEJS6JOqhjH+m/jVztjEiSQCNmPtklq5Cgyl3U -VvE5oz/bAJVL0wJIFBD1t0F2UCCYq6RtFfDMAr4+kO2E6l3g/5rcq/hEcpLprrVU -mutWLioOX/0bp8ivrJB5PdSt/jrXfjeX+9vhOhE7ZkuWJhvafNEJ+eL/mpfexbbe -wzkHKPiJHZyeyuq9tShGO7bxK3YkFTqNxV/DAJp/CCtHkdOQtVHUK72MURuzld06 -rYY1FQcknD6dD8M/ePbgaWD6fwFc+zbj4D/Wfh4eHKIZ4y+hdCMO4rgcD0v5tssm -PH7IdT61bTuOUqA/zpT1mlH9VqCB/HPnkpAHGz5JgpFAequZ/xivQWszld2gjGep -jp9SXqPybBsUsMyYRphvqzsJaCGG+x9PUF3pvZ/VK+3FQYHjVUwqEcyFPf/5ht3A -VF8DXKNykg3+S0bpX9SNQt8mVo3DxsMIZpnGL6293fPW36qmkZKLquBGpLKK8sWE -2sVmM6SzWqCenV2PVVIj3hu9xap52vvU+81P1xiS7MgwavbfehY8oK0hByRq3ax/ -Fb6uEb2CRVRv9BHKuhoiTCug096c8iMBAMB6u4G4kDMijoKry+UeEJmzrKHKiVP9 -A3iWFWshXRyPkFOu1x+Y21963kmwsItbFrHBBJbOnKp2 ------END CERTIFICATE----- diff --git a/disperser/auth/test.key b/disperser/auth/test.key deleted file mode 100644 index 649526fc1d..0000000000 --- a/disperser/auth/test.key +++ /dev/null @@ -1,60 +0,0 @@ - _______________________________________ -< this key is for testing purposes only > - --------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || ------BEGIN PRIVATE KEY----- -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 -esM9YhtyGK3Pc3Z4huJmGrpIoBqtSA== ------END PRIVATE KEY----- From df38980c6bcbc973119906f94f7a8de95f25b0ed Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 13:24:02 -0600 Subject: [PATCH 07/50] It works now, kind of Signed-off-by: Cody Littley --- disperser/auth/auth_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/disperser/auth/auth_test.go b/disperser/auth/auth_test.go index 96de6b1e7b..e3a70d7589 100644 --- a/disperser/auth/auth_test.go +++ b/disperser/auth/auth_test.go @@ -67,14 +67,13 @@ func buildServer(t *testing.T) (v2.DispersalServer, *grpc.Server) { disperserCert, err := os.ReadFile("./test-disperser.crt") require.NoError(t, err) certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM(disperserCert) ok := certPool.AppendCertsFromPEM(disperserCert) require.True(t, ok) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, - RootCAs: certPool, - ClientAuth: tls.RequireAndVerifyClientCert, // TODO commenting this makes things pass + ClientCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, //ServerName: "0.0.0.0", }) From d54c7f7aaae0e2a3edc54616c28daca62c795213 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 5 Dec 2024 08:52:07 -0600 Subject: [PATCH 08/50] Incremental progress. Signed-off-by: Cody Littley --- disperser/auth/auth_test.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/disperser/auth/auth_test.go b/disperser/auth/auth_test.go index e3a70d7589..c38b550e1a 100644 --- a/disperser/auth/auth_test.go +++ b/disperser/auth/auth_test.go @@ -39,17 +39,17 @@ func buildClient(t *testing.T) v2.DispersalClient { cert, err := tls.LoadX509KeyPair("./test-disperser.crt", "./test-disperser.key") require.NoError(t, err) - nodeCert, err := os.ReadFile("./test-node.crt") - require.NoError(t, err) - certPool := x509.NewCertPool() - ok := certPool.AppendCertsFromPEM(nodeCert) - require.True(t, ok) + // This is what we'd enable if we wanted to verify the server's certificate + //nodeCert, err := os.ReadFile("./test-node.crt") + //require.NoError(t, err) + //certPool := x509.NewCertPool() + //ok := certPool.AppendCertsFromPEM(nodeCert) + //require.True(t, ok) creds := credentials.NewTLS(&tls.Config{ Certificates: []tls.Certificate{cert}, - RootCAs: certPool, - //ServerName: "0.0.0.0", - //InsecureSkipVerify: true, + //RootCAs: certPool, + InsecureSkipVerify: true, }) conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds)) @@ -74,7 +74,6 @@ func buildServer(t *testing.T) (v2.DispersalServer, *grpc.Server) { Certificates: []tls.Certificate{cert}, ClientCAs: certPool, ClientAuth: tls.RequireAndVerifyClientCert, - //ServerName: "0.0.0.0", }) server := grpc.NewServer(grpc.Creds(creds)) From 083d67d18520fbcbecbc274ac996102416ae3a30 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 6 Dec 2024 11:37:10 -0600 Subject: [PATCH 09/50] Start experimenting with ecdsa. Signed-off-by: Cody Littley --- .gitignore | 4 +- disperser/auth/authenticator.go | 74 ++++++++++++++++++++++++ disperser/auth/authenticator_test.go | 44 ++++++++++++++ disperser/auth/generate-cert.sh | 72 ----------------------- disperser/auth/generate-ecdsa-keypair.sh | 6 ++ disperser/auth/generate-private-key.sh | 5 -- disperser/auth/next-year.py | 10 ---- disperser/auth/test-disperser.crt | 40 ------------- disperser/auth/test-disperser.key | 52 ----------------- disperser/auth/test-node.crt | 40 ------------- disperser/auth/test-node.key | 52 ----------------- disperser/auth/test-private.pem | 16 +++++ disperser/auth/test-public.pem | 15 +++++ 13 files changed, 157 insertions(+), 273 deletions(-) create mode 100644 disperser/auth/authenticator.go create mode 100644 disperser/auth/authenticator_test.go delete mode 100755 disperser/auth/generate-cert.sh create mode 100755 disperser/auth/generate-ecdsa-keypair.sh delete mode 100755 disperser/auth/generate-private-key.sh delete mode 100755 disperser/auth/next-year.py delete mode 100644 disperser/auth/test-disperser.crt delete mode 100644 disperser/auth/test-disperser.key delete mode 100644 disperser/auth/test-node.crt delete mode 100644 disperser/auth/test-node.key create mode 100644 disperser/auth/test-private.pem create mode 100644 disperser/auth/test-public.pem diff --git a/.gitignore b/.gitignore index b5043399c1..c49c17e810 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,5 @@ lightnode/docker/args.sh icicle/* -# Just to make sure somebody doesn't accidentally commit the disperser's private TLS key. -eigenda-disperser-private.key +# Just to make sure somebody doesn't accidentally commit the disperser's private key. +eigenda-disperser-private.pem diff --git a/disperser/auth/authenticator.go b/disperser/auth/authenticator.go new file mode 100644 index 0000000000..4cd6c8d028 --- /dev/null +++ b/disperser/auth/authenticator.go @@ -0,0 +1,74 @@ +package auth + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "fmt" + "io" + "os" +) + +// ReadPublicECDSAKeyFile reads a public ECDSA key from a .pem file. +func ReadPublicECDSAKeyFile(publicKeyFile string) (*ecdsa.PublicKey, error) { + file, err := os.Open(publicKeyFile) + if err != nil { + return nil, fmt.Errorf("error opening public key file: %w", err) + } + defer file.Close() + + bytes, err := io.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("error reading public key file: %w", err) + } + + block, _ := pem.Decode(bytes) + if block == nil { + return nil, fmt.Errorf("no PEM data found in public key file") + } + + if block.Type != "PUBLIC KEY" { + return nil, fmt.Errorf("unexpected block type: %s", block.Type) + } + + genericPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("error parsing public key: %w", err) + } + + publicKey := genericPublicKey.(*ecdsa.PublicKey) + + return publicKey, nil +} + +// ReadPrivateECDSAKeyFile reads a private ECDSA key from a .pem file. +func ReadPrivateECDSAKeyFile(privateKeyFile string) (*ecdsa.PrivateKey, error) { + //publicKey, err := ReadPublicECDSAKeyFile(publicKeyFile) + + file, err := os.Open(privateKeyFile) + if err != nil { + return nil, fmt.Errorf("error opening private key file: %w", err) + } + defer file.Close() + + bytes, err := io.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("error reading private key file: %w", err) + } + + block, _ := pem.Decode(bytes) + if block == nil { + return nil, fmt.Errorf("no PEM data found in private key file") + } + + if block.Type != "EC PRIVATE KEY" { + return nil, fmt.Errorf("unexpected block type: %s", block.Type) + } + + privateKey, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("error parsing private key: %w", err) + } + + return privateKey, nil +} diff --git a/disperser/auth/authenticator_test.go b/disperser/auth/authenticator_test.go new file mode 100644 index 0000000000..8919f266d7 --- /dev/null +++ b/disperser/auth/authenticator_test.go @@ -0,0 +1,44 @@ +package auth + +import ( + "crypto/ecdsa" + tu "github.com/Layr-Labs/eigenda/common/testutils" + "github.com/aws/smithy-go/rand" + "github.com/stretchr/testify/require" + "testing" +) + +func TestReadingKeysFromFile(t *testing.T) { + tu.InitializeRandom() + + publicKey, err := ReadPublicECDSAKeyFile("./test-public.pem") + require.NoError(t, err) + require.NotNil(t, publicKey) + + privateKey, err := ReadPrivateECDSAKeyFile("./test-private.pem") + require.NoError(t, err) + require.NotNil(t, privateKey) + + bytesToSign := tu.RandomBytes(32) + + signature, err := ecdsa.SignASN1(rand.Reader, privateKey, bytesToSign) + require.NoError(t, err) + + isValid := ecdsa.VerifyASN1(publicKey, bytesToSign, signature) + require.True(t, isValid) + + // Change some bytes in the signature, it should be invalid now + signature2 := make([]byte, len(signature)) + copy(signature2, signature) + signature2[0] = signature2[0] + 1 + isValid = ecdsa.VerifyASN1(publicKey, bytesToSign, signature2) + require.False(t, isValid) + + // Change some bytes in the message, it should be invalid now + bytesToSign2 := make([]byte, len(bytesToSign)) + copy(bytesToSign2, bytesToSign) + bytesToSign2[0] = bytesToSign2[0] + 1 + isValid = ecdsa.VerifyASN1(publicKey, bytesToSign2, signature) + require.False(t, isValid) + +} diff --git a/disperser/auth/generate-cert.sh b/disperser/auth/generate-cert.sh deleted file mode 100755 index cb45ce53bb..0000000000 --- a/disperser/auth/generate-cert.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash - -if [ -z "${1}" ]; then - echo "Usage: $0 " - exit 1 -fi - -if ! command -v cowsay 2>&1 >/dev/null -then - echo "cowsay is not installed. Please install it ('brew install cowsay' or 'apt-get install cowsay')." - exit 1 -fi - -# The location where this script can be found. -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -# Used to pass options to the 'openssl req' command. -# It expects a human in the loop, but it's preferable to automate it. -options() { - # Country Name (2 letter code) [AU]: - echo 'US' - # State or Province Name (full name) [Some-State]: - echo 'Washington' - # Locality Name (eg, city) []: - echo 'Seattle' - # Organization Name (eg, company) [Internet Widgits Pty Ltd]: - echo 'Eigen Labs' - # Organizational Unit Name (eg, section) []: - echo 'EigenDA' - # Common Name (e.g. server FQDN or YOUR name) []: - echo '.' - # Email Address []: - echo '.' - # A challenge password []: - echo '.' - # An optional company name []: - echo '.' -} - -# Generate a new certificate signing request. -options | \ - openssl req -new \ - -key "${1}" \ - -noenc \ - -addext "subjectAltName = IP:0.0.0.0" \ - -out cert.csr - -if [ $? -ne 0 ]; then - echo "Failed to generate certificate signing request." - exit 1 -fi - -# Self sign the certificate. -openssl x509 -req \ - -days 365 \ - -in cert.csr \ - -copy_extensions=copyall \ - -signkey "${1}" -out eigenda-disperser-public.crt - -if [ $? -ne 0 ]; then - echo "Failed to generate certificate." - exit 1 -fi - -# Clean up the certificate signing request. -rm cert.csr - -# Document the expiration date of the certificate. -NEXT_YEAR=$("${SCRIPT_DIR}"'/next-year.py') -EXPIRATION_MESSAGE=$(cowsay "This certificate expires on ${NEXT_YEAR}.") -echo -e "${EXPIRATION_MESSAGE}\n$(cat eigenda-disperser-public.crt)" > eigenda-disperser-public.crt -cowsay "This certificate expires on ${NEXT_YEAR}. Ensure that a new one is made available to node operators before then." \ No newline at end of file diff --git a/disperser/auth/generate-ecdsa-keypair.sh b/disperser/auth/generate-ecdsa-keypair.sh new file mode 100755 index 0000000000..1a017b6552 --- /dev/null +++ b/disperser/auth/generate-ecdsa-keypair.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# This script generates a new ECDSA keypair for the disperser service. + +openssl ecparam -name prime256v1 -genkey -noout -out eigenda-disperser-private.pem +openssl ec -in eigenda-disperser-private.pem -pubout -out eigenda-disperser-public.pem diff --git a/disperser/auth/generate-private-key.sh b/disperser/auth/generate-private-key.sh deleted file mode 100755 index e5e2f9c5ea..0000000000 --- a/disperser/auth/generate-private-key.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -# This script generates a new private key for the disperser service. - -openssl genrsa -out eigenda-disperser-private.key 4096 \ No newline at end of file diff --git a/disperser/auth/next-year.py b/disperser/auth/next-year.py deleted file mode 100755 index 89ad8d7d40..0000000000 --- a/disperser/auth/next-year.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 - -# This script prints the date one year from now. This can't be done in a shell script since the date -# command differs substantially between Linux and macOS. - -import datetime - -now = datetime.datetime.now() -nextYear = now.replace(year=now.year + 1) -print(nextYear.strftime('%Y-%m-%d')) diff --git a/disperser/auth/test-disperser.crt b/disperser/auth/test-disperser.crt deleted file mode 100644 index af43313b90..0000000000 --- a/disperser/auth/test-disperser.crt +++ /dev/null @@ -1,40 +0,0 @@ - _________________________________________ -< This certificate expires on 2025-12-04. > - ----------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || ------BEGIN CERTIFICATE----- -MIIFdjCCA16gAwIBAgIUK6diFhVTWqRt5ehQFZSwRkXxsO8wDQYJKoZIhvcNAQEL -BQAwWzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM -B1NlYXR0bGUxEzARBgNVBAoMCkVpZ2VuIExhYnMxEDAOBgNVBAsMB0VpZ2VuREEw -HhcNMjQxMjA0MTkwNTQ0WhcNMjUxMjA0MTkwNTQ0WjBbMQswCQYDVQQGEwJVUzET -MBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECgwK -RWlnZW4gTGFiczEQMA4GA1UECwwHRWlnZW5EQTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBANPr2kpaJnVCha5VG8sfVm3ZVTSKJvX1/fKmSNjeQdRSRNPK -gIVWhQFwBL6ZYI9uQug/NUEvJyMQ8CsUOCEzRE9TKV41t30shf7SR900CEuFskHW -NXKiLmJUjtCc+K1d3+5zya99A+gwsO0KzADROUk/KcduAlJ/yZ4b0ZZF+rvZ7v36 -+efrJeJ6jARrEiY29CKc5oPAbSxQFTII/3XWENZR9igE+HzfC1DXRMqM35gbzpGC -mLoG+tEzNkysct4hHV6xjpK5tEEFmU7haMIZGbNVhncJT15gCF76zEZTMe2AZte3 -D/KkmfYcjjiLnKyF0jMEKk23h26gUKG3qik2st8hPmYmnipwfWJb433FuhFDKBXF -wMY82k+b0TpX8Pk6wmAWSsDz02xE1KETSTvJzQ6/s6uc0O5nrfj0e9M6wnkWb8EG -su35DarlvQFMFu45eYhGgvgEfedMGwiptpmul+VMCJtTxXQQy8ZHZgfLkXK0XJm8 -TiExj6ae8q+V3DDMDAKGwRBNlj+Enw8wZMQLgS/4QGhvVZWiWXnjtxUkZIjO/TBX -dd0Y5uTqTr8ynNg5JE/nywuwr9Us9DZdZm11AzLurrZ5zAt3yvzjFC3zSXv6xfVL -+X1kNV/Pje2EuPMr3LMxOmtY+jfixZEZXCDyk0u9nhfdJicMTLeg/uBFow3LAgMB -AAGjMjAwMA8GA1UdEQQIMAaHBAAAAAAwHQYDVR0OBBYEFNplMkMMZQckbsO1UNga -kHj2IqqEMA0GCSqGSIb3DQEBCwUAA4ICAQByff61bLfXbC80j7n7fQki4oU2XcXM -bT7W3Dc/s8IWbRKC5KGmXn4qkOeqxOSnXWIQavzebou6gbqJe7s3ulA8shDPIGNL -XwETMul1ikJEAnFMGrlWTYvBr2FYI+wcK/tDryyRRyUVV23GLbCHTDCb9tAG94mA -7TFHGEqDl+KCaxvedcqjdQJ96kHoVTd+wx0f//wCsUMEtnGfHtmclWBiuGYs/LIq -mlSrehc4PVazNw0AJHb01YN3OtWqfA5rE+7HEpVz8jc0JkJ1o4F/Ui9HcZ+UWVF7 -qWolCSLEe9/XQAbPd7PTdwFkRIMRvcfw8Aj3MdmnI6sdy7dmkcWgOfLe2HtXUv4G -DLlT6Zy6R3Wh7L+vcOI2SBF5W9JAiwrqlhFcSq+F+r8CyxtE8wkCGfgZYaf4gZu/ -/D32hxSyMo2WkEEmOKVcJ5pX2jnE3AL4yGwEkrLOAJxRqFm0t/xi/cdO8z0o1tbk -qawyZz2YX2VIXXTxlepcitBh/s1Y1JaETLbeNg7FSYtVLkJQcCHJxR51c1r7326z -htyovlK2foJYjfilfaWqVe2UkxUuhy77bX5N5mucZgOm/zdg/5LjJEBPgCFP3y9c -jZEG7k9RNSvPIR8bsVYy7lD9SlgnxBrZkwu4nl8AU1iKk4lvl+l6t3Uktdue9kIn -wV8xikDOZ/3kKw== ------END CERTIFICATE----- diff --git a/disperser/auth/test-disperser.key b/disperser/auth/test-disperser.key deleted file mode 100644 index b754feb3b1..0000000000 --- a/disperser/auth/test-disperser.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -Hcp7KaKkfbbscYe86aArwdI/3e5ucg== ------END PRIVATE KEY----- diff --git a/disperser/auth/test-node.crt b/disperser/auth/test-node.crt deleted file mode 100644 index dfa7238ccf..0000000000 --- a/disperser/auth/test-node.crt +++ /dev/null @@ -1,40 +0,0 @@ - _________________________________________ -< This certificate expires on 2025-12-04. > - ----------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || ------BEGIN CERTIFICATE----- -MIIFdjCCA16gAwIBAgIURmsPi7n6NYchkG9fkrYPnj+HJQYwDQYJKoZIhvcNAQEL -BQAwWzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM -B1NlYXR0bGUxEzARBgNVBAoMCkVpZ2VuIExhYnMxEDAOBgNVBAsMB0VpZ2VuREEw -HhcNMjQxMjA0MTkwNjI1WhcNMjUxMjA0MTkwNjI1WjBbMQswCQYDVQQGEwJVUzET -MBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECgwK -RWlnZW4gTGFiczEQMA4GA1UECwwHRWlnZW5EQTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBALJnA78QyKdEdTQ/SVG4qlUfhXhi8vKq4oFBiCBT2zsU7fra -mIkp99LB1obPjx11uArfbFYE/36G/SzTOT/eVWvDnAbW7vxz7kd3ehzpaQvkwFZz -K6mJOsUScMRhmU4+91QVeYThOv6lmCsA8jEBfnkC19sflI1Aios8NAGGLmcFPXpk -/fzmSUBhlFgw/AKb2px3sgVOECRz1hQBlLc9/XmCLJEVmWfp4Hh+fRTSfXmLdXvC -WkzVz91kBKY56gkI1KDZnP4+hIxb7nSBmiFcUdOlMeWHqX7b5rKxNvs2SQUff9d2 -jy/hGiiCW9xwh28SCT9QSnK1mmGm5qCp2SFEpCcq/3ohf3YEAHHGKKDHUXY5DJJq -VUB1UdLpgdwx1iVm5BRWwjRbfmxcf1AgZcwUAwASU6li2HyHbi5rD4lsrDGfNS/M -0NXJG8tEG+OmfAJrRNTeV3pkXMoDS2bfWpvCjjBSrPrfIBl0xed/omRy6FYAual9 -IxGr1QrSkYj+Zv4puNlQBPWan9oM6xeuU+1LE3gJWRcIKE23/UAYh+kKmkv10hG3 -AyosvBlorB9SNi3l3CbNNXS8XiqFO6pGbZya5eFB1x7yHHrb3JrOer2iHe2Hhd+Y -5U4XgDXDQ+jhmv6PTxO/Ej/LLlyVLIRXNz2U+mVDmHzJU9bEbGKadxXIdo67AgMB -AAGjMjAwMA8GA1UdEQQIMAaHBAAAAAAwHQYDVR0OBBYEFD8lGujebczd7MIwTGHd -tHNCVhkFMA0GCSqGSIb3DQEBCwUAA4ICAQCWXU86yDoie4O4bcYDodadEmeh1dlS -a9uCQIRuFxrR8L4AsZ5+F0zQ4sLGUxhkaYK0lESsLiirEnGYMZfSnF+CwMglSsdE -gRj5tUSutoC9dBMTYp+7DXNZR9Ixx2qHOPufj4zX5+zMrElhHNSIFBwxzGfhpSxW -dXFvXJmj6gZB5JAqxj1EVl1VDdXlj/JEZcKJ25ImXh1VEnHp97sxLaVB21DlwkYC -agQGHCHizMmUbrHQw22TzTvE1+6ALmcaMbk8d3Q1tJmqh+6lPxIae/q46+rPCH2k -/LP3Okgbc1UVd4OplSvRmlV6k2+H39vAOieau8tGINvkuS2lxptu2T6W5DaZUwhA -1x5A1bDUWv8Uei/DH8T27782loLPt/RbrvW65117PgNv4MgWpFqPwFyIrka/bsw9 -+afgq9CpC1/KsT4hXr5Bl2IT5v9d6ggk1cEccQRzvfpWDGlfbgsLOH9L1+qvEt68 -vdV2l9TyVI7vTIMMoEuv9Ymgu4jMzEx4rNmwx29Bb3I3KF6c3uLyO8MOpuENMWE+ -Ul41FVgQuUfHahJ+LbaeN/CIwCO0R1twkf7ES4NPzWxOre47T71a40LO11dexAXl -DnDpVhfNCbVQom0W6WipbFMWATGBTHPys0lekmh6O1dspxxWxqZoHv5ZJNQugvi0 -PEsl3ch0zDd1Rg== ------END CERTIFICATE----- diff --git a/disperser/auth/test-node.key b/disperser/auth/test-node.key deleted file mode 100644 index f788818e35..0000000000 --- a/disperser/auth/test-node.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCyZwO/EMinRHU0 -P0lRuKpVH4V4YvLyquKBQYggU9s7FO362piJKffSwdaGz48ddbgK32xWBP9+hv0s -0zk/3lVrw5wG1u78c+5Hd3oc6WkL5MBWcyupiTrFEnDEYZlOPvdUFXmE4Tr+pZgr -APIxAX55AtfbH5SNQIqLPDQBhi5nBT16ZP385klAYZRYMPwCm9qcd7IFThAkc9YU -AZS3Pf15giyRFZln6eB4fn0U0n15i3V7wlpM1c/dZASmOeoJCNSg2Zz+PoSMW+50 -gZohXFHTpTHlh6l+2+aysTb7NkkFH3/Xdo8v4RooglvccIdvEgk/UEpytZphpuag -qdkhRKQnKv96IX92BABxxiigx1F2OQySalVAdVHS6YHcMdYlZuQUVsI0W35sXH9Q -IGXMFAMAElOpYth8h24uaw+JbKwxnzUvzNDVyRvLRBvjpnwCa0TU3ld6ZFzKA0tm -31qbwo4wUqz63yAZdMXnf6JkcuhWALmpfSMRq9UK0pGI/mb+KbjZUAT1mp/aDOsX -rlPtSxN4CVkXCChNt/1AGIfpCppL9dIRtwMqLLwZaKwfUjYt5dwmzTV0vF4qhTuq -Rm2cmuXhQdce8hx629yaznq9oh3th4XfmOVOF4A1w0Po4Zr+j08TvxI/yy5clSyE -Vzc9lPplQ5h8yVPWxGximncVyHaOuwIDAQABAoICADkRQt2HnOgEugkwhV+i1rnM -D4HnBRgjGbmHIOhWaraHh2jLLCgUSLYeU2VkV79wvKKdvaX2i1QKEkqYdqO7l0+V -jv+RGXmgDATIb0N4VqX7UptU1A6zWx1XPMNVBRlGgw9enzSmZ5y1k+uNreuHMtG6 -2xm5DGfy0V6gr3IdXhKOVpAkZirT73KsxCtB4Btlh5StpUCVLAy9ESntJ4lGRyLr -RR5T7nKTGb7xl0ll28VZmwcvsHsLmiaTq/kQZZDmRj393n5luTi835ZdLtSlF3fA -TCnEL6/o3+8hSRkq0EjXPqnJvxvtwdRbfs8RIAGjn1mlcWsoNG4wWW3jZxWBQ5DK -SRnU0eZR39fAb+J+pyDuvnf1EPdWI8LDsQLSWUCdm2r8ZEzc+ST4Uh5F19eJCS67 -mrkUp1IRPBLyHf+9fUAKmv7Y7Xq4UTzV9HlZX1nE/YpVZn2tfTALyvYUnRY7dGBy -+bnGklqRCdH8Y7vaGUmLFgjt+KeT3Nvgh1BqK2t9o3Z/kYimwydjvN26PlZE5QA2 -NrjNWFZ+4Bhyw4eUaNuZo6WYTUkT1lmPz1MKwV5BjX6OXaeguFSBHH/721P4GBVz -6lQmiOrTq0vjGKEOQQlykpT6pYgEX5dE29Un0Mn1OgAC3gMebNaxAyNRLmYhHMKd -cPGMVFGnK+7Wi/3O7jyhAoIBAQDv4ldkKscwVaOsUI9WD0jsFqG/tSd4+oZfeZnv -DyzIJw7qoD2DnszX6aTPq8cZoeoLBKbSr/nQFINRx84zHYtN0xYO9ZBus8oCiufo -OKZX8h1EQJU87Zxxfvj1dG4rZxyFn7ZPlS0Q5fkRO76vJ6CK+joWgqGrBnqXd7Wq -CEY1SqPjtxs1xiUMFsZv73C0tmuevTPfjnDGOz1Cae7rpPmonJ+tO4qk5vVciVMZ -lm2/HvmjtixOAWI88NDlBoNJ/3ItJnQWHysAwZqXvgraAuj63QiAaZWfwo4PhZ42 -/Fluph461vKC72jxgTu0U+yZb1vmXm6dgDqg7pbth/BUpQL3AoIBAQC+Y0bQI8AF -86m1IPV4wsrnE3VvRENMBO8nK+KtgQlv2JyaGupGPXsPqMj3Ea43q/ggGryfr66f -JUE5NrXbqe+KDlAClA7qy2RGrdwRldJTnMaDRLk990rEai2KUYGMqvY1XAvx6oyc -e2tA9Bn+sg870m+YoBOhNeyIPO8ayHL4ymD008kAVBaDPn1pOremr9QO48zV17Qe -eASoWVg/SewJhJWyNpVn5P+hiz5lmxkQReTfTFAFo0zt9utA7cS48NGzPue14Hn0 -IynZlQiqesJFS7JyR55cZF6foUC0zgDuygMuxqJdsI2UBCFDRq1CCnpqOaDKTtLB -GM6f9JzYJZ1dAoIBAQCdX717joD9GcH2Ayf2CrMJh9N3xK3vtVPAgTNW3XrAmLc1 -gAi7N8wlfjfMsmI9U7cKoXOcVyypsTtxxIZnjGNenDQlfj7SEYte3ahE9h1TJxjC -NShzP2NaJjXIOikouk3A8EWXskNNicI75xkzKekuI/lF2U+ctvRoOHXq5eDBh4U7 -mF32ila1tp9awhgLxn2WN4Q3jug3dJe84WiIGcRcNNygtqY/hvHDUqg86i53qyeV -mqc4SbocRtSU3A/31Okf69FOzgXVSi5UjK3r1urn4Wh5bktl8yplzoA8jJNTfxHo -Aio5cj1D60ezBzf6dU8yNBOXqo9MExrbHEq0DUmDAoIBAHtlwybylOoGpO81/oQX -1QTycsH8P5YM+Kit5AzKvsAUaGPloASIorNilWa1ufJxbq/4RFtHtemGbwDTOgm2 -2f/kCO2y4vxBeavp0eI/9gOlcHDyYRINrxMhMoUdENeIk23ATCmu+RYPVFPUIukW -pZMDcLs+vZpWZgljXSJB22rvWOo2PmgNGE5WeVhz60aJXeuMsF3FogPBjFtFFVJn -6im9Gn1YrXuaTCl7I6UdYqBOfOpR/ue4kQsHaDE8Kq4nv/LqiaozZTfcdqqE0woT -6MibKHyzeKuvjjjufg7yGl6q5mcx7VjGLu1Jw/lj3LYaLn+c/F9DuYvYNUwtcl8R -+i0CggEBANFY3G/1cYFnHbV7M1A74tjmT+SKxWX04dUE8d/HhoiIO2laQHCNDftv -jwjFxQ8XKrUmYqbNUM9+oldnqcVnK70ofw5V86nCYheFct/WQ2+FOl1kyTzU0uN+ -TfBcVNibs8/jL+cCBmEM7XN1lwu4OD0wGADIBBFEhVe6XAe6/iYaFYfa6D+vyXNG -I/LA8xEr/91dgTUwq79C3I2nLRF42HHUK1NiXzBZO6iJpdotOEOOm46emQHylu0V -4pGaFSamn5miI27CZHS1xauDreiSzmanNVHR05uht6Teo8+3SvgcLmC45eM6ML2e -d/czU7IvbTeqQE0aHPnFNaWJfgKs2Fs= ------END PRIVATE KEY----- diff --git a/disperser/auth/test-private.pem b/disperser/auth/test-private.pem new file mode 100644 index 0000000000..cc5a69b240 --- /dev/null +++ b/disperser/auth/test-private.pem @@ -0,0 +1,16 @@ + ________________________________________ +/ This key is for testing purposes only, \ +| and is no way intended to be secret. | +| This private key corresponds to the | +\ public key test-public.pem. / + ---------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOP8un9zVKyLMwI7JHRfL10ggWEAIJwQZD7hhrkTcGU5oAoGCCqGSM49 +AwEHoUQDQgAERrZQAbrxLSoXbOqKx/3bC/f8YDQ1uDVyJNAys4DH8AQeq59lhqYv +XEuSUlR1qgkRjllsA1wKedW3P2fkH1kbGw== +-----END EC PRIVATE KEY----- diff --git a/disperser/auth/test-public.pem b/disperser/auth/test-public.pem new file mode 100644 index 0000000000..c7c6567297 --- /dev/null +++ b/disperser/auth/test-public.pem @@ -0,0 +1,15 @@ + ________________________________________ +/ This key is for testing purposes only, \ +| and is no way intended to be secret. | +| This public key corresponds to the | +\ private key test-private.pem. / + ---------------------------------------- + \ ^__^ + \ (oo)\_______ + (__)\ )\/\ + ||----w | + || || +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERrZQAbrxLSoXbOqKx/3bC/f8YDQ1 +uDVyJNAys4DH8AQeq59lhqYvXEuSUlR1qgkRjllsA1wKedW3P2fkH1kbGw== +-----END PUBLIC KEY----- From 0a5c91a77f7bd28a2ccb7d6778870e2493da9454 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 10 Dec 2024 13:59:51 -0600 Subject: [PATCH 10/50] Authorize StoreChunks() requests. Signed-off-by: Cody Littley --- api/grpc/node/v2/node_v2.pb.go | 124 +++++++++++------- api/proto/node/v2/node_v2.proto | 28 ++++ .../auth/{authenticator.go => key_utils.go} | 0 ...uthenticator_test.go => key_utils_test.go} | 0 disperser/auth/request_signing.go | 85 ++++++++++++ 5 files changed, 193 insertions(+), 44 deletions(-) rename disperser/auth/{authenticator.go => key_utils.go} (100%) rename disperser/auth/{authenticator_test.go => key_utils_test.go} (100%) create mode 100644 disperser/auth/request_signing.go diff --git a/api/grpc/node/v2/node_v2.pb.go b/api/grpc/node/v2/node_v2.pb.go index 98c98ee40c..7f75acfa91 100644 --- a/api/grpc/node/v2/node_v2.pb.go +++ b/api/grpc/node/v2/node_v2.pb.go @@ -21,6 +21,7 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Request that the Node store a batch of chunks. type StoreChunksRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -28,6 +29,32 @@ type StoreChunksRequest struct { // batch of blobs to store Batch *v2.Batch `protobuf:"bytes,1,opt,name=batch,proto3" json:"batch,omitempty"` + // Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature + // is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. + // + // Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). + // A reference implementation (golang) can be found at + // https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go + // + // 1. digest batch.BatchHeader.BatchRoot + // 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) + // 3. for each certificate in batch.BlobCertificates: + // a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) + // b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: + // i. digest quorum_number (4 bytes, unsigned big endian) + // c. digest certificate.BlobHeader.Commitment.Commitment + // d. digest certificate.BlobHeader.Commitment.LengthCommitment + // e. digest certificate.BlobHeader.Commitment.LengthProof + // f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) + // g. digest certificate.BlobHeader.PaymentHeader.AccountId + // h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + // i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment + // j. digest certificate.BlobHeader.Signature + // k. for each relay in certificate.Relays: + // i. digest relay (4 bytes, unsigned big endian) + // + // Note that this signature is not included in the hash for obvious reasons. + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *StoreChunksRequest) Reset() { @@ -69,6 +96,13 @@ func (x *StoreChunksRequest) GetBatch() *v2.Batch { return nil } +func (x *StoreChunksRequest) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + type StoreChunksReply struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -347,53 +381,55 @@ var file_node_v2_node_v2_proto_rawDesc = []byte{ 0x0a, 0x15, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x76, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x1a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x3c, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5a, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x22, 0x30, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, - 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, - 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x62, 0x6c, 0x6f, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x71, 0x75, 0x6f, 0x72, 0x75, - 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x71, 0x75, 0x6f, 0x72, - 0x75, 0x6d, 0x49, 0x64, 0x22, 0x28, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, - 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0x11, - 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, - 0x72, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, - 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, - 0x17, 0x0a, 0x07, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x43, 0x70, 0x75, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x5f, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x65, 0x6d, - 0x42, 0x79, 0x74, 0x65, 0x73, 0x32, 0x94, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, - 0x73, 0x61, 0x6c, 0x12, 0x47, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, - 0x6b, 0x73, 0x12, 0x1b, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, - 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x19, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, - 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, - 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, - 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, - 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x8e, 0x01, 0x0a, - 0x09, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x12, 0x41, 0x0a, 0x09, 0x47, 0x65, - 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, - 0x32, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, - 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, - 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x2f, 0x5a, - 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4c, 0x61, 0x79, 0x72, - 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x32, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x22, 0x30, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, + 0x6f, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x6c, + 0x6f, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, + 0x49, 0x64, 0x22, 0x28, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0x11, 0x0a, 0x0f, + 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x81, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, + 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, 0x0e, 0x0a, + 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x17, 0x0a, + 0x07, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, + 0x6e, 0x75, 0x6d, 0x43, 0x70, 0x75, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x32, 0x94, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x61, + 0x6c, 0x12, 0x47, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, + 0x12, 0x1b, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, + 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, + 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x4e, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x8e, 0x01, 0x0a, 0x09, 0x52, + 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x12, 0x41, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, + 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, + 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x4e, + 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, + 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x2f, 0x5a, 0x2d, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4c, 0x61, 0x79, 0x72, 0x2d, 0x4c, + 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x67, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/node/v2/node_v2.proto b/api/proto/node/v2/node_v2.proto index 7a13378be4..9e2acef010 100644 --- a/api/proto/node/v2/node_v2.proto +++ b/api/proto/node/v2/node_v2.proto @@ -21,9 +21,37 @@ service Retrieval { // Requests and replies +// Request that the Node store a batch of chunks. message StoreChunksRequest { // batch of blobs to store common.v2.Batch batch = 1; + + // Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature + // is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. + // + // Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). + // A reference implementation (golang) can be found at + // https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go + // + // 1. digest batch.BatchHeader.BatchRoot + // 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) + // 3. for each certificate in batch.BlobCertificates: + // a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) + // b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: + // i. digest quorum_number (4 bytes, unsigned big endian) + // c. digest certificate.BlobHeader.Commitment.Commitment + // d. digest certificate.BlobHeader.Commitment.LengthCommitment + // e. digest certificate.BlobHeader.Commitment.LengthProof + // f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) + // g. digest certificate.BlobHeader.PaymentHeader.AccountId + // h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + // i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment + // j. digest certificate.BlobHeader.Signature + // k. for each relay in certificate.Relays: + // i. digest relay (4 bytes, unsigned big endian) + // + // Note that this signature is not included in the hash for obvious reasons. + bytes signature = 2; } message StoreChunksReply { diff --git a/disperser/auth/authenticator.go b/disperser/auth/key_utils.go similarity index 100% rename from disperser/auth/authenticator.go rename to disperser/auth/key_utils.go diff --git a/disperser/auth/authenticator_test.go b/disperser/auth/key_utils_test.go similarity index 100% rename from disperser/auth/authenticator_test.go rename to disperser/auth/key_utils_test.go diff --git a/disperser/auth/request_signing.go b/disperser/auth/request_signing.go new file mode 100644 index 0000000000..b07003095c --- /dev/null +++ b/disperser/auth/request_signing.go @@ -0,0 +1,85 @@ +package auth + +import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/binary" + "fmt" + commonv1 "github.com/Layr-Labs/eigenda/api/grpc/common" + common "github.com/Layr-Labs/eigenda/api/grpc/common/v2" + grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "golang.org/x/crypto/sha3" + "hash" +) + +// HashStoreChunksRequest hashes the given StoreChunksRequest. TODO document how +func HashStoreChunksRequest(request *grpc.StoreChunksRequest) []byte { + hasher := sha3.NewLegacyKeccak256() + + hashBatchHeader(hasher, request.Batch.Header) + for _, blobCertificate := range request.Batch.BlobCertificates { + hashBlobCertificate(hasher, blobCertificate) + } + + return hasher.Sum(nil) +} + +func hashBlobCertificate(hasher hash.Hash, blobCertificate *common.BlobCertificate) { + hashBlobHeader(hasher, blobCertificate.BlobHeader) + for _, relayID := range blobCertificate.Relays { + hashUint32(hasher, relayID) + } +} + +func hashBlobHeader(hasher hash.Hash, header *common.BlobHeader) { + hashUint32(hasher, header.Version) + for _, quorum := range header.QuorumNumbers { + hashUint32(hasher, quorum) + } + hashBlobCommitment(hasher, header.Commitment) + hashPaymentHeader(hasher, header.PaymentHeader) + hasher.Write(header.Signature) +} + +func hashBatchHeader(hasher hash.Hash, header *common.BatchHeader) { + hasher.Write(header.BatchRoot) + hashUint64(hasher, header.ReferenceBlockNumber) +} + +func hashBlobCommitment(hasher hash.Hash, commitment *commonv1.BlobCommitment) { + hasher.Write(commitment.Commitment) + hasher.Write(commitment.LengthCommitment) + hasher.Write(commitment.LengthProof) + hashUint32(hasher, commitment.Length) +} + +func hashPaymentHeader(hasher hash.Hash, header *commonv1.PaymentHeader) { + hasher.Write([]byte(header.AccountId)) + hashUint32(hasher, header.BinIndex) + hasher.Write(header.CumulativePayment) +} + +func hashUint32(hasher hash.Hash, value uint32) { + bytes := make([]byte, 4) + binary.BigEndian.PutUint32(bytes, value) + hasher.Write(bytes) +} + +func hashUint64(hasher hash.Hash, value uint64) { + bytes := make([]byte, 8) + binary.BigEndian.PutUint64(bytes, value) + hasher.Write(bytes) +} + +// SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not +// write the signature into the request. +func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { + hash := HashStoreChunksRequest(request) + + signature, err := ecdsa.SignASN1(rand.Reader, key, hash) + if err != nil { + return nil, fmt.Errorf("failed to sign request: %w", err) + } + + return signature, nil +} From f3485506a6a0d2cd7c0478a041643267fccead41 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 10 Dec 2024 14:00:47 -0600 Subject: [PATCH 11/50] Update docs. Signed-off-by: Cody Littley --- api/docs/eigenda-protos.html | 33 ++++++++++++++++++++++++++++++++- api/docs/eigenda-protos.md | 9 ++++++++- api/docs/node_v2.html | 33 ++++++++++++++++++++++++++++++++- api/docs/node_v2.md | 9 ++++++++- 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/api/docs/eigenda-protos.html b/api/docs/eigenda-protos.html index 9f1c0c1c9a..29bec7d598 100644 --- a/api/docs/eigenda-protos.html +++ b/api/docs/eigenda-protos.html @@ -3479,7 +3479,7 @@

StoreChunksReply

StoreChunksRequest

-

+

Request that the Node store a batch of chunks.

@@ -3495,6 +3495,37 @@

StoreChunksRequest

+ + + + + + +

batch of blobs to store

signaturebytes

Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature +is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. + +Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). +A reference implementation (golang) can be found at +https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go + +1. digest batch.BatchHeader.BatchRoot +2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) +3. for each certificate in batch.BlobCertificates: + a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) + b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: + i. digest quorum_number (4 bytes, unsigned big endian) + c. digest certificate.BlobHeader.Commitment.Commitment + d. digest certificate.BlobHeader.Commitment.LengthCommitment + e. digest certificate.BlobHeader.Commitment.LengthProof + f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) + g. digest certificate.BlobHeader.PaymentHeader.AccountId + h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment + j. digest certificate.BlobHeader.Signature + k. for each relay in certificate.Relays: + i. digest relay (4 bytes, unsigned big endian) + +Note that this signature is not included in the hash for obvious reasons.

diff --git a/api/docs/eigenda-protos.md b/api/docs/eigenda-protos.md index 8b4992a428..7c6777c81f 100644 --- a/api/docs/eigenda-protos.md +++ b/api/docs/eigenda-protos.md @@ -1476,12 +1476,19 @@ Node info request ### StoreChunksRequest - +Request that the Node store a batch of chunks. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | batch | [common.v2.Batch](#common-v2-Batch) | | batch of blobs to store | +| signature | [bytes](#bytes) | | Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. + +Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). A reference implementation (golang) can be found at https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go + +1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) + +Note that this signature is not included in the hash for obvious reasons. | diff --git a/api/docs/node_v2.html b/api/docs/node_v2.html index 22d657aca4..3cbebeaaa9 100644 --- a/api/docs/node_v2.html +++ b/api/docs/node_v2.html @@ -369,7 +369,7 @@

StoreChunksReply

StoreChunksRequest

-

+

Request that the Node store a batch of chunks.

@@ -385,6 +385,37 @@

StoreChunksRequest

+ + + + + + +

batch of blobs to store

signaturebytes

Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature +is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. + +Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). +A reference implementation (golang) can be found at +https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go + +1. digest batch.BatchHeader.BatchRoot +2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) +3. for each certificate in batch.BlobCertificates: + a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) + b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: + i. digest quorum_number (4 bytes, unsigned big endian) + c. digest certificate.BlobHeader.Commitment.Commitment + d. digest certificate.BlobHeader.Commitment.LengthCommitment + e. digest certificate.BlobHeader.Commitment.LengthProof + f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) + g. digest certificate.BlobHeader.PaymentHeader.AccountId + h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment + j. digest certificate.BlobHeader.Signature + k. for each relay in certificate.Relays: + i. digest relay (4 bytes, unsigned big endian) + +Note that this signature is not included in the hash for obvious reasons.

diff --git a/api/docs/node_v2.md b/api/docs/node_v2.md index 27dbaace45..2e2d36eac7 100644 --- a/api/docs/node_v2.md +++ b/api/docs/node_v2.md @@ -103,12 +103,19 @@ Node info request ### StoreChunksRequest - +Request that the Node store a batch of chunks. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | batch | [common.v2.Batch](#common-v2-Batch) | | batch of blobs to store | +| signature | [bytes](#bytes) | | Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. + +Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). A reference implementation (golang) can be found at https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go + +1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) + +Note that this signature is not included in the hash for obvious reasons. | From 01b7eced0edec852c7b83cfbfb50c87a3e2998fd Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 11 Dec 2024 08:00:22 -0600 Subject: [PATCH 12/50] Incremental progress. Signed-off-by: Cody Littley --- api/clients/node_client_v2.go | 32 ++++++++++++++++++++++++++++--- disperser/auth/request_signing.go | 6 +++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/api/clients/node_client_v2.go b/api/clients/node_client_v2.go index 58452aa94a..e45dfc9869 100644 --- a/api/clients/node_client_v2.go +++ b/api/clients/node_client_v2.go @@ -2,7 +2,9 @@ package clients import ( "context" + "crypto/ecdsa" "fmt" + "github.com/Layr-Labs/eigenda/disperser/auth" "sync" commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2" @@ -16,6 +18,8 @@ type NodeClientV2Config struct { Hostname string Port string UseSecureGrpcFlag bool + // The .pem file containing the private key used to sign StoreChunks() requests. If "" then no signing is done. + PrivateKeyFile string } type NodeClientV2 interface { @@ -27,6 +31,7 @@ type nodeClientV2 struct { config *NodeClientV2Config initOnce sync.Once conn *grpc.ClientConn + key *ecdsa.PrivateKey dispersalClient nodegrpc.DispersalClient } @@ -37,8 +42,19 @@ func NewNodeClientV2(config *NodeClientV2Config) (*nodeClientV2, error) { if config == nil || config.Hostname == "" || config.Port == "" { return nil, fmt.Errorf("invalid config: %v", config) } + + var key *ecdsa.PrivateKey // TODO update flags + if config.PrivateKeyFile != "" { + var err error + key, err = auth.ReadPrivateECDSAKeyFile(config.PrivateKeyFile) + if err != nil { + return nil, fmt.Errorf("failed to read private key file: %v", err) + } + } + return &nodeClientV2{ config: config, + key: key, }, nil } @@ -60,8 +76,7 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c } } - // 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[:], @@ -69,7 +84,18 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c }, BlobCertificates: blobCerts, }, - }) + } + + if c.key != nil { + signature, err := auth.SignStoreChunksRequest(c.key, request) // TODO + if err != nil { + return nil, fmt.Errorf("failed to sign 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 } diff --git a/disperser/auth/request_signing.go b/disperser/auth/request_signing.go index b07003095c..ec0669662d 100644 --- a/disperser/auth/request_signing.go +++ b/disperser/auth/request_signing.go @@ -12,7 +12,7 @@ import ( "hash" ) -// HashStoreChunksRequest hashes the given StoreChunksRequest. TODO document how +// HashStoreChunksRequest hashes the given StoreChunksRequest. func HashStoreChunksRequest(request *grpc.StoreChunksRequest) []byte { hasher := sha3.NewLegacyKeccak256() @@ -74,9 +74,9 @@ func hashUint64(hasher hash.Hash, value uint64) { // SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not // write the signature into the request. func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { - hash := HashStoreChunksRequest(request) + requestHash := HashStoreChunksRequest(request) - signature, err := ecdsa.SignASN1(rand.Reader, key, hash) + signature, err := ecdsa.SignASN1(rand.Reader, key, requestHash) if err != nil { return nil, fmt.Errorf("failed to sign request: %w", err) } From e09a77c4b7ddb9c49a598aeb7b9a35695d010d82 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 11 Dec 2024 10:22:20 -0600 Subject: [PATCH 13/50] Incremental progress. Signed-off-by: Cody Littley --- api/docs/eigenda-protos.html | 8 + api/docs/eigenda-protos.md | 3 +- api/docs/node_v2.html | 8 + api/docs/node_v2.md | 3 +- api/grpc/node/v2/node_v2.pb.go | 106 +++++----- api/proto/node/v2/node_v2.proto | 6 +- disperser/auth/request_signing.go | 36 ++-- .../auth/{key_utils.go => serialization.go} | 2 - ...ey_utils_test.go => serialization_test.go} | 0 node/auth/authenticator.go | 184 ++++++++++++++++++ 10 files changed, 291 insertions(+), 65 deletions(-) rename disperser/auth/{key_utils.go => serialization.go} (96%) rename disperser/auth/{key_utils_test.go => serialization_test.go} (100%) create mode 100644 node/auth/authenticator.go diff --git a/api/docs/eigenda-protos.html b/api/docs/eigenda-protos.html index 29bec7d598..94dc2c02e4 100644 --- a/api/docs/eigenda-protos.html +++ b/api/docs/eigenda-protos.html @@ -3495,6 +3495,13 @@

StoreChunksRequest

batch of blobs to store

+ + disperserID + uint32 + +

ID of the disperser that is requesting the storage of the batch.

+ + signature bytes @@ -3522,6 +3529,7 @@

StoreChunksRequest

j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) +4. digest disperserID (4 bytes, unsigned big endian) Note that this signature is not included in the hash for obvious reasons.

diff --git a/api/docs/eigenda-protos.md b/api/docs/eigenda-protos.md index 7c6777c81f..fdd5bb20e8 100644 --- a/api/docs/eigenda-protos.md +++ b/api/docs/eigenda-protos.md @@ -1482,11 +1482,12 @@ Request that the Node store a batch of chunks. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | batch | [common.v2.Batch](#common-v2-Batch) | | batch of blobs to store | +| disperserID | [uint32](#uint32) | | ID of the disperser that is requesting the storage of the batch. | | signature | [bytes](#bytes) | | Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). A reference implementation (golang) can be found at https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go -1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) +1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) Note that this signature is not included in the hash for obvious reasons. | diff --git a/api/docs/node_v2.html b/api/docs/node_v2.html index 3cbebeaaa9..b092e74eb4 100644 --- a/api/docs/node_v2.html +++ b/api/docs/node_v2.html @@ -385,6 +385,13 @@

StoreChunksRequest

batch of blobs to store

+ + disperserID + uint32 + +

ID of the disperser that is requesting the storage of the batch.

+ + signature bytes @@ -412,6 +419,7 @@

StoreChunksRequest

j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) +4. digest disperserID (4 bytes, unsigned big endian) Note that this signature is not included in the hash for obvious reasons.

diff --git a/api/docs/node_v2.md b/api/docs/node_v2.md index 2e2d36eac7..cc17bbd46c 100644 --- a/api/docs/node_v2.md +++ b/api/docs/node_v2.md @@ -109,11 +109,12 @@ Request that the Node store a batch of chunks. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | batch | [common.v2.Batch](#common-v2-Batch) | | batch of blobs to store | +| disperserID | [uint32](#uint32) | | ID of the disperser that is requesting the storage of the batch. | | signature | [bytes](#bytes) | | Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). A reference implementation (golang) can be found at https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go -1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) +1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) Note that this signature is not included in the hash for obvious reasons. | diff --git a/api/grpc/node/v2/node_v2.pb.go b/api/grpc/node/v2/node_v2.pb.go index 7f75acfa91..f97be225aa 100644 --- a/api/grpc/node/v2/node_v2.pb.go +++ b/api/grpc/node/v2/node_v2.pb.go @@ -29,6 +29,8 @@ type StoreChunksRequest struct { // batch of blobs to store Batch *v2.Batch `protobuf:"bytes,1,opt,name=batch,proto3" json:"batch,omitempty"` + // ID of the disperser that is requesting the storage of the batch. + DisperserID uint32 `protobuf:"varint,2,opt,name=disperserID,proto3" json:"disperserID,omitempty"` // Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature // is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. // @@ -52,9 +54,10 @@ type StoreChunksRequest struct { // j. digest certificate.BlobHeader.Signature // k. for each relay in certificate.Relays: // i. digest relay (4 bytes, unsigned big endian) + // 4. digest disperserID (4 bytes, unsigned big endian) // // Note that this signature is not included in the hash for obvious reasons. - Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *StoreChunksRequest) Reset() { @@ -96,6 +99,13 @@ func (x *StoreChunksRequest) GetBatch() *v2.Batch { return nil } +func (x *StoreChunksRequest) GetDisperserID() uint32 { + if x != nil { + return x.DisperserID + } + return 0 +} + func (x *StoreChunksRequest) GetSignature() []byte { if x != nil { return x.Signature @@ -381,55 +391,57 @@ var file_node_v2_node_v2_proto_rawDesc = []byte{ 0x0a, 0x15, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x76, 0x32, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x1a, 0x16, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5a, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, + 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7c, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x22, 0x30, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, - 0x6f, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x6c, - 0x6f, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x71, 0x75, 0x6f, 0x72, 0x75, 0x6d, - 0x49, 0x64, 0x22, 0x28, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, - 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0x11, 0x0a, 0x0f, - 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x81, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x63, - 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, 0x0e, 0x0a, - 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x17, 0x0a, - 0x07, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, - 0x6e, 0x75, 0x6d, 0x43, 0x70, 0x75, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x5f, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x65, 0x6d, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x32, 0x94, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x61, - 0x6c, 0x12, 0x47, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, - 0x12, 0x1b, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x4e, 0x6f, - 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, - 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x8e, 0x01, 0x0a, 0x09, 0x52, - 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x12, 0x41, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, - 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, - 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, 0x4e, - 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, - 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x2f, 0x5a, 0x2d, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4c, 0x61, 0x79, 0x72, 0x2d, 0x4c, - 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x67, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x32, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x64, 0x69, 0x73, + 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x30, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, + 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x4a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x43, + 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x62, 0x6c, 0x6f, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x71, 0x75, 0x6f, 0x72, 0x75, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x71, 0x75, 0x6f, 0x72, + 0x75, 0x6d, 0x49, 0x64, 0x22, 0x28, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, + 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x22, 0x11, + 0x0a, 0x0f, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0x81, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x6d, 0x76, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x61, + 0x72, 0x63, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x63, 0x68, 0x12, + 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x12, + 0x17, 0x0a, 0x07, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x43, 0x70, 0x75, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6d, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x65, 0x6d, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x32, 0x94, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, + 0x73, 0x61, 0x6c, 0x12, 0x47, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x73, 0x12, 0x1b, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x43, + 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x08, + 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x32, 0x8e, 0x01, 0x0a, + 0x09, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x12, 0x41, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x19, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, + 0x32, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, + 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, + 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x2e, 0x6e, 0x6f, 0x64, 0x65, + 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x2f, 0x5a, + 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4c, 0x61, 0x79, 0x72, + 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x76, 0x32, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/node/v2/node_v2.proto b/api/proto/node/v2/node_v2.proto index 9e2acef010..652154f63a 100644 --- a/api/proto/node/v2/node_v2.proto +++ b/api/proto/node/v2/node_v2.proto @@ -26,6 +26,9 @@ message StoreChunksRequest { // batch of blobs to store common.v2.Batch batch = 1; + // ID of the disperser that is requesting the storage of the batch. + uint32 disperserID = 2; + // Signature using the disperser's ECDSA key over keccak hash of the batch. The purpose of this signature // is to prevent hooligans from tricking DA nodes into storing data that they shouldn't be storing. // @@ -49,9 +52,10 @@ message StoreChunksRequest { // j. digest certificate.BlobHeader.Signature // k. for each relay in certificate.Relays: // i. digest relay (4 bytes, unsigned big endian) + // 4. digest disperserID (4 bytes, unsigned big endian) // // Note that this signature is not included in the hash for obvious reasons. - bytes signature = 2; + bytes signature = 3; } message StoreChunksReply { diff --git a/disperser/auth/request_signing.go b/disperser/auth/request_signing.go index ec0669662d..3d7216c1b6 100644 --- a/disperser/auth/request_signing.go +++ b/disperser/auth/request_signing.go @@ -12,6 +12,28 @@ import ( "hash" ) +// TODO test these methods + +// SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not +// write the signature into the request. +func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { + requestHash := HashStoreChunksRequest(request) + + signature, err := ecdsa.SignASN1(rand.Reader, key, requestHash) + if err != nil { + return nil, fmt.Errorf("failed to sign request: %w", err) + } + + return signature, nil +} + +// VerifyStoreChunksRequest verifies the given signature of the given StoreChunksRequest with the given +// public key. +func VerifyStoreChunksRequest(key *ecdsa.PublicKey, request *grpc.StoreChunksRequest, signature []byte) bool { + requestHash := HashStoreChunksRequest(request) + return ecdsa.VerifyASN1(key, requestHash, signature) +} + // HashStoreChunksRequest hashes the given StoreChunksRequest. func HashStoreChunksRequest(request *grpc.StoreChunksRequest) []byte { hasher := sha3.NewLegacyKeccak256() @@ -20,6 +42,7 @@ func HashStoreChunksRequest(request *grpc.StoreChunksRequest) []byte { for _, blobCertificate := range request.Batch.BlobCertificates { hashBlobCertificate(hasher, blobCertificate) } + hashUint32(hasher, request.DisperserID) return hasher.Sum(nil) } @@ -70,16 +93,3 @@ func hashUint64(hasher hash.Hash, value uint64) { binary.BigEndian.PutUint64(bytes, value) hasher.Write(bytes) } - -// SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not -// write the signature into the request. -func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { - requestHash := HashStoreChunksRequest(request) - - signature, err := ecdsa.SignASN1(rand.Reader, key, requestHash) - if err != nil { - return nil, fmt.Errorf("failed to sign request: %w", err) - } - - return signature, nil -} diff --git a/disperser/auth/key_utils.go b/disperser/auth/serialization.go similarity index 96% rename from disperser/auth/key_utils.go rename to disperser/auth/serialization.go index 4cd6c8d028..43ac84bf64 100644 --- a/disperser/auth/key_utils.go +++ b/disperser/auth/serialization.go @@ -43,8 +43,6 @@ func ReadPublicECDSAKeyFile(publicKeyFile string) (*ecdsa.PublicKey, error) { // ReadPrivateECDSAKeyFile reads a private ECDSA key from a .pem file. func ReadPrivateECDSAKeyFile(privateKeyFile string) (*ecdsa.PrivateKey, error) { - //publicKey, err := ReadPublicECDSAKeyFile(publicKeyFile) - file, err := os.Open(privateKeyFile) if err != nil { return nil, fmt.Errorf("error opening private key file: %w", err) diff --git a/disperser/auth/key_utils_test.go b/disperser/auth/serialization_test.go similarity index 100% rename from disperser/auth/key_utils_test.go rename to disperser/auth/serialization_test.go diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go new file mode 100644 index 0000000000..e05c487b2d --- /dev/null +++ b/node/auth/authenticator.go @@ -0,0 +1,184 @@ +package auth + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/Layr-Labs/eigenda/core" + dauth "github.com/Layr-Labs/eigenda/disperser/auth" + lru "github.com/hashicorp/golang-lru/v2" + "time" +) + +// RequestAuthenticator authenticates requests to the DA node. This object is thread safe. +type RequestAuthenticator interface { + // AuthenticateStoreChunksRequest authenticates a StoreChunksRequest, returning an error if the request is invalid. + // The origin is the address of the peer that sent the request. This may be used to cache auth results + // in order to save node resources. + AuthenticateStoreChunksRequest( + ctx context.Context, + origin string, + request *grpc.StoreChunksRequest, + now time.Time) error +} + +// keyWithTimeout is a key with that key's expiration time. After a key "expires", it should be reloaded +// from the chain state in case the key has been changed. +type keyWithTimeout struct { + key *ecdsa.PublicKey + expiration time.Time +} + +var _ RequestAuthenticator = &requestAuthenticator{} + +type requestAuthenticator struct { + ics core.IndexedChainState + + // keyCache is used to cache the public keys of dispersers. + keyCache *lru.Cache[uint32, *keyWithTimeout] + + // keyTimeoutDuration is the duration for which a key is cached. After this duration, the key should be + // reloaded from the chain state in case the key has been changed. + keyTimeoutDuration time.Duration + + // authenticatedDispersers is a set of disperser addresses that have been recently authenticated, mapped + // to the time when that cached authentication will expire. + authenticatedDispersers *lru.Cache[string, time.Time] + + // authenticationTimeoutDuration is the duration for which an auth is valid. + // If this is zero, then auth saving is disabled, and each request will be authenticated independently. + authenticationTimeoutDuration time.Duration +} + +// NewRequestAuthenticator creates a new RequestAuthenticator. +func NewRequestAuthenticator( + ctx context.Context, + ics core.IndexedChainState, + keyCacheSize int, + keyTimeoutDuration time.Duration, + authenticationTimeoutDuration time.Duration, + now time.Time) (RequestAuthenticator, error) { + + keyCache, err := lru.New[uint32, *keyWithTimeout](keyCacheSize) + if err != nil { + return nil, fmt.Errorf("failed to create key cache: %w", err) + } + + authenticatedDispersers, err := lru.New[string, time.Time](keyCacheSize) + if err != nil { + return nil, fmt.Errorf("failed to create authenticated dispersers cache: %w", err) + } + + authenticator := &requestAuthenticator{ + ics: ics, + keyCache: keyCache, + keyTimeoutDuration: keyTimeoutDuration, + authenticatedDispersers: authenticatedDispersers, + authenticationTimeoutDuration: authenticationTimeoutDuration, + } + + err = authenticator.preloadCache(ctx, now) + if err != nil { + return nil, fmt.Errorf("failed to preload cache: %w", err) + } + + return authenticator, nil +} + +func (a *requestAuthenticator) preloadCache(ctx context.Context, now time.Time) error { + // TODO (cody-littley): this will need to be updated for decentralized dispersers + key, err := a.getDisperserKey(ctx, now, 0) + if err != nil { + return fmt.Errorf("failed to get operator key: %w", err) + } + + if key == nil { + return errors.New("key is nil") + } + + return nil +} + +func (a *requestAuthenticator) AuthenticateStoreChunksRequest( + ctx context.Context, + origin string, + request *grpc.StoreChunksRequest, + now time.Time) error { + + if a.isAuthenticationStillValid(now, origin) { + // We've recently authenticated this client. Do not authenticate again for a while. + return nil + } + + key, err := a.getDisperserKey(ctx, now, request.DisperserID) + if err != nil { + return fmt.Errorf("failed to get operator key: %w", err) + } + + signature := request.Signature + isValid := dauth.VerifyStoreChunksRequest(key, request, signature) + + if !isValid { + return errors.New("signature verification failed") + } + + a.saveAuthenticationResult(now, origin) + return nil +} + +// getDisperserKey returns the public key of the operator with the given ID, caching the result. +func (a *requestAuthenticator) getDisperserKey( + ctx context.Context, + now time.Time, + disperserID uint32) (*ecdsa.PublicKey, error) { + key, ok := a.keyCache.Get(disperserID) + if ok { + expirationTime := key.expiration + if now.Before(expirationTime) { + return key.key, nil + } + } + + // TODO add logic for fetching key + + //blockNumber, err := a.ics.GetCurrentBlockNumber() + //if err != nil { + // return nil, fmt.Errorf("failed to get current block number: %w", err) + //} + //operators, err := a.ics.GetIndexedOperators(ctx, blockNumber) + //if err != nil { + // return nil, fmt.Errorf("failed to get operators: %w", err) + //} + // + //operator, ok := operators[operatorID] + //if !ok { + // return nil, errors.New("operator not found") + //} + //key = operator.PubkeyG2 + // + //a.keyCache.Add(operatorID, key) + return nil, nil +} + +// saveAuthenticationResult saves the result of an auth. +func (a *requestAuthenticator) saveAuthenticationResult(now time.Time, origin string) { + if a.authenticationTimeoutDuration == 0 { + // Authentication saving is disabled. + return + } + + a.authenticatedDispersers.Add(origin, now.Add(a.authenticationTimeoutDuration)) +} + +// isAuthenticationStillValid returns true if the client at the given address has been authenticated recently. +func (a *requestAuthenticator) isAuthenticationStillValid(now time.Time, address string) bool { + if a.authenticationTimeoutDuration == 0 { + // Authentication saving is disabled. + return false + } + + expiration, ok := a.authenticatedDispersers.Get(address) + return ok && now.Before(expiration) +} From 290c93ccb956251ed36dcbc77b759c0f63f5be19 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 11 Dec 2024 12:02:21 -0600 Subject: [PATCH 14/50] Incremental progress. Signed-off-by: Cody Littley --- core/chainio.go | 3 +++ core/eth/reader.go | 7 ++++++ core/mock/writer.go | 11 +++++++++ node/auth/authenticator.go | 49 +++++++++++++++++--------------------- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/core/chainio.go b/core/chainio.go index b5b587c8d2..eefac7ceac 100644 --- a/core/chainio.go +++ b/core/chainio.go @@ -126,6 +126,9 @@ type Reader interface { // GetRelayURLs returns the relay URL addresses for all relays. GetRelayURLs(ctx context.Context) (map[uint32]string, error) + + // GetDisperserAddress returns the disperser address with the given ID. + GetDisperserAddress(ctx context.Context, disperserID uint32) (gethcommon.Address, error) } type Writer interface { diff --git a/core/eth/reader.go b/core/eth/reader.go index 8e231f8675..1c0cfbb1a6 100644 --- a/core/eth/reader.go +++ b/core/eth/reader.go @@ -748,3 +748,10 @@ func (t *Reader) GetRelayURLs(ctx context.Context) (map[uint32]string, error) { return res, nil } + +func (t *Reader) GetDisperserAddress(ctx context.Context, disperserID uint32) (gethcommon.Address, error) { + // TODO(cody-littley/arch): this is just a place holder until we register dispersers on chain + bytes := make([]byte, gethcommon.AddressLength) + address := gethcommon.BytesToAddress(bytes) + return address, nil +} diff --git a/core/mock/writer.go b/core/mock/writer.go index 7e56e64cc3..36fb0906a4 100644 --- a/core/mock/writer.go +++ b/core/mock/writer.go @@ -266,3 +266,14 @@ func (t *MockWriter) GetRelayURLs(ctx context.Context) (map[uint32]string, error return result.(map[uint32]string), args.Error(1) } + +func (t *MockWriter) GetDisperserAddress(ctx context.Context, disperserID uint32) (gethcommon.Address, error) { + args := t.Called() + result := args.Get(0) + if result == nil { + var zeroValue gethcommon.Address + return zeroValue, args.Error(1) + } + + return result.(gethcommon.Address), args.Error(1) +} diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index e05c487b2d..a36bba74c5 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -8,6 +8,7 @@ import ( grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/core" dauth "github.com/Layr-Labs/eigenda/disperser/auth" + "github.com/ethereum/go-ethereum/crypto" lru "github.com/hashicorp/golang-lru/v2" "time" ) @@ -34,7 +35,8 @@ type keyWithTimeout struct { var _ RequestAuthenticator = &requestAuthenticator{} type requestAuthenticator struct { - ics core.IndexedChainState + // chainReader is used to read the chain state. + chainReader core.Reader // keyCache is used to cache the public keys of dispersers. keyCache *lru.Cache[uint32, *keyWithTimeout] @@ -55,7 +57,7 @@ type requestAuthenticator struct { // NewRequestAuthenticator creates a new RequestAuthenticator. func NewRequestAuthenticator( ctx context.Context, - ics core.IndexedChainState, + chainReader core.Reader, keyCacheSize int, keyTimeoutDuration time.Duration, authenticationTimeoutDuration time.Duration, @@ -72,7 +74,7 @@ func NewRequestAuthenticator( } authenticator := &requestAuthenticator{ - ics: ics, + chainReader: chainReader, keyCache: keyCache, keyTimeoutDuration: keyTimeoutDuration, authenticatedDispersers: authenticatedDispersers, @@ -89,15 +91,11 @@ func NewRequestAuthenticator( func (a *requestAuthenticator) preloadCache(ctx context.Context, now time.Time) error { // TODO (cody-littley): this will need to be updated for decentralized dispersers - key, err := a.getDisperserKey(ctx, now, 0) + _, err := a.getDisperserKey(ctx, now, 0) if err != nil { return fmt.Errorf("failed to get operator key: %w", err) } - if key == nil { - return errors.New("key is nil") - } - return nil } @@ -141,25 +139,22 @@ func (a *requestAuthenticator) getDisperserKey( } } - // TODO add logic for fetching key - - //blockNumber, err := a.ics.GetCurrentBlockNumber() - //if err != nil { - // return nil, fmt.Errorf("failed to get current block number: %w", err) - //} - //operators, err := a.ics.GetIndexedOperators(ctx, blockNumber) - //if err != nil { - // return nil, fmt.Errorf("failed to get operators: %w", err) - //} - // - //operator, ok := operators[operatorID] - //if !ok { - // return nil, errors.New("operator not found") - //} - //key = operator.PubkeyG2 - // - //a.keyCache.Add(operatorID, key) - return nil, nil + address, err := a.chainReader.GetDisperserAddress(ctx, disperserID) + if err != nil { + return nil, fmt.Errorf("failed to get disperser address: %w", err) + } + + ecdsaKey, err := crypto.UnmarshalPubkey(address.Bytes()) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal public key: %w", err) + } + + a.keyCache.Add(disperserID, &keyWithTimeout{ + key: ecdsaKey, + expiration: now.Add(a.keyTimeoutDuration), + }) + + return ecdsaKey, nil } // saveAuthenticationResult saves the result of an auth. From 5ba4b7b6bd3159b2aa43dfe4581e82be89340376 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 11 Dec 2024 13:47:44 -0600 Subject: [PATCH 15/50] Incremental progress. Signed-off-by: Cody Littley --- node/cmd/main.go | 16 +++++- node/config.go | 109 +++++++++++++++++++++--------------- node/flags/flags.go | 31 ++++++++++ node/grpc/server_test.go | 15 ++--- node/grpc/server_v2.go | 66 ++++++++++++++++++---- node/grpc/server_v2_test.go | 8 ++- node/node.go | 8 +-- test/integration_test.go | 34 ++++++----- 8 files changed, 200 insertions(+), 87 deletions(-) diff --git a/node/cmd/main.go b/node/cmd/main.go index 0225b33d71..f0740001d8 100644 --- a/node/cmd/main.go +++ b/node/cmd/main.go @@ -3,6 +3,8 @@ package main import ( "context" "fmt" + "github.com/Layr-Labs/eigenda/common/geth" + rpccalls "github.com/Layr-Labs/eigensdk-go/metrics/collectors/rpc_calls" "log" "os" "time" @@ -72,8 +74,14 @@ func NodeMain(ctx *cli.Context) error { ratelimiter := ratelimit.NewRateLimiter(reg, globalParams, bucketStore, logger) + rpcCallsCollector := rpccalls.NewCollector(node.AppName, reg) + client, err := geth.NewInstrumentedEthClient(config.EthClientConfig, rpcCallsCollector, logger) + if err != nil { + return fmt.Errorf("cannot create chain.Client: %w", err) + } + // Create the node. - node, err := node.NewNode(reg, config, pubIPProvider, logger) + node, err := node.NewNode(reg, config, pubIPProvider, client, logger) if err != nil { return err } @@ -86,7 +94,11 @@ func NodeMain(ctx *cli.Context) error { // Creates the GRPC server. server := nodegrpc.NewServer(config, node, logger, ratelimiter) - serverV2 := nodegrpc.NewServerV2(config, node, logger, ratelimiter) + serverV2, err := nodegrpc.NewServerV2(context.Background(), config, node, logger, ratelimiter, client) + if err != nil { + return fmt.Errorf("failed to create grpc v2 server: %w", err) + } + err = nodegrpc.RunServers(server, serverV2, config, logger) return err diff --git a/node/config.go b/node/config.go index fd8d530853..64874db718 100644 --- a/node/config.go +++ b/node/config.go @@ -3,7 +3,6 @@ package node import ( "errors" "fmt" - "os" "strconv" "strings" @@ -95,6 +94,20 @@ type Config struct { PprofHttpPort string EnablePprof bool + + // TODO update flags + + // if true then the node will not authenticate StoreChunks requests from dispersers (v2 only) + DisableDispersalAuthentication bool + // the size of the cache for storing public keys of dispersers + DispersalAuthenticationKeyCacheSize int + // the timeout for disperser keys (after which the disperser key is reloaded from the chain) + DisperserKeyTimeout time.Duration + // the timeout for disperser authentication (set to 0 to disable), if enabled then a successful authentication + // of a StoreChunks request causes the node to skip validation for requests coming from the same IP address + // for this duration. Adds risk of disruptive behavior if an attacker is able to send requests from the same IP + // address as a legitimate disperser, but reduces performance overhead of StoreChunks validation. + DispersalAuthenticationTimeout time.Duration } // NewConfig parses the Config from the provided flags or environment variables and @@ -199,50 +212,54 @@ func NewConfig(ctx *cli.Context) (*Config, error) { } return &Config{ - Hostname: ctx.GlobalString(flags.HostnameFlag.Name), - DispersalPort: ctx.GlobalString(flags.DispersalPortFlag.Name), - RetrievalPort: ctx.GlobalString(flags.RetrievalPortFlag.Name), - InternalDispersalPort: internalDispersalFlag, - InternalRetrievalPort: internalRetrievalFlag, - EnableNodeApi: ctx.GlobalBool(flags.EnableNodeApiFlag.Name), - NodeApiPort: ctx.GlobalString(flags.NodeApiPortFlag.Name), - EnableMetrics: ctx.GlobalBool(flags.EnableMetricsFlag.Name), - MetricsPort: ctx.GlobalString(flags.MetricsPortFlag.Name), - OnchainMetricsInterval: ctx.GlobalInt64(flags.OnchainMetricsIntervalFlag.Name), - Timeout: timeout, - RegisterNodeAtStart: registerNodeAtStart, - ExpirationPollIntervalSec: expirationPollIntervalSec, - ReachabilityPollIntervalSec: reachabilityPollIntervalSec, - EnableTestMode: testMode, - OverrideBlockStaleMeasure: ctx.GlobalInt64(flags.OverrideBlockStaleMeasureFlag.Name), - OverrideStoreDurationBlocks: ctx.GlobalInt64(flags.OverrideStoreDurationBlocksFlag.Name), - QuorumIDList: ids, - DbPath: ctx.GlobalString(flags.DbPathFlag.Name), - PrivateBls: privateBls, - EthClientConfig: ethClientConfig, - EncoderConfig: kzg.ReadCLIConfig(ctx), - LoggerConfig: *loggerConfig, - BLSOperatorStateRetrieverAddr: ctx.GlobalString(flags.BlsOperatorStateRetrieverFlag.Name), - EigenDAServiceManagerAddr: ctx.GlobalString(flags.EigenDAServiceManagerFlag.Name), - PubIPProvider: ctx.GlobalString(flags.PubIPProviderFlag.Name), - PubIPCheckInterval: pubIPCheckInterval, - ChurnerUrl: ctx.GlobalString(flags.ChurnerUrlFlag.Name), - DataApiUrl: ctx.GlobalString(flags.DataApiUrlFlag.Name), - NumBatchValidators: ctx.GlobalInt(flags.NumBatchValidatorsFlag.Name), - NumBatchDeserializationWorkers: ctx.GlobalInt(flags.NumBatchDeserializationWorkersFlag.Name), - EnableGnarkBundleEncoding: ctx.Bool(flags.EnableGnarkBundleEncodingFlag.Name), - ClientIPHeader: ctx.GlobalString(flags.ClientIPHeaderFlag.Name), - UseSecureGrpc: ctx.GlobalBoolT(flags.ChurnerUseSecureGRPC.Name), - DisableNodeInfoResources: ctx.GlobalBool(flags.DisableNodeInfoResourcesFlag.Name), - BLSRemoteSignerUrl: ctx.GlobalString(flags.BLSRemoteSignerUrlFlag.Name), - BLSPublicKeyHex: ctx.GlobalString(flags.BLSPublicKeyHexFlag.Name), - BLSKeyPassword: ctx.GlobalString(flags.BlsKeyPasswordFlag.Name), - BLSSignerTLSCertFilePath: ctx.GlobalString(flags.BLSSignerCertFileFlag.Name), - BLSRemoteSignerEnabled: blsRemoteSignerEnabled, - EnableV2: ctx.GlobalBool(flags.EnableV2Flag.Name), - OnchainStateRefreshInterval: ctx.GlobalDuration(flags.OnchainStateRefreshIntervalFlag.Name), - ChunkDownloadTimeout: ctx.GlobalDuration(flags.ChunkDownloadTimeoutFlag.Name), - PprofHttpPort: ctx.GlobalString(flags.PprofHttpPort.Name), - EnablePprof: ctx.GlobalBool(flags.EnablePprof.Name), + Hostname: ctx.GlobalString(flags.HostnameFlag.Name), + DispersalPort: ctx.GlobalString(flags.DispersalPortFlag.Name), + RetrievalPort: ctx.GlobalString(flags.RetrievalPortFlag.Name), + InternalDispersalPort: internalDispersalFlag, + InternalRetrievalPort: internalRetrievalFlag, + EnableNodeApi: ctx.GlobalBool(flags.EnableNodeApiFlag.Name), + NodeApiPort: ctx.GlobalString(flags.NodeApiPortFlag.Name), + EnableMetrics: ctx.GlobalBool(flags.EnableMetricsFlag.Name), + MetricsPort: ctx.GlobalString(flags.MetricsPortFlag.Name), + OnchainMetricsInterval: ctx.GlobalInt64(flags.OnchainMetricsIntervalFlag.Name), + Timeout: timeout, + RegisterNodeAtStart: registerNodeAtStart, + ExpirationPollIntervalSec: expirationPollIntervalSec, + ReachabilityPollIntervalSec: reachabilityPollIntervalSec, + EnableTestMode: testMode, + OverrideBlockStaleMeasure: ctx.GlobalInt64(flags.OverrideBlockStaleMeasureFlag.Name), + OverrideStoreDurationBlocks: ctx.GlobalInt64(flags.OverrideStoreDurationBlocksFlag.Name), + QuorumIDList: ids, + DbPath: ctx.GlobalString(flags.DbPathFlag.Name), + PrivateBls: privateBls, + EthClientConfig: ethClientConfig, + EncoderConfig: kzg.ReadCLIConfig(ctx), + LoggerConfig: *loggerConfig, + BLSOperatorStateRetrieverAddr: ctx.GlobalString(flags.BlsOperatorStateRetrieverFlag.Name), + EigenDAServiceManagerAddr: ctx.GlobalString(flags.EigenDAServiceManagerFlag.Name), + PubIPProvider: ctx.GlobalString(flags.PubIPProviderFlag.Name), + PubIPCheckInterval: pubIPCheckInterval, + ChurnerUrl: ctx.GlobalString(flags.ChurnerUrlFlag.Name), + DataApiUrl: ctx.GlobalString(flags.DataApiUrlFlag.Name), + NumBatchValidators: ctx.GlobalInt(flags.NumBatchValidatorsFlag.Name), + NumBatchDeserializationWorkers: ctx.GlobalInt(flags.NumBatchDeserializationWorkersFlag.Name), + EnableGnarkBundleEncoding: ctx.Bool(flags.EnableGnarkBundleEncodingFlag.Name), + ClientIPHeader: ctx.GlobalString(flags.ClientIPHeaderFlag.Name), + UseSecureGrpc: ctx.GlobalBoolT(flags.ChurnerUseSecureGRPC.Name), + DisableNodeInfoResources: ctx.GlobalBool(flags.DisableNodeInfoResourcesFlag.Name), + BLSRemoteSignerUrl: ctx.GlobalString(flags.BLSRemoteSignerUrlFlag.Name), + BLSPublicKeyHex: ctx.GlobalString(flags.BLSPublicKeyHexFlag.Name), + BLSKeyPassword: ctx.GlobalString(flags.BlsKeyPasswordFlag.Name), + BLSSignerTLSCertFilePath: ctx.GlobalString(flags.BLSSignerCertFileFlag.Name), + BLSRemoteSignerEnabled: blsRemoteSignerEnabled, + EnableV2: ctx.GlobalBool(flags.EnableV2Flag.Name), + OnchainStateRefreshInterval: ctx.GlobalDuration(flags.OnchainStateRefreshIntervalFlag.Name), + ChunkDownloadTimeout: ctx.GlobalDuration(flags.ChunkDownloadTimeoutFlag.Name), + PprofHttpPort: ctx.GlobalString(flags.PprofHttpPort.Name), + EnablePprof: ctx.GlobalBool(flags.EnablePprof.Name), + DisableDispersalAuthentication: ctx.GlobalBool(flags.DisableDispersalAuthenticationFlag.Name), + DispersalAuthenticationKeyCacheSize: ctx.GlobalInt(flags.DispersalAuthenticationKeyCacheSizeFlag.Name), + DisperserKeyTimeout: ctx.GlobalDuration(flags.DisperserKeyTimeoutFlag.Name), + DispersalAuthenticationTimeout: ctx.GlobalDuration(flags.DispersalAuthenticationTimeoutFlag.Name), }, nil } diff --git a/node/flags/flags.go b/node/flags/flags.go index ac0bbbae87..a10ef2dc51 100644 --- a/node/flags/flags.go +++ b/node/flags/flags.go @@ -238,6 +238,33 @@ var ( EnvVar: common.PrefixEnvVar(EnvVarPrefix, "CHUNK_DOWNLOAD_TIMEOUT"), Value: 20 * time.Second, } + DisableDispersalAuthenticationFlag = cli.BoolFlag{ + Name: common.PrefixFlag(FlagPrefix, "disable-dispersal-authentication"), + Usage: "Disable authentication for StoreChunks() calls from the disperser", + Required: false, + EnvVar: common.PrefixEnvVar(EnvVarPrefix, "DISABLE_DISPERSAL_AUTHENTICATION"), + } + DispersalAuthenticationKeyCacheSizeFlag = cli.IntFlag{ + Name: common.PrefixFlag(FlagPrefix, "dispersal-authentication-key-cache-size"), + Usage: "The size of the dispersal authentication key cache", + Required: false, + EnvVar: common.PrefixEnvVar(EnvVarPrefix, "DISPERSAL_AUTHENTICATION_KEY_CACHE_SIZE"), + Value: 1024, + } + DisperserKeyTimeoutFlag = cli.DurationFlag{ + Name: common.PrefixFlag(FlagPrefix, "disperser-key-timeout"), + Usage: "The duration for which a disperser key is cached", + Required: false, + EnvVar: common.PrefixEnvVar(EnvVarPrefix, "DISPERSER_KEY_TIMEOUT"), + Value: 1 * time.Hour, + } + DispersalAuthenticationTimeoutFlag = cli.DurationFlag{ + Name: common.PrefixFlag(FlagPrefix, "dispersal-authentication-timeout"), + Usage: "The duration for which a disperser authentication is valid", + Required: false, + EnvVar: common.PrefixEnvVar(EnvVarPrefix, "DISPERSAL_AUTHENTICATION_TIMEOUT"), + Value: 5 * time.Minute, + } // Test only, DO NOT USE the following flags in production @@ -384,6 +411,10 @@ var optionalFlags = []cli.Flag{ ChunkDownloadTimeoutFlag, PprofHttpPort, EnablePprof, + DisableDispersalAuthenticationFlag, + DispersalAuthenticationKeyCacheSizeFlag, + DisperserKeyTimeoutFlag, + DispersalAuthenticationTimeoutFlag, } func init() { diff --git a/node/grpc/server_test.go b/node/grpc/server_test.go index 825876192e..edbc45a7aa 100644 --- a/node/grpc/server_test.go +++ b/node/grpc/server_test.go @@ -78,13 +78,14 @@ func makeTestComponents() (encoding.Prover, encoding.Verifier, error) { func makeConfig(t *testing.T) *node.Config { return &node.Config{ - Timeout: 10 * time.Second, - ExpirationPollIntervalSec: 1, - QuorumIDList: []core.QuorumID{0}, - DbPath: t.TempDir(), - ID: opID, - NumBatchValidators: runtime.GOMAXPROCS(0), - EnableV2: false, + Timeout: 10 * time.Second, + ExpirationPollIntervalSec: 1, + QuorumIDList: []core.QuorumID{0}, + DbPath: t.TempDir(), + ID: opID, + NumBatchValidators: runtime.GOMAXPROCS(0), + EnableV2: false, + DisableDispersalAuthentication: true, // TODO re-enable } } diff --git a/node/grpc/server_v2.go b/node/grpc/server_v2.go index 4f46a70d53..1979593b7e 100644 --- a/node/grpc/server_v2.go +++ b/node/grpc/server_v2.go @@ -3,8 +3,13 @@ package grpc import ( "context" "encoding/hex" + "errors" "fmt" + coreeth "github.com/Layr-Labs/eigenda/core/eth" + "github.com/Layr-Labs/eigenda/node/auth" + "google.golang.org/grpc/peer" "runtime" + "time" "github.com/Layr-Labs/eigenda/api" pb "github.com/Layr-Labs/eigenda/api/grpc/node/v2" @@ -22,25 +27,53 @@ type ServerV2 struct { pb.UnimplementedDispersalServer pb.UnimplementedRetrievalServer - config *node.Config - node *node.Node - ratelimiter common.RateLimiter - logger logging.Logger + config *node.Config + node *node.Node + ratelimiter common.RateLimiter + authenticator auth.RequestAuthenticator + + logger logging.Logger } // NewServerV2 creates a new Server instance with the provided parameters. func NewServerV2( + ctx context.Context, config *node.Config, node *node.Node, logger logging.Logger, ratelimiter common.RateLimiter, -) *ServerV2 { - return &ServerV2{ - config: config, - node: node, - ratelimiter: ratelimiter, - logger: logger, + client common.EthClient, +) (*ServerV2, error) { + var authenticator auth.RequestAuthenticator + if !config.DisableDispersalAuthentication { + reader, err := coreeth.NewReader( + logger, + client, + config.BLSOperatorStateRetrieverAddr, + config.EigenDAServiceManagerAddr) + if err != nil { + return nil, fmt.Errorf("cannot create eth.Reader: %w", err) + } + + authenticator, err = auth.NewRequestAuthenticator( + ctx, + reader, + config.DispersalAuthenticationKeyCacheSize, + config.DisperserKeyTimeout, + config.DispersalAuthenticationTimeout, + time.Now()) + if err != nil { + return nil, fmt.Errorf("failed to create authenticator: %w", err) + } } + + return &ServerV2{ + config: config, + node: node, + ratelimiter: ratelimiter, + authenticator: authenticator, + logger: logger, + }, nil } func (s *ServerV2) NodeInfo(ctx context.Context, in *pb.NodeInfoRequest) (*pb.NodeInfoReply, error) { @@ -62,6 +95,19 @@ func (s *ServerV2) StoreChunks(ctx context.Context, in *pb.StoreChunksRequest) ( return nil, api.NewErrorInvalidArg("v2 API is disabled") } + if s.authenticator != nil { + disperserPeer, ok := peer.FromContext(ctx) + if !ok { + return nil, errors.New("could not get peer information") + } + disperserAddress := disperserPeer.Addr.String() + + err := s.authenticator.AuthenticateStoreChunksRequest(ctx, disperserAddress, in, time.Now()) + if err != nil { + return nil, fmt.Errorf("failed to authenticate request: %w", err) + } + } + if s.node.StoreV2 == nil { return nil, api.NewErrorInternal("v2 store not initialized") } diff --git a/node/grpc/server_v2_test.go b/node/grpc/server_v2_test.go index 11ef1fce3e..8e16ddd8af 100644 --- a/node/grpc/server_v2_test.go +++ b/node/grpc/server_v2_test.go @@ -82,7 +82,13 @@ func newTestComponents(t *testing.T, config *node.Config) *testComponents { RelayClient: atomicRelayClient, } node.BlobVersionParams.Store(v2.NewBlobVersionParameterMap(blobParamsMap)) - server := grpc.NewServerV2(config, node, logger, ratelimiter) + + // The eth client is only utilized for StoreChunks validation, which is disabled in these tests + // TODO write test for StoreChunks validation + var client common.EthClient + + server, err := grpc.NewServerV2(context.Background(), config, node, logger, ratelimiter, client) + require.NoError(t, err) return &testComponents{ server: server, node: node, diff --git a/node/node.go b/node/node.go index 3e36d5f787..0c63d80502 100644 --- a/node/node.go +++ b/node/node.go @@ -42,7 +42,6 @@ import ( v2 "github.com/Layr-Labs/eigenda/core/v2" "github.com/Layr-Labs/eigensdk-go/logging" "github.com/Layr-Labs/eigensdk-go/metrics" - rpccalls "github.com/Layr-Labs/eigensdk-go/metrics/collectors/rpc_calls" "github.com/Layr-Labs/eigensdk-go/nodeapi" "github.com/gammazero/workerpool" @@ -94,6 +93,7 @@ func NewNode( reg *prometheus.Registry, config *Config, pubIPProvider pubip.Provider, + client *geth.InstrumentedEthClient, logger logging.Logger, ) (*Node, error) { // Setup metrics @@ -105,7 +105,6 @@ func NewNode( nodeLogger := logger.With("component", "Node") eigenMetrics := metrics.NewEigenMetrics(AppName, ":"+config.MetricsPort, reg, logger.With("component", "EigenMetrics")) - rpcCallsCollector := rpccalls.NewCollector(AppName, reg) // Make sure config folder exists. err := os.MkdirAll(config.DbPath, os.ModePerm) @@ -113,11 +112,6 @@ func NewNode( return nil, fmt.Errorf("could not create db directory at %s: %w", config.DbPath, err) } - client, err := geth.NewInstrumentedEthClient(config.EthClientConfig, rpcCallsCollector, logger) - if err != nil { - return nil, fmt.Errorf("cannot create chain.Client: %w", err) - } - chainID, err := client.ChainID(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get chainID: %w", err) diff --git a/test/integration_test.go b/test/integration_test.go index b3efdc25f1..eae4e1dd29 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/stretchr/testify/require" "log" "math" "math/big" @@ -349,19 +350,20 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging } config := &node.Config{ - Hostname: op.Host, - DispersalPort: op.DispersalPort, - RetrievalPort: op.RetrievalPort, - InternalRetrievalPort: op.RetrievalPort, - InternalDispersalPort: op.DispersalPort, - EnableMetrics: false, - Timeout: 10, - ExpirationPollIntervalSec: 10, - DbPath: dbPath, - LogPath: logPath, - PrivateBls: string(op.KeyPair.GetPubKeyG1().Serialize()), - ID: id, - QuorumIDList: registeredQuorums, + Hostname: op.Host, + DispersalPort: op.DispersalPort, + RetrievalPort: op.RetrievalPort, + InternalRetrievalPort: op.RetrievalPort, + InternalDispersalPort: op.DispersalPort, + EnableMetrics: false, + Timeout: 10, + ExpirationPollIntervalSec: 10, + DbPath: dbPath, + LogPath: logPath, + PrivateBls: string(op.KeyPair.GetPubKeyG1().Serialize()), + ID: id, + QuorumIDList: registeredQuorums, + DisableDispersalAuthentication: true, // TODO re-enable } // creating a new instance of encoder instead of sharing enc because enc is not thread safe @@ -417,8 +419,12 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging ratelimiter := &commonmock.NoopRatelimiter{} + // TODO enable request validation + var client common.EthClient + serverV1 := nodegrpc.NewServer(config, n, logger, ratelimiter) - serverV2 := nodegrpc.NewServerV2(config, n, logger, ratelimiter) + serverV2, err := nodegrpc.NewServerV2(context.Background(), config, n, logger, ratelimiter, client) + require.NoError(t, err) ops[id] = TestOperator{ Node: n, From e0943716c9608b3105c69eba51bd24dd1ba1fea2 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 11 Dec 2024 14:03:25 -0600 Subject: [PATCH 16/50] Added test placeholder. Signed-off-by: Cody Littley --- node/auth/authenticator_tests.go | 1 + 1 file changed, 1 insertion(+) create mode 100644 node/auth/authenticator_tests.go diff --git a/node/auth/authenticator_tests.go b/node/auth/authenticator_tests.go new file mode 100644 index 0000000000..8832b06d18 --- /dev/null +++ b/node/auth/authenticator_tests.go @@ -0,0 +1 @@ +package auth From db0885a765a0e6b9b0c1c9de736112d961c24a25 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 12 Dec 2024 09:22:03 -0600 Subject: [PATCH 17/50] shuffle stuff around Signed-off-by: Cody Littley --- api/clients/node_client_v2.go | 3 +- disperser/auth/auth_test.go | 104 -------------------- node/auth/authenticator.go | 3 +- {disperser => node}/auth/request_signing.go | 2 - node/auth/request_signing_tests.go | 8 ++ 5 files changed, 11 insertions(+), 109 deletions(-) delete mode 100644 disperser/auth/auth_test.go rename {disperser => node}/auth/request_signing.go (99%) create mode 100644 node/auth/request_signing_tests.go diff --git a/api/clients/node_client_v2.go b/api/clients/node_client_v2.go index e45dfc9869..25d1016969 100644 --- a/api/clients/node_client_v2.go +++ b/api/clients/node_client_v2.go @@ -5,6 +5,7 @@ import ( "crypto/ecdsa" "fmt" "github.com/Layr-Labs/eigenda/disperser/auth" + auth2 "github.com/Layr-Labs/eigenda/node/auth" "sync" commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2" @@ -87,7 +88,7 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c } if c.key != nil { - signature, err := auth.SignStoreChunksRequest(c.key, request) // TODO + signature, err := auth2.SignStoreChunksRequest(c.key, request) // TODO if err != nil { return nil, fmt.Errorf("failed to sign request: %v", err) } diff --git a/disperser/auth/auth_test.go b/disperser/auth/auth_test.go deleted file mode 100644 index c38b550e1a..0000000000 --- a/disperser/auth/auth_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package auth - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "github.com/Layr-Labs/eigenda/api/grpc/node/v2" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "net" - "os" - "testing" -) - -// The purpose of these tests are to verify that TLS key generation works as expected. -// TODO recreate keys each time a test is run - -var _ v2.DispersalServer = (*mockDispersalServer)(nil) - -type mockDispersalServer struct { - v2.DispersalServer -} - -func (s *mockDispersalServer) StoreChunks(context.Context, *v2.StoreChunksRequest) (*v2.StoreChunksReply, error) { - fmt.Printf("called StoreChunks\n") - return nil, nil -} - -func (s *mockDispersalServer) NodeInfo(context.Context, *v2.NodeInfoRequest) (*v2.NodeInfoReply, error) { - fmt.Printf("called NodeInfo\n") - return nil, nil -} - -func buildClient(t *testing.T) v2.DispersalClient { - addr := "0.0.0.0:50051" - - cert, err := tls.LoadX509KeyPair("./test-disperser.crt", "./test-disperser.key") - require.NoError(t, err) - - // This is what we'd enable if we wanted to verify the server's certificate - //nodeCert, err := os.ReadFile("./test-node.crt") - //require.NoError(t, err) - //certPool := x509.NewCertPool() - //ok := certPool.AppendCertsFromPEM(nodeCert) - //require.True(t, ok) - - creds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{cert}, - //RootCAs: certPool, - InsecureSkipVerify: true, - }) - - conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds)) - require.NoError(t, err) - - return v2.NewDispersalClient(conn) -} - -func buildServer(t *testing.T) (v2.DispersalServer, *grpc.Server) { - dispersalServer := &mockDispersalServer{} - - cert, err := tls.LoadX509KeyPair("./test-node.crt", "./test-node.key") - require.NoError(t, err) - - disperserCert, err := os.ReadFile("./test-disperser.crt") - require.NoError(t, err) - certPool := x509.NewCertPool() - ok := certPool.AppendCertsFromPEM(disperserCert) - require.True(t, ok) - - creds := credentials.NewTLS(&tls.Config{ - Certificates: []tls.Certificate{cert}, - ClientCAs: certPool, - ClientAuth: tls.RequireAndVerifyClientCert, - }) - - server := grpc.NewServer(grpc.Creds(creds)) - v2.RegisterDispersalServer(server, dispersalServer) - - addr := "0.0.0.0:50051" - listener, err := net.Listen("tcp", addr) - require.NoError(t, err) - - go func() { - err = server.Serve(listener) - require.NoError(t, err) - }() - - return dispersalServer, server -} - -func TestServerWithTLS(t *testing.T) { - dispersalServer, server := buildServer(t) - defer server.Stop() - require.NotNil(t, dispersalServer) // TODO remove - - client := buildClient(t) - - response, err := client.NodeInfo(context.Background(), &v2.NodeInfoRequest{}) - require.NoError(t, err) - require.NotNil(t, response) -} diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index a36bba74c5..eac071e794 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -7,7 +7,6 @@ import ( "fmt" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/core" - dauth "github.com/Layr-Labs/eigenda/disperser/auth" "github.com/ethereum/go-ethereum/crypto" lru "github.com/hashicorp/golang-lru/v2" "time" @@ -116,7 +115,7 @@ func (a *requestAuthenticator) AuthenticateStoreChunksRequest( } signature := request.Signature - isValid := dauth.VerifyStoreChunksRequest(key, request, signature) + isValid := VerifyStoreChunksRequest(key, request, signature) if !isValid { return errors.New("signature verification failed") diff --git a/disperser/auth/request_signing.go b/node/auth/request_signing.go similarity index 99% rename from disperser/auth/request_signing.go rename to node/auth/request_signing.go index 3d7216c1b6..f4b9df3f0a 100644 --- a/disperser/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -12,8 +12,6 @@ import ( "hash" ) -// TODO test these methods - // SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not // write the signature into the request. func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { diff --git a/node/auth/request_signing_tests.go b/node/auth/request_signing_tests.go new file mode 100644 index 0000000000..afd0bd499e --- /dev/null +++ b/node/auth/request_signing_tests.go @@ -0,0 +1,8 @@ +package auth + +import grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + +func randomStoreChunksRequest(random tu.TestRandom) *grpc.StoreChunksRequest { + + return nil +} From b04fcfa46e39af89278afcbdea882cd6752c5773 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 12 Dec 2024 11:13:18 -0600 Subject: [PATCH 18/50] Unit tests for request signing. Signed-off-by: Cody Littley --- api/proto/node/v2/node_v2.proto | 7 +- common/testutils/random/test_random.go | 42 +++- common/testutils/random/test_random_test.go | 8 +- node/auth/request_signing.go | 3 +- node/auth/request_signing_test.go | 233 ++++++++++++++++++++ node/auth/request_signing_tests.go | 8 - 6 files changed, 283 insertions(+), 18 deletions(-) create mode 100644 node/auth/request_signing_test.go delete mode 100644 node/auth/request_signing_tests.go diff --git a/api/proto/node/v2/node_v2.proto b/api/proto/node/v2/node_v2.proto index 652154f63a..4005249f2d 100644 --- a/api/proto/node/v2/node_v2.proto +++ b/api/proto/node/v2/node_v2.proto @@ -47,10 +47,11 @@ message StoreChunksRequest { // e. digest certificate.BlobHeader.Commitment.LengthProof // f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) // g. digest certificate.BlobHeader.PaymentHeader.AccountId - // h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + // h. digest certificate.BlobHeader.PaymentHeader.ReservationPeriod (4 bytes, unsigned big endian) // i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment - // j. digest certificate.BlobHeader.Signature - // k. for each relay in certificate.Relays: + // j. digest certificate.BlobHeader.PaymentHeader.Salt (4 bytes, unsigned big endian) + // k. digest certificate.BlobHeader.Signature + // l. for each relay in certificate.Relays: // i. digest relay (4 bytes, unsigned big endian) // 4. digest disperserID (4 bytes, unsigned big endian) // diff --git a/common/testutils/random/test_random.go b/common/testutils/random/test_random.go index ed4699a6dc..f8a0ecff15 100644 --- a/common/testutils/random/test_random.go +++ b/common/testutils/random/test_random.go @@ -1,19 +1,31 @@ package random import ( + "crypto/ecdsa" + "crypto/elliptic" "fmt" + "github.com/stretchr/testify/require" + "io" "math/rand" + "testing" "time" ) // TestRandom provides all the functionality of math/rand.Rand, plus additional randomness functionality useful for testing type TestRandom struct { + // The source of randomness *rand.Rand + + // The testing object + t *testing.T + + // The seed used to initialize the random number generator + seed int64 } // NewTestRandom creates a new instance of TestRandom // This method may either be seeded, or not seeded. If no seed is provided, then current unix nano time is used. -func NewTestRandom(fixedSeed ...int64) *TestRandom { +func NewTestRandom(t *testing.T, fixedSeed ...int64) *TestRandom { var seed int64 if len(fixedSeed) == 0 { seed = time.Now().UnixNano() @@ -25,10 +37,18 @@ func NewTestRandom(fixedSeed ...int64) *TestRandom { fmt.Printf("Random seed: %d\n", seed) return &TestRandom{ - rand.New(rand.NewSource(seed)), + Rand: rand.New(rand.NewSource(seed)), + t: t, + seed: seed, } } +// Reset resets the random number generator to the state it was in when it was first created. +// This method is not thread safe with respect to other methods in this struct. +func (r *TestRandom) Reset() { + r.Seed(r.seed) +} + // RandomBytes generates a random byte slice of a given length. func (r *TestRandom) RandomBytes(length int) []byte { bytes := make([]byte, length) @@ -53,3 +73,21 @@ func (r *TestRandom) RandomString(length int) string { } return string(b) } + +var _ io.Reader = &randIOReader{} + +// randIOReader is an io.Reader that reads from a random number generator. +type randIOReader struct { + rand *TestRandom +} + +func (i *randIOReader) Read(p []byte) (n int, err error) { + return i.rand.Read(p) +} + +// RandomECDSA generates a random ECDSA key. FOR TESTING PURPOSES ONLY. DO NOT USE THESE KEYS FOR SECURITY PURPOSES. +func (r *TestRandom) RandomECDSA() (*ecdsa.PublicKey, *ecdsa.PrivateKey) { + key, err := ecdsa.GenerateKey(elliptic.P256(), &randIOReader{r}) + require.NoError(r.t, err) + return &key.PublicKey, key +} diff --git a/common/testutils/random/test_random_test.go b/common/testutils/random/test_random_test.go index 822afd8555..228faf5dda 100644 --- a/common/testutils/random/test_random_test.go +++ b/common/testutils/random/test_random_test.go @@ -8,19 +8,19 @@ import ( // Tests that random seeding produces random results, and that consistent seeding produces consistent results func TestSetup(t *testing.T) { - testRandom1 := NewTestRandom() + testRandom1 := NewTestRandom(t) x := testRandom1.Int() - testRandom2 := NewTestRandom() + testRandom2 := NewTestRandom(t) y := testRandom2.Int() assert.NotEqual(t, x, y) seed := rand.Int63() - testRandom3 := NewTestRandom(seed) + testRandom3 := NewTestRandom(t, seed) a := testRandom3.Int() - testRandom4 := NewTestRandom(seed) + testRandom4 := NewTestRandom(t, seed) b := testRandom4.Int() assert.Equal(t, a, b) diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index f4b9df3f0a..7150b212a4 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -76,8 +76,9 @@ func hashBlobCommitment(hasher hash.Hash, commitment *commonv1.BlobCommitment) { func hashPaymentHeader(hasher hash.Hash, header *commonv1.PaymentHeader) { hasher.Write([]byte(header.AccountId)) - hashUint32(hasher, header.BinIndex) + hashUint32(hasher, header.ReservationPeriod) hasher.Write(header.CumulativePayment) + hashUint32(hasher, header.Salt) } func hashUint32(hasher hash.Hash, value uint32) { diff --git a/node/auth/request_signing_test.go b/node/auth/request_signing_test.go new file mode 100644 index 0000000000..c33333ed12 --- /dev/null +++ b/node/auth/request_signing_test.go @@ -0,0 +1,233 @@ +package auth + +import ( + "github.com/Layr-Labs/eigenda/api/grpc/common" + v2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2" + grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/Layr-Labs/eigenda/common/testutils/random" + "github.com/stretchr/testify/require" + "testing" +) + +func randomStoreChunksRequest(rand *random.TestRandom) *grpc.StoreChunksRequest { + certificateCount := rand.Intn(10) + 1 + blobCertificates := make([]*v2.BlobCertificate, certificateCount) + for i := 0; i < certificateCount; i++ { + + relayCount := rand.Intn(10) + 1 + relays := make([]uint32, relayCount) + for j := 0; j < relayCount; j++ { + relays[j] = rand.Uint32() + } + + quorumCount := rand.Intn(10) + 1 + quorumNumbers := make([]uint32, quorumCount) + for j := 0; j < quorumCount; j++ { + quorumNumbers[j] = rand.Uint32() + } + + blobCertificates[i] = &v2.BlobCertificate{ + BlobHeader: &v2.BlobHeader{ + Version: rand.Uint32(), + QuorumNumbers: quorumNumbers, + Commitment: &common.BlobCommitment{ + Commitment: rand.RandomBytes(32), + LengthCommitment: rand.RandomBytes(32), + LengthProof: rand.RandomBytes(32), + Length: rand.Uint32(), + }, + PaymentHeader: &common.PaymentHeader{ + AccountId: rand.RandomString(32), + ReservationPeriod: rand.Uint32(), + CumulativePayment: rand.RandomBytes(32), + Salt: rand.Uint32(), + }, + Signature: rand.RandomBytes(32), + }, + Relays: relays, + } + } + + return &grpc.StoreChunksRequest{ + Batch: &v2.Batch{ + Header: &v2.BatchHeader{ + BatchRoot: rand.RandomBytes(32), + ReferenceBlockNumber: rand.Uint64(), + }, + BlobCertificates: blobCertificates, + }, + DisperserID: rand.Uint32(), + Signature: rand.RandomBytes(32), + } +} + +func TestHashing(t *testing.T) { + rand := random.NewTestRandom(t) + + request := randomStoreChunksRequest(rand) + originalRequestHash := HashStoreChunksRequest(request) + + // modifying the signature should not change the hash + request.Signature = rand.RandomBytes(32) + hash := HashStoreChunksRequest(request) + require.Equal(t, originalRequestHash, hash) + + // modify the disperser id + rand.Reset() + request = randomStoreChunksRequest(rand) + request.DisperserID = request.DisperserID + 1 + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // remove a blob cert + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates = request.Batch.BlobCertificates[:len(request.Batch.BlobCertificates)-1] + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify a relay + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].Relays[0] = request.Batch.BlobCertificates[0].Relays[0] + 1 + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, remove a relay + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].Relays = + request.Batch.BlobCertificates[0].Relays[:len(request.Batch.BlobCertificates[0].Relays)-1] + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, add a relay + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].Relays = append(request.Batch.BlobCertificates[0].Relays, rand.Uint32()) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify a quorum number + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[0] = + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[0] + 1 + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, remove a quorum number + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers = + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[:len( + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers)-1] + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, add a quorum number + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers = append( + request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers, rand.Uint32()) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the Commitment.Commitment + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.Commitment = rand.RandomBytes(32) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the Commitment.LengthCommitment + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthCommitment = rand.RandomBytes(32) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the Commitment.LengthProof + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.RandomBytes(32) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the Commitment.Length + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.Length = rand.Uint32() + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the PaymentHeader.AccountId + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.AccountId = rand.RandomString(32) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the PaymentHeader.ReservationPeriod + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.ReservationPeriod = rand.Uint32() + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the PaymentHeader.CumulativePayment + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.CumulativePayment = rand.RandomBytes(32) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the PaymentHeader.Salt + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.Salt = rand.Uint32() + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) + + // within a blob cert, modify the Signature + rand.Reset() + request = randomStoreChunksRequest(rand) + request.Batch.BlobCertificates[0].BlobHeader.Signature = rand.RandomBytes(32) + hash = HashStoreChunksRequest(request) + require.NotEqual(t, originalRequestHash, hash) +} + +func TestRequestSigning(t *testing.T) { + rand := random.NewTestRandom(t) + + public, private := rand.RandomECDSA() + request := randomStoreChunksRequest(rand) + + signature, err := SignStoreChunksRequest(private, request) + require.NoError(t, err) + + isValid := VerifyStoreChunksRequest(public, request, signature) + require.True(t, isValid) + + // Adding the signature to the request should not change the hash, so it should still be valid + request.Signature = signature + isValid = VerifyStoreChunksRequest(public, request, signature) + require.True(t, isValid) + + // Using a different public key should make the signature invalid + otherPublic, _ := rand.RandomECDSA() + isValid = VerifyStoreChunksRequest(otherPublic, request, signature) + require.False(t, isValid) + + // Changing a byte in the signature should make it invalid + alteredSignature := make([]byte, len(signature)) + copy(alteredSignature, signature) + alteredSignature[0] = alteredSignature[0] + 1 + isValid = VerifyStoreChunksRequest(public, request, alteredSignature) + require.False(t, isValid) + + // Changing a field in the request should make it invalid + request.DisperserID = request.DisperserID + 1 + isValid = VerifyStoreChunksRequest(public, request, signature) + require.False(t, isValid) +} diff --git a/node/auth/request_signing_tests.go b/node/auth/request_signing_tests.go deleted file mode 100644 index afd0bd499e..0000000000 --- a/node/auth/request_signing_tests.go +++ /dev/null @@ -1,8 +0,0 @@ -package auth - -import grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" - -func randomStoreChunksRequest(random tu.TestRandom) *grpc.StoreChunksRequest { - - return nil -} From 29e7e7d16bb88ed9b993e946200fdb2bd5dd9880 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 12 Dec 2024 11:16:30 -0600 Subject: [PATCH 19/50] Delete stale code. Signed-off-by: Cody Littley --- disperser/auth/generate-ecdsa-keypair.sh | 6 -- disperser/auth/serialization.go | 72 ------------------------ disperser/auth/serialization_test.go | 44 --------------- disperser/auth/test-private.pem | 16 ------ disperser/auth/test-public.pem | 15 ----- 5 files changed, 153 deletions(-) delete mode 100755 disperser/auth/generate-ecdsa-keypair.sh delete mode 100644 disperser/auth/serialization.go delete mode 100644 disperser/auth/serialization_test.go delete mode 100644 disperser/auth/test-private.pem delete mode 100644 disperser/auth/test-public.pem diff --git a/disperser/auth/generate-ecdsa-keypair.sh b/disperser/auth/generate-ecdsa-keypair.sh deleted file mode 100755 index 1a017b6552..0000000000 --- a/disperser/auth/generate-ecdsa-keypair.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -# This script generates a new ECDSA keypair for the disperser service. - -openssl ecparam -name prime256v1 -genkey -noout -out eigenda-disperser-private.pem -openssl ec -in eigenda-disperser-private.pem -pubout -out eigenda-disperser-public.pem diff --git a/disperser/auth/serialization.go b/disperser/auth/serialization.go deleted file mode 100644 index 43ac84bf64..0000000000 --- a/disperser/auth/serialization.go +++ /dev/null @@ -1,72 +0,0 @@ -package auth - -import ( - "crypto/ecdsa" - "crypto/x509" - "encoding/pem" - "fmt" - "io" - "os" -) - -// ReadPublicECDSAKeyFile reads a public ECDSA key from a .pem file. -func ReadPublicECDSAKeyFile(publicKeyFile string) (*ecdsa.PublicKey, error) { - file, err := os.Open(publicKeyFile) - if err != nil { - return nil, fmt.Errorf("error opening public key file: %w", err) - } - defer file.Close() - - bytes, err := io.ReadAll(file) - if err != nil { - return nil, fmt.Errorf("error reading public key file: %w", err) - } - - block, _ := pem.Decode(bytes) - if block == nil { - return nil, fmt.Errorf("no PEM data found in public key file") - } - - if block.Type != "PUBLIC KEY" { - return nil, fmt.Errorf("unexpected block type: %s", block.Type) - } - - genericPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("error parsing public key: %w", err) - } - - publicKey := genericPublicKey.(*ecdsa.PublicKey) - - return publicKey, nil -} - -// ReadPrivateECDSAKeyFile reads a private ECDSA key from a .pem file. -func ReadPrivateECDSAKeyFile(privateKeyFile string) (*ecdsa.PrivateKey, error) { - file, err := os.Open(privateKeyFile) - if err != nil { - return nil, fmt.Errorf("error opening private key file: %w", err) - } - defer file.Close() - - bytes, err := io.ReadAll(file) - if err != nil { - return nil, fmt.Errorf("error reading private key file: %w", err) - } - - block, _ := pem.Decode(bytes) - if block == nil { - return nil, fmt.Errorf("no PEM data found in private key file") - } - - if block.Type != "EC PRIVATE KEY" { - return nil, fmt.Errorf("unexpected block type: %s", block.Type) - } - - privateKey, err := x509.ParseECPrivateKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("error parsing private key: %w", err) - } - - return privateKey, nil -} diff --git a/disperser/auth/serialization_test.go b/disperser/auth/serialization_test.go deleted file mode 100644 index 8919f266d7..0000000000 --- a/disperser/auth/serialization_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package auth - -import ( - "crypto/ecdsa" - tu "github.com/Layr-Labs/eigenda/common/testutils" - "github.com/aws/smithy-go/rand" - "github.com/stretchr/testify/require" - "testing" -) - -func TestReadingKeysFromFile(t *testing.T) { - tu.InitializeRandom() - - publicKey, err := ReadPublicECDSAKeyFile("./test-public.pem") - require.NoError(t, err) - require.NotNil(t, publicKey) - - privateKey, err := ReadPrivateECDSAKeyFile("./test-private.pem") - require.NoError(t, err) - require.NotNil(t, privateKey) - - bytesToSign := tu.RandomBytes(32) - - signature, err := ecdsa.SignASN1(rand.Reader, privateKey, bytesToSign) - require.NoError(t, err) - - isValid := ecdsa.VerifyASN1(publicKey, bytesToSign, signature) - require.True(t, isValid) - - // Change some bytes in the signature, it should be invalid now - signature2 := make([]byte, len(signature)) - copy(signature2, signature) - signature2[0] = signature2[0] + 1 - isValid = ecdsa.VerifyASN1(publicKey, bytesToSign, signature2) - require.False(t, isValid) - - // Change some bytes in the message, it should be invalid now - bytesToSign2 := make([]byte, len(bytesToSign)) - copy(bytesToSign2, bytesToSign) - bytesToSign2[0] = bytesToSign2[0] + 1 - isValid = ecdsa.VerifyASN1(publicKey, bytesToSign2, signature) - require.False(t, isValid) - -} diff --git a/disperser/auth/test-private.pem b/disperser/auth/test-private.pem deleted file mode 100644 index cc5a69b240..0000000000 --- a/disperser/auth/test-private.pem +++ /dev/null @@ -1,16 +0,0 @@ - ________________________________________ -/ This key is for testing purposes only, \ -| and is no way intended to be secret. | -| This private key corresponds to the | -\ public key test-public.pem. / - ---------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIOP8un9zVKyLMwI7JHRfL10ggWEAIJwQZD7hhrkTcGU5oAoGCCqGSM49 -AwEHoUQDQgAERrZQAbrxLSoXbOqKx/3bC/f8YDQ1uDVyJNAys4DH8AQeq59lhqYv -XEuSUlR1qgkRjllsA1wKedW3P2fkH1kbGw== ------END EC PRIVATE KEY----- diff --git a/disperser/auth/test-public.pem b/disperser/auth/test-public.pem deleted file mode 100644 index c7c6567297..0000000000 --- a/disperser/auth/test-public.pem +++ /dev/null @@ -1,15 +0,0 @@ - ________________________________________ -/ This key is for testing purposes only, \ -| and is no way intended to be secret. | -| This public key corresponds to the | -\ private key test-private.pem. / - ---------------------------------------- - \ ^__^ - \ (oo)\_______ - (__)\ )\/\ - ||----w | - || || ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERrZQAbrxLSoXbOqKx/3bC/f8YDQ1 -uDVyJNAys4DH8AQeq59lhqYvXEuSUlR1qgkRjllsA1wKedW3P2fkH1kbGw== ------END PUBLIC KEY----- From fb1ebfb2c3ffa4524c96e421ec934c926a12f406 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 12 Dec 2024 14:22:07 -0600 Subject: [PATCH 20/50] Get things kind of working Signed-off-by: Cody Littley --- api/clients/node_client_v2.go | 19 ++++---- api/docs/eigenda-protos.html | 7 +-- api/docs/eigenda-protos.md | 2 +- api/docs/node_v2.html | 7 +-- api/docs/node_v2.md | 2 +- api/grpc/node/v2/node_v2.pb.go | 7 +-- common/testutils/random/test_random.go | 20 ++++---- common/testutils/test_utils.go | 6 +-- core/mock/writer.go | 2 +- node/auth/authenticator.go | 31 +++++------- node/auth/authenticator_test.go | 66 ++++++++++++++++++++++++++ node/auth/authenticator_tests.go | 1 - node/auth/request_signing.go | 20 ++++++-- node/auth/request_signing_test.go | 58 +++++++++++----------- 14 files changed, 163 insertions(+), 85 deletions(-) create mode 100644 node/auth/authenticator_test.go delete mode 100644 node/auth/authenticator_tests.go diff --git a/api/clients/node_client_v2.go b/api/clients/node_client_v2.go index 25d1016969..2bc3588337 100644 --- a/api/clients/node_client_v2.go +++ b/api/clients/node_client_v2.go @@ -4,8 +4,7 @@ import ( "context" "crypto/ecdsa" "fmt" - "github.com/Layr-Labs/eigenda/disperser/auth" - auth2 "github.com/Layr-Labs/eigenda/node/auth" + "github.com/Layr-Labs/eigenda/node/auth" "sync" commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2" @@ -45,13 +44,13 @@ func NewNodeClientV2(config *NodeClientV2Config) (*nodeClientV2, error) { } var key *ecdsa.PrivateKey // TODO update flags - if config.PrivateKeyFile != "" { - var err error - key, err = auth.ReadPrivateECDSAKeyFile(config.PrivateKeyFile) - if err != nil { - return nil, fmt.Errorf("failed to read private key file: %v", err) - } - } + //if config.PrivateKeyFile != "" { + // var err error + // key, err = auth.ReadPrivateECDSAKeyFile(config.PrivateKeyFile) + // if err != nil { + // return nil, fmt.Errorf("failed to read private key file: %v", err) + // } + //} return &nodeClientV2{ config: config, @@ -88,7 +87,7 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c } if c.key != nil { - signature, err := auth2.SignStoreChunksRequest(c.key, request) // TODO + signature, err := auth.SignStoreChunksRequest(c.key, request) // TODO if err != nil { return nil, fmt.Errorf("failed to sign request: %v", err) } diff --git a/api/docs/eigenda-protos.html b/api/docs/eigenda-protos.html index 161e5cb9da..51b2d4ee1b 100644 --- a/api/docs/eigenda-protos.html +++ b/api/docs/eigenda-protos.html @@ -3524,10 +3524,11 @@

StoreChunksRequest

e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId - h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + h. digest certificate.BlobHeader.PaymentHeader.ReservationPeriod (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment - j. digest certificate.BlobHeader.Signature - k. for each relay in certificate.Relays: + j. digest certificate.BlobHeader.PaymentHeader.Salt (4 bytes, unsigned big endian) + k. digest certificate.BlobHeader.Signature + l. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) diff --git a/api/docs/eigenda-protos.md b/api/docs/eigenda-protos.md index 3c78a0c34b..f1231c974a 100644 --- a/api/docs/eigenda-protos.md +++ b/api/docs/eigenda-protos.md @@ -1486,7 +1486,7 @@ Request that the Node store a batch of chunks. Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). A reference implementation (golang) can be found at https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go -1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) +1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.ReservationPeriod (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.PaymentHeader.Salt (4 bytes, unsigned big endian) k. digest certificate.BlobHeader.Signature l. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) Note that this signature is not included in the hash for obvious reasons. | diff --git a/api/docs/node_v2.html b/api/docs/node_v2.html index b092e74eb4..ad54d473b6 100644 --- a/api/docs/node_v2.html +++ b/api/docs/node_v2.html @@ -414,10 +414,11 @@

StoreChunksRequest

e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId - h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + h. digest certificate.BlobHeader.PaymentHeader.ReservationPeriod (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment - j. digest certificate.BlobHeader.Signature - k. for each relay in certificate.Relays: + j. digest certificate.BlobHeader.PaymentHeader.Salt (4 bytes, unsigned big endian) + k. digest certificate.BlobHeader.Signature + l. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) diff --git a/api/docs/node_v2.md b/api/docs/node_v2.md index cc17bbd46c..46d43c7d41 100644 --- a/api/docs/node_v2.md +++ b/api/docs/node_v2.md @@ -114,7 +114,7 @@ Request that the Node store a batch of chunks. Algorithm for computing the hash is as follows. All integer values are serialized in big-endian order (unsigned). A reference implementation (golang) can be found at https://github.com/Layr-Labs/eigenda/blob/master/disperser/auth/request_signing.go -1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.Signature k. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) +1. digest batch.BatchHeader.BatchRoot 2. digest batch.BatchHeader.ReferenceBlockNumber (8 bytes, unsigned big endian) 3. for each certificate in batch.BlobCertificates: a. digest certificate.BlobHeader.Version (4 bytes, unsigned big endian) b. for each quorum_number in certificate.BlobHeader.QuorumNumbers: i. digest quorum_number (4 bytes, unsigned big endian) c. digest certificate.BlobHeader.Commitment.Commitment d. digest certificate.BlobHeader.Commitment.LengthCommitment e. digest certificate.BlobHeader.Commitment.LengthProof f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) g. digest certificate.BlobHeader.PaymentHeader.AccountId h. digest certificate.BlobHeader.PaymentHeader.ReservationPeriod (4 bytes, unsigned big endian) i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment j. digest certificate.BlobHeader.PaymentHeader.Salt (4 bytes, unsigned big endian) k. digest certificate.BlobHeader.Signature l. for each relay in certificate.Relays: i. digest relay (4 bytes, unsigned big endian) 4. digest disperserID (4 bytes, unsigned big endian) Note that this signature is not included in the hash for obvious reasons. | diff --git a/api/grpc/node/v2/node_v2.pb.go b/api/grpc/node/v2/node_v2.pb.go index f97be225aa..60f69facdf 100644 --- a/api/grpc/node/v2/node_v2.pb.go +++ b/api/grpc/node/v2/node_v2.pb.go @@ -49,10 +49,11 @@ type StoreChunksRequest struct { // e. digest certificate.BlobHeader.Commitment.LengthProof // f. digest certificate.BlobHeader.Commitment.Length (4 bytes, unsigned big endian) // g. digest certificate.BlobHeader.PaymentHeader.AccountId - // h. digest certificate.BlobHeader.PaymentHeader.BinIndex (4 bytes, unsigned big endian) + // h. digest certificate.BlobHeader.PaymentHeader.ReservationPeriod (4 bytes, unsigned big endian) // i. digest certificate.BlobHeader.PaymentHeader.CumulativePayment - // j. digest certificate.BlobHeader.Signature - // k. for each relay in certificate.Relays: + // j. digest certificate.BlobHeader.PaymentHeader.Salt (4 bytes, unsigned big endian) + // k. digest certificate.BlobHeader.Signature + // l. for each relay in certificate.Relays: // i. digest relay (4 bytes, unsigned big endian) // 4. digest disperserID (4 bytes, unsigned big endian) // diff --git a/common/testutils/random/test_random.go b/common/testutils/random/test_random.go index f8a0ecff15..b9a9933dac 100644 --- a/common/testutils/random/test_random.go +++ b/common/testutils/random/test_random.go @@ -2,8 +2,8 @@ package random import ( "crypto/ecdsa" - "crypto/elliptic" "fmt" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "io" "math/rand" @@ -49,8 +49,8 @@ func (r *TestRandom) Reset() { r.Seed(r.seed) } -// RandomBytes generates a random byte slice of a given length. -func (r *TestRandom) RandomBytes(length int) []byte { +// Bytes generates a random byte slice of a given length. +func (r *TestRandom) Bytes(length int) []byte { bytes := make([]byte, length) _, err := r.Read(bytes) if err != nil { @@ -59,13 +59,13 @@ func (r *TestRandom) RandomBytes(length int) []byte { return bytes } -// RandomTime generates a random time. -func (r *TestRandom) RandomTime() time.Time { +// Time generates a random time. +func (r *TestRandom) Time() time.Time { return time.Unix(r.Int63(), r.Int63()) } -// RandomString generates a random string out of printable ASCII characters. -func (r *TestRandom) RandomString(length int) string { +// String generates a random string out of printable ASCII characters. +func (r *TestRandom) String(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { @@ -85,9 +85,9 @@ func (i *randIOReader) Read(p []byte) (n int, err error) { return i.rand.Read(p) } -// RandomECDSA generates a random ECDSA key. FOR TESTING PURPOSES ONLY. DO NOT USE THESE KEYS FOR SECURITY PURPOSES. -func (r *TestRandom) RandomECDSA() (*ecdsa.PublicKey, *ecdsa.PrivateKey) { - key, err := ecdsa.GenerateKey(elliptic.P256(), &randIOReader{r}) +// ECDSA generates a random ECDSA key. FOR TESTING PURPOSES ONLY. DO NOT USE THESE KEYS FOR SECURITY PURPOSES. +func (r *TestRandom) ECDSA() (*ecdsa.PublicKey, *ecdsa.PrivateKey) { + key, err := ecdsa.GenerateKey(crypto.S256(), &randIOReader{r}) require.NoError(r.t, err) return &key.PublicKey, key } diff --git a/common/testutils/test_utils.go b/common/testutils/test_utils.go index ad06ecabb1..38daa774b2 100644 --- a/common/testutils/test_utils.go +++ b/common/testutils/test_utils.go @@ -86,7 +86,7 @@ func ExecuteWithTimeout(f func(), duration time.Duration, debugInfo ...any) { } // RandomBytes generates a random byte slice of a given length. -// Deprecated: use TestRandom.RandomBytes instead +// Deprecated: use TestRandom.Bytes instead func RandomBytes(length int) []byte { bytes := make([]byte, length) _, err := rand.Read(bytes) @@ -97,13 +97,13 @@ func RandomBytes(length int) []byte { } // RandomTime generates a random time. -// Deprecated: use TestRandom.RandomTime instead +// Deprecated: use TestRandom.Time instead func RandomTime() time.Time { return time.Unix(int64(rand.Int31()), 0) } // RandomString generates a random string out of printable ASCII characters. -// Deprecated: use TestRandom.RandomString instead +// Deprecated: use TestRandom.String instead func RandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) diff --git a/core/mock/writer.go b/core/mock/writer.go index f367ae2ebe..5c8340ef72 100644 --- a/core/mock/writer.go +++ b/core/mock/writer.go @@ -286,7 +286,7 @@ func (t *MockWriter) GetRelayURLs(ctx context.Context) (map[uint32]string, error } func (t *MockWriter) GetDisperserAddress(ctx context.Context, disperserID uint32) (gethcommon.Address, error) { - args := t.Called() + args := t.Called(disperserID) result := args.Get(0) if result == nil { var zeroValue gethcommon.Address diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index eac071e794..ce7181be29 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -2,17 +2,18 @@ package auth import ( "context" - "crypto/ecdsa" - "errors" "fmt" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/core" - "github.com/ethereum/go-ethereum/crypto" + gethcommon "github.com/ethereum/go-ethereum/common" lru "github.com/hashicorp/golang-lru/v2" "time" ) // RequestAuthenticator authenticates requests to the DA node. This object is thread safe. +// +// This class has largely been future-proofed for decentralized dispersers, with the exception of the +// preloadCache method, which will need to be updated to handle decentralized dispersers. type RequestAuthenticator interface { // AuthenticateStoreChunksRequest authenticates a StoreChunksRequest, returning an error if the request is invalid. // The origin is the address of the peer that sent the request. This may be used to cache auth results @@ -27,7 +28,7 @@ type RequestAuthenticator interface { // keyWithTimeout is a key with that key's expiration time. After a key "expires", it should be reloaded // from the chain state in case the key has been changed. type keyWithTimeout struct { - key *ecdsa.PublicKey + key gethcommon.Address expiration time.Time } @@ -89,7 +90,7 @@ func NewRequestAuthenticator( } func (a *requestAuthenticator) preloadCache(ctx context.Context, now time.Time) error { - // TODO (cody-littley): this will need to be updated for decentralized dispersers + // this will need to be updated for decentralized dispersers _, err := a.getDisperserKey(ctx, now, 0) if err != nil { return fmt.Errorf("failed to get operator key: %w", err) @@ -115,10 +116,9 @@ func (a *requestAuthenticator) AuthenticateStoreChunksRequest( } signature := request.Signature - isValid := VerifyStoreChunksRequest(key, request, signature) - - if !isValid { - return errors.New("signature verification failed") + err = VerifyStoreChunksRequest(*key, request, signature) + if err != nil { + return fmt.Errorf("failed to verify request: %w", err) } a.saveAuthenticationResult(now, origin) @@ -129,12 +129,12 @@ func (a *requestAuthenticator) AuthenticateStoreChunksRequest( func (a *requestAuthenticator) getDisperserKey( ctx context.Context, now time.Time, - disperserID uint32) (*ecdsa.PublicKey, error) { + disperserID uint32) (*gethcommon.Address, error) { key, ok := a.keyCache.Get(disperserID) if ok { expirationTime := key.expiration if now.Before(expirationTime) { - return key.key, nil + return &key.key, nil } } @@ -143,17 +143,12 @@ func (a *requestAuthenticator) getDisperserKey( return nil, fmt.Errorf("failed to get disperser address: %w", err) } - ecdsaKey, err := crypto.UnmarshalPubkey(address.Bytes()) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal public key: %w", err) - } - a.keyCache.Add(disperserID, &keyWithTimeout{ - key: ecdsaKey, + key: address, expiration: now.Add(a.keyTimeoutDuration), }) - return ecdsaKey, nil + return &address, nil } // saveAuthenticationResult saves the result of an auth. diff --git a/node/auth/authenticator_test.go b/node/auth/authenticator_test.go new file mode 100644 index 0000000000..b36a671c57 --- /dev/null +++ b/node/auth/authenticator_test.go @@ -0,0 +1,66 @@ +package auth + +import ( + "context" + "github.com/Layr-Labs/eigenda/common/testutils/random" + wmock "github.com/Layr-Labs/eigenda/core/mock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +// TODO: +// - test good request +// - test bad request +// - test request from disperser that doesn't exist +// - test auth caching +// - test key caching +// - test cache sizes + +//// Verify that public key can be converted to an eth address and back +//func TestPubKeyRoundTrip(t *testing.T) { +// rand := random.NewTestRandom(t) +// +// publicKey, _ := rand.ECDSA() +// +// ethAddress := crypto.PubkeyToAddress(*publicKey) +// +// publicKey2, err := crypto.UnmarshalPubkey(ethAddress.Bytes()) +// require.NoError(t, err) +// +// require.Equal(t, publicKey, publicKey2) +//} + +func TestValidRequest(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + //disperserAddressBytes := crypto.FromECDSAPub(publicKey) + //disperserAddress := gethcommon.BytesToAddress(disperserAddressBytes) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.NoError(t, err) +} diff --git a/node/auth/authenticator_tests.go b/node/auth/authenticator_tests.go deleted file mode 100644 index 8832b06d18..0000000000 --- a/node/auth/authenticator_tests.go +++ /dev/null @@ -1 +0,0 @@ -package auth diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index 7150b212a4..dd7e24cd3d 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -2,12 +2,13 @@ package auth import ( "crypto/ecdsa" - "crypto/rand" "encoding/binary" "fmt" commonv1 "github.com/Layr-Labs/eigenda/api/grpc/common" common "github.com/Layr-Labs/eigenda/api/grpc/common/v2" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "golang.org/x/crypto/sha3" "hash" ) @@ -17,7 +18,7 @@ import ( func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { requestHash := HashStoreChunksRequest(request) - signature, err := ecdsa.SignASN1(rand.Reader, key, requestHash) + signature, err := crypto.Sign(requestHash, key) if err != nil { return nil, fmt.Errorf("failed to sign request: %w", err) } @@ -27,9 +28,20 @@ func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequ // VerifyStoreChunksRequest verifies the given signature of the given StoreChunksRequest with the given // public key. -func VerifyStoreChunksRequest(key *ecdsa.PublicKey, request *grpc.StoreChunksRequest, signature []byte) bool { +func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksRequest, signature []byte) error { requestHash := HashStoreChunksRequest(request) - return ecdsa.VerifyASN1(key, requestHash, signature) + + signingPublicKey, err := crypto.SigToPub(requestHash, signature) + if err != nil { + return fmt.Errorf("failed to recover public key from signature: %w", err) + } + + signingAddress := crypto.PubkeyToAddress(*signingPublicKey) + + if key.Cmp(signingAddress) != 0 { + return fmt.Errorf("signature doesn't match with provided public key") + } + return nil } // HashStoreChunksRequest hashes the given StoreChunksRequest. diff --git a/node/auth/request_signing_test.go b/node/auth/request_signing_test.go index c33333ed12..1d6a6e6220 100644 --- a/node/auth/request_signing_test.go +++ b/node/auth/request_signing_test.go @@ -5,6 +5,7 @@ import ( v2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/common/testutils/random" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "testing" ) @@ -31,18 +32,18 @@ func randomStoreChunksRequest(rand *random.TestRandom) *grpc.StoreChunksRequest Version: rand.Uint32(), QuorumNumbers: quorumNumbers, Commitment: &common.BlobCommitment{ - Commitment: rand.RandomBytes(32), - LengthCommitment: rand.RandomBytes(32), - LengthProof: rand.RandomBytes(32), + Commitment: rand.Bytes(32), + LengthCommitment: rand.Bytes(32), + LengthProof: rand.Bytes(32), Length: rand.Uint32(), }, PaymentHeader: &common.PaymentHeader{ - AccountId: rand.RandomString(32), + AccountId: rand.String(32), ReservationPeriod: rand.Uint32(), - CumulativePayment: rand.RandomBytes(32), + CumulativePayment: rand.Bytes(32), Salt: rand.Uint32(), }, - Signature: rand.RandomBytes(32), + Signature: rand.Bytes(32), }, Relays: relays, } @@ -51,13 +52,13 @@ func randomStoreChunksRequest(rand *random.TestRandom) *grpc.StoreChunksRequest return &grpc.StoreChunksRequest{ Batch: &v2.Batch{ Header: &v2.BatchHeader{ - BatchRoot: rand.RandomBytes(32), + BatchRoot: rand.Bytes(32), ReferenceBlockNumber: rand.Uint64(), }, BlobCertificates: blobCertificates, }, DisperserID: rand.Uint32(), - Signature: rand.RandomBytes(32), + Signature: rand.Bytes(32), } } @@ -68,7 +69,7 @@ func TestHashing(t *testing.T) { originalRequestHash := HashStoreChunksRequest(request) // modifying the signature should not change the hash - request.Signature = rand.RandomBytes(32) + request.Signature = rand.Bytes(32) hash := HashStoreChunksRequest(request) require.Equal(t, originalRequestHash, hash) @@ -136,21 +137,21 @@ func TestHashing(t *testing.T) { // within a blob cert, modify the Commitment.Commitment rand.Reset() request = randomStoreChunksRequest(rand) - request.Batch.BlobCertificates[0].BlobHeader.Commitment.Commitment = rand.RandomBytes(32) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.Commitment = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.LengthCommitment rand.Reset() request = randomStoreChunksRequest(rand) - request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthCommitment = rand.RandomBytes(32) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthCommitment = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.LengthProof rand.Reset() request = randomStoreChunksRequest(rand) - request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.RandomBytes(32) + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) @@ -164,7 +165,7 @@ func TestHashing(t *testing.T) { // within a blob cert, modify the PaymentHeader.AccountId rand.Reset() request = randomStoreChunksRequest(rand) - request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.AccountId = rand.RandomString(32) + request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.AccountId = rand.String(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) @@ -178,7 +179,7 @@ func TestHashing(t *testing.T) { // within a blob cert, modify the PaymentHeader.CumulativePayment rand.Reset() request = randomStoreChunksRequest(rand) - request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.CumulativePayment = rand.RandomBytes(32) + request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.CumulativePayment = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) @@ -192,7 +193,7 @@ func TestHashing(t *testing.T) { // within a blob cert, modify the Signature rand.Reset() request = randomStoreChunksRequest(rand) - request.Batch.BlobCertificates[0].BlobHeader.Signature = rand.RandomBytes(32) + request.Batch.BlobCertificates[0].BlobHeader.Signature = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) } @@ -200,34 +201,37 @@ func TestHashing(t *testing.T) { func TestRequestSigning(t *testing.T) { rand := random.NewTestRandom(t) - public, private := rand.RandomECDSA() + public, private := rand.ECDSA() + publicAddress := crypto.PubkeyToAddress(*public) + request := randomStoreChunksRequest(rand) signature, err := SignStoreChunksRequest(private, request) require.NoError(t, err) - isValid := VerifyStoreChunksRequest(public, request, signature) - require.True(t, isValid) + err = VerifyStoreChunksRequest(publicAddress, request, signature) + require.NoError(t, err) // Adding the signature to the request should not change the hash, so it should still be valid request.Signature = signature - isValid = VerifyStoreChunksRequest(public, request, signature) - require.True(t, isValid) + err = VerifyStoreChunksRequest(publicAddress, request, signature) + require.NoError(t, err) // Using a different public key should make the signature invalid - otherPublic, _ := rand.RandomECDSA() - isValid = VerifyStoreChunksRequest(otherPublic, request, signature) - require.False(t, isValid) + otherPublic, _ := rand.ECDSA() + otherPublicAddress := crypto.PubkeyToAddress(*otherPublic) + err = VerifyStoreChunksRequest(otherPublicAddress, request, signature) + require.Error(t, err) // Changing a byte in the signature should make it invalid alteredSignature := make([]byte, len(signature)) copy(alteredSignature, signature) alteredSignature[0] = alteredSignature[0] + 1 - isValid = VerifyStoreChunksRequest(public, request, alteredSignature) - require.False(t, isValid) + err = VerifyStoreChunksRequest(publicAddress, request, alteredSignature) + require.Error(t, err) // Changing a field in the request should make it invalid request.DisperserID = request.DisperserID + 1 - isValid = VerifyStoreChunksRequest(public, request, signature) - require.False(t, isValid) + err = VerifyStoreChunksRequest(publicAddress, request, signature) + require.Error(t, err) } From 6ca7c9b95ec9dccad0d25498a11664d07eab5bb3 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 13 Dec 2024 11:11:20 -0600 Subject: [PATCH 21/50] Finished unit tests. Signed-off-by: Cody Littley --- node/auth/authenticator_test.go | 455 ++++++++++++++++++++++++++++++-- 1 file changed, 435 insertions(+), 20 deletions(-) diff --git a/node/auth/authenticator_test.go b/node/auth/authenticator_test.go index b36a671c57..1ea701f4e4 100644 --- a/node/auth/authenticator_test.go +++ b/node/auth/authenticator_test.go @@ -2,6 +2,9 @@ package auth import ( "context" + "crypto/ecdsa" + "errors" + "fmt" "github.com/Layr-Labs/eigenda/common/testutils/random" wmock "github.com/Layr-Labs/eigenda/core/mock" "github.com/ethereum/go-ethereum/crypto" @@ -11,27 +14,9 @@ import ( ) // TODO: -// - test good request -// - test bad request -// - test request from disperser that doesn't exist -// - test auth caching // - test key caching // - test cache sizes -//// Verify that public key can be converted to an eth address and back -//func TestPubKeyRoundTrip(t *testing.T) { -// rand := random.NewTestRandom(t) -// -// publicKey, _ := rand.ECDSA() -// -// ethAddress := crypto.PubkeyToAddress(*publicKey) -// -// publicKey2, err := crypto.UnmarshalPubkey(ethAddress.Bytes()) -// require.NoError(t, err) -// -// require.Equal(t, publicKey, publicKey2) -//} - func TestValidRequest(t *testing.T) { rand := random.NewTestRandom(t) @@ -40,8 +25,242 @@ func TestValidRequest(t *testing.T) { publicKey, privateKey := rand.ECDSA() disperserAddress := crypto.PubkeyToAddress(*publicKey) - //disperserAddressBytes := crypto.FromECDSAPub(publicKey) - //disperserAddress := gethcommon.BytesToAddress(disperserAddressBytes) + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.NoError(t, err) +} + +func TestInvalidRequestWrongHash(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + // Modify the request so that the hash is different + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.Error(t, err) +} + +func TestInvalidRequestWrongKey(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, _ := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + + _, differentPrivateKey := rand.ECDSA() + signature, err := SignStoreChunksRequest(differentPrivateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.Error(t, err) +} + +func TestInvalidRequestInvalidDisperserID(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, _ := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + chainReader.Mock.On("GetDisperserAddress", uint32(1234)).Return( + nil, errors.New("disperser not found")) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + request := randomStoreChunksRequest(rand) + request.DisperserID = 1234 + + _, differentPrivateKey := rand.ECDSA() + signature, err := SignStoreChunksRequest(differentPrivateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.Error(t, err) +} + +func TestAuthCaching(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + // The first request will actually be validated. + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.NoError(t, err) + + // Make some more requests. Intentionally fiddle with the hash to make them invalid if checked. + // With auth caching, those checks won't happen until the auth timeout has passed (configured to 1 minute). + now := start + for i := 0; i < 60; i++ { + request = randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err = SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, now) + now = now.Add(time.Second) + require.NoError(t, err) + + // making the same request from a different origin should cause validation to happen and for it to fail + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "otherhost", request, now) + require.Error(t, err) + } + + // The next request will be made after the auth timeout has passed, so it will be validated. + // Since it is actually invalid, the authenticator should reject it. + request = randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err = SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, now) + require.Error(t, err) +} + +func TestAuthCachingDisabled(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + 10, + time.Minute, + 0, // This disables auth caching + start) + require.NoError(t, err) + + // The first request will always be validated. + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.NoError(t, err) + + // Make another request without moving time forward. It should be validated. + request = randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err = SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) + require.Error(t, err) +} + +func TestKeyExpiry(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) chainReader := wmock.MockWriter{} chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) @@ -55,6 +274,9 @@ func TestValidRequest(t *testing.T) { start) require.NoError(t, err) + // Preloading the cache should have grabbed Disperser 0's key + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", 1) + request := randomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) @@ -63,4 +285,197 @@ func TestValidRequest(t *testing.T) { err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, start) require.NoError(t, err) + + // Since time hasn't advanced, the authenticator shouldn't have fetched the key again + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", 1) + + // Move time forward to just before the key expires. + now := start.Add(59 * time.Second) + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, now) + require.NoError(t, err) + + // The key should not yet have been fetched again. + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", 1) + + // Move time forward to just after the key expires. + now = now.Add(2 * time.Second) + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "localhost", request, now) + require.NoError(t, err) + + // The key should have been fetched again. + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", 2) +} + +func TestAuthCacheSize(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + + chainReader := wmock.MockWriter{} + chainReader.Mock.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) + + cacheSize := rand.Intn(10) + 2 + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + cacheSize, + time.Minute, + time.Minute, + start) + require.NoError(t, err) + + // Make requests from cacheSize different origins. + for i := 0; i < cacheSize; i++ { + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + origin := fmt.Sprintf("%d", i) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), origin, request, start) + require.NoError(t, err) + } + + // All origins should be authenticated in the auth cache. If we send invalid requests from the same origins, + // they should still be authenticated (since the authenticator won't re-check). + for i := 0; i < cacheSize; i++ { + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) + + origin := fmt.Sprintf("%d", i) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), origin, request, start) + require.NoError(t, err) + } + + // Make a request from a new origin. This should boot origin 0 from the cache. + request := randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err := SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "neworigin", request, start) + require.NoError(t, err) + + for i := 0; i < cacheSize; i++ { + request = randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err = SignStoreChunksRequest(privateKey, request) + require.NoError(t, err) + request.Signature = signature + + request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) + + origin := fmt.Sprintf("%d", i) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), origin, request, start) + + if i == 0 { + // Origin 0 should have been booted from the cache, so this request should be re-validated. + require.Error(t, err) + } else { + // All other origins should still be in the cache. + require.NoError(t, err) + } + } +} + +func TestKeyCacheSize(t *testing.T) { + rand := random.NewTestRandom(t) + + start := rand.Time() + + cacheSize := rand.Intn(10) + 2 + + chainReader := wmock.MockWriter{} + keyMap := make(map[uint32]*ecdsa.PrivateKey, cacheSize+1) + for i := 0; i < cacheSize+1; i++ { + publicKey, privateKey := rand.ECDSA() + disperserAddress := crypto.PubkeyToAddress(*publicKey) + keyMap[uint32(i)] = privateKey + + chainReader.Mock.On("GetDisperserAddress", uint32(i)).Return(disperserAddress, nil) + } + + authenticator, err := NewRequestAuthenticator( + context.Background(), + &chainReader, + cacheSize, + time.Minute, + 0, // disable auth caching + start) + require.NoError(t, err) + + // The authenticator will preload key 0 into the cache. + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", 1) + + // Make a request for each key (except for the last one, which won't fit in the cache). + for i := 0; i < cacheSize; i++ { + request := randomStoreChunksRequest(rand) + request.DisperserID = uint32(i) + signature, err := SignStoreChunksRequest(keyMap[uint32(i)], request) + require.NoError(t, err) + request.Signature = signature + + origin := fmt.Sprintf("%d", i) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), origin, request, start) + require.NoError(t, err) + } + + // All keys should have required exactly one read except from 0, which was preloaded. + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", cacheSize) + + // Make another request for each key. None should require a read from the chain. + for i := 0; i < cacheSize; i++ { + request := randomStoreChunksRequest(rand) + request.DisperserID = uint32(i) + signature, err := SignStoreChunksRequest(keyMap[uint32(i)], request) + require.NoError(t, err) + request.Signature = signature + + origin := fmt.Sprintf("%d", i) + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), origin, request, start) + require.NoError(t, err) + } + + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", cacheSize) + + // Make a request for the last key. This should require a read from the chain and will boot key 0 from the cache. + request := randomStoreChunksRequest(rand) + request.DisperserID = uint32(cacheSize) + signature, err := SignStoreChunksRequest(keyMap[uint32(cacheSize)], request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), + fmt.Sprintf("%d", cacheSize), request, start) + require.NoError(t, err) + + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", cacheSize+1) + + // Make another request for key 0. This should require a read from the chain. + request = randomStoreChunksRequest(rand) + request.DisperserID = 0 + signature, err = SignStoreChunksRequest(keyMap[0], request) + require.NoError(t, err) + request.Signature = signature + + err = authenticator.AuthenticateStoreChunksRequest(context.Background(), "0", request, start) + require.NoError(t, err) + + chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", cacheSize+2) } From 3348883c1b22674358452e78faf1ff6f4e250326 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 13 Dec 2024 11:40:07 -0600 Subject: [PATCH 22/50] Cleanup. Signed-off-by: Cody Littley --- .gitignore | 3 --- node/auth/authenticator_test.go | 4 ---- node/config.go | 2 -- 3 files changed, 9 deletions(-) diff --git a/.gitignore b/.gitignore index c49c17e810..40fe5f34c5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,3 @@ lightnode/docker/args.sh .vscode icicle/* - -# Just to make sure somebody doesn't accidentally commit the disperser's private key. -eigenda-disperser-private.pem diff --git a/node/auth/authenticator_test.go b/node/auth/authenticator_test.go index 1ea701f4e4..d331174690 100644 --- a/node/auth/authenticator_test.go +++ b/node/auth/authenticator_test.go @@ -13,10 +13,6 @@ import ( "time" ) -// TODO: -// - test key caching -// - test cache sizes - func TestValidRequest(t *testing.T) { rand := random.NewTestRandom(t) diff --git a/node/config.go b/node/config.go index 64874db718..7cfb219cdf 100644 --- a/node/config.go +++ b/node/config.go @@ -95,8 +95,6 @@ type Config struct { PprofHttpPort string EnablePprof bool - // TODO update flags - // if true then the node will not authenticate StoreChunks requests from dispersers (v2 only) DisableDispersalAuthentication bool // the size of the cache for storing public keys of dispersers From e5966f0f2b6d17bb1641c898fa523a402336fd1c Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 13 Dec 2024 13:03:37 -0600 Subject: [PATCH 23/50] Cleanup Signed-off-by: Cody Littley --- api/clients/node_client_v2.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/clients/node_client_v2.go b/api/clients/node_client_v2.go index 2bc3588337..73433aa6e1 100644 --- a/api/clients/node_client_v2.go +++ b/api/clients/node_client_v2.go @@ -84,10 +84,11 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c }, BlobCertificates: blobCerts, }, + DisperserID: uint32(0), // currently hard coded, update for decentralized dispersers } if c.key != nil { - signature, err := auth.SignStoreChunksRequest(c.key, request) // TODO + signature, err := auth.SignStoreChunksRequest(c.key, request) if err != nil { return nil, fmt.Errorf("failed to sign request: %v", err) } From f0c6f4efc8398da3957e418c2503047b8658421f Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 08:43:18 -0600 Subject: [PATCH 24/50] Cleanup. Signed-off-by: Cody Littley --- node/grpc/server_test.go | 2 +- test/integration_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node/grpc/server_test.go b/node/grpc/server_test.go index edbc45a7aa..de996504c6 100644 --- a/node/grpc/server_test.go +++ b/node/grpc/server_test.go @@ -85,7 +85,7 @@ func makeConfig(t *testing.T) *node.Config { ID: opID, NumBatchValidators: runtime.GOMAXPROCS(0), EnableV2: false, - DisableDispersalAuthentication: true, // TODO re-enable + DisableDispersalAuthentication: true, // TODO enable } } diff --git a/test/integration_test.go b/test/integration_test.go index 47684456c5..c31a286e87 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -363,7 +363,7 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging PrivateBls: string(op.KeyPair.GetPubKeyG1().Serialize()), ID: id, QuorumIDList: registeredQuorums, - DisableDispersalAuthentication: true, // TODO re-enable + DisableDispersalAuthentication: true, // TODO enable } // creating a new instance of encoder instead of sharing enc because enc is not thread safe @@ -419,7 +419,7 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging ratelimiter := &commonmock.NoopRatelimiter{} - // TODO enable request validation + // TODO this needs to be non-null once we enable dispersal authentication var client common.EthClient serverV1 := nodegrpc.NewServer(config, n, logger, ratelimiter) From 6a002566b05670b6a3296be9ad7d4d0dc1bf177e Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 10:23:41 -0600 Subject: [PATCH 25/50] Added request signer. Signed-off-by: Cody Littley --- api/clients/node_client_v2.go | 33 ++----------------- api/clients/request_signer.go | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 api/clients/request_signer.go diff --git a/api/clients/node_client_v2.go b/api/clients/node_client_v2.go index 73433aa6e1..58452aa94a 100644 --- a/api/clients/node_client_v2.go +++ b/api/clients/node_client_v2.go @@ -2,9 +2,7 @@ package clients import ( "context" - "crypto/ecdsa" "fmt" - "github.com/Layr-Labs/eigenda/node/auth" "sync" commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2" @@ -18,8 +16,6 @@ type NodeClientV2Config struct { Hostname string Port string UseSecureGrpcFlag bool - // The .pem file containing the private key used to sign StoreChunks() requests. If "" then no signing is done. - PrivateKeyFile string } type NodeClientV2 interface { @@ -31,7 +27,6 @@ type nodeClientV2 struct { config *NodeClientV2Config initOnce sync.Once conn *grpc.ClientConn - key *ecdsa.PrivateKey dispersalClient nodegrpc.DispersalClient } @@ -42,19 +37,8 @@ func NewNodeClientV2(config *NodeClientV2Config) (*nodeClientV2, error) { if config == nil || config.Hostname == "" || config.Port == "" { return nil, fmt.Errorf("invalid config: %v", config) } - - var key *ecdsa.PrivateKey // TODO update flags - //if config.PrivateKeyFile != "" { - // var err error - // key, err = auth.ReadPrivateECDSAKeyFile(config.PrivateKeyFile) - // if err != nil { - // return nil, fmt.Errorf("failed to read private key file: %v", err) - // } - //} - return &nodeClientV2{ config: config, - key: key, }, nil } @@ -76,7 +60,8 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c } } - request := &nodegrpc.StoreChunksRequest{ + // Call the gRPC method to store chunks + response, err := c.dispersalClient.StoreChunks(ctx, &nodegrpc.StoreChunksRequest{ Batch: &commonpb.Batch{ Header: &commonpb.BatchHeader{ BatchRoot: batch.BatchHeader.BatchRoot[:], @@ -84,19 +69,7 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c }, BlobCertificates: blobCerts, }, - DisperserID: uint32(0), // currently hard coded, update for decentralized dispersers - } - - if c.key != nil { - signature, err := auth.SignStoreChunksRequest(c.key, request) - if err != nil { - return nil, fmt.Errorf("failed to sign 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 } diff --git a/api/clients/request_signer.go b/api/clients/request_signer.go new file mode 100644 index 0000000000..eecc2ce5be --- /dev/null +++ b/api/clients/request_signer.go @@ -0,0 +1,61 @@ +package clients + +import ( + "context" + "fmt" + grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "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" +) + +// RequestSigner encapsulates the logic for signing GetChunks requests. +type RequestSigner 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 _ RequestSigner = &requestSigner{} + +type requestSigner struct { + keyID string + keyManager *kms.Client +} + +// NewRequestSigner creates a new RequestSigner. +func NewRequestSigner( + region string, + endpoint string, + keyID string) RequestSigner { + + keyManager := kms.New(kms.Options{ + Region: region, + BaseEndpoint: aws.String(endpoint), + }) + + return &requestSigner{ + keyID: keyID, + keyManager: keyManager, + } +} + +func (s *requestSigner) SignStoreChunksRequest(ctx context.Context, request *grpc.StoreChunksRequest) ([]byte, error) { + hash := auth.HashStoreChunksRequest(request) + + signOutput, err := s.keyManager.Sign( + ctx, + &kms.SignInput{ + KeyId: aws.String(s.keyID), + Message: hash, + SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, + MessageType: types.MessageTypeDigest, + }) + + if err != nil { + return nil, fmt.Errorf("failed to sign request: %w", err) + } + + return signOutput.Signature, nil +} From 27d5b8aed97b3fa18cc42ffcb3985ca949708265 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 11:07:36 -0600 Subject: [PATCH 26/50] Incremental progress. Signed-off-by: Cody Littley --- api/clients/mock/static_request_signer.go | 30 +++++++++++++++++++ api/clients/node_client_v2.go | 28 ++++++++++++----- disperser/cmd/controller/main.go | 6 +++- disperser/controller/node_client_manager.go | 26 ++++++++++------ .../controller/node_client_manager_test.go | 11 ++++++- go.mod | 2 +- 6 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 api/clients/mock/static_request_signer.go diff --git a/api/clients/mock/static_request_signer.go b/api/clients/mock/static_request_signer.go new file mode 100644 index 0000000000..3e7ad8b0d9 --- /dev/null +++ b/api/clients/mock/static_request_signer.go @@ -0,0 +1,30 @@ +package mock + +import ( + "context" + "crypto/ecdsa" + "github.com/Layr-Labs/eigenda/api/clients" + v2 "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/Layr-Labs/eigenda/node/auth" +) + +var _ clients.RequestSigner = &staticRequestSigner{} + +// StaticRequestSigner is a RequestSigner 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.RequestSigner { + return &staticRequestSigner{ + key: key, + } +} + +func (s *staticRequestSigner) SignStoreChunksRequest( + ctx context.Context, + request *v2.StoreChunksRequest) ([]byte, error) { + + return auth.SignStoreChunksRequest(s.key, request) +} diff --git a/api/clients/node_client_v2.go b/api/clients/node_client_v2.go index 58452aa94a..2e12f52782 100644 --- a/api/clients/node_client_v2.go +++ b/api/clients/node_client_v2.go @@ -24,21 +24,23 @@ type NodeClientV2 interface { } type nodeClientV2 struct { - config *NodeClientV2Config - initOnce sync.Once - conn *grpc.ClientConn + config *NodeClientV2Config + initOnce sync.Once + conn *grpc.ClientConn + requestSigner RequestSigner dispersalClient nodegrpc.DispersalClient } var _ NodeClientV2 = (*nodeClientV2)(nil) -func NewNodeClientV2(config *NodeClientV2Config) (*nodeClientV2, error) { +func NewNodeClientV2(config *NodeClientV2Config, requestSigner RequestSigner) (NodeClientV2, error) { if config == nil || config.Hostname == "" || config.Port == "" { return nil, fmt.Errorf("invalid config: %v", config) } return &nodeClientV2{ - config: config, + config: config, + requestSigner: requestSigner, }, nil } @@ -60,8 +62,7 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c } } - // 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[:], @@ -69,7 +70,18 @@ func (c *nodeClientV2) StoreChunks(ctx context.Context, batch *corev2.Batch) (*c }, BlobCertificates: blobCerts, }, - }) + DisperserID: 0, // this will need to be updated dispersers are decentralized + } + + // 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 } diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index 2725c3fd45..9ca924fdbd 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "github.com/Layr-Labs/eigenda/api/clients" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -148,7 +149,10 @@ func RunController(ctx *cli.Context) error { return err } } - nodeClientManager, err := controller.NewNodeClientManager(config.NodeClientCacheSize, logger) + + requestSigner := clients.NewRequestSigner("", "", "") // TODO: fill in the parameters + + nodeClientManager, err := controller.NewNodeClientManager(config.NodeClientCacheSize, requestSigner, logger) if err != nil { return fmt.Errorf("failed to create node client manager: %v", err) } diff --git a/disperser/controller/node_client_manager.go b/disperser/controller/node_client_manager.go index 0957428c68..50a29c1e4c 100644 --- a/disperser/controller/node_client_manager.go +++ b/disperser/controller/node_client_manager.go @@ -14,13 +14,18 @@ type NodeClientManager interface { type nodeClientManager struct { // nodeClients is a cache of node clients keyed by socket address - nodeClients *lru.Cache[string, clients.NodeClientV2] - logger logging.Logger + nodeClients *lru.Cache[string, clients.NodeClientV2] + requestSigner clients.RequestSigner + logger logging.Logger } var _ NodeClientManager = (*nodeClientManager)(nil) -func NewNodeClientManager(cacheSize int, logger logging.Logger) (*nodeClientManager, error) { +func NewNodeClientManager( + cacheSize int, + requestSigner clients.RequestSigner, + logger logging.Logger) (*nodeClientManager, error) { + closeClient := func(socket string, value clients.NodeClientV2) { if err := value.Close(); err != nil { logger.Error("failed to close node client", "err", err) @@ -32,8 +37,9 @@ func NewNodeClientManager(cacheSize int, logger logging.Logger) (*nodeClientMana } return &nodeClientManager{ - nodeClients: nodeClients, - logger: logger, + nodeClients: nodeClients, + requestSigner: requestSigner, + logger: logger, }, nil } @@ -42,10 +48,12 @@ func (m *nodeClientManager) GetClient(host, port string) (clients.NodeClientV2, client, ok := m.nodeClients.Get(socket) if !ok { var err error - client, err = clients.NewNodeClientV2(&clients.NodeClientV2Config{ - Hostname: host, - Port: port, - }) + client, err = clients.NewNodeClientV2( + &clients.NodeClientV2Config{ + Hostname: host, + Port: port, + }, + m.requestSigner) if err != nil { return nil, fmt.Errorf("failed to create node client at %s: %w", socket, err) } diff --git a/disperser/controller/node_client_manager_test.go b/disperser/controller/node_client_manager_test.go index ffe0dc5a6a..c394340feb 100644 --- a/disperser/controller/node_client_manager_test.go +++ b/disperser/controller/node_client_manager_test.go @@ -1,6 +1,8 @@ package controller_test import ( + "github.com/Layr-Labs/eigenda/api/clients/mock" + "github.com/Layr-Labs/eigenda/common/testutils/random" "testing" "github.com/Layr-Labs/eigenda/disperser/controller" @@ -8,7 +10,12 @@ import ( ) func TestNodeClientManager(t *testing.T) { - m, err := controller.NewNodeClientManager(2, nil) + rand := random.NewTestRandom(t) + + _, private := rand.ECDSA() + requestSigner := mock.NewStaticRequestSigner(private) + + m, err := controller.NewNodeClientManager(2, requestSigner, nil) require.NoError(t, err) client0, err := m.GetClient("localhost", "0000") @@ -38,3 +45,5 @@ func TestNodeClientManager(t *testing.T) { require.NotSame(t, client0, client4) } + +// TODO add test for request signing diff --git a/go.mod b/go.mod index f14a3556e6..b5de6d4e7b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2/credentials v1.17.11 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.12 + github.com/aws/aws-sdk-go-v2/service/kms v1.31.0 github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.6 github.com/consensys/gnark-crypto v0.12.1 github.com/emirpasic/gods v1.18.1 @@ -63,7 +64,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.4 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.31.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect From bbd82ecf3f5679bea9a926ec32651a4d718c7fcf Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 11:23:59 -0600 Subject: [PATCH 27/50] Update flags. Signed-off-by: Cody Littley --- disperser/cmd/controller/config.go | 22 ++++++++++++---------- disperser/cmd/controller/flags/flags.go | 7 +++++++ disperser/cmd/controller/main.go | 5 ++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/disperser/cmd/controller/config.go b/disperser/cmd/controller/config.go index 62000b765b..f833701a9a 100644 --- a/disperser/cmd/controller/config.go +++ b/disperser/cmd/controller/config.go @@ -25,12 +25,13 @@ type Config struct { DynamoDBTableName string - EthClientConfig geth.EthClientConfig - AwsClientConfig aws.ClientConfig - LoggerConfig common.LoggerConfig - IndexerConfig indexer.Config - ChainStateConfig thegraph.Config - UseGraph bool + EthClientConfig geth.EthClientConfig + AwsClientConfig aws.ClientConfig + DisperserSigningKeyName string + LoggerConfig common.LoggerConfig + IndexerConfig indexer.Config + ChainStateConfig thegraph.Config + UseGraph bool BLSOperatorStateRetrieverAddr string EigenDAServiceManagerAddr string @@ -60,10 +61,11 @@ func NewConfig(ctx *cli.Context) (Config, error) { relays[i] = corev2.RelayKey(relay) } config := Config{ - DynamoDBTableName: ctx.GlobalString(flags.DynamoDBTableNameFlag.Name), - EthClientConfig: ethClientConfig, - AwsClientConfig: aws.ReadClientConfig(ctx, flags.FlagPrefix), - LoggerConfig: *loggerConfig, + DynamoDBTableName: ctx.GlobalString(flags.DynamoDBTableNameFlag.Name), + EthClientConfig: ethClientConfig, + AwsClientConfig: aws.ReadClientConfig(ctx, flags.FlagPrefix), + DisperserSigningKeyName: ctx.GlobalString(flags.DisperserSigningKeyNameFlag.Name), + LoggerConfig: *loggerConfig, EncodingManagerConfig: controller.EncodingManagerConfig{ PullInterval: ctx.GlobalDuration(flags.EncodingPullIntervalFlag.Name), EncodingRequestTimeout: ctx.GlobalDuration(flags.EncodingRequestTimeoutFlag.Name), diff --git a/disperser/cmd/controller/flags/flags.go b/disperser/cmd/controller/flags/flags.go index 7b3055234b..ac6d5db412 100644 --- a/disperser/cmd/controller/flags/flags.go +++ b/disperser/cmd/controller/flags/flags.go @@ -178,6 +178,12 @@ var ( EnvVar: common.PrefixEnvVar(envVarPrefix, "METRICS_PORT"), Value: 9101, } + DisperserSigningKeyNameFlag = cli.StringFlag{ + Name: common.PrefixFlag(FlagPrefix, "disperser-signing-key-name"), + Usage: "Name of the key used to sign disperser requests (key must be stored in AWS KMS under this name)", + Required: true, + EnvVar: common.PrefixEnvVar(envVarPrefix, "DISPERSER_SIGNING_KEY_NAME"), + } ) var requiredFlags = []cli.Flag{ @@ -192,6 +198,7 @@ var requiredFlags = []cli.Flag{ DispatcherPullIntervalFlag, NodeRequestTimeoutFlag, NumConnectionsToNodesFlag, + DisperserSigningKeyNameFlag, } var optionalFlags = []cli.Flag{ diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index 9ca924fdbd..8e8b187826 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -150,7 +150,10 @@ func RunController(ctx *cli.Context) error { } } - requestSigner := clients.NewRequestSigner("", "", "") // TODO: fill in the parameters + requestSigner := clients.NewRequestSigner( + config.AwsClientConfig.Region, + config.AwsClientConfig.EndpointURL, + config.DisperserSigningKeyName) nodeClientManager, err := controller.NewNodeClientManager(config.NodeClientCacheSize, requestSigner, logger) if err != nil { From 157c2ea09578d6334c88eabaae79a49d7c8050bd Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 13:08:59 -0600 Subject: [PATCH 28/50] Started work on unit test, not yet working. Signed-off-by: Cody Littley --- api/clients/request_signer_test.go | 110 ++++++++++++++++++++ disperser/controller/node_client_manager.go | 2 +- node/auth/authenticator.go | 3 +- node/auth/authenticator_test.go | 36 +++---- node/auth/request_signing.go | 4 +- node/auth/request_signing_test.go | 109 +++++-------------- node/auth/request_signing_test_utils.go | 60 +++++++++++ 7 files changed, 218 insertions(+), 106 deletions(-) create mode 100644 api/clients/request_signer_test.go create mode 100644 node/auth/request_signing_test_utils.go diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go new file mode 100644 index 0000000000..c787c6ecb3 --- /dev/null +++ b/api/clients/request_signer_test.go @@ -0,0 +1,110 @@ +package clients + +import ( + "context" + "crypto/ecdsa" + "crypto/x509" + "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 { + log.Panicf("Failed to change directories. Error: %s", 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), + }) + createKeyOutput, err := keyManager.CreateKey(context.Background(), &kms.CreateKeyInput{ + KeySpec: types.KeySpecEccNistP256, + KeyUsage: types.KeyUsageTypeSignVerify, + }) + require.NoError(t, err) + + keyID := *createKeyOutput.KeyMetadata.KeyId + + getPublicKeyOutput, err := keyManager.GetPublicKey(context.Background(), &kms.GetPublicKeyInput{ + KeyId: aws.String(keyID), + }) + require.NoError(t, err) + + k, err := x509.ParsePKIXPublicKey(getPublicKeyOutput.PublicKey) + require.NoError(t, err) + + publicKey := k.(*ecdsa.PublicKey) + publicAddress := crypto.PubkeyToAddress(*publicKey) + + request := auth.RandomStoreChunksRequest(rand) + request.Signature = nil + + signer := NewRequestSigner(region, localstackHost, keyID) + + 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) +} diff --git a/disperser/controller/node_client_manager.go b/disperser/controller/node_client_manager.go index 50a29c1e4c..160dba6a99 100644 --- a/disperser/controller/node_client_manager.go +++ b/disperser/controller/node_client_manager.go @@ -24,7 +24,7 @@ var _ NodeClientManager = (*nodeClientManager)(nil) func NewNodeClientManager( cacheSize int, requestSigner clients.RequestSigner, - logger logging.Logger) (*nodeClientManager, error) { + logger logging.Logger) (NodeClientManager, error) { closeClient := func(socket string, value clients.NodeClientV2) { if err := value.Close(); err != nil { diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index ce7181be29..87621ec17b 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -115,8 +115,7 @@ func (a *requestAuthenticator) AuthenticateStoreChunksRequest( return fmt.Errorf("failed to get operator key: %w", err) } - signature := request.Signature - err = VerifyStoreChunksRequest(*key, request, signature) + err = VerifyStoreChunksRequest(*key, request) if err != nil { return fmt.Errorf("failed to verify request: %w", err) } diff --git a/node/auth/authenticator_test.go b/node/auth/authenticator_test.go index d331174690..cac6bf8915 100644 --- a/node/auth/authenticator_test.go +++ b/node/auth/authenticator_test.go @@ -33,7 +33,7 @@ func TestValidRequest(t *testing.T) { start) require.NoError(t, err) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -63,7 +63,7 @@ func TestInvalidRequestWrongHash(t *testing.T) { start) require.NoError(t, err) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -96,7 +96,7 @@ func TestInvalidRequestWrongKey(t *testing.T) { start) require.NoError(t, err) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 _, differentPrivateKey := rand.ECDSA() @@ -130,7 +130,7 @@ func TestInvalidRequestInvalidDisperserID(t *testing.T) { start) require.NoError(t, err) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 1234 _, differentPrivateKey := rand.ECDSA() @@ -163,7 +163,7 @@ func TestAuthCaching(t *testing.T) { require.NoError(t, err) // The first request will actually be validated. - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -176,7 +176,7 @@ func TestAuthCaching(t *testing.T) { // With auth caching, those checks won't happen until the auth timeout has passed (configured to 1 minute). now := start for i := 0; i < 60; i++ { - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err = SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -195,7 +195,7 @@ func TestAuthCaching(t *testing.T) { // The next request will be made after the auth timeout has passed, so it will be validated. // Since it is actually invalid, the authenticator should reject it. - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err = SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -228,7 +228,7 @@ func TestAuthCachingDisabled(t *testing.T) { require.NoError(t, err) // The first request will always be validated. - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -238,7 +238,7 @@ func TestAuthCachingDisabled(t *testing.T) { require.NoError(t, err) // Make another request without moving time forward. It should be validated. - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err = SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -273,7 +273,7 @@ func TestKeyExpiry(t *testing.T) { // Preloading the cache should have grabbed Disperser 0's key chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", 1) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -326,7 +326,7 @@ func TestAuthCacheSize(t *testing.T) { // Make requests from cacheSize different origins. for i := 0; i < cacheSize; i++ { - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -341,7 +341,7 @@ func TestAuthCacheSize(t *testing.T) { // All origins should be authenticated in the auth cache. If we send invalid requests from the same origins, // they should still be authenticated (since the authenticator won't re-check). for i := 0; i < cacheSize; i++ { - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -356,7 +356,7 @@ func TestAuthCacheSize(t *testing.T) { } // Make a request from a new origin. This should boot origin 0 from the cache. - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err := SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -366,7 +366,7 @@ func TestAuthCacheSize(t *testing.T) { require.NoError(t, err) for i := 0; i < cacheSize; i++ { - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err = SignStoreChunksRequest(privateKey, request) require.NoError(t, err) @@ -419,7 +419,7 @@ func TestKeyCacheSize(t *testing.T) { // Make a request for each key (except for the last one, which won't fit in the cache). for i := 0; i < cacheSize; i++ { - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = uint32(i) signature, err := SignStoreChunksRequest(keyMap[uint32(i)], request) require.NoError(t, err) @@ -436,7 +436,7 @@ func TestKeyCacheSize(t *testing.T) { // Make another request for each key. None should require a read from the chain. for i := 0; i < cacheSize; i++ { - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = uint32(i) signature, err := SignStoreChunksRequest(keyMap[uint32(i)], request) require.NoError(t, err) @@ -451,7 +451,7 @@ func TestKeyCacheSize(t *testing.T) { chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", cacheSize) // Make a request for the last key. This should require a read from the chain and will boot key 0 from the cache. - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) request.DisperserID = uint32(cacheSize) signature, err := SignStoreChunksRequest(keyMap[uint32(cacheSize)], request) require.NoError(t, err) @@ -464,7 +464,7 @@ func TestKeyCacheSize(t *testing.T) { chainReader.Mock.AssertNumberOfCalls(t, "GetDisperserAddress", cacheSize+1) // Make another request for key 0. This should require a read from the chain. - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.DisperserID = 0 signature, err = SignStoreChunksRequest(keyMap[0], request) require.NoError(t, err) diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index dd7e24cd3d..24ca80b8b9 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -28,10 +28,10 @@ func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequ // VerifyStoreChunksRequest verifies the given signature of the given StoreChunksRequest with the given // public key. -func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksRequest, signature []byte) error { +func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksRequest) error { requestHash := HashStoreChunksRequest(request) - signingPublicKey, err := crypto.SigToPub(requestHash, signature) + signingPublicKey, err := crypto.SigToPub(requestHash, request.Signature) if err != nil { return fmt.Errorf("failed to recover public key from signature: %w", err) } diff --git a/node/auth/request_signing_test.go b/node/auth/request_signing_test.go index 1d6a6e6220..f43ac75ca5 100644 --- a/node/auth/request_signing_test.go +++ b/node/auth/request_signing_test.go @@ -1,71 +1,16 @@ package auth import ( - "github.com/Layr-Labs/eigenda/api/grpc/common" - v2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2" - grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/common/testutils/random" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "testing" ) -func randomStoreChunksRequest(rand *random.TestRandom) *grpc.StoreChunksRequest { - certificateCount := rand.Intn(10) + 1 - blobCertificates := make([]*v2.BlobCertificate, certificateCount) - for i := 0; i < certificateCount; i++ { - - relayCount := rand.Intn(10) + 1 - relays := make([]uint32, relayCount) - for j := 0; j < relayCount; j++ { - relays[j] = rand.Uint32() - } - - quorumCount := rand.Intn(10) + 1 - quorumNumbers := make([]uint32, quorumCount) - for j := 0; j < quorumCount; j++ { - quorumNumbers[j] = rand.Uint32() - } - - blobCertificates[i] = &v2.BlobCertificate{ - BlobHeader: &v2.BlobHeader{ - Version: rand.Uint32(), - QuorumNumbers: quorumNumbers, - Commitment: &common.BlobCommitment{ - Commitment: rand.Bytes(32), - LengthCommitment: rand.Bytes(32), - LengthProof: rand.Bytes(32), - Length: rand.Uint32(), - }, - PaymentHeader: &common.PaymentHeader{ - AccountId: rand.String(32), - ReservationPeriod: rand.Uint32(), - CumulativePayment: rand.Bytes(32), - Salt: rand.Uint32(), - }, - Signature: rand.Bytes(32), - }, - Relays: relays, - } - } - - return &grpc.StoreChunksRequest{ - Batch: &v2.Batch{ - Header: &v2.BatchHeader{ - BatchRoot: rand.Bytes(32), - ReferenceBlockNumber: rand.Uint64(), - }, - BlobCertificates: blobCertificates, - }, - DisperserID: rand.Uint32(), - Signature: rand.Bytes(32), - } -} - func TestHashing(t *testing.T) { rand := random.NewTestRandom(t) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) originalRequestHash := HashStoreChunksRequest(request) // modifying the signature should not change the hash @@ -75,28 +20,28 @@ func TestHashing(t *testing.T) { // modify the disperser id rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.DisperserID = request.DisperserID + 1 hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // remove a blob cert rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates = request.Batch.BlobCertificates[:len(request.Batch.BlobCertificates)-1] hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify a relay rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].Relays[0] = request.Batch.BlobCertificates[0].Relays[0] + 1 hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, remove a relay rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].Relays = request.Batch.BlobCertificates[0].Relays[:len(request.Batch.BlobCertificates[0].Relays)-1] hash = HashStoreChunksRequest(request) @@ -104,14 +49,14 @@ func TestHashing(t *testing.T) { // within a blob cert, add a relay rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].Relays = append(request.Batch.BlobCertificates[0].Relays, rand.Uint32()) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify a quorum number rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[0] = request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[0] + 1 hash = HashStoreChunksRequest(request) @@ -119,7 +64,7 @@ func TestHashing(t *testing.T) { // within a blob cert, remove a quorum number rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers = request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[:len( request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers)-1] @@ -128,7 +73,7 @@ func TestHashing(t *testing.T) { // within a blob cert, add a quorum number rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers = append( request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers, rand.Uint32()) hash = HashStoreChunksRequest(request) @@ -136,63 +81,63 @@ func TestHashing(t *testing.T) { // within a blob cert, modify the Commitment.Commitment rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.Commitment = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.LengthCommitment rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthCommitment = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.LengthProof rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.Length rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.Length = rand.Uint32() hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.AccountId rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.AccountId = rand.String(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.ReservationPeriod rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.ReservationPeriod = rand.Uint32() hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.CumulativePayment rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.CumulativePayment = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.Salt rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.Salt = rand.Uint32() hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Signature rand.Reset() - request = randomStoreChunksRequest(rand) + request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Signature = rand.Bytes(32) hash = HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) @@ -204,34 +149,32 @@ func TestRequestSigning(t *testing.T) { public, private := rand.ECDSA() publicAddress := crypto.PubkeyToAddress(*public) - request := randomStoreChunksRequest(rand) + request := RandomStoreChunksRequest(rand) signature, err := SignStoreChunksRequest(private, request) require.NoError(t, err) - - err = VerifyStoreChunksRequest(publicAddress, request, signature) - require.NoError(t, err) - - // Adding the signature to the request should not change the hash, so it should still be valid request.Signature = signature - err = VerifyStoreChunksRequest(publicAddress, request, signature) + + err = VerifyStoreChunksRequest(publicAddress, request) require.NoError(t, err) // Using a different public key should make the signature invalid otherPublic, _ := rand.ECDSA() otherPublicAddress := crypto.PubkeyToAddress(*otherPublic) - err = VerifyStoreChunksRequest(otherPublicAddress, request, signature) + err = VerifyStoreChunksRequest(otherPublicAddress, request) require.Error(t, err) // Changing a byte in the signature should make it invalid alteredSignature := make([]byte, len(signature)) copy(alteredSignature, signature) alteredSignature[0] = alteredSignature[0] + 1 - err = VerifyStoreChunksRequest(publicAddress, request, alteredSignature) + request.Signature = alteredSignature + err = VerifyStoreChunksRequest(publicAddress, request) require.Error(t, err) // Changing a field in the request should make it invalid request.DisperserID = request.DisperserID + 1 - err = VerifyStoreChunksRequest(publicAddress, request, signature) + request.Signature = signature + err = VerifyStoreChunksRequest(publicAddress, request) require.Error(t, err) } diff --git a/node/auth/request_signing_test_utils.go b/node/auth/request_signing_test_utils.go new file mode 100644 index 0000000000..cdc82956a4 --- /dev/null +++ b/node/auth/request_signing_test_utils.go @@ -0,0 +1,60 @@ +package auth + +import ( + "github.com/Layr-Labs/eigenda/api/grpc/common" + v2 "github.com/Layr-Labs/eigenda/api/grpc/common/v2" + grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/Layr-Labs/eigenda/common/testutils/random" +) + +func RandomStoreChunksRequest(rand *random.TestRandom) *grpc.StoreChunksRequest { + certificateCount := rand.Intn(10) + 1 + blobCertificates := make([]*v2.BlobCertificate, certificateCount) + for i := 0; i < certificateCount; i++ { + + relayCount := rand.Intn(10) + 1 + relays := make([]uint32, relayCount) + for j := 0; j < relayCount; j++ { + relays[j] = rand.Uint32() + } + + quorumCount := rand.Intn(10) + 1 + quorumNumbers := make([]uint32, quorumCount) + for j := 0; j < quorumCount; j++ { + quorumNumbers[j] = rand.Uint32() + } + + blobCertificates[i] = &v2.BlobCertificate{ + BlobHeader: &v2.BlobHeader{ + Version: rand.Uint32(), + QuorumNumbers: quorumNumbers, + Commitment: &common.BlobCommitment{ + Commitment: rand.Bytes(32), + LengthCommitment: rand.Bytes(32), + LengthProof: rand.Bytes(32), + Length: rand.Uint32(), + }, + PaymentHeader: &common.PaymentHeader{ + AccountId: rand.String(32), + ReservationPeriod: rand.Uint32(), + CumulativePayment: rand.Bytes(32), + Salt: rand.Uint32(), + }, + Signature: rand.Bytes(32), + }, + Relays: relays, + } + } + + return &grpc.StoreChunksRequest{ + Batch: &v2.Batch{ + Header: &v2.BatchHeader{ + BatchRoot: rand.Bytes(32), + ReferenceBlockNumber: rand.Uint64(), + }, + BlobCertificates: blobCertificates, + }, + DisperserID: rand.Uint32(), + Signature: rand.Bytes(32), + } +} From d793501405fd5bcf72a05fff0c6deb26e1dad2ed Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 16:06:20 -0600 Subject: [PATCH 29/50] Incremental progress in kludging something together Signed-off-by: Cody Littley --- api/clients/request_signer_test.go | 172 +++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 24 deletions(-) diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index c787c6ecb3..f443c7eb9a 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -3,7 +3,11 @@ package clients import ( "context" "crypto/ecdsa" - "crypto/x509" + "crypto/elliptic" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" "github.com/Layr-Labs/eigenda/common/testutils/random" "github.com/Layr-Labs/eigenda/inabox/deploy" "github.com/Layr-Labs/eigenda/node/auth" @@ -11,9 +15,12 @@ import ( "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/ethereum/go-ethereum/crypto/secp256k1" "github.com/ory/dockertest/v3" "github.com/stretchr/testify/require" + "golang.org/x/crypto/cryptobyte" "log" + "math/big" "os" "path/filepath" "runtime" @@ -66,6 +73,98 @@ func teardown() { } } +type publicKeyInfo struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +func ParsePublicKey(keyBytes []byte) (*ecdsa.PublicKey, error) { + pki := publicKeyInfo{} + rest, err := asn1.Unmarshal(keyBytes, &pki) + + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, fmt.Errorf("trailing data after public key (%d bytes)", len(rest)) + } + + rightAlignedKey := cryptobyte.String(pki.PublicKey.RightAlign()) + + x, y := elliptic.Unmarshal(crypto.S256(), rightAlignedKey) // TODO deprecated method + if x == nil { + return nil, errors.New("x509: failed to unmarshal elliptic curve point") + } + + return &ecdsa.PublicKey{ + Curve: crypto.S256(), + X: x, + Y: y, + }, nil +} + +type signatureInfo struct { + R *big.Int + S *big.Int +} + +// TODO this was generated by AI, verify correctness and clean up +func ComputeRecoveryID(hash []byte, r, s *big.Int, pubKey *ecdsa.PublicKey) (int, error) { + rBytes := r.Bytes() + sBytes := s.Bytes() + + // Ensure R and S are 32 bytes each + rPadded := make([]byte, 32) + sPadded := make([]byte, 32) + copy(rPadded[32-len(rBytes):], rBytes) + copy(sPadded[32-len(sBytes):], sBytes) + + for v := 0; v < 2; v++ { + sig := append(rPadded, append(sPadded, byte(v))...) + recoveredPubKey, err := secp256k1.RecoverPubkey(hash, sig) + if err != nil { + return -1, fmt.Errorf("failed to recover public key: %w", err) + } + + x, y := elliptic.Unmarshal(secp256k1.S256(), recoveredPubKey) + if x.Cmp(pubKey.X) == 0 && y.Cmp(pubKey.Y) == 0 { + return v, nil + } + } + + return -1, fmt.Errorf("no valid recovery ID found") +} + +// ParseSignature parses a signature from AWS into eth format +func ParseSignature( + publicKey *ecdsa.PublicKey, + hash []byte, + signatureBytes []byte) ([]byte, error) { + + si := signatureInfo{} + rest, err := asn1.Unmarshal(signatureBytes, &si) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal signature: %w", err) + } + if len(rest) > 0 { + return nil, fmt.Errorf("trailing data after signature (%d bytes)", len(rest)) + } + + result := make([]byte, 65) + copy(result[0:32], si.R.Bytes()) + copy(result[32:64], si.S.Bytes()) + + recoveryID, err := ComputeRecoveryID(hash, si.R, si.S, publicKey) + if err != nil { + return nil, fmt.Errorf("failed to compute recovery ID: %w", err) + } + + result[64] = byte(recoveryID) + + return result, nil +} + func TestRequestSigning(t *testing.T) { rand := random.NewTestRandom(t) setup(t) @@ -75,36 +174,61 @@ func TestRequestSigning(t *testing.T) { Region: region, BaseEndpoint: aws.String(localstackHost), }) - createKeyOutput, err := keyManager.CreateKey(context.Background(), &kms.CreateKeyInput{ - KeySpec: types.KeySpecEccNistP256, - KeyUsage: types.KeyUsageTypeSignVerify, - }) - require.NoError(t, err) - keyID := *createKeyOutput.KeyMetadata.KeyId + for i := 0; i < 1000; i++ { + fmt.Printf("iteration %d\n", i) // TODO remove - getPublicKeyOutput, err := keyManager.GetPublicKey(context.Background(), &kms.GetPublicKeyInput{ - KeyId: aws.String(keyID), - }) - require.NoError(t, err) + createKeyOutput, err := keyManager.CreateKey(context.Background(), &kms.CreateKeyInput{ + KeySpec: types.KeySpecEccSecgP256k1, + KeyUsage: types.KeyUsageTypeSignVerify, + }) + require.NoError(t, err) - k, err := x509.ParsePKIXPublicKey(getPublicKeyOutput.PublicKey) - require.NoError(t, err) + keyID := *createKeyOutput.KeyMetadata.KeyId - publicKey := k.(*ecdsa.PublicKey) - publicAddress := crypto.PubkeyToAddress(*publicKey) + getPublicKeyOutput, err := keyManager.GetPublicKey(context.Background(), &kms.GetPublicKeyInput{ + KeyId: aws.String(keyID), + }) + require.NoError(t, err) + + key, err := ParsePublicKey(getPublicKeyOutput.PublicKey) + require.NoError(t, err) - request := auth.RandomStoreChunksRequest(rand) - request.Signature = nil + publicAddress := crypto.PubkeyToAddress(*key) - signer := NewRequestSigner(region, localstackHost, keyID) + request := auth.RandomStoreChunksRequest(rand) + request.Signature = nil + + signer := NewRequestSigner(region, localstackHost, keyID) + + signature, err := signer.SignStoreChunksRequest(context.Background(), request) + require.NoError(t, err) - signature, err := signer.SignStoreChunksRequest(context.Background(), request) - require.NoError(t, err) + // TODO this doesn't belong here + signature, err = ParseSignature( + key, + auth.HashStoreChunksRequest(request), + signature) - require.Nil(t, request.Signature) - request.Signature = signature + require.Nil(t, request.Signature) + request.Signature = signature - err = auth.VerifyStoreChunksRequest(publicAddress, request) - require.NoError(t, err) + 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) + + } } From b81e430016171eee322c5e536eb528df0a8a0eb4 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Mon, 16 Dec 2024 16:11:35 -0600 Subject: [PATCH 30/50] Incremental test iteration. Signed-off-by: Cody Littley --- api/clients/request_signer_test.go | 59 ++++++++++++++++-------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index f443c7eb9a..14d8635e36 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -175,8 +175,8 @@ func TestRequestSigning(t *testing.T) { BaseEndpoint: aws.String(localstackHost), }) - for i := 0; i < 1000; i++ { - fmt.Printf("iteration %d\n", i) // TODO remove + for i := 0; i < 100; i++ { + fmt.Printf("key iteration %d\n", i) // TODO remove createKeyOutput, err := keyManager.CreateKey(context.Background(), &kms.CreateKeyInput{ KeySpec: types.KeySpecEccSecgP256k1, @@ -196,39 +196,42 @@ func TestRequestSigning(t *testing.T) { publicAddress := crypto.PubkeyToAddress(*key) - request := auth.RandomStoreChunksRequest(rand) - request.Signature = nil + for j := 0; j < 100; j++ { + fmt.Printf(" request iteration %d\n", j) // TODO remove - signer := NewRequestSigner(region, localstackHost, keyID) + request := auth.RandomStoreChunksRequest(rand) + request.Signature = nil - signature, err := signer.SignStoreChunksRequest(context.Background(), request) - require.NoError(t, err) + signer := NewRequestSigner(region, localstackHost, keyID) - // TODO this doesn't belong here - signature, err = ParseSignature( - key, - auth.HashStoreChunksRequest(request), - signature) + signature, err := signer.SignStoreChunksRequest(context.Background(), request) + require.NoError(t, err) - require.Nil(t, request.Signature) - request.Signature = signature + // TODO this doesn't belong here + signature, err = ParseSignature( + key, + auth.HashStoreChunksRequest(request), + signature) - err = auth.VerifyStoreChunksRequest(publicAddress, request) - require.NoError(t, err) + require.Nil(t, request.Signature) + request.Signature = signature - // 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) + err = auth.VerifyStoreChunksRequest(publicAddress, request) + require.NoError(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) + // 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) + } } } From 7327db1c4fde418bc9474b51322a343f8faee916 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 17 Dec 2024 08:53:57 -0600 Subject: [PATCH 31/50] Added debug printing. Signed-off-by: Cody Littley --- api/clients/request_signer_test.go | 17 +++++++++++++---- node/auth/request_signing.go | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index 14d8635e36..8dc1545ac5 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -92,7 +92,7 @@ func ParsePublicKey(keyBytes []byte) (*ecdsa.PublicKey, error) { rightAlignedKey := cryptobyte.String(pki.PublicKey.RightAlign()) - x, y := elliptic.Unmarshal(crypto.S256(), rightAlignedKey) // TODO deprecated method + x, y := elliptic.Unmarshal(crypto.S256(), rightAlignedKey) if x == nil { return nil, errors.New("x509: failed to unmarshal elliptic curve point") } @@ -115,13 +115,20 @@ func ComputeRecoveryID(hash []byte, r, s *big.Int, pubKey *ecdsa.PublicKey) (int sBytes := s.Bytes() // Ensure R and S are 32 bytes each + fmt.Printf(" len of rBytes: %d, %x\n", len(rBytes), rBytes) // TODO remove + fmt.Printf(" len of sBytes: %d, %x\n", len(sBytes), sBytes) // TODO remove + + // TODO necessary? rPadded := make([]byte, 32) sPadded := make([]byte, 32) copy(rPadded[32-len(rBytes):], rBytes) copy(sPadded[32-len(sBytes):], sBytes) - for v := 0; v < 2; v++ { - sig := append(rPadded, append(sPadded, byte(v))...) + for v := 0; v < 4; v++ { + sig := make([]byte, 65) + copy(sig[0:32], rPadded) + copy(sig[32:64], sPadded) + sig[64] = byte(v) recoveredPubKey, err := secp256k1.RecoverPubkey(hash, sig) if err != nil { return -1, fmt.Errorf("failed to recover public key: %w", err) @@ -204,6 +211,9 @@ func TestRequestSigning(t *testing.T) { signer := NewRequestSigner(region, localstackHost, keyID) + fmt.Printf(" >public key: %x\n", key) // TODO remove + + // Test a valid signature. signature, err := signer.SignStoreChunksRequest(context.Background(), request) require.NoError(t, err) @@ -215,7 +225,6 @@ func TestRequestSigning(t *testing.T) { require.Nil(t, request.Signature) request.Signature = signature - err = auth.VerifyStoreChunksRequest(publicAddress, request) require.NoError(t, err) diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index 24ca80b8b9..da09c99331 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -36,6 +36,8 @@ func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksR return fmt.Errorf("failed to recover public key from signature: %w", err) } + fmt.Printf(" public key: %v\n", signingPublicKey) + signingAddress := crypto.PubkeyToAddress(*signingPublicKey) if key.Cmp(signingAddress) != 0 { From 8cfa9716d457bbd76e420672ac5461562dc6ca2e Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 17 Dec 2024 08:59:17 -0600 Subject: [PATCH 32/50] IT'S ALIVE! Signed-off-by: Cody Littley --- api/clients/request_signer_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index 8dc1545ac5..40d44525ea 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -158,9 +158,16 @@ func ParseSignature( return nil, fmt.Errorf("trailing data after signature (%d bytes)", len(rest)) } + rBytes := si.R.Bytes() + sBytes := si.S.Bytes() + rPadded := make([]byte, 32) + sPadded := make([]byte, 32) + copy(rPadded[32-len(rBytes):], rBytes) + copy(sPadded[32-len(sBytes):], sBytes) + result := make([]byte, 65) - copy(result[0:32], si.R.Bytes()) - copy(result[32:64], si.S.Bytes()) + copy(result[0:32], rPadded) + copy(result[32:64], sPadded) recoveryID, err := ComputeRecoveryID(hash, si.R, si.S, publicKey) if err != nil { From 306746ecc713b9e20090f5e9a27ce1713c57896b Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 17 Dec 2024 09:13:13 -0600 Subject: [PATCH 33/50] Partial cleanup. Signed-off-by: Cody Littley --- api/clients/request_signer_test.go | 67 +++++++++++------------------- node/auth/request_signing.go | 2 - 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index 40d44525ea..ff761f140f 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -109,38 +109,33 @@ type signatureInfo struct { S *big.Int } -// TODO this was generated by AI, verify correctness and clean up -func ComputeRecoveryID(hash []byte, r, s *big.Int, pubKey *ecdsa.PublicKey) (int, error) { - rBytes := r.Bytes() - sBytes := s.Bytes() - - // Ensure R and S are 32 bytes each - fmt.Printf(" len of rBytes: %d, %x\n", len(rBytes), rBytes) // TODO remove - fmt.Printf(" len of sBytes: %d, %x\n", len(sBytes), sBytes) // TODO remove - - // TODO necessary? - rPadded := make([]byte, 32) - sPadded := make([]byte, 32) - copy(rPadded[32-len(rBytes):], rBytes) - copy(sPadded[32-len(sBytes):], sBytes) - +// AddRecoveryID computes the recovery ID for a given signature and public key and adds it to the signature. +func AddRecoveryID(hash []byte, pubKey *ecdsa.PublicKey, partialSignature []byte) error { for v := 0; v < 4; v++ { - sig := make([]byte, 65) - copy(sig[0:32], rPadded) - copy(sig[32:64], sPadded) - sig[64] = byte(v) - recoveredPubKey, err := secp256k1.RecoverPubkey(hash, sig) + partialSignature[64] = byte(v) + recoveredPubKey, err := secp256k1.RecoverPubkey(hash, partialSignature) if err != nil { - return -1, fmt.Errorf("failed to recover public key: %w", err) + return fmt.Errorf("failed to recover public key: %w", err) } x, y := elliptic.Unmarshal(secp256k1.S256(), recoveredPubKey) if x.Cmp(pubKey.X) == 0 && y.Cmp(pubKey.Y) == 0 { - return v, nil + return nil } } - return -1, fmt.Errorf("no valid recovery ID found") + return fmt.Errorf("no valid recovery ID found") +} + +// pad32 pads a byte slice to 32 bytes, inserting zeros at the beginning if necessary. +func pad32(bytes []byte) []byte { + if len(bytes) == 32 { + return bytes + } + + padded := make([]byte, 32) + copy(padded[32-len(bytes):], bytes) + return padded } // ParseSignature parses a signature from AWS into eth format @@ -158,24 +153,18 @@ func ParseSignature( return nil, fmt.Errorf("trailing data after signature (%d bytes)", len(rest)) } - rBytes := si.R.Bytes() - sBytes := si.S.Bytes() - rPadded := make([]byte, 32) - sPadded := make([]byte, 32) - copy(rPadded[32-len(rBytes):], rBytes) - copy(sPadded[32-len(sBytes):], sBytes) + rBytes := pad32(si.R.Bytes()) + sBytes := pad32(si.S.Bytes()) result := make([]byte, 65) - copy(result[0:32], rPadded) - copy(result[32:64], sPadded) + copy(result[0:32], rBytes) + copy(result[32:64], sBytes) - recoveryID, err := ComputeRecoveryID(hash, si.R, si.S, publicKey) + err = AddRecoveryID(hash, publicKey, result) if err != nil { return nil, fmt.Errorf("failed to compute recovery ID: %w", err) } - result[64] = byte(recoveryID) - return result, nil } @@ -189,9 +178,7 @@ func TestRequestSigning(t *testing.T) { BaseEndpoint: aws.String(localstackHost), }) - for i := 0; i < 100; i++ { - fmt.Printf("key iteration %d\n", i) // TODO remove - + for i := 0; i < 10; i++ { createKeyOutput, err := keyManager.CreateKey(context.Background(), &kms.CreateKeyInput{ KeySpec: types.KeySpecEccSecgP256k1, KeyUsage: types.KeyUsageTypeSignVerify, @@ -210,16 +197,12 @@ func TestRequestSigning(t *testing.T) { publicAddress := crypto.PubkeyToAddress(*key) - for j := 0; j < 100; j++ { - fmt.Printf(" request iteration %d\n", j) // TODO remove - + for j := 0; j < 10; j++ { request := auth.RandomStoreChunksRequest(rand) request.Signature = nil signer := NewRequestSigner(region, localstackHost, keyID) - fmt.Printf(" >public key: %x\n", key) // TODO remove - // Test a valid signature. signature, err := signer.SignStoreChunksRequest(context.Background(), request) require.NoError(t, err) diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index da09c99331..24ca80b8b9 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -36,8 +36,6 @@ func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksR return fmt.Errorf("failed to recover public key from signature: %w", err) } - fmt.Printf(" public key: %v\n", signingPublicKey) - signingAddress := crypto.PubkeyToAddress(*signingPublicKey) if key.Cmp(signingAddress) != 0 { From b41d72952b002098affa3d0e4d94209a7d6980cc Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 17 Dec 2024 09:27:16 -0600 Subject: [PATCH 34/50] Move code to proper locations. Signed-off-by: Cody Littley --- api/clients/request_signer.go | 27 ++++++- api/clients/request_signer_test.go | 115 +---------------------------- node/auth/request_signing.go | 105 ++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 115 deletions(-) diff --git a/api/clients/request_signer.go b/api/clients/request_signer.go index eecc2ce5be..67da5d4b87 100644 --- a/api/clients/request_signer.go +++ b/api/clients/request_signer.go @@ -2,6 +2,7 @@ package clients import ( "context" + "crypto/ecdsa" "fmt" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/node/auth" @@ -21,24 +22,39 @@ var _ RequestSigner = &requestSigner{} type requestSigner struct { keyID string + publicKey *ecdsa.PublicKey keyManager *kms.Client } // NewRequestSigner creates a new RequestSigner. func NewRequestSigner( + ctx context.Context, region string, endpoint string, - keyID string) RequestSigner { + keyID string) (RequestSigner, error) { keyManager := kms.New(kms.Options{ Region: region, BaseEndpoint: aws.String(endpoint), }) + getPublicKeyOutput, err := keyManager.GetPublicKey(ctx, &kms.GetPublicKeyInput{ + KeyId: aws.String(keyID), + }) + if err != nil { + return nil, fmt.Errorf("failed to get public key: %w", err) + } + + publicKey, err := auth.ParseKMSPublicKey(getPublicKeyOutput.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to parse public key: %w", err) + } + return &requestSigner{ keyID: keyID, + publicKey: publicKey, keyManager: keyManager, - } + }, nil } func (s *requestSigner) SignStoreChunksRequest(ctx context.Context, request *grpc.StoreChunksRequest) ([]byte, error) { @@ -57,5 +73,10 @@ func (s *requestSigner) SignStoreChunksRequest(ctx context.Context, request *grp return nil, fmt.Errorf("failed to sign request: %w", err) } - return signOutput.Signature, nil + signature, err := auth.ParseKMSSignature(s.publicKey, hash, signOutput.Signature) + if err != nil { + return nil, fmt.Errorf("failed to parse signature: %w", err) + } + + return signature, nil } diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index ff761f140f..9862dbf121 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -2,12 +2,6 @@ package clients import ( "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" "github.com/Layr-Labs/eigenda/common/testutils/random" "github.com/Layr-Labs/eigenda/inabox/deploy" "github.com/Layr-Labs/eigenda/node/auth" @@ -15,12 +9,9 @@ import ( "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/ethereum/go-ethereum/crypto/secp256k1" "github.com/ory/dockertest/v3" "github.com/stretchr/testify/require" - "golang.org/x/crypto/cryptobyte" "log" - "math/big" "os" "path/filepath" "runtime" @@ -73,101 +64,6 @@ func teardown() { } } -type publicKeyInfo struct { - Raw asn1.RawContent - Algorithm pkix.AlgorithmIdentifier - PublicKey asn1.BitString -} - -func ParsePublicKey(keyBytes []byte) (*ecdsa.PublicKey, error) { - pki := publicKeyInfo{} - rest, err := asn1.Unmarshal(keyBytes, &pki) - - if err != nil { - return nil, err - } - if len(rest) > 0 { - return nil, fmt.Errorf("trailing data after public key (%d bytes)", len(rest)) - } - - rightAlignedKey := cryptobyte.String(pki.PublicKey.RightAlign()) - - x, y := elliptic.Unmarshal(crypto.S256(), rightAlignedKey) - if x == nil { - return nil, errors.New("x509: failed to unmarshal elliptic curve point") - } - - return &ecdsa.PublicKey{ - Curve: crypto.S256(), - X: x, - Y: y, - }, nil -} - -type signatureInfo struct { - R *big.Int - S *big.Int -} - -// AddRecoveryID computes the recovery ID for a given signature and public key and adds it to the signature. -func AddRecoveryID(hash []byte, pubKey *ecdsa.PublicKey, partialSignature []byte) error { - for v := 0; v < 4; v++ { - partialSignature[64] = byte(v) - recoveredPubKey, err := secp256k1.RecoverPubkey(hash, partialSignature) - if err != nil { - return fmt.Errorf("failed to recover public key: %w", err) - } - - x, y := elliptic.Unmarshal(secp256k1.S256(), recoveredPubKey) - if x.Cmp(pubKey.X) == 0 && y.Cmp(pubKey.Y) == 0 { - return nil - } - } - - return fmt.Errorf("no valid recovery ID found") -} - -// pad32 pads a byte slice to 32 bytes, inserting zeros at the beginning if necessary. -func pad32(bytes []byte) []byte { - if len(bytes) == 32 { - return bytes - } - - padded := make([]byte, 32) - copy(padded[32-len(bytes):], bytes) - return padded -} - -// ParseSignature parses a signature from AWS into eth format -func ParseSignature( - publicKey *ecdsa.PublicKey, - hash []byte, - signatureBytes []byte) ([]byte, error) { - - si := signatureInfo{} - rest, err := asn1.Unmarshal(signatureBytes, &si) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal signature: %w", err) - } - if len(rest) > 0 { - return nil, fmt.Errorf("trailing data after signature (%d bytes)", len(rest)) - } - - rBytes := pad32(si.R.Bytes()) - sBytes := pad32(si.S.Bytes()) - - result := make([]byte, 65) - copy(result[0:32], rBytes) - copy(result[32:64], sBytes) - - err = AddRecoveryID(hash, publicKey, result) - if err != nil { - return nil, fmt.Errorf("failed to compute recovery ID: %w", err) - } - - return result, nil -} - func TestRequestSigning(t *testing.T) { rand := random.NewTestRandom(t) setup(t) @@ -192,7 +88,7 @@ func TestRequestSigning(t *testing.T) { }) require.NoError(t, err) - key, err := ParsePublicKey(getPublicKeyOutput.PublicKey) + key, err := auth.ParseKMSPublicKey(getPublicKeyOutput.PublicKey) require.NoError(t, err) publicAddress := crypto.PubkeyToAddress(*key) @@ -201,18 +97,13 @@ func TestRequestSigning(t *testing.T) { request := auth.RandomStoreChunksRequest(rand) request.Signature = nil - signer := NewRequestSigner(region, localstackHost, keyID) + signer, err := NewRequestSigner(context.Background(), region, localstackHost, keyID) + require.NoError(t, err) // Test a valid signature. signature, err := signer.SignStoreChunksRequest(context.Background(), request) require.NoError(t, err) - // TODO this doesn't belong here - signature, err = ParseSignature( - key, - auth.HashStoreChunksRequest(request), - signature) - require.Nil(t, request.Signature) request.Signature = signature err = auth.VerifyStoreChunksRequest(publicAddress, request) diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index 24ca80b8b9..2815a30314 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -2,17 +2,122 @@ package auth import ( "crypto/ecdsa" + "crypto/elliptic" + "crypto/x509/pkix" + "encoding/asn1" "encoding/binary" + "errors" "fmt" commonv1 "github.com/Layr-Labs/eigenda/api/grpc/common" common "github.com/Layr-Labs/eigenda/api/grpc/common/v2" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/sha3" "hash" + "math/big" ) +type signatureInfo struct { + R *big.Int + S *big.Int +} + +// AddRecoveryID computes the recovery ID for a given signature and public key and adds it to the signature. +func addRecoveryID(hash []byte, pubKey *ecdsa.PublicKey, partialSignature []byte) error { + for v := 0; v < 4; v++ { + partialSignature[64] = byte(v) + recoveredPubKey, err := secp256k1.RecoverPubkey(hash, partialSignature) + if err != nil { + return fmt.Errorf("failed to recover public key: %w", err) + } + + x, y := elliptic.Unmarshal(secp256k1.S256(), recoveredPubKey) + if x.Cmp(pubKey.X) == 0 && y.Cmp(pubKey.Y) == 0 { + return nil + } + } + + return fmt.Errorf("no valid recovery ID found") +} + +// pad32 pads a byte slice to 32 bytes, inserting zeros at the beginning if necessary. +func pad32(bytes []byte) []byte { + if len(bytes) == 32 { + return bytes + } + + padded := make([]byte, 32) + copy(padded[32-len(bytes):], bytes) + return padded +} + +// ParseKMSSignature parses a signature (KeySpecEccSecgP256k1) in the format returned by amazon KMS into the +// 65-byte format used by Ethereum. +func ParseKMSSignature( + publicKey *ecdsa.PublicKey, + hash []byte, + signatureBytes []byte) ([]byte, error) { + + si := signatureInfo{} + rest, err := asn1.Unmarshal(signatureBytes, &si) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal signature: %w", err) + } + if len(rest) > 0 { + return nil, fmt.Errorf("trailing data after signature (%d bytes)", len(rest)) + } + + rBytes := pad32(si.R.Bytes()) + sBytes := pad32(si.S.Bytes()) + + result := make([]byte, 65) + copy(result[0:32], rBytes) + copy(result[32:64], sBytes) + + err = addRecoveryID(hash, publicKey, result) + if err != nil { + return nil, fmt.Errorf("failed to compute recovery ID: %w", err) + } + + return result, nil +} + +type publicKeyInfo struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +// ParseKMSPublicKey parses a public key (KeySpecEccSecgP256k1) in the format returned by amazon KMS +// into an ecdsa.PublicKey. +func ParseKMSPublicKey(keyBytes []byte) (*ecdsa.PublicKey, error) { + pki := publicKeyInfo{} + rest, err := asn1.Unmarshal(keyBytes, &pki) + + if err != nil { + return nil, err + } + if len(rest) > 0 { + return nil, fmt.Errorf("trailing data after public key (%d bytes)", len(rest)) + } + + rightAlignedKey := cryptobyte.String(pki.PublicKey.RightAlign()) + + x, y := elliptic.Unmarshal(crypto.S256(), rightAlignedKey) + if x == nil { + return nil, errors.New("x509: failed to unmarshal elliptic curve point") + } + + return &ecdsa.PublicKey{ + Curve: crypto.S256(), + X: x, + Y: y, + }, nil +} + // SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not // write the signature into the request. func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { From d709520e27190a50b68aeafed47d8ce8c290d44b Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Tue, 17 Dec 2024 15:08:53 -0600 Subject: [PATCH 35/50] Copy eigensdk-go implementation of kms parsing. Signed-off-by: Cody Littley --- api/clients/request_signer.go | 30 +----- api/clients/request_signer_test.go | 8 +- common/kms.go | 158 ++++++++++++++++++++++++++++ contracts/lib/eigenlayer-middleware | 2 +- disperser/cmd/controller/main.go | 6 +- node/auth/request_signing.go | 105 ------------------ 6 files changed, 171 insertions(+), 138 deletions(-) create mode 100644 common/kms.go diff --git a/api/clients/request_signer.go b/api/clients/request_signer.go index 67da5d4b87..645a2ab5b7 100644 --- a/api/clients/request_signer.go +++ b/api/clients/request_signer.go @@ -5,10 +5,10 @@ import ( "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" - "github.com/aws/aws-sdk-go-v2/service/kms/types" ) // RequestSigner encapsulates the logic for signing GetChunks requests. @@ -38,21 +38,14 @@ func NewRequestSigner( BaseEndpoint: aws.String(endpoint), }) - getPublicKeyOutput, err := keyManager.GetPublicKey(ctx, &kms.GetPublicKeyInput{ - KeyId: aws.String(keyID), - }) - if err != nil { - return nil, fmt.Errorf("failed to get public key: %w", err) - } - - publicKey, err := auth.ParseKMSPublicKey(getPublicKeyOutput.PublicKey) + key, err := common.LoadPublicKeyKMS(ctx, keyManager, keyID) if err != nil { - return nil, fmt.Errorf("failed to parse public key: %w", err) + return nil, fmt.Errorf("failed to get ecdsa public key: %w", err) } return &requestSigner{ keyID: keyID, - publicKey: publicKey, + publicKey: key, keyManager: keyManager, }, nil } @@ -60,23 +53,10 @@ func NewRequestSigner( func (s *requestSigner) SignStoreChunksRequest(ctx context.Context, request *grpc.StoreChunksRequest) ([]byte, error) { hash := auth.HashStoreChunksRequest(request) - signOutput, err := s.keyManager.Sign( - ctx, - &kms.SignInput{ - KeyId: aws.String(s.keyID), - Message: hash, - SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, - MessageType: types.MessageTypeDigest, - }) - + 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) } - signature, err := auth.ParseKMSSignature(s.publicKey, hash, signOutput.Signature) - if err != nil { - return nil, fmt.Errorf("failed to parse signature: %w", err) - } - return signature, nil } diff --git a/api/clients/request_signer_test.go b/api/clients/request_signer_test.go index 9862dbf121..98452f4570 100644 --- a/api/clients/request_signer_test.go +++ b/api/clients/request_signer_test.go @@ -2,6 +2,7 @@ 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" @@ -83,12 +84,7 @@ func TestRequestSigning(t *testing.T) { keyID := *createKeyOutput.KeyMetadata.KeyId - getPublicKeyOutput, err := keyManager.GetPublicKey(context.Background(), &kms.GetPublicKeyInput{ - KeyId: aws.String(keyID), - }) - require.NoError(t, err) - - key, err := auth.ParseKMSPublicKey(getPublicKeyOutput.PublicKey) + key, err := common.LoadPublicKeyKMS(context.Background(), keyManager, keyID) require.NoError(t, err) publicAddress := crypto.PubkeyToAddress(*key) diff --git a/common/kms.go b/common/kms.go new file mode 100644 index 0000000000..e61e09b6dc --- /dev/null +++ b/common/kms.go @@ -0,0 +1,158 @@ +package common + +import ( + "bytes" + "context" + "crypto/ecdsa" + "encoding/asn1" + "encoding/hex" + "errors" + "fmt" + "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/ethereum/go-ethereum/crypto/secp256k1" + "math/big" +) + +// This file contains utility methods for working with AWS KMS using ecdsa on the KeySpecEccSecgP256k1 curve. +// This code was adapted from code in https://github.com/Layr-Labs/eigensdk-go/tree/dev/signerv2 + +var secp256k1N = crypto.S256().Params().N +var secp256k1HalfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) + +type asn1EcPublicKey struct { + EcPublicKeyInfo asn1EcPublicKeyInfo + PublicKey asn1.BitString +} + +type asn1EcPublicKeyInfo struct { + Algorithm asn1.ObjectIdentifier + Parameters asn1.ObjectIdentifier +} + +type asn1EcSig struct { + R asn1.RawValue + S asn1.RawValue +} + +// LoadPublicKeyKMS loads the public key from AWS KMS. +func LoadPublicKeyKMS( + ctx context.Context, + client *kms.Client, + keyId string) (*ecdsa.PublicKey, error) { + + getPubKeyOutput, err := client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ + KeyId: aws.String(keyId), + }) + if err != nil { + return nil, fmt.Errorf("failed to get public key for KeyId=%s: %w", keyId, err) + } + + key, err := ParsePublicKeyKMS(getPubKeyOutput.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to parse public key for KeyId=%s: %w", keyId, err) + } + + return key, nil +} + +// ParsePublicKeyKMS parses the public key from AWS KMS format into an ecdsa.PublicKey. +func ParsePublicKeyKMS(bytes []byte) (*ecdsa.PublicKey, error) { + var asn1pubk asn1EcPublicKey + _, err := asn1.Unmarshal(bytes, &asn1pubk) + if err != nil { + return nil, fmt.Errorf("asn1.Uunmarshal failed: %w", err) + } + + key, err := crypto.UnmarshalPubkey(asn1pubk.PublicKey.Bytes) + if err != nil { + return nil, fmt.Errorf("crypto.UnmarshalPubkey failed: %w", err) + } + + return key, nil +} + +func adjustSignatureLength(buffer []byte) []byte { + buffer = bytes.TrimLeft(buffer, "\x00") + for len(buffer) < 32 { + zeroBuf := []byte{0} + buffer = append(zeroBuf, buffer...) + } + return buffer +} + +// SignKMS signs a hash using the provided public using AWS KMS. +// The signature is returned in the 65-byte format used by Ethereum. +func SignKMS( + ctx context.Context, + client *kms.Client, + keyId string, + publicKey *ecdsa.PublicKey, + hash []byte) ([]byte, error) { + + signOutput, err := client.Sign(ctx, &kms.SignInput{ + KeyId: aws.String(keyId), + SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, + MessageType: types.MessageTypeDigest, + Message: hash, + }) + if err != nil { + return nil, fmt.Errorf("failed to sign hash: %w", err) + } + + signature, err := ParseSignatureKMS(publicKey, hash, signOutput.Signature) + if err != nil { + return nil, fmt.Errorf("failed to parse signature: %w", err) + } + + return signature, nil +} + +// ParseSignatureKMS parses a signature (KeySpecEccSecgP256k1) in the format returned by amazon KMS into the +// 65-byte format used by Ethereum. +func ParseSignatureKMS( + publicKey *ecdsa.PublicKey, + hash []byte, + bytes []byte) ([]byte, error) { + + publicKeyBytes := secp256k1.S256().Marshal(publicKey.X, publicKey.Y) + + var sigAsn1 asn1EcSig + _, err := asn1.Unmarshal(bytes, &sigAsn1) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal failed: %w", err) + } + + r := sigAsn1.R.Bytes + s := sigAsn1.S.Bytes + + // Adjust S value from signature according to Ethereum standard + sBigInt := new(big.Int).SetBytes(s) + if sBigInt.Cmp(secp256k1HalfN) > 0 { + s = new(big.Int).Sub(secp256k1N, sBigInt).Bytes() + } + + rsSignature := append(adjustSignatureLength(r), adjustSignatureLength(s)...) + signature := append(rsSignature, []byte{0}...) + + recoveredPublicKeyBytes, err := crypto.Ecrecover(hash, signature) + if err != nil { + return nil, err + } + + if hex.EncodeToString(recoveredPublicKeyBytes) != hex.EncodeToString(publicKeyBytes) { + signature = append(rsSignature, []byte{1}...) + recoveredPublicKeyBytes, err = crypto.Ecrecover(hash, signature) + if err != nil { + return nil, err + } + + if hex.EncodeToString(recoveredPublicKeyBytes) != hex.EncodeToString(publicKeyBytes) { + return nil, errors.New("can not reconstruct public key from sig") + } + } + + return signature, nil +} diff --git a/contracts/lib/eigenlayer-middleware b/contracts/lib/eigenlayer-middleware index b42aa0b7ae..91400d9776 160000 --- a/contracts/lib/eigenlayer-middleware +++ b/contracts/lib/eigenlayer-middleware @@ -1 +1 @@ -Subproject commit b42aa0b7aefb5b30721c3f4b9ad7ab02e8215648 +Subproject commit 91400d9776d4f7276a2807a53559f49f7edf5378 diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index 8e8b187826..d726242d2d 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -150,10 +150,14 @@ func RunController(ctx *cli.Context) error { } } - requestSigner := clients.NewRequestSigner( + requestSigner, err := clients.NewRequestSigner( + context.Background(), config.AwsClientConfig.Region, config.AwsClientConfig.EndpointURL, config.DisperserSigningKeyName) + if err != nil { + return fmt.Errorf("failed to create request signer: %v", err) + } nodeClientManager, err := controller.NewNodeClientManager(config.NodeClientCacheSize, requestSigner, logger) if err != nil { diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index 2815a30314..24ca80b8b9 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -2,122 +2,17 @@ package auth import ( "crypto/ecdsa" - "crypto/elliptic" - "crypto/x509/pkix" - "encoding/asn1" "encoding/binary" - "errors" "fmt" commonv1 "github.com/Layr-Labs/eigenda/api/grpc/common" common "github.com/Layr-Labs/eigenda/api/grpc/common/v2" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/crypto/secp256k1" - "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/sha3" "hash" - "math/big" ) -type signatureInfo struct { - R *big.Int - S *big.Int -} - -// AddRecoveryID computes the recovery ID for a given signature and public key and adds it to the signature. -func addRecoveryID(hash []byte, pubKey *ecdsa.PublicKey, partialSignature []byte) error { - for v := 0; v < 4; v++ { - partialSignature[64] = byte(v) - recoveredPubKey, err := secp256k1.RecoverPubkey(hash, partialSignature) - if err != nil { - return fmt.Errorf("failed to recover public key: %w", err) - } - - x, y := elliptic.Unmarshal(secp256k1.S256(), recoveredPubKey) - if x.Cmp(pubKey.X) == 0 && y.Cmp(pubKey.Y) == 0 { - return nil - } - } - - return fmt.Errorf("no valid recovery ID found") -} - -// pad32 pads a byte slice to 32 bytes, inserting zeros at the beginning if necessary. -func pad32(bytes []byte) []byte { - if len(bytes) == 32 { - return bytes - } - - padded := make([]byte, 32) - copy(padded[32-len(bytes):], bytes) - return padded -} - -// ParseKMSSignature parses a signature (KeySpecEccSecgP256k1) in the format returned by amazon KMS into the -// 65-byte format used by Ethereum. -func ParseKMSSignature( - publicKey *ecdsa.PublicKey, - hash []byte, - signatureBytes []byte) ([]byte, error) { - - si := signatureInfo{} - rest, err := asn1.Unmarshal(signatureBytes, &si) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal signature: %w", err) - } - if len(rest) > 0 { - return nil, fmt.Errorf("trailing data after signature (%d bytes)", len(rest)) - } - - rBytes := pad32(si.R.Bytes()) - sBytes := pad32(si.S.Bytes()) - - result := make([]byte, 65) - copy(result[0:32], rBytes) - copy(result[32:64], sBytes) - - err = addRecoveryID(hash, publicKey, result) - if err != nil { - return nil, fmt.Errorf("failed to compute recovery ID: %w", err) - } - - return result, nil -} - -type publicKeyInfo struct { - Raw asn1.RawContent - Algorithm pkix.AlgorithmIdentifier - PublicKey asn1.BitString -} - -// ParseKMSPublicKey parses a public key (KeySpecEccSecgP256k1) in the format returned by amazon KMS -// into an ecdsa.PublicKey. -func ParseKMSPublicKey(keyBytes []byte) (*ecdsa.PublicKey, error) { - pki := publicKeyInfo{} - rest, err := asn1.Unmarshal(keyBytes, &pki) - - if err != nil { - return nil, err - } - if len(rest) > 0 { - return nil, fmt.Errorf("trailing data after public key (%d bytes)", len(rest)) - } - - rightAlignedKey := cryptobyte.String(pki.PublicKey.RightAlign()) - - x, y := elliptic.Unmarshal(crypto.S256(), rightAlignedKey) - if x == nil { - return nil, errors.New("x509: failed to unmarshal elliptic curve point") - } - - return &ecdsa.PublicKey{ - Curve: crypto.S256(), - X: x, - Y: y, - }, nil -} - // SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not // write the signature into the request. func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { From 2e44b02e8777c32b7f6fccb0044487b7980a374e Mon Sep 17 00:00:00 2001 From: anupsv Date: Wed, 18 Dec 2024 18:55:07 +0530 Subject: [PATCH 36/50] Update kms.go --- common/kms.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/kms.go b/common/kms.go index e61e09b6dc..bec69ab305 100644 --- a/common/kms.go +++ b/common/kms.go @@ -75,6 +75,11 @@ func ParsePublicKeyKMS(bytes []byte) (*ecdsa.PublicKey, error) { } func adjustSignatureLength(buffer []byte) []byte { + + if len(buffer) > 32 { + buffer = buffer[len(buffer)-32:] // Take last 32 bytes + } + buffer = bytes.TrimLeft(buffer, "\x00") for len(buffer) < 32 { zeroBuf := []byte{0} @@ -117,6 +122,10 @@ func ParseSignatureKMS( hash []byte, bytes []byte) ([]byte, error) { + if !secp256k1.S256().IsOnCurve(publicKey.X, publicKey.Y) { + return nil, errors.New("public key is not on curve") + } + publicKeyBytes := secp256k1.S256().Marshal(publicKey.X, publicKey.Y) var sigAsn1 asn1EcSig From aebda81c1bd99f236eddaee338c41b3e7b8d5eb4 Mon Sep 17 00:00:00 2001 From: anupsv Date: Wed, 18 Dec 2024 18:55:58 +0530 Subject: [PATCH 37/50] Create kms_fuzz_test.go adding fuzz tests for kms key methods. --- common/kms_fuzz_test.go | 245 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 common/kms_fuzz_test.go diff --git a/common/kms_fuzz_test.go b/common/kms_fuzz_test.go new file mode 100644 index 0000000000..158ddd8ac6 --- /dev/null +++ b/common/kms_fuzz_test.go @@ -0,0 +1,245 @@ +package common + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "encoding/asn1" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/crypto" +) + +// ecdsaSignature defines the ASN.1 structure for ECDSA signatures. +type ecdsaSignature struct { + R, S *big.Int +} + +// generateValidSignature generates a valid ECDSA signature and returns the public key, hash, and DER signature. +func generateValidSignature() (*ecdsa.PublicKey, []byte, []byte, error) { + // Generate a secp256k1 ECDSA key pair. + privateKey, err := crypto.GenerateKey() + if err != nil { + return nil, nil, nil, err + } + publicKey := &privateKey.PublicKey + + // Define a message and compute its SHA-256 hash. + message := "Test message for ECDSA signature" + hash := sha256.Sum256([]byte(message)) + + // Sign the hash using the private key. + signatureBytes, err := crypto.Sign(hash[:], privateKey) + if err != nil { + return nil, nil, nil, err + } + + // Convert the signature to DER format. + r := new(big.Int).SetBytes(signatureBytes[:32]) + s := new(big.Int).SetBytes(signatureBytes[32:64]) + + // Marshal R and S into ASN.1 DER format. + derSignature, err := asn1.Marshal(ecdsaSignature{R: r, S: s}) + if err != nil { + return nil, nil, nil, err + } + + return publicKey, hash[:], derSignature, nil +} + +// defineEdgeCases returns a slice of tuples containing publicKeyBytes, hashBytes, derSignatureBytes +func defineEdgeCases() [][3][]byte { + var edgeCases [][3][]byte + + // Helper: Generate a valid signature to obtain a public key. + pubKeyValidBytes, hashValid, derSigValid, err := generateValidSignature() + if err != nil { + panic("Failed to generate valid signature for edge cases") + } + publicKeyValid := crypto.FromECDSAPub(pubKeyValidBytes) + + // 1. Malformed Public Keys + + // a. Incorrect length (too short) + publicKeyShort := []byte{0x04, 0x01, 0x02} + derSignatureValid := derSigValid + edgeCases = append(edgeCases, [3][]byte{publicKeyShort, hashValid, derSignatureValid}) + + // b. Incorrect prefix + publicKeyBadPrefix := make([]byte, 65) + publicKeyBadPrefix[0] = 0x05 // Invalid prefix + copy(publicKeyBadPrefix[1:], bytes.Repeat([]byte{0x01}, 64)) + edgeCases = append(edgeCases, [3][]byte{publicKeyBadPrefix, hashValid, derSignatureValid}) + + // c. Coordinates not on curve (invalid X, Y) + publicKeyInvalidXY := make([]byte, 65) + publicKeyInvalidXY[0] = 0x04 + // Set X and Y to values that are not on the curve + copy(publicKeyInvalidXY[1:], bytes.Repeat([]byte{0xFF}, 64)) + edgeCases = append(edgeCases, [3][]byte{publicKeyInvalidXY, hashValid, derSignatureValid}) + + // 2. Malformed Signatures + + // a. Invalid DER encoding (truncated) + derSignatureInvalidDER := []byte{0x30, 0x00} // Incomplete DER + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureInvalidDER}) + + // b. R too long (33 bytes with leading zero) + derSignatureRTooLong := []byte{ + 0x30, 0x46, // SEQUENCE, length 70 + 0x02, 0x21, // INTEGER, length 33 + 0x00, // Leading zero + } + derSignatureRTooLong = append(derSignatureRTooLong, bytes.Repeat([]byte{0x01}, 32)...) // R + derSignatureRTooLong = append(derSignatureRTooLong, 0x02, 0x20) // S INTEGER, length 32 + derSignatureRTooLong = append(derSignatureRTooLong, bytes.Repeat([]byte{0x02}, 32)...) // S + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureRTooLong}) + + // c. S too short (31 bytes) + derSignatureSTooShort := []byte{ + 0x30, 0x44, // SEQUENCE, length 68 + 0x02, 0x20, // INTEGER, length 32 + } + derSignatureSTooShort = append(derSignatureSTooShort, bytes.Repeat([]byte{0x03}, 32)...) // R + derSignatureSTooShort = append(derSignatureSTooShort, 0x02, 0x1F) // S INTEGER, length 31 + derSignatureSTooShort = append(derSignatureSTooShort, bytes.Repeat([]byte{0x04}, 31)...) // S + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureSTooShort}) + + // 3. Invalid Hashes + + // a. Incorrect hash length (too short) + hashTooShort := make([]byte, 16) + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashTooShort, derSignatureValid}) + + // b. Empty hash + hashEmpty := []byte{} + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashEmpty, derSignatureValid}) + + // 4. Random Data + + // a. Completely random bytes + randomPublicKey := bytes.Repeat([]byte{0xAB}, 65) + randomHash := bytes.Repeat([]byte{0xCD}, 32) + randomSignature := bytes.Repeat([]byte{0xEF}, 70) + edgeCases = append(edgeCases, [3][]byte{randomPublicKey, randomHash, randomSignature}) + + // 5. Boundary Conditions + + // a. R equals zero + derSignatureRZero, _ := asn1.Marshal(ecdsaSignature{R: big.NewInt(0), S: big.NewInt(1)}) + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureRZero}) + + // b. S equals N (curve order) + secp256k1N := crypto.S256().Params().N + derSignatureSEqualsN, _ := asn1.Marshal(ecdsaSignature{R: big.NewInt(1), S: new(big.Int).Set(secp256k1N)}) + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureSEqualsN}) + + // c. S just above N/2 + secp256k1HalfN := new(big.Int).Div(crypto.S256().Params().N, big.NewInt(2)) + sAboveHalfN := new(big.Int).Add(secp256k1HalfN, big.NewInt(1)) + derSignatureSAboveHalfN, _ := asn1.Marshal(ecdsaSignature{R: big.NewInt(1), S: sAboveHalfN}) + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureSAboveHalfN}) + + // d. S just below N/2 + sBelowHalfN := new(big.Int).Sub(secp256k1HalfN, big.NewInt(1)) + derSignatureSBelowHalfN, _ := asn1.Marshal(ecdsaSignature{R: big.NewInt(1), S: sBelowHalfN}) + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureSBelowHalfN}) + + // 6. Extra Data + + // a. Extra bytes appended to the signature + derSignatureExtra := append(derSignatureValid, 0x00, 0x01, 0x02) + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureExtra}) + + // b. Missing bytes in the signature + if len(derSignatureValid) > 2 { + derSignatureMissing := derSignatureValid[:len(derSignatureValid)-2] + edgeCases = append(edgeCases, [3][]byte{publicKeyValid, hashValid, derSignatureMissing}) + } + + return edgeCases +} + +// FuzzParseSignatureKMS tests the ParseSignatureKMS function with various inputs, including edge cases. +func FuzzParseSignatureKMS(f *testing.F) { + // Generate multiple valid seed inputs + for i := 0; i < 5; i++ { + publicKey, hash, derSignature, err := generateValidSignature() + if err != nil { + f.Fatalf("Failed to generate valid signature: %v", err) + } + publicKeyBytes := crypto.FromECDSAPub(publicKey) + f.Add(publicKeyBytes, hash, derSignature) + } + + // Incorporate edge cases into the fuzz corpus + edgeCases := defineEdgeCases() + for _, ec := range edgeCases { + f.Add(ec[0], ec[1], ec[2]) + } + + // Define the fuzzing function + f.Fuzz(func(t *testing.T, publicKeyBytes []byte, hashBytes []byte, derSignatureBytes []byte) { + // Skip iteration if publicKeyBytes is not the correct length + if len(publicKeyBytes) != 65 { + return + } + + // Attempt to parse the public key + pubKey, err := ParsePublicKeyKMS(publicKeyBytes) + if err != nil { + // Invalid public key; acceptable for fuzzing + return + } + + // Attempt to parse the signature + signature, err := ParseSignatureKMS(pubKey, hashBytes, derSignatureBytes) + if err != nil { + // Parsing failed; acceptable for fuzzing + return + } + + // Validate that the signature is exactly 65 bytes + if len(signature) != 65 { + t.Errorf("Expected signature length 65 bytes, got %d bytes", len(signature)) + } + + // if the code made it this far, then the pubkey and signature are valid so recovery must work. + recoveredPubBytes, err := crypto.Ecrecover(hashBytes, signature) + if err != nil { + t.Errorf("Ecrecover failed: %v", err) + return + } + + // Compare the recovered public key with the original + if !bytes.Equal(recoveredPubBytes, publicKeyBytes) { + // Attempt with the possible V values + signatureCheck := false + if signature[64] == 27 { + recoveredPubBytes, err = crypto.Ecrecover(hashBytes, signature) + if err != nil { + t.Errorf("Ecrecover failed with V=27: %v", err) + } else if !bytes.Equal(recoveredPubBytes, publicKeyBytes) { + t.Errorf("Recovered public key does not match original") + } else { + signatureCheck = true + } + } + + if !signatureCheck { + signature[64] = 28 + recoveredPubBytes, err = crypto.Ecrecover(hashBytes, signature) + if err != nil { + t.Errorf("Ecrecover failed with V=28: %v", err) + return + } + + if !bytes.Equal(recoveredPubBytes, publicKeyBytes) { + t.Errorf("Recovered public key does not match original") + return + } + } + } + }) +} From 260bf7925209f4dceff59046a9f4e4d53d79ecf7 Mon Sep 17 00:00:00 2001 From: anupsv Date: Wed, 18 Dec 2024 19:02:44 +0530 Subject: [PATCH 38/50] Update Makefile for fuzz tests --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 033741a38d..f092560872 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,9 @@ dataapi-build: unit-tests: ./test.sh +fuzz-tests: + go test --fuzz=FuzzParseSignatureKMS -fuzztime=5m ./common + integration-tests-churner: go test -v ./churner/tests From dcd3e06f279bf2e2b86951dc4d909582ae153e86 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 18 Dec 2024 11:40:55 -0600 Subject: [PATCH 39/50] Disable request signing if KMS key name is not provided. Signed-off-by: Cody Littley --- api/clients/v2/node_client.go | 12 +++++++----- disperser/cmd/controller/main.go | 17 ++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/api/clients/v2/node_client.go b/api/clients/v2/node_client.go index 4d2b7097f7..5880022af8 100644 --- a/api/clients/v2/node_client.go +++ b/api/clients/v2/node_client.go @@ -73,12 +73,14 @@ func (c *nodeClient) StoreChunks(ctx context.Context, batch *corev2.Batch) (*cor DisperserID: 0, // this will need to be updated dispersers are decentralized } - // 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) + 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 } - request.Signature = signature // Call the gRPC method to store chunks response, err := c.dispersalClient.StoreChunks(ctx, request) diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index 7fa6056226..aa0ec068d6 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -150,13 +150,16 @@ func RunController(ctx *cli.Context) error { } } - requestSigner, err := clients.NewRequestSigner( - context.Background(), - config.AwsClientConfig.Region, - config.AwsClientConfig.EndpointURL, - config.DisperserSigningKeyName) - if err != nil { - return fmt.Errorf("failed to create request signer: %v", err) + var requestSigner clients.RequestSigner + if config.DisperserSigningKeyName != "" { + requestSigner, err = clients.NewRequestSigner( + context.Background(), + config.AwsClientConfig.Region, + config.AwsClientConfig.EndpointURL, + config.DisperserSigningKeyName) + if err != nil { + return fmt.Errorf("failed to create request signer: %v", err) + } } nodeClientManager, err := controller.NewNodeClientManager(config.NodeClientCacheSize, requestSigner, logger) From f6b008ff39f458989dcbddfafb1b0382fa3d055d Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 18 Dec 2024 13:25:35 -0600 Subject: [PATCH 40/50] Tie into bindings. Signed-off-by: Cody Littley --- core/eth/reader.go | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/core/eth/reader.go b/core/eth/reader.go index fb1da3adc7..3360e6b3f4 100644 --- a/core/eth/reader.go +++ b/core/eth/reader.go @@ -3,6 +3,7 @@ package eth import ( "context" "crypto/ecdsa" + "fmt" "math/big" "strings" @@ -10,6 +11,7 @@ import ( avsdir "github.com/Layr-Labs/eigenda/contracts/bindings/AVSDirectory" blsapkreg "github.com/Layr-Labs/eigenda/contracts/bindings/BLSApkRegistry" delegationmgr "github.com/Layr-Labs/eigenda/contracts/bindings/DelegationManager" + disperserreg "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDADisperserRegistry" relayreg "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDARelayRegistry" eigendasrvmg "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" thresholdreg "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAThresholdRegistry" @@ -45,6 +47,7 @@ type ContractBindings struct { PaymentVault *paymentvault.ContractPaymentVault RelayRegistry *relayreg.ContractEigenDARelayRegistry ThresholdRegistry *thresholdreg.ContractEigenDAThresholdRegistry + DisperserRegistry *disperserreg.ContractEigenDADisperserRegistry } type Reader struct { @@ -225,6 +228,21 @@ func (t *Reader) updateContractBindings(blsOperatorStateRetrieverAddr, eigenDASe } + var contractEigenDADisperserRegistry *disperserreg.ContractEigenDADisperserRegistry + disperserRegistryAddr, err := contractEigenDAServiceManager.EigenDADisperserRegistry(&bind.CallOpts{}) + if err != nil { + t.logger.Error("Failed to fetch EigenDADisperserRegistry address", "err", err) + // TODO(cody-littley): return err when the contract is deployed + // return err + } else { + contractEigenDADisperserRegistry, err = + disperserreg.NewContractEigenDADisperserRegistry(disperserRegistryAddr, t.ethClient) + if err != nil { + t.logger.Error("Failed to fetch EigenDADisperserRegistry contract", "err", err) + return err + } + } + t.bindings = &ContractBindings{ ServiceManagerAddr: eigenDAServiceManagerAddr, RegCoordinatorAddr: registryCoordinatorAddr, @@ -241,6 +259,7 @@ func (t *Reader) updateContractBindings(blsOperatorStateRetrieverAddr, eigenDASe RelayRegistry: contractRelayRegistry, PaymentVault: contractPaymentVault, ThresholdRegistry: contractThresholdRegistry, + DisperserRegistry: contractEigenDADisperserRegistry, } return nil } @@ -918,8 +937,20 @@ func (t *Reader) GetRelayURLs(ctx context.Context) (map[uint32]string, error) { } func (t *Reader) GetDisperserAddress(ctx context.Context, disperserID uint32) (gethcommon.Address, error) { - // TODO(cody-littley/arch): this is just a place holder until we register dispersers on chain - bytes := make([]byte, gethcommon.AddressLength) - address := gethcommon.BytesToAddress(bytes) + registry := t.bindings.DisperserRegistry + if registry == nil { + return gethcommon.Address{}, errors.New("disperser registry not deployed") + } + + address, err := registry.DisperserKeyToAddress( + &bind.CallOpts{ + Context: ctx, + }, + disperserID) + + if err != nil { + return gethcommon.Address{}, fmt.Errorf("failed to get disperser address: %w", err) + } + return address, nil } From 1493ded335816b6be99793d24f8492904027f1cf Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 18 Dec 2024 13:43:50 -0600 Subject: [PATCH 41/50] Incremental progress. Signed-off-by: Cody Littley --- core/eth/reader.go | 4 ++-- node/grpc/server_test.go | 2 +- test/integration_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/eth/reader.go b/core/eth/reader.go index 3360e6b3f4..122965264c 100644 --- a/core/eth/reader.go +++ b/core/eth/reader.go @@ -915,10 +915,10 @@ func (t *Reader) GetRelayURLs(ctx context.Context) (map[uint32]string, error) { } res := make(map[uint32]string) - for relayKey := uint32(0); relayKey < uint32(numRelays); relayKey++ { + for relayKey := uint32(0); relayKey < numRelays; relayKey++ { url, err := t.bindings.RelayRegistry.RelayKeyToUrl(&bind.CallOpts{ Context: ctx, - }, uint32(relayKey)) + }, relayKey) if err != nil && strings.Contains(err.Error(), "execution reverted") { break diff --git a/node/grpc/server_test.go b/node/grpc/server_test.go index de996504c6..b608e91c2b 100644 --- a/node/grpc/server_test.go +++ b/node/grpc/server_test.go @@ -85,7 +85,7 @@ func makeConfig(t *testing.T) *node.Config { ID: opID, NumBatchValidators: runtime.GOMAXPROCS(0), EnableV2: false, - DisableDispersalAuthentication: true, // TODO enable + DisableDispersalAuthentication: true, } } diff --git a/test/integration_test.go b/test/integration_test.go index 3f747573be..dfedb6b8e5 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -364,7 +364,7 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging PrivateBls: string(op.KeyPair.GetPubKeyG1().Serialize()), ID: id, QuorumIDList: registeredQuorums, - DisableDispersalAuthentication: true, // TODO enable + DisableDispersalAuthentication: false, // TODO things break when this is enabled } // creating a new instance of encoder instead of sharing enc because enc is not thread safe From 52f235420676fb66f421ac776cc959d74a85ff13 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 19 Dec 2024 09:09:38 -0600 Subject: [PATCH 42/50] Add debug code. Signed-off-by: Cody Littley --- api/clients/v2/request_signer_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/clients/v2/request_signer_test.go b/api/clients/v2/request_signer_test.go index 98452f4570..2043b87e94 100644 --- a/api/clients/v2/request_signer_test.go +++ b/api/clients/v2/request_signer_test.go @@ -47,7 +47,13 @@ func setup(t *testing.T) { func changeDirectory(path string) { err := os.Chdir(path) if err != nil { - log.Panicf("Failed to change directories. Error: %s", err) + + 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() From 7c5cc9363edc1db7c8abf661f0891054f6c2597d Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 19 Dec 2024 09:57:57 -0600 Subject: [PATCH 43/50] Fix unit test. Signed-off-by: Cody Littley --- api/clients/v2/request_signer_test.go | 2 +- node/cmd/main.go | 12 ++++++- node/grpc/server_v2.go | 12 +------ node/grpc/server_v2_test.go | 7 ++-- test/integration_test.go | 46 +++++++++++++++------------ 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/api/clients/v2/request_signer_test.go b/api/clients/v2/request_signer_test.go index 2043b87e94..153ad7eff4 100644 --- a/api/clients/v2/request_signer_test.go +++ b/api/clients/v2/request_signer_test.go @@ -34,7 +34,7 @@ func setup(t *testing.T) { deployLocalStack := !(os.Getenv("DEPLOY_LOCALSTACK") == "false") _, b, _, _ := runtime.Caller(0) - rootPath := filepath.Join(filepath.Dir(b), "../..") + rootPath := filepath.Join(filepath.Dir(b), "../../..") changeDirectory(filepath.Join(rootPath, "inabox")) if deployLocalStack { diff --git a/node/cmd/main.go b/node/cmd/main.go index a69b3b724e..f4b696fd73 100644 --- a/node/cmd/main.go +++ b/node/cmd/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/Layr-Labs/eigenda/common/geth" + coreeth "github.com/Layr-Labs/eigenda/core/eth" rpccalls "github.com/Layr-Labs/eigensdk-go/metrics/collectors/rpc_calls" "log" "os" @@ -80,6 +81,15 @@ func NodeMain(ctx *cli.Context) error { return fmt.Errorf("cannot create chain.Client: %w", err) } + reader, err := coreeth.NewReader( + logger, + client, + config.BLSOperatorStateRetrieverAddr, + config.EigenDAServiceManagerAddr) + if err != nil { + return fmt.Errorf("cannot create eth.Reader: %w", err) + } + // Create the node. node, err := node.NewNode(reg, config, pubIPProvider, client, logger) if err != nil { @@ -97,7 +107,7 @@ func NodeMain(ctx *cli.Context) error { // TODO(cody-littley): the metrics server is currently started by eigenmetrics, which is in another repo. // When we fully remove v1 support, we need to start the metrics server inside the v2 metrics code. server := nodegrpc.NewServer(config, node, logger, ratelimiter) - serverV2, err := nodegrpc.NewServerV2(context.Background(), config, node, logger, ratelimiter, reg, client) + serverV2, err := nodegrpc.NewServerV2(context.Background(), config, node, logger, ratelimiter, reg, reader) if err != nil { return fmt.Errorf("failed to create server v2: %v", err) } diff --git a/node/grpc/server_v2.go b/node/grpc/server_v2.go index 05c7a42a01..b75f018e1b 100644 --- a/node/grpc/server_v2.go +++ b/node/grpc/server_v2.go @@ -10,7 +10,6 @@ import ( "github.com/Layr-Labs/eigenda/common" "github.com/Layr-Labs/eigenda/common/kvstore" "github.com/Layr-Labs/eigenda/core" - coreeth "github.com/Layr-Labs/eigenda/core/eth" corev2 "github.com/Layr-Labs/eigenda/core/v2" "github.com/Layr-Labs/eigenda/node" "github.com/Layr-Labs/eigenda/node/auth" @@ -43,7 +42,7 @@ func NewServerV2( logger logging.Logger, ratelimiter common.RateLimiter, registry *prometheus.Registry, - client common.EthClient) (*ServerV2, error) { + reader core.Reader) (*ServerV2, error) { metrics, err := NewV2Metrics(logger, registry) if err != nil { @@ -52,15 +51,6 @@ func NewServerV2( var authenticator auth.RequestAuthenticator if !config.DisableDispersalAuthentication { - reader, err := coreeth.NewReader( - logger, - client, - config.BLSOperatorStateRetrieverAddr, - config.EigenDAServiceManagerAddr) - if err != nil { - return nil, fmt.Errorf("cannot create eth.Reader: %w", err) - } - authenticator, err = auth.NewRequestAuthenticator( ctx, reader, diff --git a/node/grpc/server_v2_test.go b/node/grpc/server_v2_test.go index 15b587120d..05bcd12db3 100644 --- a/node/grpc/server_v2_test.go +++ b/node/grpc/server_v2_test.go @@ -3,6 +3,7 @@ package grpc_test import ( "context" "errors" + coreeth "github.com/Layr-Labs/eigenda/core/eth" "os" "sync/atomic" "testing" @@ -84,7 +85,9 @@ func newTestComponents(t *testing.T, config *node.Config) *testComponents { node.BlobVersionParams.Store(v2.NewBlobVersionParameterMap(blobParamsMap)) // The eth client is only utilized for StoreChunks validation, which is disabled in these tests - var client common.EthClient + // TODO enable auth for these tests + var reader *coreeth.Reader + server, err := grpc.NewServerV2( context.Background(), config, @@ -92,7 +95,7 @@ func newTestComponents(t *testing.T, config *node.Config) *testComponents { logger, ratelimiter, prometheus.NewRegistry(), - client) + reader) require.NoError(t, err) return &testComponents{ diff --git a/test/integration_test.go b/test/integration_test.go index dfedb6b8e5..6c93cd3821 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -31,6 +31,7 @@ import ( clientsmock "github.com/Layr-Labs/eigenda/api/clients/mock" commonaws "github.com/Layr-Labs/eigenda/common/aws" "github.com/Layr-Labs/eigenda/core/meterer" + coremock "github.com/Layr-Labs/eigenda/core/mock" "github.com/Layr-Labs/eigenda/disperser/apiserver" dispatcher "github.com/Layr-Labs/eigenda/disperser/batcher/grpc" "github.com/Layr-Labs/eigenda/disperser/encoder" @@ -45,7 +46,6 @@ import ( "github.com/Layr-Labs/eigenda/common" commonmock "github.com/Layr-Labs/eigenda/common/mock" "github.com/Layr-Labs/eigenda/core" - coremock "github.com/Layr-Labs/eigenda/core/mock" "github.com/Layr-Labs/eigenda/disperser" "github.com/Layr-Labs/eigenda/disperser/batcher" batchermock "github.com/Layr-Labs/eigenda/disperser/batcher/mock" @@ -351,20 +351,21 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging } config := &node.Config{ - Hostname: op.Host, - DispersalPort: op.DispersalPort, - RetrievalPort: op.RetrievalPort, - InternalRetrievalPort: op.RetrievalPort, - InternalDispersalPort: op.DispersalPort, - EnableMetrics: false, - Timeout: 10, - ExpirationPollIntervalSec: 10, - DbPath: dbPath, - LogPath: logPath, - PrivateBls: string(op.KeyPair.GetPubKeyG1().Serialize()), - ID: id, - QuorumIDList: registeredQuorums, - DisableDispersalAuthentication: false, // TODO things break when this is enabled + Hostname: op.Host, + DispersalPort: op.DispersalPort, + RetrievalPort: op.RetrievalPort, + InternalRetrievalPort: op.RetrievalPort, + InternalDispersalPort: op.DispersalPort, + EnableMetrics: false, + Timeout: 10, + ExpirationPollIntervalSec: 10, + DbPath: dbPath, + LogPath: logPath, + PrivateBls: string(op.KeyPair.GetPubKeyG1().Serialize()), + ID: id, + QuorumIDList: registeredQuorums, + DispersalAuthenticationKeyCacheSize: 1024, + DisableDispersalAuthentication: false, } // creating a new instance of encoder instead of sharing enc because enc is not thread safe @@ -418,20 +419,23 @@ func mustMakeOperators(t *testing.T, cst *coremock.ChainDataMock, logger logging OperatorSocketsFilterer: mockOperatorSocketsFilterer, } - ratelimiter := &commonmock.NoopRatelimiter{} + rateLimiter := &commonmock.NoopRatelimiter{} - // TODO this needs to be non-null once we enable dispersal authentication - var client common.EthClient + // TODO(cody-littley): Once we switch this test to use the v2 disperser, we will need to properly set up + // the disperser's public/private keys for signing StoreChunks() requests + disperserAddress := gethcommon.Address{} + reader := &coremock.MockWriter{} + reader.On("GetDisperserAddress", uint32(0)).Return(disperserAddress, nil) - serverV1 := nodegrpc.NewServer(config, n, logger, ratelimiter) + serverV1 := nodegrpc.NewServer(config, n, logger, rateLimiter) serverV2, err := nodegrpc.NewServerV2( context.Background(), config, n, logger, - ratelimiter, + rateLimiter, prometheus.NewRegistry(), - client) + reader) require.NoError(t, err) ops[id] = TestOperator{ From c83db79c304f51f2b0dc6d678a3ae25ee5d73a02 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 19 Dec 2024 12:15:03 -0600 Subject: [PATCH 44/50] Disable request signing for inabox e2e test. Signed-off-by: Cody Littley --- disperser/cmd/controller/config.go | 24 ++++---- disperser/cmd/controller/flags/flags.go | 8 +-- disperser/cmd/controller/main.go | 4 +- inabox/deploy/config.go | 82 +++++++++++++------------ inabox/deploy/env_vars.go | 24 +++++++- 5 files changed, 82 insertions(+), 60 deletions(-) diff --git a/disperser/cmd/controller/config.go b/disperser/cmd/controller/config.go index f833701a9a..6c9cbe3bc9 100644 --- a/disperser/cmd/controller/config.go +++ b/disperser/cmd/controller/config.go @@ -25,13 +25,13 @@ type Config struct { DynamoDBTableName string - EthClientConfig geth.EthClientConfig - AwsClientConfig aws.ClientConfig - DisperserSigningKeyName string - LoggerConfig common.LoggerConfig - IndexerConfig indexer.Config - ChainStateConfig thegraph.Config - UseGraph bool + EthClientConfig geth.EthClientConfig + AwsClientConfig aws.ClientConfig + DisperserKMSKeyID string + LoggerConfig common.LoggerConfig + IndexerConfig indexer.Config + ChainStateConfig thegraph.Config + UseGraph bool BLSOperatorStateRetrieverAddr string EigenDAServiceManagerAddr string @@ -61,11 +61,11 @@ func NewConfig(ctx *cli.Context) (Config, error) { relays[i] = corev2.RelayKey(relay) } config := Config{ - DynamoDBTableName: ctx.GlobalString(flags.DynamoDBTableNameFlag.Name), - EthClientConfig: ethClientConfig, - AwsClientConfig: aws.ReadClientConfig(ctx, flags.FlagPrefix), - DisperserSigningKeyName: ctx.GlobalString(flags.DisperserSigningKeyNameFlag.Name), - LoggerConfig: *loggerConfig, + DynamoDBTableName: ctx.GlobalString(flags.DynamoDBTableNameFlag.Name), + EthClientConfig: ethClientConfig, + AwsClientConfig: aws.ReadClientConfig(ctx, flags.FlagPrefix), + DisperserKMSKeyID: ctx.GlobalString(flags.DisperserKMSKeyIDFlag.Name), + LoggerConfig: *loggerConfig, EncodingManagerConfig: controller.EncodingManagerConfig{ PullInterval: ctx.GlobalDuration(flags.EncodingPullIntervalFlag.Name), EncodingRequestTimeout: ctx.GlobalDuration(flags.EncodingRequestTimeoutFlag.Name), diff --git a/disperser/cmd/controller/flags/flags.go b/disperser/cmd/controller/flags/flags.go index ac6d5db412..45d5ffac3e 100644 --- a/disperser/cmd/controller/flags/flags.go +++ b/disperser/cmd/controller/flags/flags.go @@ -178,11 +178,11 @@ var ( EnvVar: common.PrefixEnvVar(envVarPrefix, "METRICS_PORT"), Value: 9101, } - DisperserSigningKeyNameFlag = cli.StringFlag{ - Name: common.PrefixFlag(FlagPrefix, "disperser-signing-key-name"), + DisperserKMSKeyIDFlag = cli.StringFlag{ + Name: common.PrefixFlag(FlagPrefix, "disperser-kms-key-id"), Usage: "Name of the key used to sign disperser requests (key must be stored in AWS KMS under this name)", Required: true, - EnvVar: common.PrefixEnvVar(envVarPrefix, "DISPERSER_SIGNING_KEY_NAME"), + EnvVar: common.PrefixEnvVar(envVarPrefix, "DISPERSER_KMS_KEY_ID"), } ) @@ -198,7 +198,7 @@ var requiredFlags = []cli.Flag{ DispatcherPullIntervalFlag, NodeRequestTimeoutFlag, NumConnectionsToNodesFlag, - DisperserSigningKeyNameFlag, + DisperserKMSKeyIDFlag, } var optionalFlags = []cli.Flag{ diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index aa0ec068d6..1d792091aa 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -151,12 +151,12 @@ func RunController(ctx *cli.Context) error { } var requestSigner clients.RequestSigner - if config.DisperserSigningKeyName != "" { + if config.DisperserKMSKeyID != "" { requestSigner, err = clients.NewRequestSigner( context.Background(), config.AwsClientConfig.Region, config.AwsClientConfig.EndpointURL, - config.DisperserSigningKeyName) + config.DisperserKMSKeyID) if err != nil { return fmt.Errorf("failed to create request signer: %v", err) } diff --git a/inabox/deploy/config.go b/inabox/deploy/config.go index d4f7fe88e5..2e5654a1c6 100644 --- a/inabox/deploy/config.go +++ b/inabox/deploy/config.go @@ -343,6 +343,7 @@ func (env *Config) generateControllerVars(ind int, graphUrl string) ControllerVa CONTROLLER_AWS_ENDPOINT_URL: "", CONTROLLER_ENCODER_ADDRESS: "0.0.0.0:34001", CONTROLLER_FINALIZATION_BLOCK_DELAY: "0", + CONTROLLER_DISPERSER_KMS_KEY_ID: "", } env.applyDefaults(&v, "CONTROLLER", "controller", ind) @@ -391,46 +392,47 @@ func (env *Config) generateOperatorVars(ind int, name, key, churnerUrl, logPath, ecdsaPassword := env.Pks.EcdsaMap[name].Password v := OperatorVars{ - NODE_HOSTNAME: "", - NODE_DISPERSAL_PORT: dispersalPort, - NODE_RETRIEVAL_PORT: retrievalPort, - NODE_INTERNAL_DISPERSAL_PORT: dispersalPort, - NODE_INTERNAL_RETRIEVAL_PORT: retrievalPort, - NODE_ENABLE_METRICS: "true", - NODE_METRICS_PORT: metricsPort, - NODE_ENABLE_NODE_API: "true", - NODE_API_PORT: nodeApiPort, - NODE_TIMEOUT: "10s", - NODE_QUORUM_ID_LIST: "0,1", - NODE_DB_PATH: dbPath, - NODE_ENABLE_TEST_MODE: "false", // using encrypted key in inabox - NODE_TEST_PRIVATE_BLS: blsKey, - NODE_BLS_KEY_FILE: blsKeyFile, - NODE_ECDSA_KEY_FILE: ecdsaKeyFile, - NODE_BLS_KEY_PASSWORD: blsPassword, - NODE_ECDSA_KEY_PASSWORD: ecdsaPassword, - NODE_BLS_OPERATOR_STATE_RETRIVER: env.EigenDA.OperatorStateRetreiver, - NODE_EIGENDA_SERVICE_MANAGER: env.EigenDA.ServiceManager, - NODE_REGISTER_AT_NODE_START: "true", - NODE_CHURNER_URL: churnerUrl, - NODE_CHURNER_USE_SECURE_GRPC: "false", - NODE_EXPIRATION_POLL_INTERVAL: "10", - NODE_G1_PATH: "", - NODE_G2_PATH: "", - NODE_G2_POWER_OF_2_PATH: "", - NODE_CACHE_PATH: "", - NODE_SRS_ORDER: "", - NODE_SRS_LOAD: "", - NODE_NUM_WORKERS: fmt.Sprint(runtime.GOMAXPROCS(0)), - NODE_VERBOSE: "true", - NODE_CHAIN_RPC: "", - NODE_PRIVATE_KEY: key[2:], - NODE_NUM_BATCH_VALIDATORS: "128", - NODE_PUBLIC_IP_PROVIDER: "mockip", - NODE_PUBLIC_IP_CHECK_INTERVAL: "10s", - NODE_NUM_CONFIRMATIONS: "0", - NODE_ONCHAIN_METRICS_INTERVAL: "-1", - NODE_ENABLE_V2: "true", + NODE_HOSTNAME: "", + NODE_DISPERSAL_PORT: dispersalPort, + NODE_RETRIEVAL_PORT: retrievalPort, + NODE_INTERNAL_DISPERSAL_PORT: dispersalPort, + NODE_INTERNAL_RETRIEVAL_PORT: retrievalPort, + NODE_ENABLE_METRICS: "true", + NODE_METRICS_PORT: metricsPort, + NODE_ENABLE_NODE_API: "true", + NODE_API_PORT: nodeApiPort, + NODE_TIMEOUT: "10s", + NODE_QUORUM_ID_LIST: "0,1", + NODE_DB_PATH: dbPath, + NODE_ENABLE_TEST_MODE: "false", // using encrypted key in inabox + NODE_TEST_PRIVATE_BLS: blsKey, + NODE_BLS_KEY_FILE: blsKeyFile, + NODE_ECDSA_KEY_FILE: ecdsaKeyFile, + NODE_BLS_KEY_PASSWORD: blsPassword, + NODE_ECDSA_KEY_PASSWORD: ecdsaPassword, + NODE_BLS_OPERATOR_STATE_RETRIVER: env.EigenDA.OperatorStateRetreiver, + NODE_EIGENDA_SERVICE_MANAGER: env.EigenDA.ServiceManager, + NODE_REGISTER_AT_NODE_START: "true", + NODE_CHURNER_URL: churnerUrl, + NODE_CHURNER_USE_SECURE_GRPC: "false", + NODE_EXPIRATION_POLL_INTERVAL: "10", + NODE_G1_PATH: "", + NODE_G2_PATH: "", + NODE_G2_POWER_OF_2_PATH: "", + NODE_CACHE_PATH: "", + NODE_SRS_ORDER: "", + NODE_SRS_LOAD: "", + NODE_NUM_WORKERS: fmt.Sprint(runtime.GOMAXPROCS(0)), + NODE_VERBOSE: "true", + NODE_CHAIN_RPC: "", + NODE_PRIVATE_KEY: key[2:], + NODE_NUM_BATCH_VALIDATORS: "128", + NODE_PUBLIC_IP_PROVIDER: "mockip", + NODE_PUBLIC_IP_CHECK_INTERVAL: "10s", + NODE_NUM_CONFIRMATIONS: "0", + NODE_ONCHAIN_METRICS_INTERVAL: "-1", + NODE_ENABLE_V2: "true", + NODE_DISABLE_DISPERSAL_AUTHENTICATION: "true", } env.applyDefaults(&v, "NODE", "opr", ind) diff --git a/inabox/deploy/env_vars.go b/inabox/deploy/env_vars.go index e55984eb01..9e6de51719 100644 --- a/inabox/deploy/env_vars.go +++ b/inabox/deploy/env_vars.go @@ -271,6 +271,10 @@ type EncoderVars struct { DISPERSER_ENCODER_S3_BUCKET_NAME string + DISPERSER_ENCODER_GPU_ENABLE string + + DISPERSER_ENCODER_BACKEND string + DISPERSER_ENCODER_PREVENT_REENCODING string DISPERSER_ENCODER_PPROF_HTTP_PORT string @@ -416,10 +420,20 @@ type OperatorVars struct { NODE_ONCHAIN_STATE_REFRESH_INTERVAL string + NODE_CHUNK_DOWNLOAD_TIMEOUT string + NODE_PPROF_HTTP_PORT string NODE_ENABLE_PPROF string + NODE_DISABLE_DISPERSAL_AUTHENTICATION string + + NODE_DISPERSAL_AUTHENTICATION_KEY_CACHE_SIZE string + + NODE_DISPERSER_KEY_TIMEOUT string + + NODE_DISPERSAL_AUTHENTICATION_TIMEOUT string + NODE_G1_PATH string NODE_G2_PATH string @@ -611,6 +625,8 @@ type ControllerVars struct { CONTROLLER_NUM_CONNECTIONS_TO_NODES string + CONTROLLER_DISPERSER_KMS_KEY_ID string + CONTROLLER_INDEXER_DATA_DIR string CONTROLLER_ENCODING_REQUEST_TIMEOUT string @@ -637,6 +653,8 @@ type ControllerVars struct { CONTROLLER_MAX_BATCH_SIZE string + CONTROLLER_METRICS_PORT string + CONTROLLER_CHAIN_RPC string CONTROLLER_CHAIN_RPC_FALLBACK string @@ -716,6 +734,8 @@ type RelayVars struct { RELAY_CHUNK_MAX_CONCURRENCY string + RELAY_MAX_KEYS_PER_GET_CHUNKS_REQUEST string + RELAY_MAX_GET_BLOB_OPS_PER_SECOND string RELAY_GET_BLOB_OPS_BURSTINESS string @@ -746,8 +766,6 @@ type RelayVars struct { RELAY_MAX_CONCURRENT_GET_CHUNK_OPS_CLIENT string - RELAY_INDEXER_PULL_INTERVAL string - RELAY_AUTHENTICATION_KEY_CACHE_SIZE string RELAY_AUTHENTICATION_TIMEOUT string @@ -768,6 +786,8 @@ type RelayVars struct { RELAY_ONCHAIN_STATE_REFRESH_INTERVAL string + RELAY_METRICS_PORT string + RELAY_LOG_LEVEL string RELAY_LOG_PATH string From e5cb69c0af0b2067552527b374acd1e7a8f6e4b4 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 19 Dec 2024 12:51:26 -0600 Subject: [PATCH 45/50] Added flag for disabling signing. Signed-off-by: Cody Littley --- disperser/cmd/controller/config.go | 26 ++++++++------- disperser/cmd/controller/flags/flags.go | 11 +++++-- disperser/cmd/controller/main.go | 2 +- inabox/deploy/config.go | 42 ++++++++++++------------- inabox/deploy/env_vars.go | 6 ++-- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/disperser/cmd/controller/config.go b/disperser/cmd/controller/config.go index 6c9cbe3bc9..b07b6d60ee 100644 --- a/disperser/cmd/controller/config.go +++ b/disperser/cmd/controller/config.go @@ -25,13 +25,14 @@ type Config struct { DynamoDBTableName string - EthClientConfig geth.EthClientConfig - AwsClientConfig aws.ClientConfig - DisperserKMSKeyID string - LoggerConfig common.LoggerConfig - IndexerConfig indexer.Config - ChainStateConfig thegraph.Config - UseGraph bool + EthClientConfig geth.EthClientConfig + AwsClientConfig aws.ClientConfig + DisperserStoreChunksSigningDisabled bool + DisperserKMSKeyID string + LoggerConfig common.LoggerConfig + IndexerConfig indexer.Config + ChainStateConfig thegraph.Config + UseGraph bool BLSOperatorStateRetrieverAddr string EigenDAServiceManagerAddr string @@ -61,11 +62,12 @@ func NewConfig(ctx *cli.Context) (Config, error) { relays[i] = corev2.RelayKey(relay) } config := Config{ - DynamoDBTableName: ctx.GlobalString(flags.DynamoDBTableNameFlag.Name), - EthClientConfig: ethClientConfig, - AwsClientConfig: aws.ReadClientConfig(ctx, flags.FlagPrefix), - DisperserKMSKeyID: ctx.GlobalString(flags.DisperserKMSKeyIDFlag.Name), - LoggerConfig: *loggerConfig, + DynamoDBTableName: ctx.GlobalString(flags.DynamoDBTableNameFlag.Name), + EthClientConfig: ethClientConfig, + AwsClientConfig: aws.ReadClientConfig(ctx, flags.FlagPrefix), + DisperserStoreChunksSigningDisabled: ctx.GlobalBool(flags.DisperserStoreChunksSigningDisabledFlag.Name), + DisperserKMSKeyID: ctx.GlobalString(flags.DisperserKMSKeyIDFlag.Name), + LoggerConfig: *loggerConfig, EncodingManagerConfig: controller.EncodingManagerConfig{ PullInterval: ctx.GlobalDuration(flags.EncodingPullIntervalFlag.Name), EncodingRequestTimeout: ctx.GlobalDuration(flags.EncodingRequestTimeoutFlag.Name), diff --git a/disperser/cmd/controller/flags/flags.go b/disperser/cmd/controller/flags/flags.go index 45d5ffac3e..96c7edc8b3 100644 --- a/disperser/cmd/controller/flags/flags.go +++ b/disperser/cmd/controller/flags/flags.go @@ -178,10 +178,16 @@ var ( EnvVar: common.PrefixEnvVar(envVarPrefix, "METRICS_PORT"), Value: 9101, } + DisperserStoreChunksSigningDisabledFlag = cli.BoolFlag{ + Name: common.PrefixFlag(FlagPrefix, "disperser-store-chunks-signing-disabled"), + Usage: "Whether to disable signing of store chunks requests", + Required: false, + EnvVar: common.PrefixEnvVar(envVarPrefix, "DISPERSER_STORE_CHUNKS_SIGNING_DISABLED"), + } DisperserKMSKeyIDFlag = cli.StringFlag{ Name: common.PrefixFlag(FlagPrefix, "disperser-kms-key-id"), Usage: "Name of the key used to sign disperser requests (key must be stored in AWS KMS under this name)", - Required: true, + Required: false, EnvVar: common.PrefixEnvVar(envVarPrefix, "DISPERSER_KMS_KEY_ID"), } ) @@ -198,7 +204,6 @@ var requiredFlags = []cli.Flag{ DispatcherPullIntervalFlag, NodeRequestTimeoutFlag, NumConnectionsToNodesFlag, - DisperserKMSKeyIDFlag, } var optionalFlags = []cli.Flag{ @@ -217,6 +222,8 @@ var optionalFlags = []cli.Flag{ NodeClientCacheNumEntriesFlag, MaxBatchSizeFlag, MetricsPortFlag, + DisperserStoreChunksSigningDisabledFlag, + DisperserKMSKeyIDFlag, } var Flags []cli.Flag diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index 1d792091aa..9ba3aed83e 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -151,7 +151,7 @@ func RunController(ctx *cli.Context) error { } var requestSigner clients.RequestSigner - if config.DisperserKMSKeyID != "" { + if !config.DisperserStoreChunksSigningDisabled { requestSigner, err = clients.NewRequestSigner( context.Background(), config.AwsClientConfig.Region, diff --git a/inabox/deploy/config.go b/inabox/deploy/config.go index 2e5654a1c6..1943ca5ab8 100644 --- a/inabox/deploy/config.go +++ b/inabox/deploy/config.go @@ -323,27 +323,27 @@ func (env *Config) generateEncoderV2Vars(ind int, grpcPort string) EncoderVars { func (env *Config) generateControllerVars(ind int, graphUrl string) ControllerVars { v := ControllerVars{ - CONTROLLER_DYNAMODB_TABLE_NAME: "test-BlobMetadata-v2", - CONTROLLER_BLS_OPERATOR_STATE_RETRIVER: env.EigenDA.OperatorStateRetreiver, - CONTROLLER_EIGENDA_SERVICE_MANAGER: env.EigenDA.ServiceManager, - CONTROLLER_USE_GRAPH: "true", - CONTROLLER_GRAPH_URL: graphUrl, - CONTROLLER_ENCODING_PULL_INTERVAL: "1s", - CONTROLLER_AVAILABLE_RELAYS: "0,1,2,3", - CONTROLLER_DISPATCHER_PULL_INTERVAL: "3s", - CONTROLLER_NODE_REQUEST_TIMEOUT: "5s", - CONTROLLER_NUM_CONNECTIONS_TO_NODES: "10", - CONTROLLER_CHAIN_RPC: "", - CONTROLLER_PRIVATE_KEY: "123", - CONTROLLER_NUM_CONFIRMATIONS: "0", - CONTROLLER_INDEXER_PULL_INTERVAL: "1s", - CONTROLLER_AWS_REGION: "", - CONTROLLER_AWS_ACCESS_KEY_ID: "", - CONTROLLER_AWS_SECRET_ACCESS_KEY: "", - CONTROLLER_AWS_ENDPOINT_URL: "", - CONTROLLER_ENCODER_ADDRESS: "0.0.0.0:34001", - CONTROLLER_FINALIZATION_BLOCK_DELAY: "0", - CONTROLLER_DISPERSER_KMS_KEY_ID: "", + CONTROLLER_DYNAMODB_TABLE_NAME: "test-BlobMetadata-v2", + CONTROLLER_BLS_OPERATOR_STATE_RETRIVER: env.EigenDA.OperatorStateRetreiver, + CONTROLLER_EIGENDA_SERVICE_MANAGER: env.EigenDA.ServiceManager, + CONTROLLER_USE_GRAPH: "true", + CONTROLLER_GRAPH_URL: graphUrl, + CONTROLLER_ENCODING_PULL_INTERVAL: "1s", + CONTROLLER_AVAILABLE_RELAYS: "0,1,2,3", + CONTROLLER_DISPATCHER_PULL_INTERVAL: "3s", + CONTROLLER_NODE_REQUEST_TIMEOUT: "5s", + CONTROLLER_NUM_CONNECTIONS_TO_NODES: "10", + CONTROLLER_CHAIN_RPC: "", + CONTROLLER_PRIVATE_KEY: "123", + CONTROLLER_NUM_CONFIRMATIONS: "0", + CONTROLLER_INDEXER_PULL_INTERVAL: "1s", + CONTROLLER_AWS_REGION: "", + CONTROLLER_AWS_ACCESS_KEY_ID: "", + CONTROLLER_AWS_SECRET_ACCESS_KEY: "", + CONTROLLER_AWS_ENDPOINT_URL: "", + CONTROLLER_ENCODER_ADDRESS: "0.0.0.0:34001", + CONTROLLER_FINALIZATION_BLOCK_DELAY: "0", + CONTROLLER_DISPERSER_STORE_CHUNKS_SIGNING_DISABLED: "true", } env.applyDefaults(&v, "CONTROLLER", "controller", ind) diff --git a/inabox/deploy/env_vars.go b/inabox/deploy/env_vars.go index 9e6de51719..19ee4b9df4 100644 --- a/inabox/deploy/env_vars.go +++ b/inabox/deploy/env_vars.go @@ -625,8 +625,6 @@ type ControllerVars struct { CONTROLLER_NUM_CONNECTIONS_TO_NODES string - CONTROLLER_DISPERSER_KMS_KEY_ID string - CONTROLLER_INDEXER_DATA_DIR string CONTROLLER_ENCODING_REQUEST_TIMEOUT string @@ -655,6 +653,10 @@ type ControllerVars struct { CONTROLLER_METRICS_PORT string + CONTROLLER_DISPERSER_STORE_CHUNKS_SIGNING_DISABLED string + + CONTROLLER_DISPERSER_KMS_KEY_ID string + CONTROLLER_CHAIN_RPC string CONTROLLER_CHAIN_RPC_FALLBACK string From 20216c4e8c31bf4966fcc6064a2697cbbf6a9fd5 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Thu, 19 Dec 2024 13:06:48 -0600 Subject: [PATCH 46/50] Cleanup. Signed-off-by: Cody Littley --- disperser/controller/node_client_manager_test.go | 2 -- node/grpc/server_v2_test.go | 1 - 2 files changed, 3 deletions(-) diff --git a/disperser/controller/node_client_manager_test.go b/disperser/controller/node_client_manager_test.go index c394340feb..070ff1490f 100644 --- a/disperser/controller/node_client_manager_test.go +++ b/disperser/controller/node_client_manager_test.go @@ -45,5 +45,3 @@ func TestNodeClientManager(t *testing.T) { require.NotSame(t, client0, client4) } - -// TODO add test for request signing diff --git a/node/grpc/server_v2_test.go b/node/grpc/server_v2_test.go index 05bcd12db3..b1784a7a67 100644 --- a/node/grpc/server_v2_test.go +++ b/node/grpc/server_v2_test.go @@ -85,7 +85,6 @@ func newTestComponents(t *testing.T, config *node.Config) *testComponents { node.BlobVersionParams.Store(v2.NewBlobVersionParameterMap(blobParamsMap)) // The eth client is only utilized for StoreChunks validation, which is disabled in these tests - // TODO enable auth for these tests var reader *coreeth.Reader server, err := grpc.NewServerV2( From 3c325ef68a20fd6327d1368644a461a5b1406343 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 20 Dec 2024 08:56:11 -0600 Subject: [PATCH 47/50] Made suggested changes. Signed-off-by: Cody Littley --- api/clients/mock/static_request_signer.go | 6 +++--- api/clients/v2/node_client.go | 7 ++++--- api/clients/v2/request_signer.go | 12 ++++++------ api/clients/v2/request_signer_test.go | 2 +- core/eth/reader.go | 6 +++++- disperser/cmd/controller/config.go | 3 +++ disperser/cmd/controller/main.go | 8 +++++--- disperser/controller/node_client_manager.go | 4 ++-- node/auth/authenticator.go | 2 +- node/auth/request_signing.go | 3 +++ node/flags/flags.go | 2 +- 11 files changed, 34 insertions(+), 21 deletions(-) diff --git a/api/clients/mock/static_request_signer.go b/api/clients/mock/static_request_signer.go index 44a7b30956..cb05368bcc 100644 --- a/api/clients/mock/static_request_signer.go +++ b/api/clients/mock/static_request_signer.go @@ -8,15 +8,15 @@ import ( "github.com/Layr-Labs/eigenda/node/auth" ) -var _ clients.RequestSigner = &staticRequestSigner{} +var _ clients.DispersalRequestSigner = &staticRequestSigner{} -// StaticRequestSigner is a RequestSigner that signs requests with a static key (i.e. it doesn't use AWS KMS). +// 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.RequestSigner { +func NewStaticRequestSigner(key *ecdsa.PrivateKey) clients.DispersalRequestSigner { return &staticRequestSigner{ key: key, } diff --git a/api/clients/v2/node_client.go b/api/clients/v2/node_client.go index 5880022af8..675901192a 100644 --- a/api/clients/v2/node_client.go +++ b/api/clients/v2/node_client.go @@ -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" @@ -27,14 +28,14 @@ type nodeClient struct { config *NodeClientConfig initOnce sync.Once conn *grpc.ClientConn - requestSigner RequestSigner + requestSigner DispersalRequestSigner dispersalClient nodegrpc.DispersalClient } var _ NodeClient = (*nodeClient)(nil) -func NewNodeClient(config *NodeClientConfig, requestSigner RequestSigner) (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) } @@ -70,7 +71,7 @@ func (c *nodeClient) StoreChunks(ctx context.Context, batch *corev2.Batch) (*cor }, BlobCertificates: blobCerts, }, - DisperserID: 0, // this will need to be updated dispersers are decentralized + DisperserID: auth.EigenLabsDisperserID, // this will need to be updated when dispersers are decentralized } if c.requestSigner != nil { diff --git a/api/clients/v2/request_signer.go b/api/clients/v2/request_signer.go index 645a2ab5b7..e00b6b3d64 100644 --- a/api/clients/v2/request_signer.go +++ b/api/clients/v2/request_signer.go @@ -11,14 +11,14 @@ import ( "github.com/aws/aws-sdk-go-v2/service/kms" ) -// RequestSigner encapsulates the logic for signing GetChunks requests. -type RequestSigner interface { +// 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 _ RequestSigner = &requestSigner{} +var _ DispersalRequestSigner = &requestSigner{} type requestSigner struct { keyID string @@ -26,12 +26,12 @@ type requestSigner struct { keyManager *kms.Client } -// NewRequestSigner creates a new RequestSigner. -func NewRequestSigner( +// NewDispersalRequestSigner creates a new DispersalRequestSigner. +func NewDispersalRequestSigner( ctx context.Context, region string, endpoint string, - keyID string) (RequestSigner, error) { + keyID string) (DispersalRequestSigner, error) { keyManager := kms.New(kms.Options{ Region: region, diff --git a/api/clients/v2/request_signer_test.go b/api/clients/v2/request_signer_test.go index 153ad7eff4..98e82b7a0a 100644 --- a/api/clients/v2/request_signer_test.go +++ b/api/clients/v2/request_signer_test.go @@ -99,7 +99,7 @@ func TestRequestSigning(t *testing.T) { request := auth.RandomStoreChunksRequest(rand) request.Signature = nil - signer, err := NewRequestSigner(context.Background(), region, localstackHost, keyID) + signer, err := NewDispersalRequestSigner(context.Background(), region, localstackHost, keyID) require.NoError(t, err) // Test a valid signature. diff --git a/core/eth/reader.go b/core/eth/reader.go index 122965264c..2c06264a3a 100644 --- a/core/eth/reader.go +++ b/core/eth/reader.go @@ -948,8 +948,12 @@ func (t *Reader) GetDisperserAddress(ctx context.Context, disperserID uint32) (g }, disperserID) + var defaultAddress gethcommon.Address if err != nil { - return gethcommon.Address{}, fmt.Errorf("failed to get disperser address: %w", err) + return defaultAddress, fmt.Errorf("failed to get disperser address: %w", err) + } + if address == defaultAddress { + return defaultAddress, fmt.Errorf("disperser with id %d not found", disperserID) } return address, nil diff --git a/disperser/cmd/controller/config.go b/disperser/cmd/controller/config.go index b07b6d60ee..5a81b5f51f 100644 --- a/disperser/cmd/controller/config.go +++ b/disperser/cmd/controller/config.go @@ -97,5 +97,8 @@ func NewConfig(ctx *cli.Context) (Config, error) { EigenDAServiceManagerAddr: ctx.GlobalString(flags.EigenDAServiceManagerFlag.Name), MetricsPort: ctx.GlobalInt(flags.MetricsPortFlag.Name), } + if !config.DisperserStoreChunksSigningDisabled && config.DisperserKMSKeyID == "" { + return Config{}, fmt.Errorf("DisperserKMSKeyID is required when StoreChunks() signing is enabled") + } return config, nil } diff --git a/disperser/cmd/controller/main.go b/disperser/cmd/controller/main.go index 9ba3aed83e..c6d4b5f7da 100644 --- a/disperser/cmd/controller/main.go +++ b/disperser/cmd/controller/main.go @@ -150,9 +150,11 @@ func RunController(ctx *cli.Context) error { } } - var requestSigner clients.RequestSigner - if !config.DisperserStoreChunksSigningDisabled { - requestSigner, err = clients.NewRequestSigner( + var requestSigner clients.DispersalRequestSigner + if config.DisperserStoreChunksSigningDisabled { + logger.Warn("StoreChunks() signing is disabled") + } else { + requestSigner, err = clients.NewDispersalRequestSigner( context.Background(), config.AwsClientConfig.Region, config.AwsClientConfig.EndpointURL, diff --git a/disperser/controller/node_client_manager.go b/disperser/controller/node_client_manager.go index ef8624ec12..a1669ba00b 100644 --- a/disperser/controller/node_client_manager.go +++ b/disperser/controller/node_client_manager.go @@ -15,7 +15,7 @@ type NodeClientManager interface { type nodeClientManager struct { // nodeClients is a cache of node clients keyed by socket address nodeClients *lru.Cache[string, clients.NodeClient] - requestSigner clients.RequestSigner + requestSigner clients.DispersalRequestSigner logger logging.Logger } @@ -23,7 +23,7 @@ var _ NodeClientManager = (*nodeClientManager)(nil) func NewNodeClientManager( cacheSize int, - requestSigner clients.RequestSigner, + requestSigner clients.DispersalRequestSigner, logger logging.Logger) (NodeClientManager, error) { closeClient := func(socket string, value clients.NodeClient) { diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index 87621ec17b..2d8eb75b73 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -91,7 +91,7 @@ func NewRequestAuthenticator( func (a *requestAuthenticator) preloadCache(ctx context.Context, now time.Time) error { // this will need to be updated for decentralized dispersers - _, err := a.getDisperserKey(ctx, now, 0) + _, err := a.getDisperserKey(ctx, now, EigenLabsDisperserID) if err != nil { return fmt.Errorf("failed to get operator key: %w", err) } diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index 24ca80b8b9..76be13c21c 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -13,6 +13,9 @@ import ( "hash" ) +// EigenLabsDisperserID is the ID of the disperser that is managed by Eigen Labs. +const EigenLabsDisperserID = 0 + // SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not // write the signature into the request. func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { diff --git a/node/flags/flags.go b/node/flags/flags.go index a472d6e08a..0494b3a4fd 100644 --- a/node/flags/flags.go +++ b/node/flags/flags.go @@ -263,7 +263,7 @@ var ( Usage: "The duration for which a disperser authentication is valid", Required: false, EnvVar: common.PrefixEnvVar(EnvVarPrefix, "DISPERSAL_AUTHENTICATION_TIMEOUT"), - Value: 5 * time.Minute, + Value: time.Minute, } // Test only, DO NOT USE the following flags in production From 02ed38a7dad04258f29ad03ff1d079295bfed397 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 20 Dec 2024 11:20:42 -0600 Subject: [PATCH 48/50] tweak docker build Signed-off-by: Cody Littley --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 62ba07305f..c12ae2ba5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -94,6 +94,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ # Controller build stage FROM common-builder AS controller-builder +COPY node/auth /app/node/auth WORKDIR /app/disperser RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ From 80c7e6d3a4579415991e647bdd786ba47134d2f3 Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 20 Dec 2024 14:18:59 -0600 Subject: [PATCH 49/50] Make requested changes. Signed-off-by: Cody Littley --- api/clients/v2/node_client.go | 4 +- api/clients/v2/relay_client.go | 4 +- api/clients/v2/request_signer.go | 4 +- api/constants.go | 4 ++ api/hashing/node_hashing.go | 60 ++++++++++++++++++++++++ api/hashing/relay_hashing.go | 38 +++++++++++++++ api/hashing/utils.go | 20 ++++++++ node/auth/authenticator.go | 3 +- node/auth/request_signing.go | 74 ++---------------------------- node/auth/request_signing_test.go | 39 ++++++++-------- relay/auth/authenticator.go | 3 +- relay/auth/request_signing.go | 47 +------------------ relay/auth/request_signing_test.go | 11 +++-- 13 files changed, 163 insertions(+), 148 deletions(-) create mode 100644 api/constants.go create mode 100644 api/hashing/node_hashing.go create mode 100644 api/hashing/relay_hashing.go create mode 100644 api/hashing/utils.go diff --git a/api/clients/v2/node_client.go b/api/clients/v2/node_client.go index 675901192a..0a379b7bfe 100644 --- a/api/clients/v2/node_client.go +++ b/api/clients/v2/node_client.go @@ -3,7 +3,7 @@ package clients import ( "context" "fmt" - "github.com/Layr-Labs/eigenda/node/auth" + "github.com/Layr-Labs/eigenda/api" "sync" commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2" @@ -71,7 +71,7 @@ func (c *nodeClient) StoreChunks(ctx context.Context, batch *corev2.Batch) (*cor }, BlobCertificates: blobCerts, }, - DisperserID: auth.EigenLabsDisperserID, // this will need to be updated when dispersers are decentralized + DisperserID: api.EigenLabsDisperserID, // this will need to be updated when dispersers are decentralized } if c.requestSigner != nil { diff --git a/api/clients/v2/relay_client.go b/api/clients/v2/relay_client.go index d6be360643..eb559aa786 100644 --- a/api/clients/v2/relay_client.go +++ b/api/clients/v2/relay_client.go @@ -4,8 +4,8 @@ import ( "context" "errors" "fmt" + "github.com/Layr-Labs/eigenda/api/hashing" "github.com/Layr-Labs/eigenda/core" - "github.com/Layr-Labs/eigenda/relay/auth" "sync" relaygrpc "github.com/Layr-Labs/eigenda/api/grpc/relay" @@ -115,7 +115,7 @@ func (c *relayClient) signGetChunksRequest(ctx context.Context, request *relaygr return errors.New("no message signer provided in config, cannot sign get chunks request") } - hash := auth.HashGetChunksRequest(request) + hash := hashing.HashGetChunksRequest(request) hashArray := [32]byte{} copy(hashArray[:], hash) signature, err := c.config.MessageSigner(ctx, hashArray) diff --git a/api/clients/v2/request_signer.go b/api/clients/v2/request_signer.go index e00b6b3d64..e0d8f47e00 100644 --- a/api/clients/v2/request_signer.go +++ b/api/clients/v2/request_signer.go @@ -5,8 +5,8 @@ import ( "crypto/ecdsa" "fmt" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/Layr-Labs/eigenda/api/hashing" "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" ) @@ -51,7 +51,7 @@ func NewDispersalRequestSigner( } func (s *requestSigner) SignStoreChunksRequest(ctx context.Context, request *grpc.StoreChunksRequest) ([]byte, error) { - hash := auth.HashStoreChunksRequest(request) + hash := hashing.HashStoreChunksRequest(request) signature, err := common.SignKMS(ctx, s.keyManager, s.keyID, s.publicKey, hash) if err != nil { diff --git a/api/constants.go b/api/constants.go new file mode 100644 index 0000000000..0f3772cce7 --- /dev/null +++ b/api/constants.go @@ -0,0 +1,4 @@ +package api + +// EigenLabsDisperserID is the ID of the disperser that is managed by Eigen Labs. +const EigenLabsDisperserID = uint32(0) diff --git a/api/hashing/node_hashing.go b/api/hashing/node_hashing.go new file mode 100644 index 0000000000..fafb118724 --- /dev/null +++ b/api/hashing/node_hashing.go @@ -0,0 +1,60 @@ +package hashing + +import ( + commonv1 "github.com/Layr-Labs/eigenda/api/grpc/common" + common "github.com/Layr-Labs/eigenda/api/grpc/common/v2" + grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "golang.org/x/crypto/sha3" + "hash" +) + +// This file contains code for hashing gRPC messages that are sent to the DA node. + +// HashStoreChunksRequest hashes the given StoreChunksRequest. +func HashStoreChunksRequest(request *grpc.StoreChunksRequest) []byte { + hasher := sha3.NewLegacyKeccak256() + + hashBatchHeader(hasher, request.Batch.Header) + for _, blobCertificate := range request.Batch.BlobCertificates { + hashBlobCertificate(hasher, blobCertificate) + } + hashUint32(hasher, request.DisperserID) + + return hasher.Sum(nil) +} + +func hashBlobCertificate(hasher hash.Hash, blobCertificate *common.BlobCertificate) { + hashBlobHeader(hasher, blobCertificate.BlobHeader) + for _, relayID := range blobCertificate.Relays { + hashUint32(hasher, relayID) + } +} + +func hashBlobHeader(hasher hash.Hash, header *common.BlobHeader) { + hashUint32(hasher, header.Version) + for _, quorum := range header.QuorumNumbers { + hashUint32(hasher, quorum) + } + hashBlobCommitment(hasher, header.Commitment) + hashPaymentHeader(hasher, header.PaymentHeader) + hasher.Write(header.Signature) +} + +func hashBatchHeader(hasher hash.Hash, header *common.BatchHeader) { + hasher.Write(header.BatchRoot) + hashUint64(hasher, header.ReferenceBlockNumber) +} + +func hashBlobCommitment(hasher hash.Hash, commitment *commonv1.BlobCommitment) { + hasher.Write(commitment.Commitment) + hasher.Write(commitment.LengthCommitment) + hasher.Write(commitment.LengthProof) + hashUint32(hasher, commitment.Length) +} + +func hashPaymentHeader(hasher hash.Hash, header *commonv1.PaymentHeader) { + hasher.Write([]byte(header.AccountId)) + hashUint32(hasher, header.ReservationPeriod) + hasher.Write(header.CumulativePayment) + hashUint32(hasher, header.Salt) +} diff --git a/api/hashing/relay_hashing.go b/api/hashing/relay_hashing.go new file mode 100644 index 0000000000..2dad5ecade --- /dev/null +++ b/api/hashing/relay_hashing.go @@ -0,0 +1,38 @@ +package hashing + +import ( + pb "github.com/Layr-Labs/eigenda/api/grpc/relay" + "golang.org/x/crypto/sha3" +) + +// This file contains code for hashing gRPC messages that are sent to the relay. + +var ( + iByte = []byte{0x69} + rByte = []byte{0x72} +) + +// HashGetChunksRequest hashes the given GetChunksRequest. +func HashGetChunksRequest(request *pb.GetChunksRequest) []byte { + hasher := sha3.NewLegacyKeccak256() + + hasher.Write(request.GetOperatorId()) + for _, chunkRequest := range request.GetChunkRequests() { + if chunkRequest.GetByIndex() != nil { + getByIndex := chunkRequest.GetByIndex() + hasher.Write(iByte) + hasher.Write(getByIndex.BlobKey) + for _, index := range getByIndex.ChunkIndices { + hashUint32(hasher, index) + } + } else { + getByRange := chunkRequest.GetByRange() + hasher.Write(rByte) + hasher.Write(getByRange.BlobKey) + hashUint32(hasher, getByRange.StartIndex) + hashUint32(hasher, getByRange.EndIndex) + } + } + + return hasher.Sum(nil) +} diff --git a/api/hashing/utils.go b/api/hashing/utils.go new file mode 100644 index 0000000000..bc6f8d289e --- /dev/null +++ b/api/hashing/utils.go @@ -0,0 +1,20 @@ +package hashing + +import ( + "encoding/binary" + "hash" +) + +// hashUint32 hashes the given uint32 value. +func hashUint32(hasher hash.Hash, value uint32) { + bytes := make([]byte, 4) + binary.BigEndian.PutUint32(bytes, value) + hasher.Write(bytes) +} + +// hashUint64 hashes the given uint64 value. +func hashUint64(hasher hash.Hash, value uint64) { + bytes := make([]byte, 8) + binary.BigEndian.PutUint64(bytes, value) + hasher.Write(bytes) +} diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index 2d8eb75b73..3cb25e8c95 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -3,6 +3,7 @@ package auth import ( "context" "fmt" + "github.com/Layr-Labs/eigenda/api" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" "github.com/Layr-Labs/eigenda/core" gethcommon "github.com/ethereum/go-ethereum/common" @@ -91,7 +92,7 @@ func NewRequestAuthenticator( func (a *requestAuthenticator) preloadCache(ctx context.Context, now time.Time) error { // this will need to be updated for decentralized dispersers - _, err := a.getDisperserKey(ctx, now, EigenLabsDisperserID) + _, err := a.getDisperserKey(ctx, now, api.EigenLabsDisperserID) if err != nil { return fmt.Errorf("failed to get operator key: %w", err) } diff --git a/node/auth/request_signing.go b/node/auth/request_signing.go index 76be13c21c..6e660bbfa7 100644 --- a/node/auth/request_signing.go +++ b/node/auth/request_signing.go @@ -2,24 +2,17 @@ package auth import ( "crypto/ecdsa" - "encoding/binary" "fmt" - commonv1 "github.com/Layr-Labs/eigenda/api/grpc/common" - common "github.com/Layr-Labs/eigenda/api/grpc/common/v2" grpc "github.com/Layr-Labs/eigenda/api/grpc/node/v2" + "github.com/Layr-Labs/eigenda/api/hashing" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "golang.org/x/crypto/sha3" - "hash" ) -// EigenLabsDisperserID is the ID of the disperser that is managed by Eigen Labs. -const EigenLabsDisperserID = 0 - // SignStoreChunksRequest signs the given StoreChunksRequest with the given private key. Does not // write the signature into the request. func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequest) ([]byte, error) { - requestHash := HashStoreChunksRequest(request) + requestHash := hashing.HashStoreChunksRequest(request) signature, err := crypto.Sign(requestHash, key) if err != nil { @@ -32,7 +25,7 @@ func SignStoreChunksRequest(key *ecdsa.PrivateKey, request *grpc.StoreChunksRequ // VerifyStoreChunksRequest verifies the given signature of the given StoreChunksRequest with the given // public key. func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksRequest) error { - requestHash := HashStoreChunksRequest(request) + requestHash := hashing.HashStoreChunksRequest(request) signingPublicKey, err := crypto.SigToPub(requestHash, request.Signature) if err != nil { @@ -46,64 +39,3 @@ func VerifyStoreChunksRequest(key gethcommon.Address, request *grpc.StoreChunksR } return nil } - -// HashStoreChunksRequest hashes the given StoreChunksRequest. -func HashStoreChunksRequest(request *grpc.StoreChunksRequest) []byte { - hasher := sha3.NewLegacyKeccak256() - - hashBatchHeader(hasher, request.Batch.Header) - for _, blobCertificate := range request.Batch.BlobCertificates { - hashBlobCertificate(hasher, blobCertificate) - } - hashUint32(hasher, request.DisperserID) - - return hasher.Sum(nil) -} - -func hashBlobCertificate(hasher hash.Hash, blobCertificate *common.BlobCertificate) { - hashBlobHeader(hasher, blobCertificate.BlobHeader) - for _, relayID := range blobCertificate.Relays { - hashUint32(hasher, relayID) - } -} - -func hashBlobHeader(hasher hash.Hash, header *common.BlobHeader) { - hashUint32(hasher, header.Version) - for _, quorum := range header.QuorumNumbers { - hashUint32(hasher, quorum) - } - hashBlobCommitment(hasher, header.Commitment) - hashPaymentHeader(hasher, header.PaymentHeader) - hasher.Write(header.Signature) -} - -func hashBatchHeader(hasher hash.Hash, header *common.BatchHeader) { - hasher.Write(header.BatchRoot) - hashUint64(hasher, header.ReferenceBlockNumber) -} - -func hashBlobCommitment(hasher hash.Hash, commitment *commonv1.BlobCommitment) { - hasher.Write(commitment.Commitment) - hasher.Write(commitment.LengthCommitment) - hasher.Write(commitment.LengthProof) - hashUint32(hasher, commitment.Length) -} - -func hashPaymentHeader(hasher hash.Hash, header *commonv1.PaymentHeader) { - hasher.Write([]byte(header.AccountId)) - hashUint32(hasher, header.ReservationPeriod) - hasher.Write(header.CumulativePayment) - hashUint32(hasher, header.Salt) -} - -func hashUint32(hasher hash.Hash, value uint32) { - bytes := make([]byte, 4) - binary.BigEndian.PutUint32(bytes, value) - hasher.Write(bytes) -} - -func hashUint64(hasher hash.Hash, value uint64) { - bytes := make([]byte, 8) - binary.BigEndian.PutUint64(bytes, value) - hasher.Write(bytes) -} diff --git a/node/auth/request_signing_test.go b/node/auth/request_signing_test.go index f43ac75ca5..e4122c18b8 100644 --- a/node/auth/request_signing_test.go +++ b/node/auth/request_signing_test.go @@ -1,6 +1,7 @@ package auth import ( + "github.com/Layr-Labs/eigenda/api/hashing" "github.com/Layr-Labs/eigenda/common/testutils/random" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -11,32 +12,32 @@ func TestHashing(t *testing.T) { rand := random.NewTestRandom(t) request := RandomStoreChunksRequest(rand) - originalRequestHash := HashStoreChunksRequest(request) + originalRequestHash := hashing.HashStoreChunksRequest(request) // modifying the signature should not change the hash request.Signature = rand.Bytes(32) - hash := HashStoreChunksRequest(request) + hash := hashing.HashStoreChunksRequest(request) require.Equal(t, originalRequestHash, hash) // modify the disperser id rand.Reset() request = RandomStoreChunksRequest(rand) request.DisperserID = request.DisperserID + 1 - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // remove a blob cert rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates = request.Batch.BlobCertificates[:len(request.Batch.BlobCertificates)-1] - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify a relay rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].Relays[0] = request.Batch.BlobCertificates[0].Relays[0] + 1 - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, remove a relay @@ -44,14 +45,14 @@ func TestHashing(t *testing.T) { request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].Relays = request.Batch.BlobCertificates[0].Relays[:len(request.Batch.BlobCertificates[0].Relays)-1] - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, add a relay rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].Relays = append(request.Batch.BlobCertificates[0].Relays, rand.Uint32()) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify a quorum number @@ -59,7 +60,7 @@ func TestHashing(t *testing.T) { request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[0] = request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[0] + 1 - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, remove a quorum number @@ -68,7 +69,7 @@ func TestHashing(t *testing.T) { request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers = request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers[:len( request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers)-1] - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, add a quorum number @@ -76,70 +77,70 @@ func TestHashing(t *testing.T) { request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers = append( request.Batch.BlobCertificates[0].BlobHeader.QuorumNumbers, rand.Uint32()) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.Commitment rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.Commitment = rand.Bytes(32) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.LengthCommitment rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthCommitment = rand.Bytes(32) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.LengthProof rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.LengthProof = rand.Bytes(32) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Commitment.Length rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Commitment.Length = rand.Uint32() - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.AccountId rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.AccountId = rand.String(32) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.ReservationPeriod rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.ReservationPeriod = rand.Uint32() - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.CumulativePayment rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.CumulativePayment = rand.Bytes(32) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the PaymentHeader.Salt rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.PaymentHeader.Salt = rand.Uint32() - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) // within a blob cert, modify the Signature rand.Reset() request = RandomStoreChunksRequest(rand) request.Batch.BlobCertificates[0].BlobHeader.Signature = rand.Bytes(32) - hash = HashStoreChunksRequest(request) + hash = hashing.HashStoreChunksRequest(request) require.NotEqual(t, originalRequestHash, hash) } diff --git a/relay/auth/authenticator.go b/relay/auth/authenticator.go index 664979d366..caf611b165 100644 --- a/relay/auth/authenticator.go +++ b/relay/auth/authenticator.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/Layr-Labs/eigenda/api/hashing" "sync" "time" @@ -129,7 +130,7 @@ func (a *requestAuthenticator) AuthenticateGetChunksRequest( G1Point: g1Point, } - hash := HashGetChunksRequest(request) + hash := hashing.HashGetChunksRequest(request) isValid := signature.Verify(key, ([32]byte)(hash)) if !isValid { diff --git a/relay/auth/request_signing.go b/relay/auth/request_signing.go index 149992bce2..f08666122e 100644 --- a/relay/auth/request_signing.go +++ b/relay/auth/request_signing.go @@ -1,58 +1,15 @@ package auth import ( - "encoding/binary" pb "github.com/Layr-Labs/eigenda/api/grpc/relay" + "github.com/Layr-Labs/eigenda/api/hashing" "github.com/Layr-Labs/eigenda/core" - "golang.org/x/crypto/sha3" ) -var ( - iByte = []byte{0x69} - rByte = []byte{0x72} -) - -// HashGetChunksRequest hashes the given GetChunksRequest. -func HashGetChunksRequest(request *pb.GetChunksRequest) []byte { - - // Protobuf serialization is non-deterministic, so we can't just hash the - // serialized bytes. Instead, we have to define our own hashing function. - - hasher := sha3.NewLegacyKeccak256() - - hasher.Write(request.GetOperatorId()) - for _, chunkRequest := range request.GetChunkRequests() { - if chunkRequest.GetByIndex() != nil { - getByIndex := chunkRequest.GetByIndex() - hasher.Write(iByte) - hasher.Write(getByIndex.BlobKey) - for _, index := range getByIndex.ChunkIndices { - indexBytes := make([]byte, 4) - binary.BigEndian.PutUint32(indexBytes, index) - hasher.Write(indexBytes) - } - } else { - getByRange := chunkRequest.GetByRange() - hasher.Write(rByte) - hasher.Write(getByRange.BlobKey) - - startBytes := make([]byte, 4) - binary.BigEndian.PutUint32(startBytes, getByRange.StartIndex) - hasher.Write(startBytes) - - endBytes := make([]byte, 4) - binary.BigEndian.PutUint32(endBytes, getByRange.EndIndex) - hasher.Write(endBytes) - } - } - - return hasher.Sum(nil) -} - // SignGetChunksRequest signs the given GetChunksRequest with the given private key. Does not // write the signature into the request. func SignGetChunksRequest(keys *core.KeyPair, request *pb.GetChunksRequest) []byte { - hash := HashGetChunksRequest(request) + hash := hashing.HashGetChunksRequest(request) signature := keys.SignMessage(([32]byte)(hash)) return signature.G1Point.Serialize() } diff --git a/relay/auth/request_signing_test.go b/relay/auth/request_signing_test.go index 3c05188514..968052cf12 100644 --- a/relay/auth/request_signing_test.go +++ b/relay/auth/request_signing_test.go @@ -2,6 +2,7 @@ package auth import ( pb "github.com/Layr-Labs/eigenda/api/grpc/relay" + "github.com/Layr-Labs/eigenda/api/hashing" tu "github.com/Layr-Labs/eigenda/common/testutils" "github.com/stretchr/testify/require" "golang.org/x/exp/rand" @@ -51,21 +52,21 @@ func TestHashGetChunksRequest(t *testing.T) { requestB := randomGetChunksRequest() // Hashing the same request twice should yield the same hash - hashA := HashGetChunksRequest(requestA) - hashAA := HashGetChunksRequest(requestA) + hashA := hashing.HashGetChunksRequest(requestA) + hashAA := hashing.HashGetChunksRequest(requestA) require.Equal(t, hashA, hashAA) // Hashing different requests should yield different hashes - hashB := HashGetChunksRequest(requestB) + hashB := hashing.HashGetChunksRequest(requestB) require.NotEqual(t, hashA, hashB) // Adding a signature should not affect the hash requestA.OperatorSignature = tu.RandomBytes(32) - hashAA = HashGetChunksRequest(requestA) + hashAA = hashing.HashGetChunksRequest(requestA) require.Equal(t, hashA, hashAA) // Changing the requester ID should change the hash requestA.OperatorId = tu.RandomBytes(32) - hashAA = HashGetChunksRequest(requestA) + hashAA = hashing.HashGetChunksRequest(requestA) require.NotEqual(t, hashA, hashAA) } From 2b6cbb2cc8b2ced8a8dee1b05c6475d79c48841c Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Fri, 20 Dec 2024 14:42:33 -0600 Subject: [PATCH 50/50] Made suggested changes. Signed-off-by: Cody Littley --- .../v2/{request_signer.go => dispersal_request_signer.go} | 0 ...uest_signer_test.go => dispersal_request_signer_test.go} | 0 node/auth/authenticator.go | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) rename api/clients/v2/{request_signer.go => dispersal_request_signer.go} (100%) rename api/clients/v2/{request_signer_test.go => dispersal_request_signer_test.go} (100%) diff --git a/api/clients/v2/request_signer.go b/api/clients/v2/dispersal_request_signer.go similarity index 100% rename from api/clients/v2/request_signer.go rename to api/clients/v2/dispersal_request_signer.go diff --git a/api/clients/v2/request_signer_test.go b/api/clients/v2/dispersal_request_signer_test.go similarity index 100% rename from api/clients/v2/request_signer_test.go rename to api/clients/v2/dispersal_request_signer_test.go diff --git a/node/auth/authenticator.go b/node/auth/authenticator.go index 3cb25e8c95..3a3be7139a 100644 --- a/node/auth/authenticator.go +++ b/node/auth/authenticator.go @@ -121,7 +121,7 @@ func (a *requestAuthenticator) AuthenticateStoreChunksRequest( return fmt.Errorf("failed to verify request: %w", err) } - a.saveAuthenticationResult(now, origin) + a.cacheAuthenticationResult(now, origin) return nil } @@ -151,8 +151,8 @@ func (a *requestAuthenticator) getDisperserKey( return &address, nil } -// saveAuthenticationResult saves the result of an auth. -func (a *requestAuthenticator) saveAuthenticationResult(now time.Time, origin string) { +// cacheAuthenticationResult saves the result of an auth. +func (a *requestAuthenticator) cacheAuthenticationResult(now time.Time, origin string) { if a.authenticationTimeoutDuration == 0 { // Authentication saving is disabled. return