Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions clarity/src/vm/docs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,7 @@ 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.
Note that this is NOT the Bitcoin (or default Stacks) signature scheme, secp256k1, but rather the
Expand Down
13 changes: 9 additions & 4 deletions clarity/src/vm/functions/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()))
}
181 changes: 181 additions & 0 deletions clarity/src/vm/tests/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>, Vec<u8>, Vec<u8>) {
let privk = Secp256r1PrivateKey::from_seed(&[7u8; 32]);
let pubk = Secp256r1PublicKey::from_private(&privk);
Expand Down
9 changes: 9 additions & 0 deletions clarity/src/vm/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading