From 8f428b56fb11b1132d8dfba144d6afe0e2afdc25 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Thu, 13 Aug 2026 10:32:26 +0200 Subject: [PATCH 1/2] Register a stake pool's BLS voting key Adds --bls-signing-key-file to 'stake-pool registration-certificate'. The option is era-gated: mandatory from Dijkstra onwards and not offered at all in Conway or earlier, so existing invocations are unaffected. The signing key is read as a text envelope and turned into the registered verification key plus proof of possession via 'createBlsKeyRegistration'. Genesis pool parameters get 'sppBlsKey = SNothing', which cardano-ledger-core 1.22 requires at every StakePoolParams construction site, and the compatible pre-Dijkstra path sets 'stakePoolBlsKey = Nothing'. Note the option is not yet reachable: master only exposes 'node' under the 'dijkstra' subcommand, so 'pStakePoolCmds' is instantiated at ConwayEra only and the Dijkstra branch of the parser is dormant. It activates as soon as the era-based stake-pool commands are wired up for Dijkstra. Golden help output is therefore unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- ..._cardano-cli_sebastian.nagel_pool_reg_bls_key.yml | 8 ++++++++ .../src/Cardano/CLI/Compatible/StakePool/Run.hs | 1 + .../CLI/EraBased/Genesis/CreateTestnetData/Run.hs | 1 + cardano-cli/src/Cardano/CLI/EraBased/Genesis/Run.hs | 1 + .../src/Cardano/CLI/EraBased/StakePool/Command.hs | 3 +++ .../src/Cardano/CLI/EraBased/StakePool/Option.hs | 12 ++++++++++++ .../src/Cardano/CLI/EraBased/StakePool/Run.hs | 12 +++++++++++- .../src/Cardano/CLI/EraIndependent/Node/Option.hs | 1 + 8 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml diff --git a/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml b/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml new file mode 100644 index 0000000000..928c3772b9 --- /dev/null +++ b/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml @@ -0,0 +1,8 @@ +description: | + `stake-pool registration-certificate` can now register a stake pool's BLS voting key. The new `--bls-signing-key-file FILEPATH` option is era-gated: it is mandatory from the Dijkstra era onwards and not offered in Conway or earlier, so existing Conway invocations are unaffected. The signing key is read as a text envelope (produce one with `node key-gen-BLS`) and converted to the registered verification key plus proof of possession via `createBlsKeyRegistration`. + + Genesis pool parameters built by `genesis create-staked` and `genesis create-testnet-data` set the new ledger `sppBlsKey` field to `SNothing`. +kind: + - feature +pr: 0 +project: cardano-cli diff --git a/cardano-cli/src/Cardano/CLI/Compatible/StakePool/Run.hs b/cardano-cli/src/Cardano/CLI/Compatible/StakePool/Run.hs index 16730d4a69..26058b6d1f 100644 --- a/cardano-cli/src/Cardano/CLI/Compatible/StakePool/Run.hs +++ b/cardano-cli/src/Cardano/CLI/Compatible/StakePool/Run.hs @@ -84,6 +84,7 @@ runStakePoolRegistrationCertificateCmd StakePoolParameters { stakePoolId = stakePoolId' , stakePoolVRF = vrfKeyHash' + , stakePoolBlsKey = Nothing , stakePoolCost = poolCost , stakePoolMargin = poolMargin , stakePoolRewardAccount = rewardAccountAddr diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Genesis/CreateTestnetData/Run.hs b/cardano-cli/src/Cardano/CLI/EraBased/Genesis/CreateTestnetData/Run.hs index d43852bf05..e44156145a 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Genesis/CreateTestnetData/Run.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Genesis/CreateTestnetData/Run.hs @@ -805,6 +805,7 @@ buildPoolParams nw dir index specifiedRelays = do , L.sppOwners = mempty , L.sppRelays = lookupPoolRelay specifiedRelays , L.sppMetadata = L.SNothing + , L.sppBlsKey = L.SNothing } where lookupPoolRelay :: Map Word [L.StakePoolRelay] -> Seq.StrictSeq L.StakePoolRelay diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Genesis/Run.hs b/cardano-cli/src/Cardano/CLI/EraBased/Genesis/Run.hs index 093c768e38..5790354f82 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Genesis/Run.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Genesis/Run.hs @@ -989,6 +989,7 @@ buildPoolParams nw dir index specifiedRelays = do , L.sppOwners = mempty , L.sppRelays = lookupPoolRelay specifiedRelays , L.sppMetadata = L.SNothing + , L.sppBlsKey = L.SNothing } where lookupPoolRelay diff --git a/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Command.hs b/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Command.hs index 5c2a530bfa..48a4f6109f 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Command.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Command.hs @@ -74,6 +74,9 @@ data StakePoolRegistrationCertificateCmdArgs era -- ^ Stake pool verification key. , vrfVerificationKeyOrFile :: !(VerificationKeyOrFile VrfKey) -- ^ VRF Verification key. + , blsSkeyFile :: !(Maybe (SigningKeyFile In)) + -- ^ BLS signing key, from which the registered voting key and its proof of + -- possession are derived. Required from Dijkstra onwards, unavailable before. , poolPledge :: !Coin -- ^ Pool pledge. , poolCost :: !Coin diff --git a/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Option.hs b/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Option.hs index b5802be05c..a6f7787081 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Option.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Option.hs @@ -17,6 +17,7 @@ import Cardano.CLI.Environment (EnvCli (..)) import Cardano.CLI.EraBased.Common.Option import Cardano.CLI.EraBased.StakePool.Command qualified as Cmd import Cardano.CLI.EraIndependent.Hash.Command qualified as Cmd +import Cardano.CLI.EraIndependent.Node.Option (pBlsSigningKeyFile) import Cardano.CLI.Parser import Data.Foldable qualified as F @@ -107,6 +108,7 @@ pStakePoolRegistrationCertificateCmd envCli = do Cmd.StakePoolRegistrationCertificateCmdArgs (convert useEra) <$> pStakePoolVerificationKeyOrFile Nothing <*> pVrfVerificationKeyOrFile + <*> pBlsSigningKeyFileForEra <*> pPoolPledge <*> pPoolCost <*> pPoolMargin @@ -124,6 +126,16 @@ pStakePoolRegistrationCertificateCmd envCli = do ) $ Opt.progDesc "Create a stake pool registration certificate" +-- | A pool registers its voting key from Dijkstra onwards, so the BLS signing +-- key is mandatory there and not offered at all in earlier eras. +pBlsSigningKeyFileForEra + :: forall era + . IsEra era + => Parser (Maybe (SigningKeyFile In)) +pBlsSigningKeyFileForEra = case useEra @era of + ConwayEra -> pure Nothing + DijkstraEra -> Just <$> pBlsSigningKeyFile + pStakePoolDeregistrationCertificateCmd :: IsEra era => Maybe (Parser (Cmd.StakePoolCmds era)) pStakePoolDeregistrationCertificateCmd = do diff --git a/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Run.hs b/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Run.hs index a5a5ee4e5e..298880fb1f 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Run.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/StakePool/Run.hs @@ -43,7 +43,7 @@ import Cardano.CLI.Type.Error.HashCmdError (FetchURLError (..)) import Cardano.CLI.Type.Error.StakePoolCmdError import Cardano.CLI.Type.Key (readVerificationKeyOrFile) -import Control.Monad (when) +import Control.Monad (forM, when) import Data.ByteString.Char8 qualified as BS import Data.ByteString.Lazy qualified as LBS import Data.Function ((&)) @@ -77,6 +77,7 @@ runStakePoolRegistrationCertificateCmd { era , poolVerificationKeyOrFile , vrfVerificationKeyOrFile + , blsSkeyFile , poolPledge , poolCost , poolMargin @@ -114,10 +115,19 @@ runStakePoolRegistrationCertificateCmd ownerStakeVerificationKeyOrFiles let stakePoolOwners' = map verificationKeyHash sPoolOwnerVkeys + -- BLS voting key, registered from Dijkstra onwards + mBlsKey <- + forM blsSkeyFile $ \skeyFile -> do + blsSkey <- + fromEitherIOCli @(FileError TextEnvelopeError) $ + readFileTextEnvelope @(SigningKey BlsKey) skeyFile + pure $ createBlsKeyRegistration blsSkey + let stakePoolParams = StakePoolParameters { stakePoolId = stakePoolId' , stakePoolVRF = vrfKeyHash' + , stakePoolBlsKey = mBlsKey , stakePoolCost = poolCost , stakePoolMargin = poolMargin , stakePoolRewardAccount = rewardAccountAddr diff --git a/cardano-cli/src/Cardano/CLI/EraIndependent/Node/Option.hs b/cardano-cli/src/Cardano/CLI/EraIndependent/Node/Option.hs index ae8be2d3f5..551fb25c64 100644 --- a/cardano-cli/src/Cardano/CLI/EraIndependent/Node/Option.hs +++ b/cardano-cli/src/Cardano/CLI/EraIndependent/Node/Option.hs @@ -7,6 +7,7 @@ module Cardano.CLI.EraIndependent.Node.Option ( pNodeCmds + , pBlsSigningKeyFile ) where From 5ff15b80557d78b5eb27d7a117f8f4558a1aacba Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Thu, 13 Aug 2026 11:38:59 +0200 Subject: [PATCH 2/2] Expose stake-pool commands for the Dijkstra era Until now the `dijkstra` subcommand only offered `node`, so `stake-pool registration-certificate` was unreachable there and the era-gated `--bls-signing-key-file` could never fire. A pool's BLS voting key is only registrable from Dijkstra onwards, so the command has to be reachable for the key to be registrable at all. `runCmds` is already era-polymorphic and `runAnyEraCommand` already wraps it in `obtainCommonConstraints`, so no run-side change is needed -- this is purely parser wiring. Introduces `pDijkstraCmds` as the explicit list of command groups vetted for the new era, rather than switching Dijkstra over to the full `pCmds`. The remaining groups (query, transaction, genesis, governance, ...) can be added as each is checked over. New golden help files are required and must be generated with CREATE_GOLDEN_FILES=1. Co-Authored-By: Claude Opus 5 (1M context) --- ...ardano-cli_sebastian.nagel_pool_reg_bls_key.yml | 2 ++ cardano-cli/src/Cardano/CLI/EraBased/Option.hs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml b/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml index 928c3772b9..0de548ce3f 100644 --- a/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml +++ b/.changes/20260813_120000_cardano-cli_sebastian.nagel_pool_reg_bls_key.yml @@ -1,6 +1,8 @@ description: | `stake-pool registration-certificate` can now register a stake pool's BLS voting key. The new `--bls-signing-key-file FILEPATH` option is era-gated: it is mandatory from the Dijkstra era onwards and not offered in Conway or earlier, so existing Conway invocations are unaffected. The signing key is read as a text envelope (produce one with `node key-gen-BLS`) and converted to the registered verification key plus proof of possession via `createBlsKeyRegistration`. + The `dijkstra` subcommand now also exposes the `stake-pool` command group, so `cardano-cli dijkstra stake-pool registration-certificate` is reachable. Previously only `node` was wired up for that era. + Genesis pool parameters built by `genesis create-staked` and `genesis create-testnet-data` set the new ledger `sppBlsKey` field to `SNothing`. kind: - feature diff --git a/cardano-cli/src/Cardano/CLI/EraBased/Option.hs b/cardano-cli/src/Cardano/CLI/EraBased/Option.hs index adf63519da..10175edef8 100644 --- a/cardano-cli/src/Cardano/CLI/EraBased/Option.hs +++ b/cardano-cli/src/Cardano/CLI/EraBased/Option.hs @@ -45,6 +45,18 @@ pCmds envCli = do , fmap TransactionCmds <$> pTransactionCmds envCli ] +-- | The subset of era-based commands wired up for Dijkstra so far. Grows towards +-- 'pCmds' as each command group is checked over for the new era; a pool's BLS +-- voting key can only be registered from Dijkstra onwards, so @stake-pool@ has to +-- be reachable here. +pDijkstraCmds :: EnvCli -> Parser (Cmds DijkstraEra) +pDijkstraCmds envCli = + asum $ + catMaybes + [ Just (NodeCmds <$> pNodeCmds @DijkstraEra) + , fmap StakePoolCmds <$> pStakePoolCmds envCli + ] + pAnyEraCommand :: EnvCli -> Parser AnyEraCommand pAnyEraCommand envCli = asum @@ -54,7 +66,7 @@ pAnyEraCommand envCli = Opt.progDesc "Conway era commands" , Opt.hsubparser $ commandWithMetavar "dijkstra" $ - Opt.info (AnyEraCommandOf DijkstraEra <$> asum [NodeCmds <$> pNodeCmds @DijkstraEra]) $ + Opt.info (AnyEraCommandOf DijkstraEra <$> pDijkstraCmds envCli) $ Opt.progDesc "Dijkstra era commands" , Opt.hsubparser $ commandWithMetavar "latest" $