1use core::convert::TryFrom;
2use ssi_jwk::{Algorithm, Base64urlUInt, OctetParams, Params, JWK};
3use ssi_jws::Error as JwsError;
4
5const EDPK_PREFIX: [u8; 4] = [13, 15, 37, 217];
6const SPPK_PREFIX: [u8; 4] = [3, 254, 226, 86];
7const P2PK_PREFIX: [u8; 4] = [3, 178, 139, 127];
8
9pub fn jwk_to_tezos_key(jwk: &JWK) -> Result<String, JwsError> {
10 let mut tzkey_prefixed = Vec::new();
11 let bytes;
12 let (prefix, bytes) = match &jwk.params {
13 Params::OKP(okp_params) if okp_params.curve == "Ed25519" => {
14 if let Some(ref _sk) = okp_params.private_key {
15 return Err(JwsError::UnsupportedAlgorithm(
17 jwk.algorithm
18 .as_ref()
19 .map(ToString::to_string)
20 .unwrap_or_else(|| "OKP".to_string()),
21 ));
22 }
23 (EDPK_PREFIX, &okp_params.public_key.0)
24 }
25 Params::EC(ec_params) if ec_params.curve == Some("secp256k1".to_string()) => {
26 if let Some(ref _sk) = ec_params.ecc_private_key {
27 return Err(JwsError::UnsupportedAlgorithm(
29 jwk.algorithm
30 .as_ref()
31 .map(ToString::to_string)
32 .unwrap_or_else(|| "EC".to_string()),
33 ));
34 }
35 {
36 bytes = ssi_jwk::serialize_secp256k1(ec_params)?;
38 (SPPK_PREFIX, &bytes)
39 }
40 }
41 Params::EC(ec_params) if ec_params.curve == Some("P-256".to_string()) => {
42 if let Some(ref _sk) = ec_params.ecc_private_key {
43 return Err(JwsError::UnsupportedAlgorithm(
44 jwk.algorithm
45 .as_ref()
46 .map(ToString::to_string)
47 .unwrap_or_else(|| "EC".to_string()),
48 ));
49 }
50 {
51 bytes = ssi_jwk::serialize_p256(ec_params)?;
52 (P2PK_PREFIX, &bytes)
53 }
54 }
55 _ => {
56 return Err(JwsError::UnsupportedAlgorithm(
57 jwk.algorithm
58 .as_ref()
59 .map(ToString::to_string)
60 .unwrap_or_else(|| "OCT".to_string()),
61 ));
62 }
63 };
64 tzkey_prefixed.extend_from_slice(&prefix);
65 tzkey_prefixed.extend_from_slice(bytes);
66 let tzkey = bs58::encode(tzkey_prefixed).with_check().into_string();
67 Ok(tzkey)
68}
69
70#[derive(thiserror::Error, Debug)]
71pub enum DecodeTezosPkError {
72 #[error("Key Prefix")]
73 KeyPrefix,
74 #[error(transparent)]
75 B58(#[from] bs58::decode::Error),
76 #[error(transparent)]
77 JWK(#[from] ssi_jwk::Error),
78}
79
80pub fn jwk_from_tezos_key(tz_pk: &str) -> Result<JWK, DecodeTezosPkError> {
82 if tz_pk.len() < 4 {
83 return Err(DecodeTezosPkError::KeyPrefix);
84 }
85 let (alg, params) = match tz_pk.get(..4) {
86 Some("edpk") => (
87 Algorithm::EdBlake2b,
88 Params::OKP(OctetParams {
89 curve: "Ed25519".into(),
90 public_key: Base64urlUInt(
91 bs58::decode(&tz_pk).with_check(None).into_vec()?[4..].to_owned(),
92 ),
93 private_key: None,
94 }),
95 ),
96 Some("edsk") => {
97 let sk_bytes = bs58::decode(&tz_pk).with_check(None).into_vec()?[4..].to_owned();
98 let pk_bytes;
99 {
100 let sk = ed25519_dalek::SigningKey::try_from(sk_bytes.as_slice())
101 .map_err(ssi_jwk::Error::from)?;
102 pk_bytes = ed25519_dalek::VerifyingKey::from(&sk).as_bytes().to_vec()
103 }
104 (
105 Algorithm::EdBlake2b,
106 Params::OKP(OctetParams {
107 curve: "Ed25519".into(),
108 public_key: Base64urlUInt(pk_bytes),
109 private_key: Some(Base64urlUInt(sk_bytes)),
110 }),
111 )
112 }
113 Some("sppk") => {
114 let pk_bytes = bs58::decode(&tz_pk).with_check(None).into_vec()?[4..].to_owned();
115 let jwk = ssi_jwk::secp256k1_parse(&pk_bytes)?;
116 (Algorithm::ESBlake2bK, jwk.params)
117 }
118 Some("p2pk") => {
119 let pk_bytes = bs58::decode(&tz_pk).with_check(None).into_vec()?[4..].to_owned();
120 let jwk = ssi_jwk::p256_parse(&pk_bytes)?;
121 (Algorithm::ESBlake2b, jwk.params)
122 }
123 _ => return Err(DecodeTezosPkError::KeyPrefix),
125 };
126 Ok(JWK {
127 public_key_use: None,
128 key_operations: None,
129 algorithm: Some(alg),
130 key_id: None,
131 x509_url: None,
132 x509_certificate_chain: None,
133 x509_thumbprint_sha1: None,
134 x509_thumbprint_sha256: None,
135 params,
136 })
137}
138
139#[derive(thiserror::Error, Debug)]
140pub enum SignTezosError {
141 #[error("Unsupported algorithm for Tezos signing: {0:?}")]
142 UnsupportedAlgorithm(Algorithm),
143 #[error("Signing: {0}")]
144 Sign(String),
145}
146
147pub fn sign_tezos(data: &[u8], algorithm: Algorithm, key: &JWK) -> Result<String, SignTezosError> {
148 let sig = ssi_jws::sign_bytes(algorithm, data, key)
149 .map_err(|e| SignTezosError::Sign(e.to_string()))?;
150 let mut sig_prefixed = Vec::new();
151 const EDSIG_PREFIX: [u8; 5] = [9, 245, 205, 134, 18];
152 const SPSIG_PREFIX: [u8; 5] = [13, 115, 101, 19, 63];
153 const P2SIG_PREFIX: [u8; 4] = [54, 240, 44, 52];
154 let prefix: &[u8] = match algorithm {
155 Algorithm::EdBlake2b => &EDSIG_PREFIX,
156 Algorithm::ESBlake2bK => &SPSIG_PREFIX,
157 Algorithm::ESBlake2b => &P2SIG_PREFIX,
158 alg => return Err(SignTezosError::UnsupportedAlgorithm(alg)),
159 };
160 sig_prefixed.extend_from_slice(prefix);
161 sig_prefixed.extend_from_slice(&sig);
162 let sig_bs58 = bs58::encode(sig_prefixed).with_check().into_string();
163 Ok(sig_bs58)
164}
165
166#[derive(thiserror::Error, Debug)]
167pub enum EncodeTezosSignedMessageError {
168 #[error("Message length conversion error: {0}")]
169 Length(#[from] core::num::TryFromIntError),
170}
171
172pub fn encode_tezos_signed_message(msg: &str) -> Result<Vec<u8>, EncodeTezosSignedMessageError> {
173 const BYTES_PREFIX: [u8; 2] = [0x05, 0x01];
174 let msg_bytes = msg.as_bytes();
175 let mut bytes = Vec::with_capacity(msg_bytes.len());
176 let prefix = b"Tezos Signed Message: ";
177 let msg_len = prefix.len() + msg_bytes.len();
178
179 let len_u32 = u32::try_from(msg_len).map_err(EncodeTezosSignedMessageError::Length)?;
180 bytes.extend_from_slice(&BYTES_PREFIX);
181 bytes.extend_from_slice(&len_u32.to_be_bytes());
182 bytes.extend_from_slice(prefix);
183 bytes.extend_from_slice(msg_bytes);
184 Ok(bytes)
185}
186
187#[derive(thiserror::Error, Debug)]
188pub enum DecodeTezosSignatureError {
189 #[error("Expected signature length {0} but found {1}")]
190 SignatureLength(usize, usize),
191 #[error("Unknown signature prefix: {0}")]
192 SignaturePrefix(String),
193 #[error("Base58 decoding: {0}")]
194 Base58(#[from] bs58::decode::Error),
195}
196
197pub fn decode_tzsig(sig_bs58: &str) -> Result<(Algorithm, Vec<u8>), DecodeTezosSignatureError> {
198 let tzsig = bs58::decode(&sig_bs58).with_check(None).into_vec()?;
199 if tzsig.len() < 5 {
200 return Err(DecodeTezosSignatureError::SignaturePrefix(
201 sig_bs58.to_string(),
202 ));
203 }
204 let (algorithm, sig) = match sig_bs58.get(0..5) {
207 Some("edsig") => (Algorithm::EdBlake2b, tzsig[5..].to_vec()),
208 Some("spsig") => (Algorithm::ESBlake2bK, tzsig[5..].to_vec()),
209 Some("p2sig") => (Algorithm::ESBlake2b, tzsig[4..].to_vec()),
210 _ => {
211 return Err(DecodeTezosSignatureError::SignaturePrefix(
212 sig_bs58.to_string(),
213 ))
214 }
215 };
216 if sig.len() != 64 {
217 return Err(DecodeTezosSignatureError::SignatureLength(64, sig.len()));
218 }
219 Ok((algorithm, sig))
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use serde_json::json;
226 use ssi_jwk::blakesig::hash_public_key;
227
228 #[test]
229 fn test_jwk_from_tezos_key_glyph_split() {
230 let bad_tzk = "xx💣️";
233 jwk_from_tezos_key(bad_tzk).unwrap_err();
234 }
235
236 #[test]
237 fn edpk_jwk_tz_edsig() {
238 let tzpk = "edpkuxZ5AQVCeEJ9inUG3w6VFhio5KBwC22ekPLBzcvub3QY2DvJ7n";
239 let jwk = jwk_from_tezos_key(tzpk).unwrap();
240 assert_eq!(jwk_to_tezos_key(&jwk).unwrap(), tzpk);
241 let jwk_expected: JWK = serde_json::from_value(json!(
242 {"alg":"EdBlake2b","kty":"OKP","crv":"Ed25519","x":"rVEB0Icbomw1Ir-ck52iCZl1SICc5lCg2pxI8AmydDw"}
243 )).unwrap();
244 assert_eq!(jwk, jwk_expected);
245 let hash = hash_public_key(&jwk).unwrap();
246 assert_eq!(hash, "tz1TwZZZSShtM73oEr74aDtDcns3UmFqaca6");
247 let mut tsm =
248 encode_tezos_signed_message("example.org 2021-05-25T18:54:42Z Signed with Temple tz1")
249 .unwrap();
250 let tsm_expected_hex = "05010000004d54657a6f73205369676e6564204d6573736167653a206578616d706c652e6f726720323032312d30352d32355431383a35343a34325a205369676e656420776974682054656d706c6520747a31";
251 assert_eq!(hex::encode(&tsm), tsm_expected_hex);
252
253 let sig_bs58 = "edsigtpfAeN8PqB9dpYdzDTpbCFPFuNe6Vfo4pokAQWYzFczZCjkWtfmWH3zxsse1KbxSc2NksTfoAMJzpqCAee4PQL6gydVWyy";
254 let (_, sig) = decode_tzsig(sig_bs58).unwrap();
255 ssi_jws::verify_bytes(Algorithm::EdBlake2b, &tsm, &jwk, &sig).unwrap();
256
257 tsm[1] ^= 1;
259 ssi_jws::verify_bytes(Algorithm::EdBlake2b, &tsm, &jwk, &sig).unwrap_err();
260 }
261
262 #[test]
264 fn sppk_jwk_tz_spsig() {
265 let tzpk = "sppk7bYNanLcEPRpvLc231GBC8i6YfLBbQjiQMbz8kriz9qxASf5wHw";
266 let jwk = jwk_from_tezos_key(tzpk).unwrap();
267 assert_eq!(jwk_to_tezos_key(&jwk).unwrap(), tzpk);
268 let jwk_expected: JWK = serde_json::from_value(json!(
269 {"alg":"ESBlake2bK","kty":"EC","crv":"secp256k1","x":"JpVAlV0nDVVmPnSNdZTqes8YXoQqzyBq9R1VHWhBdgY","y":"G2jCkm3F3uu-TqtgrqCji13-MR-tlND2Tqt8rh7ZPN8"}
270 )).unwrap();
271 assert_eq!(jwk, jwk_expected);
272 let hash = hash_public_key(&jwk).unwrap();
273 assert_eq!(hash, "tz2EzZh8dhciPgTh4azWhgKCNz3HRh2ZvUhA");
274 let mut tsm =
275 encode_tezos_signed_message("example.org 2021-05-25T20:10:03Z Signed with Kukai tz2")
276 .unwrap();
277 let tsm_expected_hex = "05010000004c54657a6f73205369676e6564204d6573736167653a206578616d706c652e6f726720323032312d30352d32355432303a31303a30335a205369676e65642077697468204b756b616920747a32";
278 assert_eq!(hex::encode(&tsm), tsm_expected_hex);
279
280 let sig_bs58 = "spsig1TJjahaMVSCyvSBPvHJFXQ1WxASgHsygTvxgJxAWbYsv5R9nH1yzj5BEeHoqmHCogVYioVCbeKDNDwP17hMaM9foFdF8SS";
281 let (_, sig) = decode_tzsig(sig_bs58).unwrap();
282 ssi_jws::verify_bytes(Algorithm::ESBlake2bK, &tsm, &jwk, &sig).unwrap();
283 tsm[1] ^= 1;
284 ssi_jws::verify_bytes(Algorithm::ESBlake2bK, &tsm, &jwk, &sig).unwrap_err();
285 }
286
287 #[test]
288 fn p2pk_jwk() {
289 let tzpk = "p2pk679D18uQNkdjpRxuBXL5CqcDKTKzsiXVtc9oCUT6xb82zQmgUks";
290 let jwk = jwk_from_tezos_key(tzpk).unwrap();
291 let jwk_expected: JWK = serde_json::from_value(json!(
292 {"alg":"ESBlake2b","kty":"EC","crv":"P-256","x":"UmzXjEZzlGmpaM_CmFEJtOO5JBntW8yl_fM1LEQlWQ4","y":"OmoZmcbUadg7dEC8bg5kXryN968CJqv2UFMUKRERZ6s"}
293 )).unwrap();
294 assert_eq!(jwk, jwk_expected);
295 assert_eq!(jwk_to_tezos_key(&jwk).unwrap(), tzpk);
296 }
298
299 #[test]
300 fn edsk_sign() {
301 let mut key: JWK =
302 serde_json::from_str(include_str!("../../../tests/ed25519-2020-10-18.json")).unwrap();
303 key.algorithm = Some(Algorithm::EdBlake2b);
304 eprintln!("key: {:?}", key);
305 let hash = hash_public_key(&key).unwrap();
306 assert_eq!(hash, "tz1NcJyMQzUw7h85baBA6vwRGmpwPnM1fz83");
307 let tsm = encode_tezos_signed_message("example.org 2021-05-26T18:28:26Z Signed with ssi")
308 .unwrap();
309 eprintln!("msg: {:?}", tsm);
310 let sig = sign_tezos(&tsm, Algorithm::EdBlake2b, &key).unwrap();
311 let sig_expected = "edsigtvvyq6uFWyeoSNZq4Jq2AvsNGZ9hHYDgt4Hzdou4FVkaBLX34tWRyL9MsapFBg3RFXReJ4bNCaAg2F1XWAMgetCLU9AACo";
312 assert_eq!(sig, sig_expected);
313 }
314
315 #[test]
316 fn spsk_sign() {
317 let key: JWK = serde_json::from_value(json!({
318 "alg": "ESBlake2bK",
319 "kty": "EC",
320 "crv": "secp256k1",
321 "x": "yclqMZ0MtyVkKm1eBh2AyaUtsqT0l5RJM3g4SzRT96A",
322 "y": "yQzUwKnftWCJPGs-faGaHiYi1sxA6fGJVw2Px_LCNe8",
323 "d": "meTmccmR_6ZsOa2YuTTkKkJ4ZPYsKdAH1Wx_RRf2j_E"
324 }))
325 .unwrap();
326 eprintln!("key {:?}", key);
327 let hash = hash_public_key(&key).unwrap();
328 assert_eq!(hash, "tz2CA2f3SWWcqbWsjHsMZPZxCY5iafSN3nDz");
329 let tsm = encode_tezos_signed_message("example.org 2021-05-26T17:01:41Z Signed with ssi")
330 .unwrap();
331 eprintln!("msg: {:x?}", tsm);
332 let sig = sign_tezos(&tsm, Algorithm::ESBlake2bK, &key).unwrap();
333 let sig_expected = "spsig1NRgjYaq8jeaWTMPUSsxkawWUzW1C3RoMfczWY2JAZSkNQQGM9QvCkxtRMcauJRaSUNcKgkj6WfpzLh1upXwjcfLh4wqqX";
334 assert_eq!(sig, sig_expected);
335 }
336}