From ba16cbc29b859e87f6b12266161bc1c3eaa1b717 Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 26 Jun 2026 08:26:48 -0500 Subject: [PATCH 1/6] Initial push of content --- .../integrations/bp-kms-signing-services.mdx | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx diff --git a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx new file mode 100644 index 0000000000..4a06535dad --- /dev/null +++ b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx @@ -0,0 +1,61 @@ +--- +title: 'Batch poster: External signing (KMS)' +sidebar_label: 'KMS and signing services' +description: 'Learn how to integrate external signing—including AWS KMS for the batch poster for your chain.' +author: pete-vielhaber +sme: jason-w123 +user_story: As an Arbitrum chain operator, I want to integrate KMS and signing services for my batch poster. +content_type: how-to +--- + +Nitro's batch poster (and staker) sign their L1 transaction through the **DataPoster** component. By default it signs locally with a private key, but it also supports **generic RPC-based external signing**: instead of holding the key, Nitro sends an unsigned transaction to a remote signer over (m)TLS, gets back a signed transaction, and independently verifies it. + +:::info + +There is **no native AWS KMS integration** in the Nitro codebase. KMS support is achieved by running a _separate signer service_ that talks to KMS and exposes an Ethereum-style `eth_signTransaction` RPC endpoint. Nitro connects to that endpoint. In other words: "KMS support" = external signer pointed at a KMS-backend signing service. + +::: + +## How it works internally + +1. **Connect**—`rpcClient()` dials the signer URL with a TLS config: optional client cert/key for mTLS (`ClientCert`/`ClientPrivateKey`), optional `RootCA` (lets you use self-signed certs), and `InsecureSkipVerify`. +2. **Sign**—`externalSigner()` returns a signer callback that: + +- Converts the transaction to `apitypes.SendTxArgs` via `TxToSignTxArgs` + - Noter it fully supports EIP-4844 blob transactions (blobs, commitments, proofs), which the batch poster needs. +- Calls the configured RPC method: `client.CallContext(ctx, &data, opts.Method, args)`, expecting an RLP-encoded signed transaction back. +- **Verifies** the returned transaction: the hash must match the request and the recovered sender must equal the configured `Address`. This means TLS is _not_ relied on for authentication—the signature itself is checked at the application layer. + +## Configuration + +The config struct is `ExternalSignerCfg`: + +| Field | koanf key | Purpose | +| -------------------- | ---------------------- | -------------------------------------------------------------------------------------------- | +| `URL` | `url` | RPC endpoint of the signer. **Setting this enables external signing** (overrides local key). | +| `Address` | `address` | Hex Ethereum address the signer controls; used to verify returned signatures. | +| `Method` | `method` | RPC method name, e.g., `eth_signTransaction`. | +| `RootCA` | `root-ca` | (Optional) CA cert to trust — enables self-signed server certs. | +| `ClientCert` | `client-cert` | (Optional) client cert for mTLS. | +| `ClientPrivateKey` | `client-private-key` | (Optional) client key for mTLS (required if `client-cert` set). | +| `InsecureSkipVerify` | `insecure-skip-verify` | Skip server TLS verification (not recommended). | + +This config is nested under both the batch poster and the staker. So tghe full CLI flag paths are: + +**Batch poster:** + +``` +--node.batch-poster.data-poster.external-signer.url +--node.batch-poster.data-poster.external-signer.address +--node.batch-poster.data-poster.external-signer.method +--node.batch-poster.data-poster.external-signer.root-ca +--node.batch-poster.data-poster.external-signer.client-cert +--node.batch-poster.data-poster.external-signer.client-private-key +--node.batch-poster.data-poster.external-signer.insecure-skip-verify +``` + +**Staker** uses the same fields under `--node.staker.data-poster.external-signer.*` + +When `external-signer.url` is empty, the batch poster requires a local key. The check in `cmd/nitro/nitro.go` confirms this — and note that **AnyTrust mode still needs a local key** (external signing isn't supported alongside AnyTrust there). + +## What the signer service must implement From 678d3cbd17b2cde1538e2fd529128cb098de1b53 Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 26 Jun 2026 11:24:50 -0500 Subject: [PATCH 2/6] Adding kms signing services doc --- .../integrations/bp-kms-signing-services.mdx | 25 +++++++++++++++++-- sidebars.js | 5 ++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx index 4a06535dad..7aba0a4e6b 100644 --- a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx +++ b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx @@ -40,7 +40,7 @@ The config struct is `ExternalSignerCfg`: | `ClientPrivateKey` | `client-private-key` | (Optional) client key for mTLS (required if `client-cert` set). | | `InsecureSkipVerify` | `insecure-skip-verify` | Skip server TLS verification (not recommended). | -This config is nested under both the batch poster and the staker. So tghe full CLI flag paths are: +This config is nested under both the batch poster and the staker. So the full CLI flag paths are: **Batch poster:** @@ -56,6 +56,27 @@ This config is nested under both the batch poster and the staker. So tghe full C **Staker** uses the same fields under `--node.staker.data-poster.external-signer.*` -When `external-signer.url` is empty, the batch poster requires a local key. The check in `cmd/nitro/nitro.go` confirms this — and note that **AnyTrust mode still needs a local key** (external signing isn't supported alongside AnyTrust there). +When `external-signer.url` is empty, the batch poster requires a local key. The check in `cmd/nitro/nitro.go` confirms this—and note that **AnyTrust mode still needs a local key** (external signing isn't supported alongside AnyTrust there). ## What the signer service must implement + +Your signer (whether KMS-backed or otherwise) must expose an HTTPS RPC server with a method matching `Method`, that: + +- Accepts an Ethereum transaction object (`apitypes.SendTxArgs`—the standard `eth_signTransaction` shape, including blob fields for EIP-4844), +- Returns the RLP-encoded **signed** transaction as a hex string, +- Signs with the key for the address you configured as `address`. + +If you use mTLS, the server must require and verify the client cert that matches `client-cert`/`client-private-key`. + +## Reference implementations + +Nitro ships two working examples you can model a KMS service on: + +- **`cmd/mockexternalsigner/mockexternalsigner.go`**—a standalone signer binary. It + builds an `rpc.Server`, registers a signing method, serves over HTTPS with + `tls.RequireAndVerifyClientCert`, and prints the exact flags to pass to Nitro. Swap the + local-key `txOpts.Signer` for a KMS-backed signer and you have a KMS integration. +- **`arbnode/dataposter/externalsignertest/externalsignertest.go`**—the test harness, + showing the server side (`SignerAPI`, method registration, cert setup with + `RequireAndVerifyClientCert`). The RPC method (`externalsignertest.go:185`) takes + `*apitypes.SendTxArgs` and returns the RLP-encoded signed tx as `hexutil.Bytes`. diff --git a/sidebars.js b/sidebars.js index cb1717685d..c8d48261f0 100644 --- a/sidebars.js +++ b/sidebars.js @@ -447,6 +447,11 @@ const sidebars = { id: 'launch-arbitrum-chain/integrations/infrastructure-providers', label: 'Infrastructure providers', }, + { + type: 'doc', + id: 'launch-arbitrum-chain/integrations/bp-kms-signing-services', + label: 'KMS signing services', + }, ], }, { From 9419820fcba8feba2cb415c66a3981039805d075 Mon Sep 17 00:00:00 2001 From: Pete Date: Fri, 26 Jun 2026 11:26:40 -0500 Subject: [PATCH 3/6] Style --- .../integrations/bp-kms-signing-services.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx index 7aba0a4e6b..ec6a485196 100644 --- a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx +++ b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx @@ -12,7 +12,7 @@ Nitro's batch poster (and staker) sign their L1 transaction through the **DataPo :::info -There is **no native AWS KMS integration** in the Nitro codebase. KMS support is achieved by running a _separate signer service_ that talks to KMS and exposes an Ethereum-style `eth_signTransaction` RPC endpoint. Nitro connects to that endpoint. In other words: "KMS support" = external signer pointed at a KMS-backend signing service. +There is **no native AWS KMS integration** in the Nitro codebase. KMS support is achieved by running a _separate signer service_ that talks to KMS and exposes an Ethereum-style `eth_signTransaction` RPC endpoint. Nitro connects to that endpoint. In other words: "KMS support" = external signer pointed at a KMS-backed signing service. ::: @@ -22,7 +22,7 @@ There is **no native AWS KMS integration** in the Nitro codebase. KMS support is 2. **Sign**—`externalSigner()` returns a signer callback that: - Converts the transaction to `apitypes.SendTxArgs` via `TxToSignTxArgs` - - Noter it fully supports EIP-4844 blob transactions (blobs, commitments, proofs), which the batch poster needs. + - Note that it fully supports EIP-4844 blob transactions (blobs, commitments, proofs), which the batch poster needs. - Calls the configured RPC method: `client.CallContext(ctx, &data, opts.Method, args)`, expecting an RLP-encoded signed transaction back. - **Verifies** the returned transaction: the hash must match the request and the recovered sender must equal the configured `Address`. This means TLS is _not_ relied on for authentication—the signature itself is checked at the application layer. From 59a109f782f23a99c3d65e87de3efb7ffd0f5a53 Mon Sep 17 00:00:00 2001 From: Pete Date: Tue, 30 Jun 2026 14:47:43 -0500 Subject: [PATCH 4/6] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaël Blanchemain --- .../integrations/bp-kms-signing-services.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx index ec6a485196..1e86f34091 100644 --- a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx +++ b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx @@ -1,14 +1,14 @@ --- title: 'Batch poster: External signing (KMS)' sidebar_label: 'KMS and signing services' -description: 'Learn how to integrate external signing—including AWS KMS for the batch poster for your chain.' +description: 'Learn how to integrate external signing—including AWS KMS—for your chain's batch poster.' author: pete-vielhaber sme: jason-w123 user_story: As an Arbitrum chain operator, I want to integrate KMS and signing services for my batch poster. content_type: how-to --- -Nitro's batch poster (and staker) sign their L1 transaction through the **DataPoster** component. By default it signs locally with a private key, but it also supports **generic RPC-based external signing**: instead of holding the key, Nitro sends an unsigned transaction to a remote signer over (m)TLS, gets back a signed transaction, and independently verifies it. +Nitro's batch poster (and staker) sign their L1 transactions through the **DataPoster** component. By default it signs locally with a private key, but it also supports **generic RPC-based external signing**: instead of holding the key, Nitro sends an unsigned transaction to a remote signer over (m)TLS, gets back a signed transaction, and independently verifies it. :::info @@ -22,7 +22,7 @@ There is **no native AWS KMS integration** in the Nitro codebase. KMS support is 2. **Sign**—`externalSigner()` returns a signer callback that: - Converts the transaction to `apitypes.SendTxArgs` via `TxToSignTxArgs` - - Note that it fully supports EIP-4844 blob transactions (blobs, commitments, proofs), which the batch poster needs. + - It fully supports EIP-4844 blob transactions (blobs, commitments, proofs), which the batch poster needs. - Calls the configured RPC method: `client.CallContext(ctx, &data, opts.Method, args)`, expecting an RLP-encoded signed transaction back. - **Verifies** the returned transaction: the hash must match the request and the recovered sender must equal the configured `Address`. This means TLS is _not_ relied on for authentication—the signature itself is checked at the application layer. @@ -40,7 +40,7 @@ The config struct is `ExternalSignerCfg`: | `ClientPrivateKey` | `client-private-key` | (Optional) client key for mTLS (required if `client-cert` set). | | `InsecureSkipVerify` | `insecure-skip-verify` | Skip server TLS verification (not recommended). | -This config is nested under both the batch poster and the staker. So the full CLI flag paths are: +This config is nested under both the batch poster and the staker, so the full CLI flag paths are: **Batch poster:** @@ -54,13 +54,13 @@ This config is nested under both the batch poster and the staker. So the full CL --node.batch-poster.data-poster.external-signer.insecure-skip-verify ``` -**Staker** uses the same fields under `--node.staker.data-poster.external-signer.*` +**Staker** uses the same fields under `--node.staker.data-poster.external-signer.*`. -When `external-signer.url` is empty, the batch poster requires a local key. The check in `cmd/nitro/nitro.go` confirms this—and note that **AnyTrust mode still needs a local key** (external signing isn't supported alongside AnyTrust there). +When `external-signer.url` is empty, the batch poster requires a local key. The check in `cmd/nitro/nitro.go` confirms this—and **AnyTrust mode still needs a local key** (external signing isn't supported alongside AnyTrust). ## What the signer service must implement -Your signer (whether KMS-backed or otherwise) must expose an HTTPS RPC server with a method matching `Method`, that: +Your signer (whether KMS-backed or otherwise) must expose an HTTPS RPC server with a method matching `Method` that: - Accepts an Ethereum transaction object (`apitypes.SendTxArgs`—the standard `eth_signTransaction` shape, including blob fields for EIP-4844), - Returns the RLP-encoded **signed** transaction as a hex string, From b71836eaeba275d0a3c200b2f630e845d3d0612c Mon Sep 17 00:00:00 2001 From: Pete Date: Wed, 1 Jul 2026 08:57:52 -0500 Subject: [PATCH 5/6] Fixing frontmatter --- .../integrations/bp-kms-signing-services.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx index 1e86f34091..fa09a0033a 100644 --- a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx +++ b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx @@ -1,7 +1,7 @@ --- title: 'Batch poster: External signing (KMS)' sidebar_label: 'KMS and signing services' -description: 'Learn how to integrate external signing—including AWS KMS—for your chain's batch poster.' +description: "Learn how to integrate external signing—including AWS KMS—for your chain's batch poster." author: pete-vielhaber sme: jason-w123 user_story: As an Arbitrum chain operator, I want to integrate KMS and signing services for my batch poster. From 22390fc54ed3994e6ec13f3e7e7f2eb74d8759e2 Mon Sep 17 00:00:00 2001 From: Pete Date: Thu, 2 Jul 2026 09:23:18 -0500 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Jason-W123 <147362502+Jason-W123@users.noreply.github.com> --- .../integrations/bp-kms-signing-services.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx index fa09a0033a..d182587259 100644 --- a/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx +++ b/docs/launch-arbitrum-chain/integrations/bp-kms-signing-services.mdx @@ -8,7 +8,7 @@ user_story: As an Arbitrum chain operator, I want to integrate KMS and signing s content_type: how-to --- -Nitro's batch poster (and staker) sign their L1 transactions through the **DataPoster** component. By default it signs locally with a private key, but it also supports **generic RPC-based external signing**: instead of holding the key, Nitro sends an unsigned transaction to a remote signer over (m)TLS, gets back a signed transaction, and independently verifies it. +Nitro's batch poster (and staker) sign their parent chain transactions through the **DataPoster** component. By default it signs locally with a private key, but it also supports **generic RPC-based external signing**: instead of holding the key, Nitro sends an unsigned transaction to a remote signer over (m)TLS, gets back a signed transaction, and independently verifies it. :::info @@ -56,7 +56,7 @@ This config is nested under both the batch poster and the staker, so the full CL **Staker** uses the same fields under `--node.staker.data-poster.external-signer.*`. -When `external-signer.url` is empty, the batch poster requires a local key. The check in `cmd/nitro/nitro.go` confirms this—and **AnyTrust mode still needs a local key** (external signing isn't supported alongside AnyTrust). +When `external-signer.url` is empty, the batch poster requires a local key. **AnyTrust chains need a local key even when external signing is enabled**: the external signer covers the batch transactions posted to the parent chain, but the requests sent to the DA Committee are still signed with a local key. See [External signer support](/launch-arbitrum-chain/chain-config/data-availability/deploy-das.mdx#external-signer-support) for how to configure the separate key. ## What the signer service must implement @@ -74,7 +74,7 @@ Nitro ships two working examples you can model a KMS service on: - **`cmd/mockexternalsigner/mockexternalsigner.go`**—a standalone signer binary. It builds an `rpc.Server`, registers a signing method, serves over HTTPS with - `tls.RequireAndVerifyClientCert`, and prints the exact flags to pass to Nitro. Swap the + `tls.RequireAndVerifyClientCert`. Swap the local-key `txOpts.Signer` for a KMS-backed signer and you have a KMS integration. - **`arbnode/dataposter/externalsignertest/externalsignertest.go`**—the test harness, showing the server side (`SignerAPI`, method registration, cert setup with