From 6ebcc97098aec219c943d7bee7fefc747ccc8535 Mon Sep 17 00:00:00 2001
From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com>
Date: Mon, 15 Dec 2025 10:12:53 -0500
Subject: [PATCH 1/4] fix: do not double hash in `secp256r1-verify`
This is a consensus breaking change and will need to go out in a
hard-fork with a new version of Clarity (Clarity5).
In the existing implementation, the code was incorrectly hashing the
passed in `message-hash`. Unfortunately, the test code was also
double-hashing on the signing side as well, so this was not caught. The
bug was discovered by @eric-stacks when attempting to generate
signatures externally (e.g. with WebAuthn) and verify them in Clarity
code.
---
clarity/src/vm/docs/mod.rs | 2 +-
clarity/src/vm/functions/crypto.rs | 13 +++-
clarity/src/vm/version.rs | 9 +++
stacks-common/src/util/secp256r1.rs | 111 +++++++++++++++++++++++++++-
4 files changed, 126 insertions(+), 9 deletions(-)
diff --git a/clarity/src/vm/docs/mod.rs b/clarity/src/vm/docs/mod.rs
index 61db5ee1bc..dfcacf88fb 100644
--- a/clarity/src/vm/docs/mod.rs
+++ b/clarity/src/vm/docs/mod.rs
@@ -1410,7 +1410,7 @@ const SECP256R1VERIFY_API: SpecialAPI = SpecialAPI {
description: "The `secp256r1-verify` function verifies that the provided signature of the message-hash
was signed with the private key that generated the public key.
`message-hash` is typically the `sha256` of a message and `signature` is the raw 64-byte signature.
-High-S signatures are allowed.
+High-S signatures are allowed. In Clarity 4, the `message-hash` is SHA256 hashed again internally before verification (i.e. double SHA256).
Note that this is NOT the Bitcoin (or default Stacks) signature scheme, secp256k1, but rather the
NIST P-256 curve (also known as secp256r1).",
example: "(secp256r1-verify 0xc3abef6a775793dfbc8e0719e7a1de1fc2f90d37a7912b1ce8e300a5a03b06a8
diff --git a/clarity/src/vm/functions/crypto.rs b/clarity/src/vm/functions/crypto.rs
index 7af6ddd13e..4e6bd05f2e 100644
--- a/clarity/src/vm/functions/crypto.rs
+++ b/clarity/src/vm/functions/crypto.rs
@@ -20,7 +20,7 @@ use stacks_common::address::{
use stacks_common::types::chainstate::StacksAddress;
use stacks_common::util::hash;
use stacks_common::util::secp256k1::{secp256k1_recover, secp256k1_verify, Secp256k1PublicKey};
-use stacks_common::util::secp256r1::secp256r1_verify;
+use stacks_common::util::secp256r1::{secp256r1_verify, secp256r1_verify_digest};
use crate::vm::costs::cost_functions::ClarityCostFunction;
use crate::vm::costs::runtime_cost;
@@ -374,7 +374,12 @@ pub fn special_secp256r1_verify(
}
};
- Ok(Value::Bool(
- secp256r1_verify(message, signature, pubkey).is_ok(),
- ))
+ let version = *env.contract_context.get_clarity_version();
+ let verify_result = if version.uses_secp256r1_double_hashing() {
+ secp256r1_verify(message, signature, pubkey)
+ } else {
+ secp256r1_verify_digest(message, signature, pubkey)
+ };
+
+ Ok(Value::Bool(verify_result.is_ok()))
}
diff --git a/clarity/src/vm/version.rs b/clarity/src/vm/version.rs
index 8b38563c41..84ab9d9435 100644
--- a/clarity/src/vm/version.rs
+++ b/clarity/src/vm/version.rs
@@ -53,6 +53,15 @@ impl ClarityVersion {
StacksEpochId::Epoch33 => ClarityVersion::Clarity4,
}
}
+
+ pub fn uses_secp256r1_double_hashing(&self) -> bool {
+ match self {
+ ClarityVersion::Clarity1
+ | ClarityVersion::Clarity2
+ | ClarityVersion::Clarity3
+ | ClarityVersion::Clarity4 => true,
+ }
+ }
}
impl FromStr for ClarityVersion {
diff --git a/stacks-common/src/util/secp256r1.rs b/stacks-common/src/util/secp256r1.rs
index f344eb3f57..2076ae422f 100644
--- a/stacks-common/src/util/secp256r1.rs
+++ b/stacks-common/src/util/secp256r1.rs
@@ -13,6 +13,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+use p256::ecdsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use p256::ecdsa::signature::{Signer, Verifier};
use p256::ecdsa::{
Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,
@@ -173,6 +174,24 @@ impl Secp256r1PublicKey {
return Err(Secp256r1Error::InvalidMessage);
}
+ let p256_sig = sig
+ .to_p256_signature()
+ .map_err(|_| Secp256r1Error::InvalidSignature)?;
+
+ // Verify the signature
+ self.key
+ .verify_prehash(msg_hash, &p256_sig)
+ .map_err(|_| Secp256r1Error::InvalidSignature)
+ }
+
+ /// Verify a signature against a message hash. The hash is SHA256 hashed
+ /// again before verification (i.e. double SHA256).
+ /// Returns Ok(()) if the signature is valid, or an error otherwise.
+ pub fn verify(&self, msg_hash: &[u8], sig: &MessageSignature) -> Result<(), Secp256r1Error> {
+ if msg_hash.len() != 32 {
+ return Err(Secp256r1Error::InvalidMessage);
+ }
+
let p256_sig = sig
.to_p256_signature()
.map_err(|_| Secp256r1Error::InvalidSignature)?;
@@ -288,7 +307,8 @@ impl Secp256r1PrivateKey {
bits
}
- /// Sign a message hash, returning the signature.
+ /// Sign a message hash, SHA256 hashing it (i.e. double-hashing) before
+ /// returning the signature.
/// The message must be a 32-byte hash.
pub fn sign(&self, data_hash: &[u8]) -> Result {
if data_hash.len() != 32 {
@@ -298,12 +318,26 @@ impl Secp256r1PrivateKey {
let signature: P256Signature = self.key.sign(data_hash);
Ok(MessageSignature::from_p256_signature(&signature))
}
+
+ /// Sign a pre-hashed message, returning the signature without hashing again.
+ /// The digest must be a 32-byte hash.
+ pub fn sign_digest(&self, data_hash: &[u8]) -> Result {
+ if data_hash.len() != 32 {
+ return Err("Invalid message: must be a 32-byte hash");
+ }
+
+ let signature = self
+ .key
+ .sign_prehash(data_hash)
+ .map_err(|_| "Signing failed")?;
+ Ok(MessageSignature::from_p256_signature(&signature))
+ }
}
/// Verify a secp256r1 signature.
/// The message must be a 32-byte hash.
/// The signature must be a 64-byte compact signature
-pub fn secp256r1_verify(
+pub fn secp256r1_verify_digest(
message_arr: &[u8],
signature_arr: &[u8],
pubkey_arr: &[u8],
@@ -320,6 +354,27 @@ pub fn secp256r1_verify(
pk.verify_digest(msg, &sig)
}
+/// Verify a secp256r1 signature against the SHA256 hash of the message hash
+/// (i.e., double-hashed).
+/// The message must be a 32-byte hash.
+/// The signature must be a 64-byte compact signature
+pub fn secp256r1_verify(
+ message_arr: &[u8],
+ signature_arr: &[u8],
+ pubkey_arr: &[u8],
+) -> Result<(), Secp256r1Error> {
+ let msg: &[u8; 32] = message_arr
+ .try_into()
+ .map_err(|_| Secp256r1Error::InvalidMessage)?;
+ let sig_bytes: &[u8; 64] = signature_arr
+ .try_into()
+ .map_err(|_| Secp256r1Error::InvalidSignature)?;
+
+ let pk = Secp256r1PublicKey::from_slice(pubkey_arr).map_err(|_| Secp256r1Error::InvalidKey)?;
+ let sig = MessageSignature::from_bytes(sig_bytes).ok_or(Secp256r1Error::InvalidSignature)?;
+ pk.verify(msg, &sig)
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -423,7 +478,7 @@ mod tests {
let msg = b"hello world";
let msg_hash = Sha256Sum::from_data(msg).as_bytes().to_vec();
- let sig = privk.sign(&msg_hash).unwrap();
+ let sig = privk.sign_digest(&msg_hash).unwrap();
pubk.verify_digest(&msg_hash, &sig)
.expect("invalid signature");
}
@@ -437,13 +492,61 @@ mod tests {
let msg = b"hello world";
let msg_hash = Sha256Sum::from_data(msg).as_bytes().to_vec();
- let sig = privk1.sign(&msg_hash).unwrap();
+ let sig = privk1.sign_digest(&msg_hash).unwrap();
let e = pubk2
.verify_digest(&msg_hash, &sig)
.expect_err("expected an error");
assert_eq!(e, Secp256r1Error::InvalidSignature);
}
+ #[test]
+ fn test_verify_digest_rejects_double_hashed_signature() {
+ let privk = Secp256r1PrivateKey::random();
+ let pubk = Secp256r1PublicKey::from_private(&privk);
+ let msg_hash = Sha256Sum::from_data(b"double hash test")
+ .as_bytes()
+ .to_vec();
+
+ let sig = privk.sign(&msg_hash).unwrap();
+ let err = pubk
+ .verify_digest(&msg_hash, &sig)
+ .expect_err("double-hashed signature should fail digest verify");
+ assert_eq!(err, Secp256r1Error::InvalidSignature);
+ }
+
+ #[test]
+ fn test_verify_rejects_single_hash_signature() {
+ let privk = Secp256r1PrivateKey::random();
+ let pubk = Secp256r1PublicKey::from_private(&privk);
+ let msg_hash = Sha256Sum::from_data(b"single hash test")
+ .as_bytes()
+ .to_vec();
+
+ let sig = privk.sign_digest(&msg_hash).unwrap();
+ let err = pubk
+ .verify(&msg_hash, &sig)
+ .expect_err("single-hash signature should fail double-hash verify");
+ assert_eq!(err, Secp256r1Error::InvalidSignature);
+ }
+
+ #[test]
+ fn test_secp256r1_verify_digest_function() {
+ let privk = Secp256r1PrivateKey::random();
+ let pubk = Secp256r1PublicKey::from_private(&privk);
+ let msg_hash = Sha256Sum::from_data(b"function verify digest")
+ .as_bytes()
+ .to_vec();
+
+ let sig = privk.sign_digest(&msg_hash).unwrap();
+ let signature_bytes = sig.0;
+ let pubkey_bytes = pubk.to_bytes();
+
+ assert!(
+ secp256r1_verify_digest(&msg_hash, &signature_bytes, &pubkey_bytes).is_ok(),
+ "secp256r1_verify_digest should accept valid pre-hashed signatures"
+ );
+ }
+
#[test]
fn test_public_key_compression() {
let privk = Secp256r1PrivateKey::random();
From a83b2ea35b56f23a70ee020beb02616e292b83bb Mon Sep 17 00:00:00 2001
From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com>
Date: Mon, 15 Dec 2025 15:47:47 -0500
Subject: [PATCH 2/4] docs: update changelog
---
CHANGELOG.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0985d33202..69f253bc6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
## [Unreleased]
+### Fixed
+
+- Updated the documentation for `secp256r1-verify` to match the implementation. The message hash passed to `secp256r1-verify` is SHA256 hashed again before verifying the signature.
+
+## [3.3.0.0.3]
+
### Added
- In the `/v3/transaction/{txid}` RPC endpoint, added `block_height` and `is_canonical` to the response.
From d272eb8dd49e60bd89a3ab0f2f2eb9e01182217a Mon Sep 17 00:00:00 2001
From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com>
Date: Mon, 15 Dec 2025 17:26:00 -0500
Subject: [PATCH 3/4] docs: reorder `secp256r1-verify` documentation
---
clarity/src/vm/docs/mod.rs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clarity/src/vm/docs/mod.rs b/clarity/src/vm/docs/mod.rs
index dfcacf88fb..745429e286 100644
--- a/clarity/src/vm/docs/mod.rs
+++ b/clarity/src/vm/docs/mod.rs
@@ -1409,8 +1409,9 @@ const SECP256R1VERIFY_API: SpecialAPI = SpecialAPI {
signature: "(secp256r1-verify message-hash signature public-key)",
description: "The `secp256r1-verify` function verifies that the provided signature of the message-hash
was signed with the private key that generated the public key.
+In Clarity 4, the `message-hash` is SHA256 hashed again internally before verification (i.e. double SHA256).
`message-hash` is typically the `sha256` of a message and `signature` is the raw 64-byte signature.
-High-S signatures are allowed. In Clarity 4, the `message-hash` is SHA256 hashed again internally before verification (i.e. double SHA256).
+High-S signatures are allowed.
Note that this is NOT the Bitcoin (or default Stacks) signature scheme, secp256k1, but rather the
NIST P-256 curve (also known as secp256r1).",
example: "(secp256r1-verify 0xc3abef6a775793dfbc8e0719e7a1de1fc2f90d37a7912b1ce8e300a5a03b06a8
From 8b8451ae506d50b2ad6841940ef738cc91f68a33 Mon Sep 17 00:00:00 2001
From: Brice Dobry <232827048+brice-stacks@users.noreply.github.com>
Date: Thu, 18 Dec 2025 14:48:33 -0500
Subject: [PATCH 4/4] test: add NIST test vectors for `secp256r1-verify`
testing
---
clarity/src/vm/tests/crypto.rs | 181 +++++++++++++++++++++++++++++++++
1 file changed, 181 insertions(+)
diff --git a/clarity/src/vm/tests/crypto.rs b/clarity/src/vm/tests/crypto.rs
index 05d9460320..00cce572ce 100644
--- a/clarity/src/vm/tests/crypto.rs
+++ b/clarity/src/vm/tests/crypto.rs
@@ -10,6 +10,187 @@ use stacks_common::util::secp256r1::{Secp256r1PrivateKey, Secp256r1PublicKey};
use crate::vm::types::{ResponseData, TypeSignature, Value};
use crate::vm::{execute_with_parameters, ClarityVersion};
+struct NistVector {
+ msg: &'static str,
+ d: &'static str,
+ q_x: &'static str,
+ q_y: &'static str,
+ k: &'static str,
+ r: &'static str,
+ s: &'static str,
+}
+
+// Test vectors from NIST,
+// https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/Component-Testing
+static NIST_VECTORS: &[NistVector] = &[
+ NistVector {
+ msg: "44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56",
+ d: "519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464",
+ q_x: "1ccbe91c075fc7f4f033bfa248db8fccd3565de94bbfb12f3c59ff46c271bf83",
+ q_y: "ce4014c68811f9a21a1fdb2c0e6113e06db7ca93b7404e78dc7ccd5ca89a4ca9",
+ k: "94a1bbb14b906a61a280f245f9e93c7f3b4a6247824f5d33b9670787642a68de",
+ r: "f3ac8061b514795b8843e3d6629527ed2afd6b1f6a555a7acabb5e6f79c8c2ac",
+ s: "8bf77819ca05a6b2786c76262bf7371cef97b218e96f175a3ccdda2acc058903",
+ },
+ NistVector {
+ msg: "9b2db89cb0e8fa3cc7608b4d6cc1dec0114e0b9ff4080bea12b134f489ab2bbc",
+ d: "0f56db78ca460b055c500064824bed999a25aaf48ebb519ac201537b85479813",
+ q_x: "e266ddfdc12668db30d4ca3e8f7749432c416044f2d2b8c10bf3d4012aeffa8a",
+ q_y: "bfa86404a2e9ffe67d47c587ef7a97a7f456b863b4d02cfc6928973ab5b1cb39",
+ k: "6d3e71882c3b83b156bb14e0ab184aa9fb728068d3ae9fac421187ae0b2f34c6",
+ r: "976d3a4e9d23326dc0baa9fa560b7c4e53f42864f508483a6473b6a11079b2db",
+ s: "1b766e9ceb71ba6c01dcd46e0af462cd4cfa652ae5017d4555b8eeefe36e1932",
+ },
+ NistVector {
+ msg: "b804cf88af0c2eff8bbbfb3660ebb3294138e9d3ebd458884e19818061dacff0",
+ d: "e283871239837e13b95f789e6e1af63bf61c918c992e62bca040d64cad1fc2ef",
+ q_x: "74ccd8a62fba0e667c50929a53f78c21b8ff0c3c737b0b40b1750b2302b0bde8",
+ q_y: "29074e21f3a0ef88b9efdf10d06aa4c295cc1671f758ca0e4cd108803d0f2614",
+ k: "ad5e887eb2b380b8d8280ad6e5ff8a60f4d26243e0124c2f31a297b5d0835de2",
+ r: "35fb60f5ca0f3ca08542fb3cc641c8263a2cab7a90ee6a5e1583fac2bb6f6bd1",
+ s: "ee59d81bc9db1055cc0ed97b159d8784af04e98511d0a9a407b99bb292572e96",
+ },
+ NistVector {
+ msg: "85b957d92766235e7c880ac5447cfbe97f3cb499f486d1e43bcb5c2ff9608a1a",
+ d: "a3d2d3b7596f6592ce98b4bfe10d41837f10027a90d7bb75349490018cf72d07",
+ q_x: "322f80371bf6e044bc49391d97c1714ab87f990b949bc178cb7c43b7c22d89e1",
+ q_y: "3c15d54a5cc6b9f09de8457e873eb3deb1fceb54b0b295da6050294fae7fd999",
+ k: "24fc90e1da13f17ef9fe84cc96b9471ed1aaac17e3a4bae33a115df4e5834f18",
+ r: "d7c562370af617b581c84a2468cc8bd50bb1cbf322de41b7887ce07c0e5884ca",
+ s: "b46d9f2d8c4bf83546ff178f1d78937c008d64e8ecc5cbb825cb21d94d670d89",
+ },
+ NistVector {
+ msg: "3360d699222f21840827cf698d7cb635bee57dc80cd7733b682d41b55b666e22",
+ d: "53a0e8a8fe93db01e7ae94e1a9882a102ebd079b3a535827d583626c272d280d",
+ q_x: "1bcec4570e1ec2436596b8ded58f60c3b1ebc6a403bc5543040ba82963057244",
+ q_y: "8af62a4c683f096b28558320737bf83b9959a46ad2521004ef74cf85e67494e1",
+ k: "5d833e8d24cc7a402d7ee7ec852a3587cddeb48358cea71b0bedb8fabe84e0c4",
+ r: "18caaf7b663507a8bcd992b836dec9dc5703c080af5e51dfa3a9a7c387182604",
+ s: "77c68928ac3b88d985fb43fb615fb7ff45c18ba5c81af796c613dfa98352d29c",
+ },
+ NistVector {
+ msg: "c413c4908cd0bc6d8e32001aa103043b2cf5be7fcbd61a5cec9488c3a577ca57",
+ d: "4af107e8e2194c830ffb712a65511bc9186a133007855b49ab4b3833aefc4a1d",
+ q_x: "a32e50be3dae2c8ba3f5e4bdae14cf7645420d425ead94036c22dd6c4fc59e00",
+ q_y: "d623bf641160c289d6742c6257ae6ba574446dd1d0e74db3aaa80900b78d4ae9",
+ k: "e18f96f84dfa2fd3cdfaec9159d4c338cd54ad314134f0b31e20591fc238d0ab",
+ r: "8524c5024e2d9a73bde8c72d9129f57873bbad0ed05215a372a84fdbc78f2e68",
+ s: "d18c2caf3b1072f87064ec5e8953f51301cada03469c640244760328eb5a05cb",
+ },
+ NistVector {
+ msg: "88fc1e7d849794fc51b135fa135deec0db02b86c3cd8cebdaa79e8689e5b2898",
+ d: "78dfaa09f1076850b3e206e477494cddcfb822aaa0128475053592c48ebaf4ab",
+ q_x: "8bcfe2a721ca6d753968f564ec4315be4857e28bef1908f61a366b1f03c97479",
+ q_y: "0f67576a30b8e20d4232d8530b52fb4c89cbc589ede291e499ddd15fe870ab96",
+ k: "295544dbb2da3da170741c9b2c6551d40af7ed4e891445f11a02b66a5c258a77",
+ r: "c5a186d72df452015480f7f338970bfe825087f05c0088d95305f87aacc9b254",
+ s: "84a58f9e9d9e735344b316b1aa1ab5185665b85147dc82d92e969d7bee31ca30",
+ },
+ NistVector {
+ msg: "41fa8d8b4cd0a5fdf021f4e4829d6d1e996bab6b4a19dcb85585fe76c582d2bc",
+ d: "80e692e3eb9fcd8c7d44e7de9f7a5952686407f90025a1d87e52c7096a62618a",
+ q_x: "a88bc8430279c8c0400a77d751f26c0abc93e5de4ad9a4166357952fe041e767",
+ q_y: "2d365a1eef25ead579cc9a069b6abc1b16b81c35f18785ce26a10ba6d1381185",
+ k: "7c80fd66d62cc076cef2d030c17c0a69c99611549cb32c4ff662475adbe84b22",
+ r: "9d0c6afb6df3bced455b459cc21387e14929392664bb8741a3693a1795ca6902",
+ s: "d7f9ddd191f1f412869429209ee3814c75c72fa46a9cccf804a2f5cc0b7e739f",
+ },
+ NistVector {
+ msg: "2d72947c1731543b3d62490866a893952736757746d9bae13e719079299ae192",
+ d: "5e666c0db0214c3b627a8e48541cc84a8b6fd15f300da4dff5d18aec6c55b881",
+ q_x: "1bc487570f040dc94196c9befe8ab2b6de77208b1f38bdaae28f9645c4d2bc3a",
+ q_y: "ec81602abd8345e71867c8210313737865b8aa186851e1b48eaca140320f5d8f",
+ k: "2e7625a48874d86c9e467f890aaa7cd6ebdf71c0102bfdcfa24565d6af3fdce9",
+ r: "2f9e2b4e9f747c657f705bffd124ee178bbc5391c86d056717b140c153570fd9",
+ s: "f5413bfd85949da8d83de83ab0d19b2986613e224d1901d76919de23ccd03199",
+ },
+ NistVector {
+ msg: "e138bd577c3729d0e24a98a82478bcc7482499c4cdf734a874f7208ddbc3c116",
+ d: "f73f455271c877c4d5334627e37c278f68d143014b0a05aa62f308b2101c5308",
+ q_x: "b8188bd68701fc396dab53125d4d28ea33a91daf6d21485f4770f6ea8c565dde",
+ q_y: "423f058810f277f8fe076f6db56e9285a1bf2c2a1dae145095edd9c04970bc4a",
+ k: "62f8665fd6e26b3fa069e85281777a9b1f0dfd2c0b9f54a086d0c109ff9fd615",
+ r: "1cc628533d0004b2b20e7f4baad0b8bb5e0673db159bbccf92491aef61fc9620",
+ s: "880e0bbf82a8cf818ed46ba03cf0fc6c898e36fca36cc7fdb1d2db7503634430",
+ },
+];
+
+#[test]
+fn secp256r1_verify_valid_signatures_nist() {
+ use stacks_common::util::hash::hex_bytes;
+ use stacks_common::util::secp256r1::MessageSignature;
+
+ for NistVector {
+ msg,
+ d,
+ q_x,
+ q_y,
+ k,
+ r,
+ s,
+ } in NIST_VECTORS
+ {
+ let message_hash = hex_bytes(msg).unwrap();
+ let privk = Secp256r1PrivateKey::from_hex(d).unwrap();
+ let mut pubk = Secp256r1PublicKey::from_hex(&format!("04{q_x}{q_y}")).unwrap();
+ pubk.set_compressed(true);
+
+ let signature_dh = privk.sign(&message_hash).unwrap();
+ let signature_nist = MessageSignature::from_hex(&format!("{r}{s}")).unwrap();
+
+ let verified_double_hash = pubk.verify(&message_hash, &signature_dh).is_ok();
+ let verified_nist_sign = pubk.verify_digest(&message_hash, &signature_nist).is_ok();
+
+ info!(
+ "Signed with SK";
+ "nist_signature" => format!("{r}{s}"),
+ "double_hash" => to_hex(&signature_dh.0),
+ "pubk" => to_hex(&pubk.to_bytes()),
+ "verified_double_hash" => verified_double_hash,
+ "verified_nist_sign" => verified_nist_sign,
+ );
+
+ let program = format!(
+ "(secp256r1-verify {} {} {})",
+ buff_literal(&message_hash),
+ buff_literal(&signature_dh.0),
+ buff_literal(&pubk.to_bytes())
+ );
+
+ assert_eq!(
+ Value::Bool(true),
+ execute_with_parameters(
+ program.as_str(),
+ ClarityVersion::Clarity4,
+ StacksEpochId::Epoch33,
+ false
+ )
+ .expect("execution should succeed")
+ .expect("should return a value")
+ );
+
+ // TODO: Once implemented, check the NIST signature against the Clarity 5 function.
+ // let program = format!(
+ // "(secp256r1-verify {} {} {})",
+ // buff_literal(&message_hash),
+ // buff_literal(&signature_nist.0),
+ // buff_literal(&pubk.to_bytes())
+ // );
+
+ // assert_eq!(
+ // Value::Bool(true),
+ // execute_with_parameters(
+ // program.as_str(),
+ // ClarityVersion::Clarity5,
+ // StacksEpochId::Epoch34,
+ // false
+ // )
+ // .expect("execution should succeed")
+ // .expect("should return a value")
+ // );
+ }
+}
+
fn secp256r1_vectors() -> (Vec, Vec, Vec) {
let privk = Secp256r1PrivateKey::from_seed(&[7u8; 32]);
let pubk = Secp256r1PublicKey::from_private(&privk);