From 562cf75a0b6a834bb793a205de53645c60434b06 Mon Sep 17 00:00:00 2001 From: xjd Date: Fri, 26 Jul 2024 15:55:58 +0800 Subject: [PATCH] ecdsa: add more secp256k1 wycheproof cases test data is generated from ecdsa_secp256k1_sha256_p1363_test.json with following command: wycheproof2blb ~/projects/wycheproof secp256k1-p1316 256 ./wycheproof-p1316.blb ./desc-p1316.txt --- Cargo.lock | 1 + k256/Cargo.toml | 1 + k256/src/ecdsa.rs | 67 +++++++++++------- .../test_vectors/data/wycheproof-p1316.blb | Bin 0 -> 19808 bytes 4 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 k256/src/test_vectors/data/wycheproof-p1316.blb diff --git a/Cargo.lock b/Cargo.lock index f8de5849..559858d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,6 +613,7 @@ dependencies = [ "criterion", "ecdsa", "elliptic-curve", + "hex", "hex-literal", "num-bigint", "num-traits", diff --git a/k256/Cargo.toml b/k256/Cargo.toml index d4688435..2c5f1cf3 100644 --- a/k256/Cargo.toml +++ b/k256/Cargo.toml @@ -40,6 +40,7 @@ num-traits = "0.2" proptest = "1.5" rand_core = { version = "0.6", features = ["getrandom"] } sha3 = { version = "=0.11.0-pre.3", default-features = false } +hex = "0.4.3" [features] default = ["arithmetic", "ecdsa", "pkcs8", "precomputed-tables", "schnorr", "std"] diff --git a/k256/src/ecdsa.rs b/k256/src/ecdsa.rs index f6d894e6..d61590f3 100644 --- a/k256/src/ecdsa.rs +++ b/k256/src/ecdsa.rs @@ -294,6 +294,7 @@ mod tests { msg: &[u8], sig: &[u8], pass: bool, + p1363_sig: bool, ) -> Option<&'static str> { let x = element_from_padded_slice::(wx); let y = element_from_padded_slice::(wy); @@ -302,10 +303,18 @@ mod tests { let verifying_key = ecdsa_core::VerifyingKey::from_encoded_point(&q_encoded).unwrap(); - let sig = match Signature::::from_der(sig) { - Ok(s) => s.normalize_s(), - Err(_) if !pass => return None, - Err(_) => return Some("failed to parse signature ASN.1"), + let sig = if p1363_sig { + match Signature::::from_slice(sig) { + Ok(s) => s.normalize_s(), + Err(_) if !pass => return None, + Err(_) => return Some("failed to parse signature P1363"), + } + } else { + match Signature::::from_der(sig) { + Ok(s) => s.normalize_s(), + Err(_) if !pass => return None, + Err(_) => return Some("failed to parse signature ASN.1"), + } }; match verifying_key.verify(msg, &sig) { @@ -316,28 +325,38 @@ mod tests { } } - let data = include_bytes!(concat!("test_vectors/data/", "wycheproof", ".blb")); - - for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() { - let [wx, wy, msg, sig, status] = row.unwrap(); - let pass = match status[0] { - 0 => false, - 1 => true, - _ => panic!("invalid value for pass flag"), - }; - if let Some(desc) = run_test(wx, wy, msg, sig, pass) { - panic!( - "\n\ - Failed test №{}: {}\n\ - wx:\t{:?}\n\ - wy:\t{:?}\n\ - msg:\t{:?}\n\ - sig:\t{:?}\n\ - pass:\t{}\n", - i, desc, wx, wy, msg, sig, pass, - ); + fn run(data: &[u8], p1363_sig: bool) { + for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() { + let [wx, wy, msg, sig, status] = row.unwrap(); + let pass = match status[0] { + 0 => false, + 1 => true, + _ => panic!("invalid value for pass flag"), + }; + if let Some(desc) = run_test(wx, wy, msg, sig, pass, p1363_sig) { + panic!( + "\n\ + Failed test №{}: {}\n\ + wx:\t{:?}\n\ + wy:\t{:?}\n\ + msg:\t{:?}\n\ + sig:\t{:?}\n\ + pass:\t{}\n", + i, + desc, + hex::encode(wx), + hex::encode(wy), + hex::encode(msg), + hex::encode(sig), + pass, + ); + } } } + let data = include_bytes!(concat!("test_vectors/data/", "wycheproof", ".blb")); + run(data, false); + let data2 = include_bytes!(concat!("test_vectors/data/", "wycheproof-p1316", ".blb")); + run(data2, true); } } } diff --git a/k256/src/test_vectors/data/wycheproof-p1316.blb b/k256/src/test_vectors/data/wycheproof-p1316.blb new file mode 100644 index 0000000000000000000000000000000000000000..1de8d251a5d96988d95680dba54155ce5d17e9ea GIT binary patch literal 19808 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