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::*;
10
11/// Generates Ed25519 signatures to associate Cardano stake addresses with Partner Chain addresses.
12/// Generic over address type to support different Partner Chain implementations.
13#[derive(Clone, Debug, Parser)]
14#[command(author, version, about, long_about = None)]
15pub struct AddressAssociationSignaturesCmd<
16	PartnerchainAddress: Clone + Sync + Send + FromStrStdErr + 'static,
17> {
18	/// Genesis UTXO that identifies the target Partner Chain
19	#[arg(long)]
20	pub genesis_utxo: UtxoId,
21	/// Partner Chain address to be associated with the Cardano stake address
22	#[arg(long)]
23	pub partnerchain_address: PartnerchainAddress,
24	/// Ed25519 signing key for the Cardano stake address. Its public key will be associated with partnerchain_address.
25	#[arg(long)]
26	pub signing_key: StakeSigningKeyParam,
27}
28
29impl<PartnerchainAddress> AddressAssociationSignaturesCmd<PartnerchainAddress>
30where
31	PartnerchainAddress: Serialize + Clone + Sync + Send + FromStrStdErr + Encode + 'static,
32{
33	/// Generates signature and outputs JSON to stdout.
34	pub fn execute(&self) -> anyhow::Result<()> {
35		let signature = self.sign();
36		let output = json!({
37			"partnerchain_address": self.partnerchain_address,
38			"signature": signature,
39			"stake_public_key": self.signing_key.vkey()
40
41		});
42		println!("{}", serde_json::to_string_pretty(&output)?);
43		Ok(())
44	}
45
46	/// Signs SCALE-encoded address association message with Ed25519.
47	fn sign(&self) -> ByteString {
48		let msg = AddressAssociationSignedMessage {
49			stake_public_key: self.signing_key.vkey(),
50			partnerchain_address: self.partnerchain_address.clone(),
51			genesis_utxo: self.genesis_utxo,
52		};
53		let encoded = msg.encode();
54		self.signing_key.0.sign(&encoded).to_bytes().to_vec().into()
55	}
56}
57
58#[cfg(test)]
59mod test {
60	use super::*;
61	use hex::FromHexError;
62	use hex_literal::hex;
63	use sidechain_domain::byte_string::ByteString;
64	use std::str::FromStr;
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 signature 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}