| 
 | 1 | +use proptest::prelude::*;  | 
1 | 2 | use stacks_common::types::chainstate::{StacksPrivateKey, StacksPublicKey};  | 
2 | 3 | use stacks_common::types::{PrivateKey, StacksEpochId};  | 
3 | 4 | use stacks_common::util::hash::{to_hex, Sha256Sum};  | 
@@ -370,3 +371,285 @@ fn test_secp256k1_recover_invalid_signature_returns_err_code() {  | 
370 | 371 |         other => panic!("expected err response, found {other:?}"),  | 
371 | 372 |     }  | 
372 | 373 | }  | 
 | 374 | + | 
 | 375 | +proptest! {  | 
 | 376 | +    #[test]  | 
 | 377 | +    fn prop_secp256k1_verify_accepts_valid_signatures(  | 
 | 378 | +        seed in any::<[u8; 32]>(),  | 
 | 379 | +        message in any::<[u8; 32]>()  | 
 | 380 | +    ) {  | 
 | 381 | +        let privk = StacksPrivateKey::from_seed(&seed);  | 
 | 382 | +        let pubk = StacksPublicKey::from_private(&privk);  | 
 | 383 | +        let pubkey_bytes = pubk.to_bytes_compressed();  | 
 | 384 | +        let message = message.to_vec();  | 
 | 385 | +        let signature: Secp256k1Signature = privk.sign(&message).expect("secp256k1 signing should succeed");  | 
 | 386 | +        let signature_bytes = signature.to_rsv();  | 
 | 387 | +        let program = format!(  | 
 | 388 | +            "(secp256k1-verify {} {} {})",  | 
 | 389 | +            buff_literal(&message),  | 
 | 390 | +            buff_literal(&signature_bytes),  | 
 | 391 | +            buff_literal(&pubkey_bytes)  | 
 | 392 | +        );  | 
 | 393 | + | 
 | 394 | +        let result = execute_with_parameters(  | 
 | 395 | +            program.as_str(),  | 
 | 396 | +            ClarityVersion::Clarity4,  | 
 | 397 | +            StacksEpochId::Epoch33,  | 
 | 398 | +            false,  | 
 | 399 | +        )  | 
 | 400 | +        .expect("execution should succeed")  | 
 | 401 | +        .expect("should return a value");  | 
 | 402 | + | 
 | 403 | +        prop_assert_eq!(Value::Bool(true), result);  | 
 | 404 | +    }  | 
 | 405 | + | 
 | 406 | +    #[test]  | 
 | 407 | +    fn prop_secp256k1_recover_matches_public_key(  | 
 | 408 | +        seed in any::<[u8; 32]>(),  | 
 | 409 | +        message in any::<[u8; 32]>()  | 
 | 410 | +    ) {  | 
 | 411 | +        let privk = StacksPrivateKey::from_seed(&seed);  | 
 | 412 | +        let pubk = StacksPublicKey::from_private(&privk);  | 
 | 413 | +        let pubkey_bytes = pubk.to_bytes_compressed();  | 
 | 414 | +        let message = message.to_vec();  | 
 | 415 | +        let signature: Secp256k1Signature = privk.sign(&message).expect("secp256k1 signing should succeed");  | 
 | 416 | +        let signature_bytes = signature.to_rsv();  | 
 | 417 | +        let program = format!(  | 
 | 418 | +            "(is-eq (unwrap! (secp256k1-recover? {} {}) (err u1)) {})",  | 
 | 419 | +            buff_literal(&message),  | 
 | 420 | +            buff_literal(&signature_bytes),  | 
 | 421 | +            buff_literal(&pubkey_bytes)  | 
 | 422 | +        );  | 
 | 423 | + | 
 | 424 | +        let result = execute_with_parameters(  | 
 | 425 | +            program.as_str(),  | 
 | 426 | +            ClarityVersion::Clarity4,  | 
 | 427 | +            StacksEpochId::Epoch33,  | 
 | 428 | +            false,  | 
 | 429 | +        )  | 
 | 430 | +        .expect("execution should succeed")  | 
 | 431 | +        .expect("should return a value");  | 
 | 432 | + | 
 | 433 | +        prop_assert_eq!(Value::Bool(true), result);  | 
 | 434 | +    }  | 
 | 435 | + | 
 | 436 | +    #[test]  | 
 | 437 | +    fn prop_secp256r1_verify_accepts_valid_signatures(  | 
 | 438 | +        seed in any::<[u8; 32]>(),  | 
 | 439 | +        message in any::<[u8; 32]>()  | 
 | 440 | +    ) {  | 
 | 441 | +        let privk = Secp256r1PrivateKey::from_seed(&seed);  | 
 | 442 | +        let pubk = Secp256r1PublicKey::from_private(&privk);  | 
 | 443 | +        let pubkey_bytes = pubk.to_bytes_compressed();  | 
 | 444 | +        let message = message.to_vec();  | 
 | 445 | +        let signature = privk.sign(&message).expect("secp256r1 signing should succeed");  | 
 | 446 | +        let program = format!(  | 
 | 447 | +            "(secp256r1-verify {} {} {})",  | 
 | 448 | +            buff_literal(&message),  | 
 | 449 | +            buff_literal(&signature.0),  | 
 | 450 | +            buff_literal(&pubkey_bytes)  | 
 | 451 | +        );  | 
 | 452 | + | 
 | 453 | +        let result = execute_with_parameters(  | 
 | 454 | +            program.as_str(),  | 
 | 455 | +            ClarityVersion::Clarity4,  | 
 | 456 | +            StacksEpochId::Epoch33,  | 
 | 457 | +            false,  | 
 | 458 | +        )  | 
 | 459 | +        .expect("execution should succeed")  | 
 | 460 | +        .expect("should return a value");  | 
 | 461 | + | 
 | 462 | +        prop_assert_eq!(Value::Bool(true), result);  | 
 | 463 | +    }  | 
 | 464 | + | 
 | 465 | +    #[test]  | 
 | 466 | +    fn prop_secp256k1_verify_rejects_tampered_msg(  | 
 | 467 | +        seed in any::<[u8; 32]>(),  | 
 | 468 | +        message in any::<[u8; 32]>(),  | 
 | 469 | +        bit in 0usize..32  | 
 | 470 | +    ) {  | 
 | 471 | +        let privk = StacksPrivateKey::from_seed(&seed);  | 
 | 472 | +        let pubk = StacksPublicKey::from_private(&privk);  | 
 | 473 | +        let pubkey_bytes = pubk.to_bytes_compressed();  | 
 | 474 | +        let mut m = message.to_vec();  | 
 | 475 | +        let sig: Secp256k1Signature = privk.sign(&m).unwrap();  | 
 | 476 | +        let sig_bytes = sig.to_rsv();  | 
 | 477 | + | 
 | 478 | +        // flip one bit  | 
 | 479 | +        m[bit] ^= 0x01;  | 
 | 480 | + | 
 | 481 | +        let program = format!(  | 
 | 482 | +            "(secp256k1-verify {} {} {})",  | 
 | 483 | +            buff_literal(&m),  | 
 | 484 | +            buff_literal(&sig_bytes),  | 
 | 485 | +            buff_literal(&pubkey_bytes)  | 
 | 486 | +        );  | 
 | 487 | +        let result = execute_with_parameters(  | 
 | 488 | +            &program, ClarityVersion::Clarity4, StacksEpochId::Epoch33, false  | 
 | 489 | +        ).unwrap().unwrap();  | 
 | 490 | + | 
 | 491 | +        prop_assert_eq!(Value::Bool(false), result);  | 
 | 492 | +    }  | 
 | 493 | + | 
 | 494 | +    #[test]  | 
 | 495 | +    fn prop_secp256r1_verify_rejects_tampered_msg(  | 
 | 496 | +        seed in any::<[u8; 32]>(),  | 
 | 497 | +        message in any::<[u8; 32]>(),  | 
 | 498 | +        bit in 0usize..32  | 
 | 499 | +    ) {  | 
 | 500 | +        let privk = Secp256r1PrivateKey::from_seed(&seed);  | 
 | 501 | +        let pubk = Secp256r1PublicKey::from_private(&privk);  | 
 | 502 | +        let pubkey_bytes = pubk.to_bytes_compressed();  | 
 | 503 | +        let mut message = message.to_vec();  | 
 | 504 | +        let signature = privk.sign(&message).expect("secp256r1 signing should succeed");  | 
 | 505 | + | 
 | 506 | +        // flip one bit  | 
 | 507 | +        message[bit] ^= 0x01;  | 
 | 508 | + | 
 | 509 | +        let program = format!(  | 
 | 510 | +            "(secp256r1-verify {} {} {})",  | 
 | 511 | +            buff_literal(&message),  | 
 | 512 | +            buff_literal(&signature.0),  | 
 | 513 | +            buff_literal(&pubkey_bytes)  | 
 | 514 | +        );  | 
 | 515 | + | 
 | 516 | +        let result = execute_with_parameters(  | 
 | 517 | +            program.as_str(),  | 
 | 518 | +            ClarityVersion::Clarity4,  | 
 | 519 | +            StacksEpochId::Epoch33,  | 
 | 520 | +            false,  | 
 | 521 | +        )  | 
 | 522 | +        .expect("execution should succeed")  | 
 | 523 | +        .expect("should return a value");  | 
 | 524 | + | 
 | 525 | +        prop_assert_eq!(Value::Bool(false), result);  | 
 | 526 | +    }  | 
 | 527 | + | 
 | 528 | +    #[test]  | 
 | 529 | +    fn prop_secp256k1_recover_fails_to_match_with_tampered_msg(  | 
 | 530 | +        seed in any::<[u8; 32]>(),  | 
 | 531 | +        message in any::<[u8; 32]>(),  | 
 | 532 | +        bit in 0usize..32  | 
 | 533 | +    ) {  | 
 | 534 | +        let privk = StacksPrivateKey::from_seed(&seed);  | 
 | 535 | +        let pubk = StacksPublicKey::from_private(&privk);  | 
 | 536 | +        let pubkey_bytes = pubk.to_bytes_compressed();  | 
 | 537 | +        let mut message = message.to_vec();  | 
 | 538 | +        let signature: Secp256k1Signature = privk.sign(&message).expect("secp256k1 signing should succeed");  | 
 | 539 | +        let signature_bytes = signature.to_rsv();  | 
 | 540 | + | 
 | 541 | +        // flip one bit  | 
 | 542 | +        message[bit] ^= 0x01;  | 
 | 543 | + | 
 | 544 | +        let program = format!(  | 
 | 545 | +            "(is-eq (unwrap! (secp256k1-recover? {} {}) (err u1)) {})",  | 
 | 546 | +            buff_literal(&message),  | 
 | 547 | +            buff_literal(&signature_bytes),  | 
 | 548 | +            buff_literal(&pubkey_bytes)  | 
 | 549 | +        );  | 
 | 550 | + | 
 | 551 | +        let result = execute_with_parameters(  | 
 | 552 | +            program.as_str(),  | 
 | 553 | +            ClarityVersion::Clarity4,  | 
 | 554 | +            StacksEpochId::Epoch33,  | 
 | 555 | +            false,  | 
 | 556 | +        )  | 
 | 557 | +        .expect("execution should succeed")  | 
 | 558 | +        .expect("should return a value");  | 
 | 559 | + | 
 | 560 | +        prop_assert_eq!(Value::Bool(false), result);  | 
 | 561 | +    }  | 
 | 562 | + | 
 | 563 | +    #[test]  | 
 | 564 | +    fn prop_secp256r1_verify_rejects_wrong_key(  | 
 | 565 | +        seed_a in any::<[u8; 32]>(),  | 
 | 566 | +        seed_b in any::<[u8; 32]>(),  | 
 | 567 | +        message in any::<[u8; 32]>()  | 
 | 568 | +    ) {  | 
 | 569 | +        prop_assume!(seed_a != seed_b);  | 
 | 570 | + | 
 | 571 | +        let priv_a = Secp256r1PrivateKey::from_seed(&seed_a);  | 
 | 572 | +        let pub_b  = Secp256r1PublicKey::from_private(&Secp256r1PrivateKey::from_seed(&seed_b));  | 
 | 573 | +        let pub_b_bytes = pub_b.to_bytes_compressed();  | 
 | 574 | + | 
 | 575 | +        let msg = message.to_vec();  | 
 | 576 | +        let signature = priv_a.sign(&msg).unwrap();  | 
 | 577 | + | 
 | 578 | +        let program = format!(  | 
 | 579 | +            "(secp256r1-verify {} {} {})",  | 
 | 580 | +            buff_literal(&msg),  | 
 | 581 | +            buff_literal(&signature.0),  | 
 | 582 | +            buff_literal(&pub_b_bytes)  | 
 | 583 | +        );  | 
 | 584 | +        let result = execute_with_parameters(  | 
 | 585 | +            &program, ClarityVersion::Clarity4, StacksEpochId::Epoch33, false  | 
 | 586 | +        ).unwrap().unwrap();  | 
 | 587 | + | 
 | 588 | +        prop_assert_eq!(Value::Bool(false), result);  | 
 | 589 | +    }  | 
 | 590 | + | 
 | 591 | +    #[test]  | 
 | 592 | +    fn prop_secp256k1_verify_rejects_wrong_key(  | 
 | 593 | +        seed_a in any::<[u8; 32]>(),  | 
 | 594 | +        seed_b in any::<[u8; 32]>(),  | 
 | 595 | +        message in any::<[u8; 32]>()  | 
 | 596 | +    ) {  | 
 | 597 | +        prop_assume!(seed_a != seed_b);  | 
 | 598 | +        let priv_a = StacksPrivateKey::from_seed(&seed_a);  | 
 | 599 | +        let pub_b  = StacksPublicKey::from_private(&StacksPrivateKey::from_seed(&seed_b));  | 
 | 600 | +        let pub_b_bytes = pub_b.to_bytes_compressed();  | 
 | 601 | + | 
 | 602 | +        let message = message.to_vec();  | 
 | 603 | +        let signature: Secp256k1Signature = priv_a.sign(&message).expect("secp256k1 signing should succeed");  | 
 | 604 | +        let signature_bytes = signature.to_rsv();  | 
 | 605 | +        let program = format!(  | 
 | 606 | +            "(secp256k1-verify {} {} {})",  | 
 | 607 | +            buff_literal(&message),  | 
 | 608 | +            buff_literal(&signature_bytes),  | 
 | 609 | +            buff_literal(&pub_b_bytes)  | 
 | 610 | +        );  | 
 | 611 | + | 
 | 612 | +        let result = execute_with_parameters(  | 
 | 613 | +            program.as_str(),  | 
 | 614 | +            ClarityVersion::Clarity4,  | 
 | 615 | +            StacksEpochId::Epoch33,  | 
 | 616 | +            false,  | 
 | 617 | +        )  | 
 | 618 | +        .expect("execution should succeed")  | 
 | 619 | +        .expect("should return a value");  | 
 | 620 | + | 
 | 621 | +        prop_assert_eq!(Value::Bool(false), result);  | 
 | 622 | +    }  | 
 | 623 | + | 
 | 624 | +    #[test]  | 
 | 625 | +    fn prop_secp256k1_recover_fails_to_match_with_wrong_key(  | 
 | 626 | +        seed_a in any::<[u8; 32]>(),  | 
 | 627 | +        seed_b in any::<[u8; 32]>(),  | 
 | 628 | +        message in any::<[u8; 32]>()  | 
 | 629 | +    ) {  | 
 | 630 | +        let priv_a = StacksPrivateKey::from_seed(&seed_a);  | 
 | 631 | +        let pub_b  = StacksPublicKey::from_private(&StacksPrivateKey::from_seed(&seed_b));  | 
 | 632 | +        let pub_b_bytes = pub_b.to_bytes_compressed();  | 
 | 633 | + | 
 | 634 | +        let message = message.to_vec();  | 
 | 635 | +        let signature: Secp256k1Signature = priv_a.sign(&message).expect("secp256k1 signing should succeed");  | 
 | 636 | +        let signature_bytes = signature.to_rsv();  | 
 | 637 | +        let program = format!(  | 
 | 638 | +            "(is-eq (unwrap! (secp256k1-recover? {} {}) (err u1)) {})",  | 
 | 639 | +            buff_literal(&message),  | 
 | 640 | +            buff_literal(&signature_bytes),  | 
 | 641 | +            buff_literal(&pub_b_bytes)  | 
 | 642 | +        );  | 
 | 643 | + | 
 | 644 | +        let result = execute_with_parameters(  | 
 | 645 | +            program.as_str(),  | 
 | 646 | +            ClarityVersion::Clarity4,  | 
 | 647 | +            StacksEpochId::Epoch33,  | 
 | 648 | +            false,  | 
 | 649 | +        )  | 
 | 650 | +        .expect("execution should succeed")  | 
 | 651 | +        .expect("should return a value");  | 
 | 652 | + | 
 | 653 | +        prop_assert_eq!(Value::Bool(false), result);  | 
 | 654 | +    }  | 
 | 655 | +}  | 
0 commit comments