From 960b4ada4aca2d5a161d7196448122ee08622362 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:12:47 -0500 Subject: [PATCH 1/5] Implement version-based anchor signature verification Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_client_config.go | 2 +- api/proxy/config/v2/eigendaflags/cli.go | 4 ++-- disperser/apiserver/disperse_blob_v2.go | 14 ++++++++------ disperser/apiserver/server_v2_test.go | 1 - disperser/cmd/apiserver/flags/flags.go | 9 +-------- disperser/cmd/apiserver/lib/config.go | 1 - disperser/server_config.go | 10 ---------- inabox/tests/setup_disperser_harness.go | 1 - 8 files changed, 12 insertions(+), 30 deletions(-) diff --git a/api/clients/v2/payload_client_config.go b/api/clients/v2/payload_client_config.go index dd4b38844a..dd2c203475 100644 --- a/api/clients/v2/payload_client_config.go +++ b/api/clients/v2/payload_client_config.go @@ -33,6 +33,6 @@ type PayloadClientConfig struct { func GetDefaultPayloadClientConfig() *PayloadClientConfig { return &PayloadClientConfig{ PayloadPolynomialForm: codecs.PolynomialFormEval, - BlobVersion: 0, + BlobVersion: 1, } } diff --git a/api/proxy/config/v2/eigendaflags/cli.go b/api/proxy/config/v2/eigendaflags/cli.go index 4db7b92e3c..a0d9ec2fa4 100644 --- a/api/proxy/config/v2/eigendaflags/cli.go +++ b/api/proxy/config/v2/eigendaflags/cli.go @@ -187,10 +187,10 @@ func CLIFlags(envPrefix, category string) []cli.Flag { &cli.UintFlag{ Name: BlobParamsVersionFlagName, Usage: `Blob params version used when dispersing. This refers to a global version maintained by EigenDA -governance and is injected in the BlobHeader before dispersing. Currently only supports (0).`, +governance and is injected in the BlobHeader before dispersing.`, EnvVars: []string{withEnvPrefix(envPrefix, "BLOB_PARAMS_VERSION")}, Category: category, - Value: uint(0), + Value: uint(1), Required: false, }, &cli.StringFlag{ diff --git a/disperser/apiserver/disperse_blob_v2.go b/disperser/apiserver/disperse_blob_v2.go index 2acc359bcf..a389dad8f0 100644 --- a/disperser/apiserver/disperse_blob_v2.go +++ b/disperser/apiserver/disperse_blob_v2.go @@ -270,10 +270,10 @@ func (s *DispersalServerV2) validateDispersalRequest( // // If DisableAnchorSignatureVerification is true, then this method will skip all validation and return nil. // -// If TolerateMissingAnchorSignature is true, then this method will pass validation even if no anchor signature is -// provided in the request. +// Anchor signatures are required for blob version > 0. For blob version 0, missing anchor signatures are tolerated +// for backward compatibility with legacy clients. // -// If an anchor signature is provided, it will be validated whether or not TolerateMissingAnchorSignature is true. +// If an anchor signature is provided, it will be validated regardless of blob version. // While validating the anchor signature, this method will also verify that the disperser ID and chain ID in the request // match the expected values. func (s *DispersalServerV2) validateAnchorSignature( @@ -287,11 +287,13 @@ func (s *DispersalServerV2) validateAnchorSignature( anchorSignature := req.GetAnchorSignature() if len(anchorSignature) == 0 { - if s.serverConfig.TolerateMissingAnchorSignature { - return nil + if blobHeader.BlobVersion > 0 { + return fmt.Errorf("anchor signature is required for blob version > 0 (got version %d)", + blobHeader.BlobVersion) } - return errors.New("anchor signature is required but not provided") + // Version 0: allow missing anchor signature for backward compatibility + return nil } if len(anchorSignature) != 65 { diff --git a/disperser/apiserver/server_v2_test.go b/disperser/apiserver/server_v2_test.go index df048b21a7..b0b4b457b8 100644 --- a/disperser/apiserver/server_v2_test.go +++ b/disperser/apiserver/server_v2_test.go @@ -584,7 +584,6 @@ func newTestServerV2WithDeprecationFlag(t *testing.T, disableGetBlobCommitment b GrpcTimeout: 1 * time.Second, DisableGetBlobCommitment: disableGetBlobCommitment, DisperserId: 0, - TolerateMissingAnchorSignature: false, DisableAnchorSignatureVerification: false, }, time.Now, diff --git a/disperser/cmd/apiserver/flags/flags.go b/disperser/cmd/apiserver/flags/flags.go index ed64e2b1fb..356f4d1103 100644 --- a/disperser/cmd/apiserver/flags/flags.go +++ b/disperser/cmd/apiserver/flags/flags.go @@ -287,15 +287,9 @@ var ( Required: true, EnvVar: common.PrefixEnvVar(envVarPrefix, "DISPERSER_ID"), } - TolerateMissingAnchorSignatureFlag = cli.BoolTFlag{ - Name: common.PrefixFlag(FlagPrefix, "tolerate-missing-anchor-signature"), - Usage: "Whether to accept DisperseBlob requests without an anchor signature. Ignored if disable-anchor-signature-verification is true.", - Required: false, - EnvVar: common.PrefixEnvVar(envVarPrefix, "TOLERATE_MISSING_ANCHOR_SIGNATURE"), - } DisableAnchorSignatureVerificationFlag = cli.BoolFlag{ Name: common.PrefixFlag(FlagPrefix, "disable-anchor-signature-verification"), - Usage: "If true, anchor signature verification is skipped entirely. Takes precedence over tolerate-missing-anchor-signature.", + Usage: "If true, anchor signature verification is skipped entirely, regardless of blob version.", Required: false, EnvVar: common.PrefixEnvVar(envVarPrefix, "DISABLE_ANCHOR_SIGNATURE_VERIFICATION"), } @@ -371,7 +365,6 @@ var optionalFlags = []cli.Flag{ DisablePerAccountMetricsFlag, SigningRateRetentionPeriodFlag, SigningRatePollIntervalFlag, - TolerateMissingAnchorSignatureFlag, DisableAnchorSignatureVerificationFlag, OperatorStateRetrieverFlag, EigenDAServiceManagerFlag, diff --git a/disperser/cmd/apiserver/lib/config.go b/disperser/cmd/apiserver/lib/config.go index 9981726ed2..928d81c9c4 100644 --- a/disperser/cmd/apiserver/lib/config.go +++ b/disperser/cmd/apiserver/lib/config.go @@ -102,7 +102,6 @@ func NewConfig(ctx *cli.Context) (Config, error) { SigningRateRetentionPeriod: ctx.GlobalDuration(flags.SigningRateRetentionPeriodFlag.Name), SigningRatePollInterval: ctx.GlobalDuration(flags.SigningRatePollIntervalFlag.Name), DisperserId: uint32(ctx.GlobalUint64(flags.DisperserIdFlag.Name)), - TolerateMissingAnchorSignature: ctx.GlobalBool(flags.TolerateMissingAnchorSignatureFlag.Name), DisableAnchorSignatureVerification: ctx.GlobalBool(flags.DisableAnchorSignatureVerificationFlag.Name), }, BlobstoreConfig: blobstore.Config{ diff --git a/disperser/server_config.go b/disperser/server_config.go index 40eeacbd1d..8e5b0ea8a8 100644 --- a/disperser/server_config.go +++ b/disperser/server_config.go @@ -38,18 +38,8 @@ type ServerConfig struct { // Unique identifier for this disperser instance. DisperserId uint32 - // Whether to tolerate requests without an anchor signature. - // If false, DisperseBlob requests without an anchor_signature will be rejected. - // Ignored if DisableAnchorSignatureVerification is true. - // Default: true (for backwards compatibility with old client code during migration) - // - // TODO (litt3): this field should eventually be set to false, and then removed, once all clients have updated - // to a version that includes anchor signatures. - TolerateMissingAnchorSignature bool - // Whether to disable anchor signature verification entirely. // If true, anchor signatures will not be verified even if present. - // Takes precedence over TolerateMissingAnchorSignature. // Default: false // // TODO (litt3): This is a temporary flag to allow a second LayrLabs disperser to handle dispersal requests created diff --git a/inabox/tests/setup_disperser_harness.go b/inabox/tests/setup_disperser_harness.go index 042d5bcf5c..9863473c44 100644 --- a/inabox/tests/setup_disperser_harness.go +++ b/inabox/tests/setup_disperser_harness.go @@ -1220,7 +1220,6 @@ func startAPIServer( MaxConnectionAgeGrace: 30 * time.Second, MaxIdleConnectionAge: 1 * time.Minute, DisperserId: 0, - TolerateMissingAnchorSignature: false, DisableAnchorSignatureVerification: false, } From 080e32ca7c6e245d7992c02a7568b29364c3e218 Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:52:14 -0500 Subject: [PATCH 2/5] Regen help_out Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/proxy/docs/help_out.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/proxy/docs/help_out.txt b/api/proxy/docs/help_out.txt index f3e3ce2f69..dd5dad5325 100644 --- a/api/proxy/docs/help_out.txt +++ b/api/proxy/docs/help_out.txt @@ -131,11 +131,11 @@ GLOBAL OPTIONS: --eigenda.v2.blob-status-poll-interval value (default: 1s) ($EIGENDA_PROXY_EIGENDA_V2_BLOB_STATUS_POLL_INTERVAL) Duration to query for blob status updates during dispersal. - --eigenda.v2.blob-version value (default: 0) ($EIGENDA_PROXY_EIGENDA_V2_BLOB_PARAMS_VERSION) + --eigenda.v2.blob-version value (default: 1) ($EIGENDA_PROXY_EIGENDA_V2_BLOB_PARAMS_VERSION) Blob params version used when dispersing. This refers to a global version maintained by EigenDA governance and is injected in the BlobHeader before - dispersing. Currently only supports (0). + dispersing. --eigenda.v2.cert-verifier-router-or-immutable-verifier-addr value ($EIGENDA_PROXY_EIGENDA_V2_CERT_VERIFIER_ROUTER_OR_IMMUTABLE_VERIFIER_ADDR) Address of either the EigenDACertVerifierRouter or immutable EigenDACertVerifier From 95160eda1787cd83a08eef1697a9946801259a3d Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:58:11 -0500 Subject: [PATCH 3/5] Fix test Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- disperser/apiserver/server_v2_test.go | 5 +++++ inabox/deploy/config.go | 1 - inabox/deploy/env_vars.go | 6 ++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/disperser/apiserver/server_v2_test.go b/disperser/apiserver/server_v2_test.go index b0b4b457b8..9c505be085 100644 --- a/disperser/apiserver/server_v2_test.go +++ b/disperser/apiserver/server_v2_test.go @@ -563,6 +563,11 @@ func newTestServerV2WithDeprecationFlag(t *testing.T, disableGetBlobCommitment b CodingRate: 8, MaxNumOperators: 2048, }, + 1: { + NumChunks: 8192, + CodingRate: 8, + MaxNumOperators: 2048, + }, }, nil) // Create listener for test server diff --git a/inabox/deploy/config.go b/inabox/deploy/config.go index 43b9c33b02..c918df778f 100644 --- a/inabox/deploy/config.go +++ b/inabox/deploy/config.go @@ -384,7 +384,6 @@ func (env *Config) generateProxyVars(ind int) ProxyVars { EIGENDA_PROXY_EIGENDA_V2_ETH_RPC: "http://localhost:8545", EIGENDA_PROXY_EIGENDA_V2_MAX_BLOB_LENGTH: "16MiB", EIGENDA_PROXY_EIGENDA_V2_CERT_VERIFIER_ROUTER_OR_IMMUTABLE_VERIFIER_ADDR: env.EigenDA.CertVerifierRouter, - EIGENDA_PROXY_EIGENDA_V2_RBN_RECENCY_WINDOW_SIZE: "0", // TODO(samlaf): this should not be hardcoded EIGENDA_PROXY_EIGENDA_V2_DISPERSER_RPC: "localhost:32005", EIGENDA_PROXY_EIGENDA_V2_EIGENDA_DIRECTORY: env.EigenDA.EigenDADirectory, diff --git a/inabox/deploy/env_vars.go b/inabox/deploy/env_vars.go index 4bb85d84f1..3f6d081fdf 100644 --- a/inabox/deploy/env_vars.go +++ b/inabox/deploy/env_vars.go @@ -79,8 +79,6 @@ type DisperserVars struct { DISPERSER_SERVER_SIGNING_RATE_POLL_INTERVAL string - DISPERSER_SERVER_TOLERATE_MISSING_ANCHOR_SIGNATURE string - DISPERSER_SERVER_DISABLE_ANCHOR_SIGNATURE_VERIFICATION string DISPERSER_SERVER_BLS_OPERATOR_STATE_RETRIVER string @@ -785,6 +783,8 @@ type ControllerVars struct { CONTROLLER_MAX_DISPERSAL_AGE string + CONTROLLER_MAX_DISPERSAL_FUTURE_AGE string + CONTROLLER_SIGNATURE_TICK_INTERVAL string CONTROLLER_FINALIZATION_BLOCK_DELAY string @@ -1161,8 +1161,6 @@ type ProxyVars struct { EIGENDA_PROXY_EIGENDA_V2_NETWORK string - EIGENDA_PROXY_EIGENDA_V2_RBN_RECENCY_WINDOW_SIZE string - EIGENDA_PROXY_EIGENDA_V2_RELAY_CONNECTION_POOL_SIZE string EIGENDA_PROXY_EIGENDA_V2_CLIENT_LEDGER_MODE string From efdcd210467034a1f72ed8820c05c65726bafe6d Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Mon, 5 Jan 2026 17:03:41 -0500 Subject: [PATCH 4/5] Fix inabox test Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- inabox/templates/testconfig-anvil-nochurner.yaml | 4 ++++ inabox/templates/testconfig-anvil.yaml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/inabox/templates/testconfig-anvil-nochurner.yaml b/inabox/templates/testconfig-anvil-nochurner.yaml index ed54cfce80..9d5711729d 100644 --- a/inabox/templates/testconfig-anvil-nochurner.yaml +++ b/inabox/templates/testconfig-anvil-nochurner.yaml @@ -18,10 +18,14 @@ eigenda: # NOTE: This uses a different blob version configuration than live deployments. # Live deployments typically use higher coding rates (e.g., 8) with more chunks and operators. # TODO: Investigate the revert that occurs when trying to use codingRate 2. +# Two blob versions are registered: version 0 for backward compatibility, version 1 as the new default. blobVersions: - codingRate: 8 numChunks: 16 maxNumOperators: 3 + - codingRate: 8 + numChunks: 16 + maxNumOperators: 3 privateKeys: ecdsaMap: diff --git a/inabox/templates/testconfig-anvil.yaml b/inabox/templates/testconfig-anvil.yaml index 6e04dc716e..8975b5b164 100644 --- a/inabox/templates/testconfig-anvil.yaml +++ b/inabox/templates/testconfig-anvil.yaml @@ -16,10 +16,14 @@ eigenda: # NOTE: This uses a different blob version configuration than live deployments. # Live deployments typically use higher coding rates (e.g., 8) with more chunks and operators. # TODO: Investigate the revert that occurs when trying to use codingRate 2. +# Two blob versions are registered: version 0 for backward compatibility, version 1 as the new default. blobVersions: - codingRate: 8 numChunks: 16 maxNumOperators: 3 + - codingRate: 8 + numChunks: 16 + maxNumOperators: 3 privateKeys: ecdsaMap: From 725f235f5eb66fd91644f0f6597cb5440b64bb2b Mon Sep 17 00:00:00 2001 From: litt3 <102969658+litt3@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:38:03 -0500 Subject: [PATCH 5/5] Add better documentation Signed-off-by: litt3 <102969658+litt3@users.noreply.github.com> --- api/clients/v2/payload_client_config.go | 4 ++++ disperser/apiserver/disperse_blob_v2.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/api/clients/v2/payload_client_config.go b/api/clients/v2/payload_client_config.go index dd2c203475..7415da3d38 100644 --- a/api/clients/v2/payload_client_config.go +++ b/api/clients/v2/payload_client_config.go @@ -26,6 +26,10 @@ type PayloadClientConfig struct { // // BlobVersion needs to point to a version defined in the threshold registry contract. // https://github.com/Layr-Labs/eigenda/blob/3ed9ef6ed3eb72c46ce3050eb84af28f0afdfae2/contracts/src/interfaces/IEigenDAThresholdRegistry.sol#L6 + // + // Version > 0 signals to the disperser that anchor signatures are required, preventing replay attacks. + // Since the blob key includes the version, an attacker cannot strip the anchor signature and replay + // the request as version=0. BlobVersion v2.BlobVersion } diff --git a/disperser/apiserver/disperse_blob_v2.go b/disperser/apiserver/disperse_blob_v2.go index a389dad8f0..e17f66489f 100644 --- a/disperser/apiserver/disperse_blob_v2.go +++ b/disperser/apiserver/disperse_blob_v2.go @@ -276,6 +276,13 @@ func (s *DispersalServerV2) validateDispersalRequest( // If an anchor signature is provided, it will be validated regardless of blob version. // While validating the anchor signature, this method will also verify that the disperser ID and chain ID in the request // match the expected values. +// +// Version-based enforcement rationale: +// We co-opt the BlobVersion field to signal client capability. Without this, an attacker could strip the anchor +// signature from a request and replay it, since we can't reject unsigned requests outright (that would break legacy +// clients). By requiring anchor signatures for version > 0, the blob key hash changes, making it impossible to forge +// a valid version=0 signature from a version=1 request. This workaround is necessary since there isn't a "proper" blob +// version field included in the hash, so there are limited options available to communicate version compatibility. func (s *DispersalServerV2) validateAnchorSignature( req *pb.DisperseBlobRequest, blobHeader *corev2.BlobHeader,