|
1 | 1 | use rand::rng; |
2 | 2 |
|
3 | 3 | use super::*; |
4 | | -use crate::Felt; |
| 4 | +use crate::{Felt, rand::test_utils::seeded_rng}; |
5 | 5 |
|
6 | 6 | #[test] |
7 | 7 | fn test_key_generation() { |
@@ -211,3 +211,207 @@ fn test_signature_from_der_high_s_normalizes_and_flips_v() { |
211 | 211 | assert_eq!(sig.s(), &expected_s_low); |
212 | 212 | assert_eq!(sig.v(), v_initial ^ 1); |
213 | 213 | } |
| 214 | + |
| 215 | +#[test] |
| 216 | +fn test_public_key_from_der_success() { |
| 217 | + // Build a valid SPKI DER for the compressed SEC1 point of our generated key. |
| 218 | + let mut rng = seeded_rng([9u8; 32]); |
| 219 | + let secret_key = SecretKey::with_rng(&mut rng); |
| 220 | + let public_key = secret_key.public_key(); |
| 221 | + let public_key_bytes = public_key.to_bytes(); // compressed SEC1 (33 bytes). |
| 222 | + |
| 223 | + // AlgorithmIdentifier: id-ecPublicKey + secp256k1 |
| 224 | + let algo: [u8; 18] = [ |
| 225 | + 0x30, 0x10, // SEQUENCE, length 16 |
| 226 | + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // OID 1.2.840.10045.2.1 |
| 227 | + 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, // OID 1.3.132.0.10 (secp256k1) |
| 228 | + ]; |
| 229 | + |
| 230 | + // subjectPublicKey BIT STRING: 0 unused bits + compressed SEC1. |
| 231 | + let mut spk = Vec::with_capacity(2 + 1 + public_key_bytes.len()); |
| 232 | + spk.push(0x03); // BIT STRING |
| 233 | + spk.push((1 + public_key_bytes.len()) as u8); // length |
| 234 | + spk.push(0x00); // unused bits = 0 |
| 235 | + spk.extend_from_slice(&public_key_bytes); |
| 236 | + |
| 237 | + // Outer SEQUENCE. |
| 238 | + let mut der = Vec::with_capacity(2 + algo.len() + spk.len()); |
| 239 | + der.push(0x30); // SEQUENCE |
| 240 | + der.push((algo.len() + spk.len()) as u8); // total length |
| 241 | + der.extend_from_slice(&algo); |
| 242 | + der.extend_from_slice(&spk); |
| 243 | + |
| 244 | + let parsed = PublicKey::from_der(&der).expect("should parse valid SPKI DER"); |
| 245 | + assert_eq!(parsed, public_key); |
| 246 | +} |
| 247 | + |
| 248 | +#[test] |
| 249 | +fn test_public_key_from_der_invalid() { |
| 250 | + // Empty DER. |
| 251 | + match PublicKey::from_der(&[]) { |
| 252 | + Err(super::DeserializationError::InvalidValue(_)) => {}, |
| 253 | + other => panic!("expected InvalidValue for empty DER, got {:?}", other), |
| 254 | + } |
| 255 | + |
| 256 | + // Malformed: SEQUENCE with zero length (missing fields). |
| 257 | + let der_bad: [u8; 2] = [0x30, 0x00]; |
| 258 | + match PublicKey::from_der(&der_bad) { |
| 259 | + Err(super::DeserializationError::InvalidValue(_)) => {}, |
| 260 | + other => panic!("expected InvalidValue for malformed DER, got {:?}", other), |
| 261 | + } |
| 262 | +} |
| 263 | + |
| 264 | +#[test] |
| 265 | +fn test_public_key_from_der_rejects_non_canonical_long_form_length() { |
| 266 | + // Build a valid SPKI structure but encode the outer SEQUENCE length using non-canonical |
| 267 | + // long-form (0x81 <len>) even though the length < 128. DER should reject this. |
| 268 | + let mut rng = seeded_rng([10u8; 32]); |
| 269 | + let secret_key = SecretKey::with_rng(&mut rng); |
| 270 | + let public_key = secret_key.public_key(); |
| 271 | + let public_key_bytes = public_key.to_bytes(); // compressed SEC1 (33 bytes) |
| 272 | + |
| 273 | + // AlgorithmIdentifier: id-ecPublicKey + secp256k1 |
| 274 | + let algo: [u8; 18] = [ |
| 275 | + 0x30, 0x10, // SEQUENCE, length 16 |
| 276 | + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // OID 1.2.840.10045.2.1 |
| 277 | + 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, // OID 1.3.132.0.10 (secp256k1) |
| 278 | + ]; |
| 279 | + |
| 280 | + // subjectPublicKey BIT STRING: 0 unused bits + compressed SEC1 |
| 281 | + let mut spk = Vec::with_capacity(2 + 1 + public_key_bytes.len()); |
| 282 | + spk.push(0x03); // BIT STRING |
| 283 | + spk.push((1 + public_key_bytes.len()) as u8); // length |
| 284 | + spk.push(0x00); // unused bits = 0 |
| 285 | + spk.extend_from_slice(&public_key_bytes); |
| 286 | + |
| 287 | + // Outer SEQUENCE using non-canonical long-form length (0x81) |
| 288 | + let total_len = (algo.len() + spk.len()) as u8; // fits in one byte |
| 289 | + let mut der = Vec::with_capacity(3 + algo.len() + spk.len()); |
| 290 | + der.push(0x30); // SEQUENCE |
| 291 | + der.push(0x81); // long-form length marker with one subsequent length byte |
| 292 | + der.push(total_len); |
| 293 | + der.extend_from_slice(&algo); |
| 294 | + der.extend_from_slice(&spk); |
| 295 | + |
| 296 | + match PublicKey::from_der(&der) { |
| 297 | + Err(super::DeserializationError::InvalidValue(_)) => {}, |
| 298 | + other => { |
| 299 | + panic!("expected InvalidValue for non-canonical long-form length, got {:?}", other) |
| 300 | + }, |
| 301 | + } |
| 302 | +} |
| 303 | + |
| 304 | +#[test] |
| 305 | +fn test_public_key_from_der_rejects_trailing_bytes() { |
| 306 | + // Build a valid SPKI DER but append trailing bytes after the sequence; DER should reject. |
| 307 | + let mut rng = seeded_rng([11u8; 32]); |
| 308 | + let secret_key = SecretKey::with_rng(&mut rng); |
| 309 | + let public_key = secret_key.public_key(); |
| 310 | + let public_key_bytes = public_key.to_bytes(); // compressed SEC1 (33 bytes) |
| 311 | + |
| 312 | + // AlgorithmIdentifier: id-ecPublicKey + secp256k1. |
| 313 | + let algo: [u8; 18] = [ |
| 314 | + 0x30, 0x10, // SEQUENCE, length 16 |
| 315 | + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // OID 1.2.840.10045.2.1 |
| 316 | + 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, // OID 1.3.132.0.10 (secp256k1) |
| 317 | + ]; |
| 318 | + |
| 319 | + // subjectPublicKey BIT STRING: 0 unused bits + compressed SEC1. |
| 320 | + let mut spk = Vec::with_capacity(2 + 1 + public_key_bytes.len()); |
| 321 | + spk.push(0x03); // BIT STRING |
| 322 | + spk.push((1 + public_key_bytes.len()) as u8); // length |
| 323 | + spk.push(0x00); // unused bits = 0 |
| 324 | + spk.extend_from_slice(&public_key_bytes); |
| 325 | + |
| 326 | + // Outer SEQUENCE with short-form length. |
| 327 | + let total_len = (algo.len() + spk.len()) as u8; |
| 328 | + let mut der = Vec::with_capacity(2 + algo.len() + spk.len() + 2); |
| 329 | + der.push(0x30); // SEQUENCE |
| 330 | + der.push(total_len); |
| 331 | + der.extend_from_slice(&algo); |
| 332 | + der.extend_from_slice(&spk); |
| 333 | + |
| 334 | + // Append trailing junk. |
| 335 | + der.push(0x00); |
| 336 | + der.push(0x00); |
| 337 | + |
| 338 | + match PublicKey::from_der(&der) { |
| 339 | + Err(super::DeserializationError::InvalidValue(_)) => {}, |
| 340 | + other => panic!("expected InvalidValue for DER with trailing bytes, got {:?}", other), |
| 341 | + } |
| 342 | +} |
| 343 | + |
| 344 | +#[test] |
| 345 | +fn test_public_key_from_der_rejects_wrong_curve_oid() { |
| 346 | + // Same structure but with prime256v1 (P-256) curve OID instead of secp256k1. |
| 347 | + let mut rng = seeded_rng([12u8; 32]); |
| 348 | + let secret_key = SecretKey::with_rng(&mut rng); |
| 349 | + let public_key = secret_key.public_key(); |
| 350 | + let public_key_bytes = public_key.to_bytes(); // compressed SEC1 (33 bytes) |
| 351 | + |
| 352 | + // AlgorithmIdentifier: id-ecPublicKey + prime256v1 (1.2.840.10045.3.1.7). |
| 353 | + // Completed prime256v1 OID tail for correctness |
| 354 | + // Full DER OID bytes for 1.2.840.10045.3.1.7 are: 06 08 2A 86 48 CE 3D 03 01 07 |
| 355 | + // We'll encode properly below with 8 length, then adjust the outer lengths accordingly. |
| 356 | + |
| 357 | + // AlgorithmIdentifier with correct OID encoding but wrong curve: |
| 358 | + let algo_full: [u8; 21] = [ |
| 359 | + 0x30, 0x12, // SEQUENCE, length 18 |
| 360 | + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, // id-ecPublicKey |
| 361 | + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, // prime256v1 |
| 362 | + ]; |
| 363 | + |
| 364 | + // subjectPublicKey BIT STRING. |
| 365 | + let mut spk = Vec::with_capacity(2 + 1 + public_key_bytes.len()); |
| 366 | + spk.push(0x03); |
| 367 | + spk.push((1 + public_key_bytes.len()) as u8); |
| 368 | + spk.push(0x00); |
| 369 | + spk.extend_from_slice(&public_key_bytes); |
| 370 | + |
| 371 | + let mut der = Vec::with_capacity(2 + algo_full.len() + spk.len()); |
| 372 | + der.push(0x30); |
| 373 | + der.push((algo_full.len() + spk.len()) as u8); |
| 374 | + der.extend_from_slice(&algo_full); |
| 375 | + der.extend_from_slice(&spk); |
| 376 | + |
| 377 | + match PublicKey::from_der(&der) { |
| 378 | + Err(super::DeserializationError::InvalidValue(_)) => {}, |
| 379 | + other => panic!("expected InvalidValue for wrong curve OID, got {:?}", other), |
| 380 | + } |
| 381 | +} |
| 382 | + |
| 383 | +#[test] |
| 384 | +fn test_public_key_from_der_rejects_wrong_algorithm_oid() { |
| 385 | + // Use rsaEncryption (1.2.840.113549.1.1.1) instead of id-ecPublicKey. |
| 386 | + let mut rng = seeded_rng([13u8; 32]); |
| 387 | + let secret_key = SecretKey::with_rng(&mut rng); |
| 388 | + let public_key = secret_key.public_key(); |
| 389 | + let public_key_bytes = public_key.to_bytes(); |
| 390 | + |
| 391 | + // AlgorithmIdentifier: rsaEncryption + NULL parameter. |
| 392 | + // OID bytes for 1.2.840.113549.1.1.1: 06 09 2A 86 48 86 F7 0D 01 01 01. |
| 393 | + // NULL parameter: 05 00. |
| 394 | + let algo_rsa: [u8; 15] = [ |
| 395 | + 0x30, 0x0d, // SEQUENCE, length 13 |
| 396 | + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // rsaEncryption |
| 397 | + 0x05, 0x00, // NULL |
| 398 | + ]; |
| 399 | + |
| 400 | + // subjectPublicKey BIT STRING with EC compressed point (intentionally mismatched with algo). |
| 401 | + let mut spk = Vec::with_capacity(2 + 1 + public_key_bytes.len()); |
| 402 | + spk.push(0x03); |
| 403 | + spk.push((1 + public_key_bytes.len()) as u8); |
| 404 | + spk.push(0x00); |
| 405 | + spk.extend_from_slice(&public_key_bytes); |
| 406 | + |
| 407 | + let mut der = Vec::with_capacity(2 + algo_rsa.len() + spk.len()); |
| 408 | + der.push(0x30); |
| 409 | + der.push((algo_rsa.len() + spk.len()) as u8); |
| 410 | + der.extend_from_slice(&algo_rsa); |
| 411 | + der.extend_from_slice(&spk); |
| 412 | + |
| 413 | + match PublicKey::from_der(&der) { |
| 414 | + Err(super::DeserializationError::InvalidValue(_)) => {}, |
| 415 | + other => panic!("expected InvalidValue for wrong algorithm OID, got {:?}", other), |
| 416 | + } |
| 417 | +} |
0 commit comments