From 0936f5f5ed0c100ab5751e3d856595770438212b Mon Sep 17 00:00:00 2001 From: Cody Littley Date: Wed, 4 Dec 2024 08:55:01 -0600 Subject: [PATCH 01/22] 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 40fe5f34c..b5043399c 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 000000000..a273cb6b7 --- /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 000000000..e5e2f9c5e --- /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/22] 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 a273cb6b7..7ea6ac4c9 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 000000000..89ad8d7d4 --- /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/22] 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 7ea6ac4c9..e8afb944c 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 000000000..2f6a5681e --- /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 000000000..649526fc1 --- /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/22] 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 e8afb944c..1cbce6b9e 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/22] 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 000000000..5017b8449 --- /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/22] 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 5017b8449..96de6b1e7 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 1cbce6b9e..cb45ce53b 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 000000000..af43313b9 --- /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 000000000..b754feb3b --- /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 000000000..dfa7238cc --- /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 000000000..f788818e3 --- /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 2f6a5681e..000000000 --- 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 649526fc1..000000000 --- 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/22] 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 96de6b1e7..e3a70d758 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/22] 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 e3a70d758..c38b550e1 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/22] 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 b5043399c..c49c17e81 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 000000000..4cd6c8d02 --- /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 000000000..8919f266d --- /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 cb45ce53b..000000000 --- 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 000000000..1a017b655 --- /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 e5e2f9c5e..000000000 --- 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 89ad8d7d4..000000000 --- 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 af43313b9..000000000 --- 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 b754feb3b..000000000 --- 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 dfa7238cc..000000000 --- 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 f788818e3..000000000 --- a/disperser/auth/test-node.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -d/czU7IvbTeqQE0aHPnFNaWJfgKs2Fs= ------END PRIVATE KEY----- diff --git a/disperser/auth/test-private.pem b/disperser/auth/test-private.pem new file mode 100644 index 000000000..cc5a69b24 --- /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 000000000..c7c656729 --- /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/22] 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 98c98ee40..7f75acfa9 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 7a13378be..9e2acef01 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 000000000..b07003095 --- /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/22] 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 9f1c0c1c9..29bec7d59 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 8b4992a42..7c6777c81 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 22d657aca..3cbebeaaa 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 27dbaace4..2e2d36eac 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/22] 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 58452aa94..e45dfc986 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 b07003095..ec0669662 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/22] 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 29bec7d59..94dc2c02e 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 7c6777c81..fdd5bb20e 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 3cbebeaaa..b092e74eb 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 2e2d36eac..cc17bbd46 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 7f75acfa9..f97be225a 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 9e2acef01..652154f63 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 ec0669662..3d7216c1b 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 4cd6c8d02..43ac84bf6 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 000000000..e05c487b2 --- /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/22] 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 b5b587c8d..eefac7cea 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 8e231f867..1c0cfbb1a 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 7e56e64cc..36fb0906a 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 e05c487b2..a36bba74c 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/22] 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 0225b33d7..f0740001d 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 fd8d53085..64874db71 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 ac0bbbae8..a10ef2dc5 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 825876192..edbc45a7a 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 4f46a70d5..1979593b7 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 11ef1fce3..8e16ddd8a 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 3e36d5f78..0c63d8050 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 b3efdc25f..eae4e1dd2 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/22] 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 000000000..8832b06d1 --- /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/22] 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 e45dfc986..25d101696 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 c38b550e1..000000000 --- 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 a36bba74c..eac071e79 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 3d7216c1b..f4b9df3f0 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 000000000..afd0bd499 --- /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/22] 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 652154f63..4005249f2 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 ed4699a6d..f8a0ecff1 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 822afd855..228faf5dd 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 f4b9df3f0..7150b212a 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 000000000..c33333ed1 --- /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 afd0bd499..000000000 --- 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/22] 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 1a017b655..000000000 --- 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 43ac84bf6..000000000 --- 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 8919f266d..000000000 --- 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 cc5a69b24..000000000 --- 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 c7c656729..000000000 --- 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/22] 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 25d101696..2bc358833 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 161e5cb9d..51b2d4ee1 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 3c78a0c34..f1231c974 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 b092e74eb..ad54d473b 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 cc17bbd46..46d43c7d4 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 f97be225a..60f69facd 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 f8a0ecff1..b9a9933da 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 ad06ecabb..38daa774b 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 f367ae2eb..5c8340ef7 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 eac071e79..ce7181be2 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 000000000..b36a671c5 --- /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 8832b06d1..000000000 --- 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 7150b212a..dd7e24cd3 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 c33333ed1..1d6a6e622 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/22] 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 b36a671c5..1ea701f4e 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/22] 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 c49c17e81..40fe5f34c 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 1ea701f4e..d33117469 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 64874db71..7cfb219cd 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