1
- use sha2:: { Digest , Sha256 } ;
2
-
3
1
use cosmwasm_std:: {
4
- entry_point, to_binary, Deps , DepsMut , Env , MessageInfo , QueryResponse , Response , StdResult ,
2
+ entry_point, to_binary, Deps , DepsMut , Env , MessageInfo , QueryResponse , Response , StdError ,
3
+ StdResult ,
5
4
} ;
5
+ use sha2:: { Digest , Sha256 } ;
6
+ use sha3:: Keccak256 ;
6
7
7
8
use crate :: msg:: {
8
9
list_verifications, HandleMsg , InitMsg , ListVerificationsResponse , QueryMsg , VerifyResponse ,
@@ -38,6 +39,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<QueryResponse> {
38
39
& signature. 0 ,
39
40
& public_key. 0 ,
40
41
) ?) ,
42
+ QueryMsg :: VerifyEthereumText {
43
+ message,
44
+ signature,
45
+ signer_address,
46
+ } => to_binary ( & query_verify_ethereum_text (
47
+ deps,
48
+ & message,
49
+ & signature,
50
+ & signer_address,
51
+ ) ?) ,
41
52
QueryMsg :: VerifyTendermintSignature {
42
53
message,
43
54
signature,
@@ -62,7 +73,41 @@ pub fn query_verify_cosmos(
62
73
let hash = Sha256 :: digest ( message) ;
63
74
64
75
// Verification
65
- let result = deps. api . secp256k1_verify ( & * hash, signature, public_key) ;
76
+ let result = deps
77
+ . api
78
+ . secp256k1_verify ( hash. as_ref ( ) , signature, public_key) ;
79
+ match result {
80
+ Ok ( verifies) => Ok ( VerifyResponse { verifies } ) ,
81
+ Err ( err) => Err ( err. into ( ) ) ,
82
+ }
83
+ }
84
+
85
+ pub fn query_verify_ethereum_text (
86
+ deps : Deps ,
87
+ message : & str ,
88
+ signature : & [ u8 ] ,
89
+ signer_address : & str ,
90
+ ) -> StdResult < VerifyResponse > {
91
+ // Hashing
92
+ let mut hasher = Keccak256 :: new ( ) ;
93
+ hasher. update ( format ! ( "\x19 Ethereum Signed Message:\n {}" , message. len( ) ) ) ;
94
+ hasher. update ( message) ;
95
+ let hash = hasher. finalize ( ) ;
96
+
97
+ // Decompose signature
98
+ let ( v, rs) = match signature. split_last ( ) {
99
+ Some ( pair) => pair,
100
+ None => return Err ( StdError :: generic_err ( "Signature must not be empty" ) ) ,
101
+ } ;
102
+ let recovery = get_recovery_param ( * v) ?;
103
+
104
+ // Verification
105
+ let calculated_pubkey = deps. api . secp256k1_recover_pubkey ( & hash, rs, recovery) ?;
106
+ let calculated_address = ethereum_address ( & calculated_pubkey) ?;
107
+ if signer_address. to_ascii_lowercase ( ) != calculated_address {
108
+ return Ok ( VerifyResponse { verifies : false } ) ;
109
+ }
110
+ let result = deps. api . secp256k1_verify ( & hash, rs, & calculated_pubkey) ;
66
111
match result {
67
112
Ok ( verifies) => Ok ( VerifyResponse { verifies } ) ,
68
113
Err ( err) => Err ( err. into ( ) ) ,
@@ -87,13 +132,44 @@ pub fn query_list_verifications(deps: Deps) -> StdResult<ListVerificationsRespon
87
132
} )
88
133
}
89
134
135
+ fn ethereum_address ( pubkey : & [ u8 ] ) -> StdResult < String > {
136
+ let ( tag, data) = match pubkey. split_first ( ) {
137
+ Some ( pair) => pair,
138
+ None => return Err ( StdError :: generic_err ( "Public key must not be empty" ) ) ,
139
+ } ;
140
+ if * tag != 0x04 {
141
+ return Err ( StdError :: generic_err ( "Public key start with 0x04" ) ) ;
142
+ }
143
+ if data. len ( ) != 64 {
144
+ return Err ( StdError :: generic_err ( "Public key must be 65 bytes long" ) ) ;
145
+ }
146
+
147
+ let hash = Keccak256 :: digest ( data) ;
148
+ let mut out = String :: with_capacity ( 42 ) ;
149
+ out. push_str ( "0x" ) ;
150
+ out. push_str ( & hex:: encode ( & hash[ hash. len ( ) - 20 ..] ) ) ;
151
+ Ok ( out)
152
+ }
153
+
154
+ fn get_recovery_param ( v : u8 ) -> StdResult < u8 > {
155
+ // See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
156
+ // for how `v` is composed.
157
+ match v {
158
+ 27 => Ok ( 0 ) ,
159
+ 28 => Ok ( 1 ) ,
160
+ _ => Err ( StdError :: generic_err ( "Values of v other than 27 and 28 not supported. Replay protection (EIP-155) cannot be used here." ) )
161
+ }
162
+ }
163
+
90
164
#[ cfg( test) ]
91
165
mod tests {
92
166
use super :: * ;
93
167
use cosmwasm_std:: testing:: {
94
168
mock_dependencies, mock_env, mock_info, MockApi , MockQuerier , MockStorage ,
95
169
} ;
96
- use cosmwasm_std:: { from_slice, Binary , OwnedDeps , StdError , VerificationError } ;
170
+ use cosmwasm_std:: {
171
+ from_slice, Binary , OwnedDeps , RecoverPubkeyError , StdError , VerificationError ,
172
+ } ;
97
173
98
174
const CREATOR : & str = "creator" ;
99
175
@@ -107,6 +183,11 @@ mod tests {
107
183
const ED25519_PUBLIC_KEY_HEX : & str =
108
184
"fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025" ;
109
185
186
+ // Signed text "connect all the things" using MyEtherWallet with private key b5b1870957d373ef0eeffecc6e4812c0fd08f554b37b233526acc331bf1544f7
187
+ const ETHEREUM_MESSAGE : & str = "connect all the things" ;
188
+ const ETHEREUM_SIGNATURE_HEX : & str = "dada130255a447ecf434a2df9193e6fbba663e4546c35c075cd6eea21d8c7cb1714b9b65a4f7f604ff6aad55fba73f8c36514a512bbbba03709b37069194f8a41b" ;
189
+ const ETHEREUM_SIGNER_ADDRESS : & str = "0x12890D2cce102216644c59daE5baed380d84830c" ;
190
+
110
191
fn setup ( ) -> OwnedDeps < MockStorage , MockApi , MockQuerier > {
111
192
let mut deps = mock_dependencies ( & [ ] ) ;
112
193
let msg = InitMsg { } ;
@@ -187,6 +268,81 @@ mod tests {
187
268
)
188
269
}
189
270
271
+ #[ test]
272
+ fn ethereum_signature_verify_works ( ) {
273
+ let deps = setup ( ) ;
274
+
275
+ let message = ETHEREUM_MESSAGE ;
276
+ let signature = hex:: decode ( ETHEREUM_SIGNATURE_HEX ) . unwrap ( ) ;
277
+ let signer_address = ETHEREUM_SIGNER_ADDRESS ;
278
+
279
+ let verify_msg = QueryMsg :: VerifyEthereumText {
280
+ message : message. into ( ) ,
281
+ signature : signature. into ( ) ,
282
+ signer_address : signer_address. into ( ) ,
283
+ } ;
284
+ let raw = query ( deps. as_ref ( ) , mock_env ( ) , verify_msg) . unwrap ( ) ;
285
+ let res: VerifyResponse = from_slice ( & raw ) . unwrap ( ) ;
286
+
287
+ assert_eq ! ( res, VerifyResponse { verifies: true } ) ;
288
+ }
289
+
290
+ #[ test]
291
+ fn ethereum_signature_verify_fails_for_corrupted_message ( ) {
292
+ let deps = setup ( ) ;
293
+
294
+ let mut message = String :: from ( ETHEREUM_MESSAGE ) ;
295
+ message. push ( '!' ) ;
296
+ let signature = hex:: decode ( ETHEREUM_SIGNATURE_HEX ) . unwrap ( ) ;
297
+ let signer_address = ETHEREUM_SIGNER_ADDRESS ;
298
+
299
+ let verify_msg = QueryMsg :: VerifyEthereumText {
300
+ message : message. into ( ) ,
301
+ signature : signature. into ( ) ,
302
+ signer_address : signer_address. into ( ) ,
303
+ } ;
304
+ let raw = query ( deps. as_ref ( ) , mock_env ( ) , verify_msg) . unwrap ( ) ;
305
+ let res: VerifyResponse = from_slice ( & raw ) . unwrap ( ) ;
306
+
307
+ assert_eq ! ( res, VerifyResponse { verifies: false } ) ;
308
+ }
309
+
310
+ #[ test]
311
+ fn ethereum_signature_verify_fails_for_corrupted_signature ( ) {
312
+ let deps = setup ( ) ;
313
+
314
+ let message = ETHEREUM_MESSAGE ;
315
+ let signer_address = ETHEREUM_SIGNER_ADDRESS ;
316
+
317
+ // Wrong signature
318
+ let mut signature = hex:: decode ( ETHEREUM_SIGNATURE_HEX ) . unwrap ( ) ;
319
+ signature[ 5 ] ^= 0x01 ;
320
+ let verify_msg = QueryMsg :: VerifyEthereumText {
321
+ message : message. into ( ) ,
322
+ signature : signature. into ( ) ,
323
+ signer_address : signer_address. into ( ) ,
324
+ } ;
325
+ let raw = query ( deps. as_ref ( ) , mock_env ( ) , verify_msg) . unwrap ( ) ;
326
+ let res: VerifyResponse = from_slice ( & raw ) . unwrap ( ) ;
327
+ assert_eq ! ( res, VerifyResponse { verifies: false } ) ;
328
+
329
+ // Broken signature
330
+ let signature = vec ! [ 0x1c ; 65 ] ;
331
+ let verify_msg = QueryMsg :: VerifyEthereumText {
332
+ message : message. into ( ) ,
333
+ signature : signature. into ( ) ,
334
+ signer_address : signer_address. into ( ) ,
335
+ } ;
336
+ let result = query ( deps. as_ref ( ) , mock_env ( ) , verify_msg) ;
337
+ match result. unwrap_err ( ) {
338
+ StdError :: RecoverPubkeyErr {
339
+ source : RecoverPubkeyError :: UnknownErr { .. } ,
340
+ ..
341
+ } => { }
342
+ err => panic ! ( "Unexpected error: {:?}" , err) ,
343
+ }
344
+ }
345
+
190
346
#[ test]
191
347
fn tendermint_signature_verify_works ( ) {
192
348
let deps = setup ( ) ;
0 commit comments