partner_chains_cli/register/
register2.rs

1use super::RegisterValidatorMessage;
2use super::{PlainPublicKeyParam, SidechainPublicKeyParam, StakePoolSigningKeyParam};
3use crate::CmdRun;
4use crate::cardano_key::get_mc_staking_signing_key_from_file;
5use crate::io::IOContext;
6use clap::Parser;
7use sidechain_domain::UtxoId;
8use sidechain_domain::crypto::cardano_spo_public_key_and_signature;
9
10#[derive(Clone, Debug, Parser)]
11#[command(author, version, about, long_about = None)]
12pub struct Register2Cmd {
13	#[arg(long)]
14	pub genesis_utxo: UtxoId,
15	#[arg(long)]
16	pub registration_utxo: UtxoId,
17	#[arg(long)]
18	pub sidechain_pub_key: SidechainPublicKeyParam,
19	#[arg(long)]
20	pub aura_pub_key: PlainPublicKeyParam,
21	#[arg(long)]
22	pub grandpa_pub_key: PlainPublicKeyParam,
23	#[arg(long)]
24	pub sidechain_signature: String,
25}
26
27impl CmdRun for Register2Cmd {
28	fn run<C: IOContext>(&self, context: &C) -> anyhow::Result<()> {
29		context.print("⚙️ Register as a committee candidate (step 2/3)");
30		context.print(
31			"  This command will use SPO cold signing key for signing the registration message.",
32		);
33
34		let stake_pool_signing_key_path =
35			context.prompt("Path to Stake Pool signing key file", Some("cold.skey"));
36		let mainchain_signing_key = get_stake_pool_cold_skey(context, &stake_pool_signing_key_path)
37			.inspect_err(|_| context.eprint("Unable to read Stake Pool signing key file"))?;
38
39		let registration_message = RegisterValidatorMessage {
40			genesis_utxo: self.genesis_utxo,
41			sidechain_pub_key: self.sidechain_pub_key.0.clone(),
42			registration_utxo: self.registration_utxo,
43		};
44
45		let (verification_key, spo_signature) =
46			cardano_spo_public_key_and_signature(mainchain_signing_key.0, registration_message);
47
48		let spo_public_key = verification_key.to_hex_string();
49		let spo_signature = spo_signature.to_hex_string();
50		let executable = context.current_executable()?;
51		context.print("To finish the registration process, run the following command on the machine with the partner chain dependencies running:\n");
52		context.print(&format!(
53			"{executable} wizards register3 \\\n--genesis-utxo {} \\\n--registration-utxo {} \\\n--aura-pub-key {} \\\n--grandpa-pub-key {} \\\n--partner-chain-pub-key {} \\\n--partner-chain-signature {} \\\n--spo-public-key {} \\\n--spo-signature {}",
54			self.genesis_utxo, self.registration_utxo, self.aura_pub_key, self.grandpa_pub_key, self.sidechain_pub_key, self.sidechain_signature, spo_public_key, spo_signature));
55		Ok(())
56	}
57}
58
59fn get_stake_pool_cold_skey<C: IOContext>(
60	context: &C,
61	keys_path: &str,
62) -> Result<StakePoolSigningKeyParam, anyhow::Error> {
63	Ok(StakePoolSigningKeyParam::from(get_mc_staking_signing_key_from_file(keys_path, context)?))
64}
65
66#[cfg(test)]
67mod tests {
68	use super::*;
69	use crate::tests::{MockIO, MockIOContext};
70
71	#[test]
72	fn happy_path() {
73		let mock_context = MockIOContext::new()
74			.with_json_file("/path/to/cold.skey", coldkey_content())
75			.with_expected_io(
76				vec![intro_msg_io(), prompt_mc_cold_key_path_io(), output_result_io()]
77					.into_iter()
78					.flatten()
79					.collect::<Vec<MockIO>>(),
80			);
81
82		let result = mock_register2_cmd().run(&mock_context);
83		result.expect("should succeed");
84	}
85
86	#[test]
87	fn invalid_stake_pool_signing_key() {
88		let mock_context = MockIOContext::new().with_expected_io(vec![
89			MockIO::Group(intro_msg_io()),
90			MockIO::prompt(
91				"Path to Stake Pool signing key file",
92				Some("cold.skey"),
93				"/invalid/cold.skey",
94			),
95			MockIO::eprint("Unable to read Stake Pool signing key file"),
96		]);
97
98		let result = mock_register2_cmd().run(&mock_context);
99		result.expect_err("should return error");
100	}
101
102	fn intro_msg_io() -> Vec<MockIO> {
103		vec![
104			MockIO::print("⚙️ Register as a committee candidate (step 2/3)"),
105			MockIO::print(
106				"  This command will use SPO cold signing key for signing the registration message.",
107			),
108		]
109	}
110
111	fn prompt_mc_cold_key_path_io() -> Vec<MockIO> {
112		vec![MockIO::prompt(
113			"Path to Stake Pool signing key file",
114			Some("cold.skey"),
115			"/path/to/cold.skey",
116		)]
117	}
118
119	fn output_result_io() -> Vec<MockIO> {
120		vec![
121			MockIO::print(
122				"To finish the registration process, run the following command on the machine with the partner chain dependencies running:\n",
123			),
124			MockIO::print(
125				"<mock executable> wizards register3 \\\n--genesis-utxo 0000000000000000000000000000000000000000000000000000000000000001#0 \\\n--registration-utxo 7e9ebd0950ae1bec5606f0cd7ac88b3c60b1103d7feb6ffa36402edae4d1b617#0 \\\n--aura-pub-key 0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777 \\\n--grandpa-pub-key 0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327 \\\n--partner-chain-pub-key 0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6 \\\n--partner-chain-signature 7a7e3e585a5dc248d4a2772814e1b58c90313443dd99369f994e960ecc4931442a08305743db7ab42ab9b8672e00250e1cc7c08bc018b0630a8197c4f95528a301 \\\n--spo-public-key 0xcef2d1630c034d3b9034eb7903d61f419a3074a1ad01d4550cc72f2b733de6e7 \\\n--spo-signature 0xaaa39fbf163ed77c69820536f5dc22854e7e13f964f1e077efde0844a09bde64c1aab4d2b401e0fe39b43c91aa931cad26fa55c8766378462c06d86c85134801",
126			),
127		]
128	}
129	// non 0 genesis utxo 8ea10040249ad3033ae7c4d4b69e0b2e2b50a90741b783491cb5ddf8ced0d861
130	fn mock_register2_cmd() -> Register2Cmd {
131		Register2Cmd {
132            genesis_utxo: "0000000000000000000000000000000000000000000000000000000000000001#0".parse().unwrap(),
133            registration_utxo: "7e9ebd0950ae1bec5606f0cd7ac88b3c60b1103d7feb6ffa36402edae4d1b617#0".parse().unwrap(),
134            sidechain_pub_key: "0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6".parse().unwrap(),
135            aura_pub_key: "0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777".parse().unwrap(),
136            grandpa_pub_key: "0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327".parse().unwrap(),
137            sidechain_signature: "7a7e3e585a5dc248d4a2772814e1b58c90313443dd99369f994e960ecc4931442a08305743db7ab42ab9b8672e00250e1cc7c08bc018b0630a8197c4f95528a301".parse().unwrap()
138        }
139	}
140
141	fn coldkey_content() -> serde_json::Value {
142		serde_json::json!({
143			"type": "StakePoolSigningKey_ed25519",
144			"description": "Stake Pool Operator Signing Key",
145			"cborHex": "58200c049bb92212b779ee8ba9550536d8103cc1892634f0d21dcaa8944f5e4bf718"
146		})
147	}
148}