Skip to content

Commit 5cf4ba8

Browse files
committed
Secp256r1 plumbing / tests
1 parent eb466a5 commit 5cf4ba8

File tree

8 files changed

+250
-3
lines changed

8 files changed

+250
-3
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,18 @@ extern "C" {
227227
recovery_param: u32,
228228
) -> u64;
229229

230+
/// Verifies message hashes against a signature with a public key, using the
231+
/// secp256r1 ECDSA parametrization.
232+
/// Returns 0 on verification success, 1 on verification failure, and values
233+
/// greater than 1 in case of error.
234+
fn secp256r1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
235+
236+
fn secp256r1_recover_pubkey(
237+
message_hash_ptr: u32,
238+
signature_ptr: u32,
239+
recovery_param: u32,
240+
) -> u64;
241+
230242
/// Verifies a message against a signature with a public key, using the
231243
/// ed25519 EdDSA scheme.
232244
/// Returns 0 on verification success, 1 on verification failure, and values

packages/std/src/imports.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ extern "C" {
6464
/// greater than 1 in case of error.
6565
fn secp256r1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32;
6666

67+
fn secp256r1_recover_pubkey(
68+
message_hash_ptr: u32,
69+
signature_ptr: u32,
70+
recovery_param: u32,
71+
) -> u64;
72+
6773
/// Verifies a message against a signature with a public key, using the
6874
/// ed25519 EdDSA scheme.
6975
/// Returns 0 on verification success, 1 on verification failure, and values
@@ -439,6 +445,34 @@ impl Api for ExternalApi {
439445
}
440446
}
441447

448+
fn secp256r1_recover_pubkey(
449+
&self,
450+
message_hash: &[u8],
451+
signature: &[u8],
452+
recover_param: u8,
453+
) -> Result<Vec<u8>, RecoverPubkeyError> {
454+
let hash_send = build_region(message_hash);
455+
let hash_send_ptr = &*hash_send as *const Region as u32;
456+
let sig_send = build_region(signature);
457+
let sig_send_ptr = &*sig_send as *const Region as u32;
458+
459+
let result =
460+
unsafe { secp256r1_recover_pubkey(hash_send_ptr, sig_send_ptr, recover_param.into()) };
461+
let error_code = from_high_half(result);
462+
let pubkey_ptr = from_low_half(result);
463+
match error_code {
464+
0 => {
465+
let pubkey = unsafe { consume_region(pubkey_ptr as *mut Region) };
466+
Ok(pubkey)
467+
}
468+
2 => panic!("MessageTooLong must not happen. This is a bug in the VM."),
469+
3 => Err(RecoverPubkeyError::InvalidHashFormat),
470+
4 => Err(RecoverPubkeyError::InvalidSignatureFormat),
471+
6 => Err(RecoverPubkeyError::InvalidRecoveryParam),
472+
error_code => Err(RecoverPubkeyError::unknown_err(error_code)),
473+
}
474+
}
475+
442476
fn ed25519_verify(
443477
&self,
444478
message: &[u8],

packages/std/src/testing/mock.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,17 @@ impl Api for MockApi {
191191
)?)
192192
}
193193

194+
fn secp256r1_recover_pubkey(
195+
&self,
196+
message_hash: &[u8],
197+
signature: &[u8],
198+
recovery_param: u8,
199+
) -> Result<Vec<u8>, RecoverPubkeyError> {
200+
let pubkey =
201+
cosmwasm_crypto::secp256r1_recover_pubkey(message_hash, signature, recovery_param)?;
202+
Ok(pubkey.to_vec())
203+
}
204+
194205
fn ed25519_verify(
195206
&self,
196207
message: &[u8],
@@ -1119,6 +1130,11 @@ mod tests {
11191130
const SECP256K1_SIG_HEX: &str = "207082eb2c3dfa0b454e0906051270ba4074ac93760ba9e7110cd9471475111151eb0dbbc9920e72146fb564f99d039802bf6ef2561446eb126ef364d21ee9c4";
11201131
const SECP256K1_PUBKEY_HEX: &str = "04051c1ee2190ecfb174bfe4f90763f2b4ff7517b70a2aec1876ebcfd644c4633fb03f3cfbd94b1f376e34592d9d41ccaf640bb751b00a1fadeb0c01157769eb73";
11211132

1133+
const SECP256R1_MSG_HASH_HEX: &str =
1134+
"5eb28029ebf3c7025ff2fc2f6de6f62aecf6a72139e1cba5f20d11bbef036a7f";
1135+
const SECP256R1_SIG_HEX: &str = "e67a9717ccf96841489d6541f4f6adb12d17b59a6bef847b6183b8fcf16a32eb9ae6ba6d637706849a6a9fc388cf0232d85c26ea0d1fe7437adb48de58364333";
1136+
const SECP256R1_PUBKEY_HEX: &str = "0468229b48c2fe19d3db034e4c15077eb7471a66031f28a980821873915298ba76303e8ee3742a893f78b810991da697083dd8f11128c47651c27a56740a80c24c";
1137+
11221138
const ED25519_MSG_HEX: &str = "72";
11231139
const ED25519_SIG_HEX: &str = "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00";
11241140
const ED25519_PUBKEY_HEX: &str =
@@ -1352,6 +1368,113 @@ mod tests {
13521368
}
13531369
}
13541370

1371+
// Basic "works" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
1372+
#[test]
1373+
fn secp256r1_verify_works() {
1374+
let api = MockApi::default();
1375+
1376+
let hash = hex::decode(SECP256R1_MSG_HASH_HEX).unwrap();
1377+
let signature = hex::decode(SECP256R1_SIG_HEX).unwrap();
1378+
let public_key = hex::decode(SECP256R1_PUBKEY_HEX).unwrap();
1379+
1380+
assert!(api
1381+
.secp256r1_verify(&hash, &signature, &public_key)
1382+
.unwrap());
1383+
}
1384+
1385+
// Basic "fails" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
1386+
#[test]
1387+
fn secp256r1_verify_fails() {
1388+
let api = MockApi::default();
1389+
1390+
let mut hash = hex::decode(SECP256R1_MSG_HASH_HEX).unwrap();
1391+
// alter hash
1392+
hash[0] ^= 0x01;
1393+
let signature = hex::decode(SECP256R1_SIG_HEX).unwrap();
1394+
let public_key = hex::decode(SECP256R1_PUBKEY_HEX).unwrap();
1395+
1396+
assert!(!api
1397+
.secp256r1_verify(&hash, &signature, &public_key)
1398+
.unwrap());
1399+
}
1400+
1401+
// Basic "errors" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
1402+
#[test]
1403+
fn secp256r1_verify_errs() {
1404+
let api = MockApi::default();
1405+
1406+
let hash = hex::decode(SECP256R1_MSG_HASH_HEX).unwrap();
1407+
let signature = hex::decode(SECP256R1_SIG_HEX).unwrap();
1408+
let public_key = vec![];
1409+
1410+
let res = api.secp256r1_verify(&hash, &signature, &public_key);
1411+
assert_eq!(res.unwrap_err(), VerificationError::InvalidPubkeyFormat);
1412+
}
1413+
1414+
#[test]
1415+
fn secp256r1_recover_pubkey_works() {
1416+
let api = MockApi::default();
1417+
1418+
let hash = hex!("17b03f9f00f6692ccdde485fc63c4530751ef35da6f71336610944b0894fcfb8");
1419+
let signature = hex!("9886ae46c1415c3bc959e82b760ad760aab66885a84e620aa339fdf102465c422bf3a80bc04faa35ebecc0f4864ac02d349f6f126e0f988501b8d3075409a26c");
1420+
let recovery_param = 0;
1421+
let expected = hex!("0451f99d2d52d4a6e734484a018b7ca2f895c2929b6754a3a03224d07ae61166ce4737da963c6ef7247fb88d19f9b0c667cac7fe12837fdab88c66f10d3c14cad1");
1422+
1423+
let pubkey = api
1424+
.secp256r1_recover_pubkey(&hash, &signature, recovery_param)
1425+
.unwrap();
1426+
assert_eq!(pubkey, expected);
1427+
}
1428+
1429+
#[test]
1430+
fn secp256r1_recover_pubkey_fails_for_wrong_recovery_param() {
1431+
let api = MockApi::default();
1432+
1433+
let hash = hex!("17b03f9f00f6692ccdde485fc63c4530751ef35da6f71336610944b0894fcfb8");
1434+
let signature = hex!("9886ae46c1415c3bc959e82b760ad760aab66885a84e620aa339fdf102465c422bf3a80bc04faa35ebecc0f4864ac02d349f6f126e0f988501b8d3075409a26c");
1435+
let expected = hex!("0451f99d2d52d4a6e734484a018b7ca2f895c2929b6754a3a03224d07ae61166ce4737da963c6ef7247fb88d19f9b0c667cac7fe12837fdab88c66f10d3c14cad1");
1436+
1437+
// Wrong recovery param leads to different pubkey
1438+
let pubkey = api.secp256r1_recover_pubkey(&hash, &signature, 1).unwrap();
1439+
assert_eq!(pubkey.len(), 65);
1440+
assert_ne!(pubkey, expected);
1441+
1442+
// Invalid recovery param leads to error
1443+
let result = api.secp256r1_recover_pubkey(&hash, &signature, 42);
1444+
match result.unwrap_err() {
1445+
RecoverPubkeyError::InvalidRecoveryParam => {}
1446+
err => panic!("Unexpected error: {err:?}"),
1447+
}
1448+
}
1449+
1450+
#[test]
1451+
fn secp256r1_recover_pubkey_fails_for_wrong_hash() {
1452+
let api = MockApi::default();
1453+
1454+
let hash = hex!("17b03f9f00f6692ccdde485fc63c4530751ef35da6f71336610944b0894fcfb8");
1455+
let signature = hex!("9886ae46c1415c3bc959e82b760ad760aab66885a84e620aa339fdf102465c422bf3a80bc04faa35ebecc0f4864ac02d349f6f126e0f988501b8d3075409a26c");
1456+
let recovery_param = 0;
1457+
let expected = hex!("0451f99d2d52d4a6e734484a018b7ca2f895c2929b6754a3a03224d07ae61166ce4737da963c6ef7247fb88d19f9b0c667cac7fe12837fdab88c66f10d3c14cad1");
1458+
1459+
// Wrong hash
1460+
let mut corrupted_hash = hash;
1461+
corrupted_hash[0] ^= 0x01;
1462+
let pubkey = api
1463+
.secp256r1_recover_pubkey(&corrupted_hash, &signature, recovery_param)
1464+
.unwrap();
1465+
assert_eq!(pubkey.len(), 65);
1466+
assert_ne!(pubkey, expected);
1467+
1468+
// Malformed hash
1469+
let mut malformed_hash = hash.to_vec();
1470+
malformed_hash.push(0x8a);
1471+
let result = api.secp256r1_recover_pubkey(&malformed_hash, &signature, recovery_param);
1472+
match result.unwrap_err() {
1473+
RecoverPubkeyError::InvalidHashFormat => {}
1474+
err => panic!("Unexpected error: {err:?}"),
1475+
}
1476+
}
1477+
13551478
// Basic "works" test. Exhaustive tests on VM's side (packages/vm/src/imports.rs)
13561479
#[test]
13571480
fn ed25519_verify_works() {

packages/std/src/traits.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ pub trait Api {
172172
public_key: &[u8],
173173
) -> Result<bool, VerificationError>;
174174

175+
fn secp256r1_recover_pubkey(
176+
&self,
177+
message_hash: &[u8],
178+
signature: &[u8],
179+
recovery_param: u8,
180+
) -> Result<Vec<u8>, RecoverPubkeyError>;
181+
175182
fn ed25519_verify(
176183
&self,
177184
message: &[u8],

packages/vm/src/compatibility.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const SUPPORTED_IMPORTS: &[&str] = &[
2323
"env.secp256k1_verify",
2424
"env.secp256k1_recover_pubkey",
2525
"env.secp256r1_verify",
26+
"env.secp256r1_recover_pubkey",
2627
"env.ed25519_verify",
2728
"env.ed25519_batch_verify",
2829
"env.debug",
@@ -626,6 +627,7 @@ mod tests {
626627
(import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32)))
627628
(import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
628629
(import "env" "secp256r1_verify" (func (param i32 i32 i32) (result i32)))
630+
(import "env" "secp256r1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
629631
(import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32)))
630632
(import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32)))
631633
)"#,
@@ -646,6 +648,7 @@ mod tests {
646648
(import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32)))
647649
(import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
648650
(import "env" "secp256r1_verify" (func (param i32 i32 i32) (result i32)))
651+
(import "env" "secp256r1_recover_pubkey" (func (param i32 i32 i32) (result i64)))
649652
(import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32)))
650653
(import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32)))
651654
(import "env" "spam01" (func (param i32 i32) (result i32)))
@@ -738,7 +741,6 @@ mod tests {
738741
(import "env" "spam88" (func (param i32 i32) (result i32)))
739742
(import "env" "spam89" (func (param i32 i32) (result i32)))
740743
(import "env" "spam90" (func (param i32 i32) (result i32)))
741-
(import "env" "spam91" (func (param i32 i32) (result i32)))
742744
)"#,
743745
)
744746
.unwrap();

packages/vm/src/environment.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ mod tests {
510510
"secp256k1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
511511
"secp256k1_recover_pubkey" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }),
512512
"secp256r1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
513+
"secp256r1_recover_pubkey" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }),
513514
"ed25519_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
514515
"ed25519_batch_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
515516
"debug" => Function::new_typed(&mut store, |_a: u32| {}),

packages/vm/src/imports.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::marker::PhantomData;
55

66
use cosmwasm_crypto::{
77
ed25519_batch_verify, ed25519_verify, secp256k1_recover_pubkey, secp256k1_verify,
8-
secp256r1_verify, CryptoError,
8+
secp256r1_recover_pubkey, secp256r1_verify, CryptoError,
99
};
1010
use cosmwasm_crypto::{
1111
ECDSA_PUBKEY_MAX_LEN, ECDSA_SIGNATURE_LEN, EDDSA_PUBKEY_LEN, MESSAGE_HASH_MAX_LEN,
@@ -355,6 +355,45 @@ pub fn do_secp256r1_verify<A: BackendApi + 'static, S: Storage + 'static, Q: Que
355355
Ok(code)
356356
}
357357

358+
pub fn do_secp256r1_recover_pubkey<
359+
A: BackendApi + 'static,
360+
S: Storage + 'static,
361+
Q: Querier + 'static,
362+
>(
363+
mut env: FunctionEnvMut<Environment<A, S, Q>>,
364+
hash_ptr: u32,
365+
signature_ptr: u32,
366+
recover_param: u32,
367+
) -> VmResult<u64> {
368+
let (data, mut store) = env.data_and_store_mut();
369+
370+
let hash = read_region(&data.memory(&store), hash_ptr, MESSAGE_HASH_MAX_LEN)?;
371+
let signature = read_region(&data.memory(&store), signature_ptr, ECDSA_SIGNATURE_LEN)?;
372+
let recover_param: u8 = match recover_param.try_into() {
373+
Ok(rp) => rp,
374+
Err(_) => return Ok((CryptoError::invalid_recovery_param().code() as u64) << 32),
375+
};
376+
377+
let gas_info = GasInfo::with_cost(data.gas_config.secp256r1_recover_pubkey_cost);
378+
process_gas_info(data, &mut store, gas_info)?;
379+
let result = secp256r1_recover_pubkey(&hash, &signature, recover_param);
380+
match result {
381+
Ok(pubkey) => {
382+
let pubkey_ptr = write_to_contract(data, &mut store, pubkey.as_ref())?;
383+
Ok(to_low_half(pubkey_ptr))
384+
}
385+
Err(err) => match err {
386+
CryptoError::InvalidHashFormat { .. }
387+
| CryptoError::InvalidSignatureFormat { .. }
388+
| CryptoError::InvalidRecoveryParam { .. }
389+
| CryptoError::GenericErr { .. } => Ok(to_high_half(err.code())),
390+
CryptoError::BatchErr { .. } | CryptoError::InvalidPubkeyFormat { .. } => {
391+
panic!("Error must not happen for this call")
392+
}
393+
},
394+
}
395+
}
396+
358397
/// Return code (error code) for a valid signature
359398
const ED25519_VERIFY_CODE_VALID: u32 = 0;
360399

@@ -720,6 +759,7 @@ mod tests {
720759
"secp256k1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
721760
"secp256k1_recover_pubkey" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }),
722761
"secp256r1_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
762+
"secp256r1_recover_pubkey" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u64 { 0 }),
723763
"ed25519_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
724764
"ed25519_batch_verify" => Function::new_typed(&mut store, |_a: u32, _b: u32, _c: u32| -> u32 { 0 }),
725765
"debug" => Function::new_typed(&mut store, |_a: u32| {}),
@@ -1972,6 +2012,28 @@ mod tests {
19722012
)
19732013
}
19742014

2015+
#[test]
2016+
fn do_secp256r1_recover_pubkey_works() {
2017+
let api = MockApi::default();
2018+
let (fe, mut store, _instance) = make_instance(api);
2019+
let mut fe_mut = fe.into_mut(&mut store);
2020+
2021+
let hash = hex!("12135386c09e0bf6fd5c454a95bcfe9b3edb25c71e455c73a212405694b29002");
2022+
let sig = hex!("b53ce4da1aa7c0dc77a1896ab716b921499aed78df725b1504aba1597ba0c64bd7c246dc7ad0e67700c373edcfdd1c0a0495fc954549ad579df6ed1438840851");
2023+
let recovery_param = 0;
2024+
let expected = hex!("040a7dbb8bf50cb605eb2268b081f26d6b08e012f952c4b70a5a1e6e7d46af98bbf26dd7d799930062480849962ccf5004edcfd307c044f4e8f667c9baa834eeae");
2025+
2026+
let hash_ptr = write_data(&mut fe_mut, &hash);
2027+
let sig_ptr = write_data(&mut fe_mut, &sig);
2028+
let result =
2029+
do_secp256r1_recover_pubkey(fe_mut.as_mut(), hash_ptr, sig_ptr, recovery_param)
2030+
.unwrap();
2031+
let error = result >> 32;
2032+
let pubkey_ptr: u32 = (result & 0xFFFFFFFF).try_into().unwrap();
2033+
assert_eq!(error, 0);
2034+
assert_eq!(force_read(&mut fe_mut, pubkey_ptr), expected);
2035+
}
2036+
19752037
#[test]
19762038
fn do_ed25519_verify_works() {
19772039
let api = MockApi::default();

packages/vm/src/instance.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use crate::errors::{CommunicationError, VmError, VmResult};
1616
use crate::imports::{
1717
do_abort, do_addr_canonicalize, do_addr_humanize, do_addr_validate, do_db_read, do_db_remove,
1818
do_db_write, do_debug, do_ed25519_batch_verify, do_ed25519_verify, do_query_chain,
19-
do_secp256k1_recover_pubkey, do_secp256k1_verify, do_secp256r1_verify,
19+
do_secp256k1_recover_pubkey, do_secp256k1_verify, do_secp256r1_recover_pubkey,
20+
do_secp256r1_verify,
2021
};
2122
#[cfg(feature = "iterator")]
2223
use crate::imports::{do_db_next, do_db_next_key, do_db_next_value, do_db_scan};
@@ -162,6 +163,11 @@ where
162163
Function::new_typed_with_env(&mut store, &fe, do_secp256r1_verify),
163164
);
164165

166+
env_imports.insert(
167+
"secp256r1_recover_pubkey",
168+
Function::new_typed_with_env(&mut store, &fe, do_secp256r1_recover_pubkey),
169+
);
170+
165171
// Verifies a message against a signature with a public key, using the ed25519 EdDSA scheme.
166172
// Returns 0 on verification success, 1 on verification failure, and values greater than 1 in case of error.
167173
// Ownership of input pointers is not transferred to the host.

0 commit comments

Comments
 (0)