@@ -4,7 +4,12 @@ use crate::{prost_ext::MessageExt, proto, AccountId, Error, Result};
4
4
use eyre:: WrapErr ;
5
5
use prost:: Message ;
6
6
use prost_types:: Any ;
7
- use std:: convert:: { TryFrom , TryInto } ;
7
+ use serde:: { de, de:: Error as _, ser, Deserialize , Serialize } ;
8
+ use std:: {
9
+ convert:: { TryFrom , TryInto } ,
10
+ str:: FromStr ,
11
+ } ;
12
+ use subtle_encoding:: base64;
8
13
9
14
/// Protobuf [`Any`] type URL for Ed25519 public keys
10
15
const ED25519_TYPE_URL : & str = "/cosmos.crypto.ed25519.PubKey" ;
@@ -17,6 +22,16 @@ const SECP256K1_TYPE_URL: &str = "/cosmos.crypto.secp256k1.PubKey";
17
22
pub struct PublicKey ( tendermint:: PublicKey ) ;
18
23
19
24
impl PublicKey {
25
+ /// Parse public key from Cosmos JSON format.
26
+ pub fn from_json ( s : & str ) -> Result < Self > {
27
+ Ok ( serde_json:: from_str :: < PublicKey > ( s) ?)
28
+ }
29
+
30
+ /// Serialize public key as Cosmos JSON.
31
+ pub fn to_json ( & self ) -> String {
32
+ serde_json:: to_string ( & self ) . expect ( "JSON serialization error" )
33
+ }
34
+
20
35
/// Get the [`AccountId`] for this [`PublicKey`] (if applicable).
21
36
pub fn account_id ( & self , prefix : & str ) -> Result < AccountId > {
22
37
match & self . 0 {
@@ -28,31 +43,34 @@ impl PublicKey {
28
43
}
29
44
}
30
45
46
+ /// Get the type URL for this [`PublicKey`].
47
+ pub fn type_url ( & self ) -> & ' static str {
48
+ match & self . 0 {
49
+ tendermint:: PublicKey :: Ed25519 ( _) => ED25519_TYPE_URL ,
50
+ tendermint:: PublicKey :: Secp256k1 ( _) => SECP256K1_TYPE_URL ,
51
+ // `tendermint::PublicKey` is `non_exhaustive`
52
+ _ => unreachable ! ( "unknown pubic key type" ) ,
53
+ }
54
+ }
55
+
31
56
/// Convert this [`PublicKey`] to a Protobuf [`Any`] type.
32
57
pub fn to_any ( & self ) -> Result < Any > {
33
- match self . 0 {
34
- tendermint:: PublicKey :: Ed25519 ( _) => {
35
- let proto = proto:: cosmos:: crypto:: secp256k1:: PubKey {
36
- key : self . to_bytes ( ) ,
37
- } ;
38
-
39
- Ok ( Any {
40
- type_url : ED25519_TYPE_URL . to_owned ( ) ,
41
- value : proto. to_bytes ( ) ?,
42
- } )
58
+ let value = match self . 0 {
59
+ tendermint:: PublicKey :: Ed25519 ( _) => proto:: cosmos:: crypto:: secp256k1:: PubKey {
60
+ key : self . to_bytes ( ) ,
43
61
}
44
- tendermint:: PublicKey :: Secp256k1 ( _) => {
45
- let proto = proto:: cosmos:: crypto:: secp256k1:: PubKey {
46
- key : self . to_bytes ( ) ,
47
- } ;
48
-
49
- Ok ( Any {
50
- type_url : SECP256K1_TYPE_URL . to_owned ( ) ,
51
- value : proto. to_bytes ( ) ?,
52
- } )
62
+ . to_bytes ( ) ,
63
+ tendermint:: PublicKey :: Secp256k1 ( _) => proto:: cosmos:: crypto:: secp256k1:: PubKey {
64
+ key : self . to_bytes ( ) ,
53
65
}
66
+ . to_bytes ( ) ,
54
67
_ => Err ( Error :: Crypto . into ( ) ) ,
55
- }
68
+ } ?;
69
+
70
+ Ok ( Any {
71
+ type_url : self . type_url ( ) . to_owned ( ) ,
72
+ value,
73
+ } )
56
74
}
57
75
58
76
/// Serialize this [`PublicKey`] as a byte vector.
@@ -85,16 +103,22 @@ impl TryFrom<&Any> for PublicKey {
85
103
type Error = eyre:: Report ;
86
104
87
105
fn try_from ( any : & Any ) -> Result < PublicKey > {
88
- match any. type_url . as_str ( ) {
106
+ let tm_key = match any. type_url . as_str ( ) {
107
+ ED25519_TYPE_URL => {
108
+ let proto = proto:: cosmos:: crypto:: ed25519:: PubKey :: decode ( & * any. value ) ?;
109
+ tendermint:: PublicKey :: from_raw_ed25519 ( & proto. key )
110
+ }
89
111
SECP256K1_TYPE_URL => {
90
112
let proto = proto:: cosmos:: crypto:: secp256k1:: PubKey :: decode ( & * any. value ) ?;
91
113
tendermint:: PublicKey :: from_raw_secp256k1 ( & proto. key )
92
- . map ( Into :: into)
93
- . ok_or_else ( || Error :: Crypto . into ( ) )
94
114
}
95
- other => Err ( Error :: Crypto )
96
- . wrap_err_with ( || format ! ( "invalid type URL for public key: {}" , other) ) ,
97
- }
115
+ other => {
116
+ return Err ( Error :: Crypto )
117
+ . wrap_err_with ( || format ! ( "invalid type URL for public key: {}" , other) )
118
+ }
119
+ } ;
120
+
121
+ tm_key. map ( Into :: into) . ok_or_else ( || Error :: Crypto . into ( ) )
98
122
}
99
123
}
100
124
@@ -117,3 +141,106 @@ impl From<PublicKey> for tendermint::PublicKey {
117
141
pk. 0
118
142
}
119
143
}
144
+
145
+ impl FromStr for PublicKey {
146
+ type Err = eyre:: Report ;
147
+
148
+ fn from_str ( s : & str ) -> Result < Self > {
149
+ Self :: from_json ( s)
150
+ }
151
+ }
152
+
153
+ impl ToString for PublicKey {
154
+ fn to_string ( & self ) -> String {
155
+ self . to_json ( )
156
+ }
157
+ }
158
+
159
+ impl < ' de > Deserialize < ' de > for PublicKey {
160
+ fn deserialize < D : de:: Deserializer < ' de > > ( deserializer : D ) -> Result < Self , D :: Error > {
161
+ PublicKeyJson :: deserialize ( deserializer) ?
162
+ . try_into ( )
163
+ . map_err ( D :: Error :: custom)
164
+ }
165
+ }
166
+
167
+ impl Serialize for PublicKey {
168
+ fn serialize < S : ser:: Serializer > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error > {
169
+ PublicKeyJson :: from ( self ) . serialize ( serializer)
170
+ }
171
+ }
172
+
173
+ /// Serde encoding type for JSON public keys.
174
+ ///
175
+ /// Uses Protobuf JSON encoding conventions.
176
+ #[ derive( Deserialize , Serialize ) ]
177
+ struct PublicKeyJson {
178
+ /// `@type` field e.g. `/cosmos.crypto.ed25519.PubKey`.
179
+ #[ serde( rename = "@type" ) ]
180
+ type_url : String ,
181
+
182
+ /// Key data: standard Base64 encoded with padding.
183
+ key : String ,
184
+ }
185
+
186
+ impl From < PublicKey > for PublicKeyJson {
187
+ fn from ( public_key : PublicKey ) -> PublicKeyJson {
188
+ PublicKeyJson :: from ( & public_key)
189
+ }
190
+ }
191
+
192
+ impl From < & PublicKey > for PublicKeyJson {
193
+ fn from ( public_key : & PublicKey ) -> PublicKeyJson {
194
+ let type_url = public_key. type_url ( ) . to_owned ( ) ;
195
+ let key = String :: from_utf8 ( base64:: encode ( & public_key. to_bytes ( ) ) ) . expect ( "UTF-8 error" ) ;
196
+ PublicKeyJson { type_url, key }
197
+ }
198
+ }
199
+
200
+ impl TryFrom < PublicKeyJson > for PublicKey {
201
+ type Error = eyre:: Report ;
202
+
203
+ fn try_from ( json : PublicKeyJson ) -> Result < PublicKey > {
204
+ PublicKey :: try_from ( & json)
205
+ }
206
+ }
207
+
208
+ impl TryFrom < & PublicKeyJson > for PublicKey {
209
+ type Error = eyre:: Report ;
210
+
211
+ fn try_from ( json : & PublicKeyJson ) -> Result < PublicKey > {
212
+ let pk_bytes = base64:: decode ( & json. key ) ?;
213
+
214
+ let tm_key = match json. type_url . as_str ( ) {
215
+ ED25519_TYPE_URL => tendermint:: PublicKey :: from_raw_ed25519 ( & pk_bytes) ,
216
+ SECP256K1_TYPE_URL => tendermint:: PublicKey :: from_raw_secp256k1 ( & pk_bytes) ,
217
+ other => {
218
+ return Err ( Error :: Crypto )
219
+ . wrap_err_with ( || format ! ( "invalid public key @type: {}" , other) )
220
+ }
221
+ } ;
222
+
223
+ tm_key. map ( Into :: into) . ok_or_else ( || Error :: Crypto . into ( ) )
224
+ }
225
+ }
226
+
227
+ #[ cfg( test) ]
228
+ mod tests {
229
+ use super :: PublicKey ;
230
+
231
+ const EXAMPLE_JSON : & str = "{\" @type\" :\" /cosmos.crypto.ed25519.PubKey\" ,\" key\" :\" sEEsVGkXvyewKLWMJbHVDRkBoerW0IIwmj1rHkabtHU=\" }" ;
232
+
233
+ #[ test]
234
+ fn json_round_trip ( ) {
235
+ let example_key = EXAMPLE_JSON . parse :: < PublicKey > ( ) . unwrap ( ) ;
236
+ assert_eq ! ( example_key. type_url( ) , "/cosmos.crypto.ed25519.PubKey" ) ;
237
+ assert_eq ! (
238
+ example_key. to_bytes( ) . as_slice( ) ,
239
+ & [
240
+ 176 , 65 , 44 , 84 , 105 , 23 , 191 , 39 , 176 , 40 , 181 , 140 , 37 , 177 , 213 , 13 , 25 , 1 , 161 ,
241
+ 234 , 214 , 208 , 130 , 48 , 154 , 61 , 107 , 30 , 70 , 155 , 180 , 117
242
+ ]
243
+ ) ;
244
+ assert_eq ! ( EXAMPLE_JSON , example_key. to_string( ) ) ;
245
+ }
246
+ }
0 commit comments