Skip to content

[docs] cli commands #824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions toolkit/cli/commands/src/address_association_signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,25 @@ use serde_json::json;
use sidechain_domain::*;
use std::str::FromStr;

/// Generates Ed25519 signatures to associate Cardano stake addresses with Partner Chain addresses.
/// Generic over address type to support different Partner Chain implementations.
#[derive(Clone, Debug, Parser)]
#[command(author, version, about, long_about = None)]
pub struct AddressAssociationSignaturesCmd<
PartnerchainAddress: Clone + Sync + Send + FromStr + 'static,
> {
/// Genesis UTXO of the target Partner Chain
/// Genesis UTXO that identifies the target Partner Chain
#[arg(long)]
pub genesis_utxo: UtxoId,
/// Partner Chain address to be associated with the Cardano address
/// Partner Chain address to be associated with the Cardano stake address
#[arg(long, value_parser=parse_pc_address::<PartnerchainAddress>)]
pub partnerchain_address: PartnerchainAddress,
/// Cardano Stake Signing Key bytes in hex format. Its public key will be associated with partnerchain_address.
/// Ed25519 signing key for the Cardano stake address. Its public key will be associated with partnerchain_address.
#[arg(long)]
pub signing_key: StakeSigningKeyParam,
}

/// Parses Partner Chain address from string format.
fn parse_pc_address<T: FromStr>(s: &str) -> Result<T, String> {
T::from_str(s).map_err(|_| "Failed to parse Partner Chain address".to_owned())
}
Expand All @@ -33,6 +36,7 @@ impl<PartnerchainAddress> AddressAssociationSignaturesCmd<PartnerchainAddress>
where
PartnerchainAddress: Serialize + Clone + Sync + Send + FromStr + Encode + 'static,
{
/// Generates signature and outputs JSON to stdout.
pub fn execute(&self) -> anyhow::Result<()> {
let signature = self.sign();
let output = json!({
Expand All @@ -45,6 +49,7 @@ where
Ok(())
}

/// Signs SCALE-encoded address association message with Ed25519.
fn sign(&self) -> ByteString {
let msg = AddressAssociationSignedMessage {
stake_public_key: self.signing_key.vkey(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ use sidechain_domain::*;
use sp_block_producer_metadata::MetadataSignedMessage;
use std::io::{BufReader, Read};

/// Generates ECDSA signatures for block producer metadata using cross-chain keys.
#[derive(Clone, Debug, Parser)]
#[command(author, version, about, long_about = None)]
pub struct BlockProducerMetadataSignatureCmd {
/// Genesis UTXO of the target Partner Chain
/// Genesis UTXO that uniquely identifies the target Partner Chain
#[arg(long)]
pub genesis_utxo: UtxoId,
/// Path of the file containing the metadata in JSON format
/// Path to JSON file containing the metadata to be signed
#[arg(long)]
pub metadata_file: String,
/// ECDSA signing key of the block producer, corresponding to the public key that will be associated with new metadata
/// ECDSA private key for cross-chain operations, corresponding to the block producer's identity
#[arg(long)]
pub cross_chain_signing_key: CrossChainSigningKeyParam,
}

impl BlockProducerMetadataSignatureCmd {
/// Reads metadata file, generates signatures, and outputs JSON to stdout.
pub fn execute<M: Send + Sync + DeserializeOwned + Encode>(&self) -> anyhow::Result<()> {
let file = std::fs::File::open(self.metadata_file.clone())
.map_err(|err| anyhow!("Failed to open file {}: {err}", self.metadata_file))?;
Expand All @@ -35,6 +37,7 @@ impl BlockProducerMetadataSignatureCmd {
Ok(())
}

/// Generates ECDSA signatures for JSON metadata from reader.
pub fn get_output<M: Send + Sync + DeserializeOwned + Encode>(
&self,
metadata_reader: impl Read,
Expand Down
5 changes: 3 additions & 2 deletions toolkit/cli/commands/src/get_genesis_utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use sp_runtime::traits::Block as BlockT;
use sp_sidechain::GetGenesisUtxo;
use std::sync::Arc;

/// Retrieves the genesis UTXO from the on-chain storage.
/// This function should be used by a CLI command.
/// Queries the genesis UTXO from Partner Chain storage via runtime API.
pub async fn execute<B, C>(client: Arc<C>) -> Result<String, String>
where
B: BlockT,
Expand All @@ -22,7 +21,9 @@ where
Ok(output)
}

/// Output structure for genesis UTXO query results.
#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Debug)]
struct Output {
/// The genesis UTXO that uniquely identifies this Partner Chain instance
pub genesis_utxo: UtxoId,
}
17 changes: 17 additions & 0 deletions toolkit/cli/commands/src/key_params.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
//! CLI parameter wrappers for cryptographic keys with secure parsing and validation.

use sidechain_domain::*;
use std::convert::Infallible;
use std::fmt::Display;
use std::io;
use std::io::ErrorKind;
use std::str::FromStr;

/// ECDSA private key wrapper for Partner Chain validator operations.
#[derive(Clone, Debug)]
pub struct SidechainSigningKeyParam(pub secp256k1::SecretKey);

impl SidechainSigningKeyParam {
/// Derives the corresponding ECDSA public key.
pub fn to_pub_key(&self) -> secp256k1::PublicKey {
secp256k1::PublicKey::from_secret_key_global(&self.0)
}
Expand All @@ -24,6 +28,7 @@ impl FromStr for SidechainSigningKeyParam {
}
}

/// ECDSA public key wrapper for Partner Chain operations.
#[derive(Clone, Debug)]
pub struct SidechainPublicKeyParam(pub SidechainPublicKey);

Expand All @@ -43,6 +48,7 @@ impl FromStr for SidechainPublicKeyParam {
}
}

/// Generic string wrapper for public keys without cryptographic validation.
#[derive(Clone, Debug)]
pub struct PlainPublicKeyParam(pub String);

Expand All @@ -60,10 +66,13 @@ impl FromStr for PlainPublicKeyParam {
}
}

/// Error types that can occur during Ed25519 signing key parsing.
#[derive(Debug, thiserror::Error)]
pub enum Ed25519SigningKeyError {
/// Hexadecimal decoding error
#[error("{0}")]
HexError(#[from] hex::FromHexError),
/// Ed25519 key validation error
#[error("{0}")]
Ed25519Error(#[from] ed25519_zebra::Error),
}
Expand All @@ -74,13 +83,15 @@ impl From<Ed25519SigningKeyError> for io::Error {
}
}

/// Parses hex string into Ed25519 signing key with validation.
pub(crate) fn parse_zebra_signing_key(
s: &str,
) -> Result<ed25519_zebra::SigningKey, Ed25519SigningKeyError> {
let trimmed = s.trim_start_matches("0x");
Ok(ed25519_zebra::SigningKey::try_from(hex::decode(trimmed)?.as_slice())?)
}

/// Ed25519 private key wrapper for Cardano stake pool operations.
#[derive(Clone, Debug)]
pub struct StakePoolSigningKeyParam(pub ed25519_zebra::SigningKey);

Expand All @@ -93,17 +104,20 @@ impl FromStr for StakePoolSigningKeyParam {
}

impl From<[u8; 32]> for StakePoolSigningKeyParam {
/// Creates signing key from 32-byte array.
fn from(key: [u8; 32]) -> Self {
Self(ed25519_zebra::SigningKey::from(key))
}
}

impl StakePoolSigningKeyParam {
/// Derives the corresponding public key.
pub fn vkey(&self) -> StakePoolPublicKey {
StakePoolPublicKey(ed25519_zebra::VerificationKey::from(&self.0).into())
}
}

/// Ed25519 private key wrapper for Cardano staking operations.
#[derive(Clone, Debug)]
pub struct StakeSigningKeyParam(pub ed25519_zebra::SigningKey);

Expand All @@ -116,11 +130,13 @@ impl FromStr for StakeSigningKeyParam {
}

impl StakeSigningKeyParam {
/// Derives the corresponding public key.
pub fn vkey(&self) -> StakePublicKey {
StakePublicKey(ed25519_zebra::VerificationKey::from(&self.0).into())
}
}

/// ECDSA private key wrapper for cross-chain operations.
#[derive(Clone, Debug)]
pub struct CrossChainSigningKeyParam(pub k256::SecretKey);

Expand All @@ -133,6 +149,7 @@ impl FromStr for CrossChainSigningKeyParam {
}

impl CrossChainSigningKeyParam {
/// Derives the corresponding ECDSA public key.
pub fn vkey(&self) -> CrossChainPublicKey {
CrossChainPublicKey(self.0.public_key().to_sec1_bytes().to_vec())
}
Expand Down
5 changes: 5 additions & 0 deletions toolkit/cli/commands/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! CLI command structures for Partner Chains cryptographic operations.
//!
//! Provides clap-based commands for signature generation, address association,
//! and blockchain queries. Used by `partner-chains-node-commands`.

pub mod address_association_signatures;
pub mod block_producer_metadata_signatures;
pub mod get_genesis_utxo;
Expand Down
17 changes: 17 additions & 0 deletions toolkit/cli/commands/src/registration_signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,26 @@ use sidechain_domain::{
};
use std::fmt::{Display, Formatter};

/// Generates dual signatures (Ed25519 + ECDSA) for Partner Chain validator registration.
#[derive(Clone, Debug, Parser)]
#[command(author, version, about, long_about = None)]
pub struct RegistrationSignaturesCmd {
/// Genesis UTXO that uniquely identifies the target Partner Chain
#[arg(long)]
pub genesis_utxo: UtxoId,
/// Bytes of the Cardano Stake Pool Signing Key. Bytes of 'cbor' field of a Cardano key file content, after dropping the '5820' prefix.
#[arg(long)]
pub mainchain_signing_key: StakePoolSigningKeyParam,
/// ECDSA private key for the Partner Chain validator
#[arg(long)]
pub sidechain_signing_key: SidechainSigningKeyParam,
/// UTXO to be spend during validator registration transaction
#[arg(long)]
pub registration_utxo: UtxoId,
}

impl RegistrationSignaturesCmd {
/// Creates the structured message that will be signed by both mainchain and sidechain keys.
pub fn to_register_validator_message(&self, genesis_utxo: UtxoId) -> RegisterValidatorMessage {
RegisterValidatorMessage::new(
genesis_utxo,
Expand All @@ -33,6 +38,7 @@ impl RegistrationSignaturesCmd {
)
}

/// Generates mainchain and sidechain signatures with public keys.
pub fn execute(&self) -> RegistrationCmdOutput {
self.to_register_validator_message(self.genesis_utxo)
.sign_and_prepare_registration_cmd_output(
Expand All @@ -42,11 +48,16 @@ impl RegistrationSignaturesCmd {
}
}

/// Complete registration output with signatures and public keys for both chains.
#[derive(Clone, Debug, Serialize)]
pub struct RegistrationCmdOutput {
/// Ed25519 public key of the Cardano stake pool operator
pub spo_public_key: StakePoolPublicKey,
/// Ed25519 signature from the stake pool operator
pub spo_signature: MainchainSignature,
/// ECDSA public key for Partner Chain operations
pub sidechain_public_key: SidechainPublicKey,
/// ECDSA signature from the Partner Chain validator key
pub sidechain_signature: SidechainSignature,
}

Expand All @@ -59,14 +70,19 @@ impl Display for RegistrationCmdOutput {
}
}

/// Message structure for validator registration signatures.
#[derive(Clone, Debug, ToDatum)]
pub struct RegisterValidatorMessage {
/// Genesis UTXO identifying the specific Partner Chain instance
pub genesis_utxo: UtxoId,
/// ECDSA public key for the validator on the Partner Chain
pub sidechain_pub_key: SidechainPublicKey,
/// UTXO consumed in the registration transaction for uniqueness
pub registration_utxo: UtxoId,
}

impl RegisterValidatorMessage {
/// Creates new validator registration message.
pub fn new(
genesis_utxo: UtxoId,
pub_key: secp256k1::PublicKey,
Expand All @@ -79,6 +95,7 @@ impl RegisterValidatorMessage {
}
}

/// Signs message with both mainchain and sidechain keys.
pub fn sign_and_prepare_registration_cmd_output(
&self,
mainchain_key: ed25519_zebra::SigningKey,
Expand Down
Loading