cli_commands/
address_association_signatures.rs

1use crate::key_params::StakeSigningKeyParam;
2use anyhow::Ok;
3use byte_string::ByteString;
4use clap::Parser;
5use pallet_address_associations::AddressAssociationSignedMessage;
6use parity_scale_codec::Encode;
7use serde::Serialize;
8use serde_json::json;
9use sidechain_domain::*;
10use std::str::FromStr;
11
12#[derive(Clone, Debug, Parser)]
13#[command(author, version, about, long_about = None)]
14pub struct AddressAssociationSignaturesCmd<
15	PartnerchainAddress: Clone + Sync + Send + FromStr + 'static,
16> {
17	/// Genesis UTXO of the target Partner Chain
18	#[arg(long)]
19	pub genesis_utxo: UtxoId,
20	/// Partner Chain address to be associated with the Cardano address
21	#[arg(long, value_parser=parse_pc_address::<PartnerchainAddress>)]
22	pub partnerchain_address: PartnerchainAddress,
23	/// Cardano Stake Signing Key bytes in hex format. Its public key will be associated with partnerchain_address.
24	#[arg(long)]
25	pub signing_key: StakeSigningKeyParam,
26}
27
28fn parse_pc_address<T: FromStr>(s: &str) -> Result<T, String> {
29	T::from_str(s).map_err(|_| "Failed to parse Partner Chain address".to_owned())
30}
31
32impl<PartnerchainAddress> AddressAssociationSignaturesCmd<PartnerchainAddress>
33where
34	PartnerchainAddress: Serialize + Clone + Sync + Send + FromStr + Encode + 'static,
35{
36	pub fn execute(&self) -> anyhow::Result<()> {
37		let signature = self.sign();
38		let output = json!({
39			"partnerchain_address": self.partnerchain_address,
40			"signature": signature,
41			"stake_public_key": self.signing_key.vkey()
42
43		});
44		println!("{}", serde_json::to_string_pretty(&output)?);
45		Ok(())
46	}
47
48	fn sign(&self) -> ByteString {
49		let msg = AddressAssociationSignedMessage {
50			stake_public_key: self.signing_key.vkey(),
51			partnerchain_address: self.partnerchain_address.clone(),
52			genesis_utxo: self.genesis_utxo,
53		};
54		let encoded = msg.encode();
55		self.signing_key.0.sign(&encoded).to_bytes().to_vec().into()
56	}
57}
58
59#[cfg(test)]
60mod test {
61	use super::*;
62	use hex::FromHexError;
63	use hex_literal::hex;
64	use sidechain_domain::byte_string::ByteString;
65
66	#[derive(Clone, Encode, Serialize)]
67	struct AccountId32(pub [u8; 32]);
68
69	impl FromStr for AccountId32 {
70		type Err = anyhow::Error;
71
72		fn from_str(s: &str) -> Result<Self, Self::Err> {
73			let bytes =
74				hex::decode(s)?.try_into().map_err(|_| FromHexError::InvalidStringLength)?;
75
76			Ok(Self(bytes))
77		}
78	}
79
80	// This test is specifically kept in sync with the pallet siganture verification test
81	#[test]
82	fn signature_test() {
83		let cmd = AddressAssociationSignaturesCmd {
84			genesis_utxo: UtxoId::new(
85				hex!("59104061ffa0d66f9ba0135d6fc6a884a395b10f8ae9cb276fc2c3bfdfedc260"),
86				1,
87			),
88			partnerchain_address:
89				// re-encoding of 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY (Alice)
90				AccountId32(hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")),
91			signing_key: StakeSigningKeyParam::from_str(
92				// Private key of Alice (pubkey: 2bebcb7fbc74a6e0fd6e00a311698b047b7b659f0e047ff5349dbd984aefc52c)
93				"d75c630516c33a66b11b3444a70b65083aeb21353bd919cc5e3daa02c9732a84"
94			).unwrap(),
95		};
96
97		assert_eq!(
98			cmd.sign(),
99			ByteString(hex!("1aa8c1b363a207ddadf0c6242a0632f5a557690a327d0245f9d473b983b3d8e1c95a3dd804cab41123c36ddbcb7137b8261c35d5c8ef04ce9d0f8d5c4b3ca607").into())
100		);
101	}
102}