partner_chains_cardano_offchain/
cardano_keys.rs1use anyhow::anyhow;
2use cardano_serialization_lib::{PrivateKey, PublicKey};
3use sidechain_domain::MainchainKeyHash;
4
5pub struct CardanoPaymentSigningKey(pub(crate) PrivateKey);
10
11impl CardanoPaymentSigningKey {
12 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 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 pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
32 Ok(self.0.sign(message).to_bytes())
33 }
34
35 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 pub fn to_csl_pub_key(&self) -> PublicKey {
50 self.0.to_public()
51 }
52
53 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")]
90pub struct CardanoKeyFileContent {
94 pub r#type: String,
96 pub cbor_hex: String,
98}
99
100impl CardanoKeyFileContent {
101 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 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}