partner_chains_cardano_offchain/
cardano_keys.rs

1use anyhow::anyhow;
2use cardano_serialization_lib::{PrivateKey, PublicKey};
3use sidechain_domain::MainchainKeyHash;
4
5/// Signing (payment) wrapper layer. Hides internal crypto library details.
6/// It is either:
7/// * 32 bytes regular private key
8/// * 64 bytes extended private key
9pub struct CardanoPaymentSigningKey(pub(crate) PrivateKey);
10
11impl CardanoPaymentSigningKey {
12	/// Constructs [CardanoPaymentSigningKey] from 128 byte extended payment signing key.
13	/// The 128 bytes of the key are: 64 byte prefix, 32 byte verification key, 32 byte chain code
14	pub fn from_extended_128_bytes(bytes: [u8; 128]) -> anyhow::Result<Self> {
15		let prefix: [u8; 64] = bytes[0..64].try_into().unwrap();
16		Ok(Self(
17			PrivateKey::from_extended_bytes(&prefix)
18				.map_err(|e| anyhow!("Couldn't parse 128 bytes into a BIP32 Private Key: {e}"))?,
19		))
20	}
21
22	/// Constructs [CardanoPaymentSigningKey] from 32 byte payment signing key.
23	pub fn from_normal_bytes(bytes: [u8; 32]) -> Result<Self, anyhow::Error> {
24		Ok(Self(
25			PrivateKey::from_normal_bytes(&bytes)
26				.map_err(|e| anyhow!("Couldn't parse 32 bytes into a Private Key: {e}"))?,
27		))
28	}
29
30	/// Signs `message` with the [CardanoPaymentSigningKey].
31	pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
32		Ok(self.0.sign(message).to_bytes())
33	}
34
35	/// Hashes [CardanoPaymentSigningKey] to domain type [MainchainKeyHash].
36	pub fn to_pub_key_hash(&self) -> MainchainKeyHash {
37		MainchainKeyHash(
38			self.0
39				.to_public()
40				.hash()
41				.to_bytes()
42				.as_slice()
43				.try_into()
44				.expect("CSL PublicKeyHash is 28 bytes"),
45		)
46	}
47
48	/// Converts [CardanoPaymentSigningKey] to CSL [PublicKey].
49	pub fn to_csl_pub_key(&self) -> PublicKey {
50		self.0.to_public()
51	}
52
53	/// Returns raw bytes of [CardanoPaymentSigningKey].
54	pub fn to_bytes(&self) -> Vec<u8> {
55		self.0.as_bytes()
56	}
57}
58
59impl TryFrom<CardanoKeyFileContent> for CardanoPaymentSigningKey {
60	type Error = anyhow::Error;
61
62	fn try_from(key: CardanoKeyFileContent) -> Result<Self, Self::Error> {
63		let key_type = key.r#type.clone();
64		if key_type == "PaymentSigningKeyShelley_ed25519" {
65			Ok(CardanoPaymentSigningKey::from_normal_bytes(key.raw_key_bytes()?)?)
66		} else if key_type == "PaymentExtendedSigningKeyShelley_ed25519_bip32" {
67			Ok(CardanoPaymentSigningKey::from_extended_128_bytes(key.raw_key_bytes()?)?)
68		} else {
69			Err(anyhow!("Unsupported key type: {}. Expected a signing key", key_type))
70		}
71	}
72}
73
74impl Clone for CardanoPaymentSigningKey {
75	fn clone(&self) -> Self {
76		let bytes = self.0.as_bytes();
77		let private_key = if bytes.len() == 32 {
78			PrivateKey::from_normal_bytes(&bytes)
79				.expect("PrivateKey bytes are valid to clone the key")
80		} else {
81			PrivateKey::from_extended_bytes(&bytes)
82				.expect("PrivateKey bytes are valid to clone the key")
83		};
84		Self(private_key)
85	}
86}
87
88#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
89#[serde(rename_all = "camelCase")]
90/// Type representing Cardano key file.
91///
92/// Note: Field names are used for serialization, do not rename.
93pub struct CardanoKeyFileContent {
94	/// Type of the Cardano key.
95	pub r#type: String,
96	/// CBOR hex of the key.
97	pub cbor_hex: String,
98}
99
100impl CardanoKeyFileContent {
101	/// Parses file to [CardanoKeyFileContent].
102	pub fn parse_file(path: &str) -> anyhow::Result<Self> {
103		let file_content = std::fs::read_to_string(path)
104			.map_err(|e| anyhow!("Could not read Cardano key file at: {path}. Cause: {e}"))?;
105		serde_json::from_str::<Self>(&file_content)
106			.map_err(|e| anyhow!("{path} is not valid Cardano key JSON file. {e}"))
107	}
108	/// Parses raw bytes of 'cborHex' field of Cardano key file content.
109	/// Works for 32, 64 and 128 bytes hex strings (68, 132, 260 hex digits) - assumes CBOR prefix has 4 hex digits.
110	pub fn raw_key_bytes<const N: usize>(&self) -> anyhow::Result<[u8; N]> {
111		let (_cbor_prefix, vkey) = self.cbor_hex.split_at(4);
112		let bytes: [u8; N] = hex::decode(vkey)
113			.map_err(|err| {
114				anyhow!(
115					"Invalid cborHex value of Cardano key - not valid hex: {}\n{err:?}",
116					self.cbor_hex
117				)
118			})?
119			.try_into()
120			.map_err(|_| {
121				anyhow!(
122					"Invalid cborHex value of Cardano key - incorrect length: {}",
123					self.cbor_hex
124				)
125			})?;
126		Ok(bytes)
127	}
128}
129
130#[cfg(test)]
131mod tests {
132	use crate::cardano_keys::{CardanoKeyFileContent, CardanoPaymentSigningKey};
133
134	#[test]
135	fn cardano_key_content_key_raw_bytes() {
136		let key_file_content = CardanoKeyFileContent {
137			r#type: "PaymentSigningKeyShelley_ed25519".to_owned(),
138			cbor_hex: "5820d0a6c5c921266d15dc8d1ce1e51a01e929a686ed3ec1a9be1145727c224bf386"
139				.to_owned(),
140		};
141		assert!(key_file_content.raw_key_bytes::<31>().is_err());
142		assert_eq!(
143			hex::encode(key_file_content.raw_key_bytes::<32>().unwrap()),
144			"d0a6c5c921266d15dc8d1ce1e51a01e929a686ed3ec1a9be1145727c224bf386"
145		);
146		assert!(key_file_content.raw_key_bytes::<33>().is_err());
147	}
148
149	#[test]
150	fn signing_key_from_extended() {
151		let key_file_content = CardanoKeyFileContent {
152			r#type: "PaymentExtendedSigningKeyShelley_ed25519_bip32".to_owned(),
153			cbor_hex: "588020baf85b0e955e969cdaa852b31f223bad0348c274790c2a924602efdaba144266994eeb10f17618065431db154d28c0c7ce11277f412d614ebe82c59688b0244fbd942f1a7b94da07dfcf1c8be9826fd6222c0bae8604eebe0b6215f5d9b841203e23e0617b7e5191898dba700a7541152a3e03a816fc61b3887fe85c6d37d1".to_owned()
154		};
155		let key = CardanoPaymentSigningKey::try_from(key_file_content).unwrap();
156		assert_eq!(
157			hex::encode(key.to_pub_key_hash().0),
158			"9e287cbfac63670ff624edc69eea5f26c6f56f86f474e3e0d83f7c5c"
159		);
160	}
161
162	#[test]
163	fn signing_key_from_normal() {
164		let key_file_content = CardanoKeyFileContent {
165			r#type: "PaymentSigningKeyShelley_ed25519".to_owned(),
166			cbor_hex: "5820d0a6c5c921266d15dc8d1ce1e51a01e929a686ed3ec1a9be1145727c224bf386"
167				.to_owned(),
168		};
169		let key = CardanoPaymentSigningKey::try_from(key_file_content).unwrap();
170		assert_eq!(
171			hex::encode(key.to_pub_key_hash().0),
172			"e8c300330fe315531ca89d4a2e7d0c80211bc70b473b1ed4979dff2b"
173		);
174	}
175}