Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
1 change: 1 addition & 0 deletions changelog.d/ed25519-verify.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ed25519-verify to clarity6
5 changes: 2 additions & 3 deletions clarity/src/vm/analysis/arithmetic_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,8 @@ impl ArithmeticOnlyChecker<'_> {
Err(Error::FunctionNotPermitted(function))
}
Sha512 | Sha512Trunc256 | Secp256k1Recover | Secp256k1Verify | Secp256r1Verify
| Hash160 | Sha256 | Keccak256 | VerifyMerkleProof | GetBitcoinTxOutput => {
Err(Error::FunctionNotPermitted(function))
}
| Ed25519Verify | Hash160 | Sha256 | Keccak256 | VerifyMerkleProof
| GetBitcoinTxOutput => Err(Error::FunctionNotPermitted(function)),
Add | Subtract | Divide | Multiply | CmpGeq | CmpLeq | CmpLess | CmpGreater
| Modulo | Power | Sqrti | Log2 | BitwiseXor | And | Or | Not | Equals | If
| ConsSome | ConsOkay | ConsError | DefaultTo | UnwrapRet | UnwrapErrRet | IsOkay
Expand Down
1 change: 1 addition & 0 deletions clarity/src/vm/analysis/read_only_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ impl<'a, 'b> ReadOnlyChecker<'a, 'b> {
| Secp256k1Recover
| Secp256k1Verify
| Secp256r1Verify
| Ed25519Verify
| ConsSome
| ConsOkay
| ConsError
Expand Down
3 changes: 2 additions & 1 deletion clarity/src/vm/analysis/type_checker/v2_05/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,8 @@ impl TypedNativeFunction {
| AllowanceAll
| Secp256r1Verify
| VerifyMerkleProof
| GetBitcoinTxOutput => {
| GetBitcoinTxOutput
| Ed25519Verify => {
return Err(StaticCheckErrorKind::Unreachable(
"Clarity 2+ keywords should not show up in 2.05".into(),
));
Expand Down
15 changes: 15 additions & 0 deletions clarity/src/vm/analysis/type_checker/v2_1/natives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,20 @@ fn check_secp256r1_verify(
Ok(TypeSignature::BoolType)
}

fn check_ed25519_verify(
checker: &mut TypeChecker,
args: &[SymbolicExpression],
context: &TypingContext,
) -> Result<TypeSignature, StaticCheckError> {
let [message, signature, public_key] = get_arguments_exact::<_, 3>(args)?;

check_argument_count(3, args)?;
Comment thread
rob-stacks marked this conversation as resolved.
checker.type_check_expects(&message, context, &TypeSignature::BUFFER_MAX)?;
checker.type_check_expects(&signature, context, &TypeSignature::BUFFER_64)?;
checker.type_check_expects(&public_key, context, &TypeSignature::BUFFER_32)?;
Ok(TypeSignature::BoolType)
}

fn check_get_block_info(
checker: &mut TypeChecker,
args: &[SymbolicExpression],
Expand Down Expand Up @@ -1334,6 +1348,7 @@ impl TypedNativeFunction {
Secp256r1Verify => Special(SpecialNativeFunction(&check_secp256r1_verify)),
VerifyMerkleProof => Special(SpecialNativeFunction(&check_verify_merkle_proof)),
GetBitcoinTxOutput => Special(SpecialNativeFunction(&check_get_bitcoin_tx_output)),
Ed25519Verify => Special(SpecialNativeFunction(&check_ed25519_verify)),
};

Ok(out)
Expand Down
3 changes: 3 additions & 0 deletions clarity/src/vm/costs/cost_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ define_named_enum!(ClarityCostFunction {
Secp256r1verify("cost_secp256r1verify"),
VerifyMerkleProof("cost_verify_merkle_proof"),
GetBitcoinTxOutput("cost_get_bitcoin_tx_output"),
Ed25519verify("cost_ed25519verify"),
Unimplemented("cost_unimplemented"),
});

Expand Down Expand Up @@ -343,6 +344,7 @@ pub trait CostValues {
fn cost_secp256r1verify(n: u64) -> Result<ExecutionCost, VmExecutionError>;
fn cost_verify_merkle_proof(n: u64) -> Result<ExecutionCost, VmExecutionError>;
fn cost_get_bitcoin_tx_output(n: u64) -> Result<ExecutionCost, VmExecutionError>;
fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError>;
}

impl ClarityCostFunction {
Expand Down Expand Up @@ -502,6 +504,7 @@ impl ClarityCostFunction {
ClarityCostFunction::Secp256r1verify => C::cost_secp256r1verify(n),
ClarityCostFunction::VerifyMerkleProof => C::cost_verify_merkle_proof(n),
ClarityCostFunction::GetBitcoinTxOutput => C::cost_get_bitcoin_tx_output(n),
ClarityCostFunction::Ed25519verify => C::cost_ed25519verify(n),
ClarityCostFunction::Unimplemented => Err(RuntimeError::NotImplemented.into()),
}
}
Expand Down
4 changes: 4 additions & 0 deletions clarity/src/vm/costs/costs_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,4 +773,8 @@ impl CostValues for Costs1 {
fn cost_get_bitcoin_tx_output(_n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}

fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}
}
4 changes: 4 additions & 0 deletions clarity/src/vm/costs/costs_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,4 +773,8 @@ impl CostValues for Costs2 {
fn cost_get_bitcoin_tx_output(_n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}

fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}
}
4 changes: 4 additions & 0 deletions clarity/src/vm/costs/costs_2_testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -773,4 +773,8 @@ impl CostValues for Costs2Testnet {
fn cost_get_bitcoin_tx_output(_n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}

fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}
}
4 changes: 4 additions & 0 deletions clarity/src/vm/costs/costs_3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,4 +791,8 @@ impl CostValues for Costs3 {
fn cost_get_bitcoin_tx_output(_n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}

fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}
}
4 changes: 4 additions & 0 deletions clarity/src/vm/costs/costs_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,4 +481,8 @@ impl CostValues for Costs4 {
fn cost_get_bitcoin_tx_output(_n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}

fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Err(RuntimeError::NotImplemented.into())
}
}
4 changes: 4 additions & 0 deletions clarity/src/vm/costs/costs_5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,4 +474,8 @@ impl CostValues for Costs5 {
fn cost_get_bitcoin_tx_output(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Ok(ExecutionCost::runtime(linear(n >> 10, 125, 291)))
}

fn cost_ed25519verify(n: u64) -> Result<ExecutionCost, VmExecutionError> {
Ok(ExecutionCost::runtime(linear(n >> 10, 125, 7880)))
}
}
18 changes: 18 additions & 0 deletions clarity/src/vm/docs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,23 @@ without trusting the caller to have correctly hashed or stripped witness data fr
(get-bitcoin-tx-output? 0x00 u0) ;; Returns (err u1)",
};

const ED25519VERIFY_API: SpecialAPI = SpecialAPI {
input_type: "(buff 1048576), (buff 64), (buff 32)",
snippet: "ed25519-verify ${1:message} ${2:signature} ${3:public-key})",
output_type: "bool",
signature: "(ed25519-verify message signature public-key)",
description: "The `ed25519-verify` function verifies that the provided signature of the message
was signed with the private key that generated the public key.
The `message` can be up to 1 MiB in size. The `signature` is the raw 64-byte signature, and the `public-key` is the raw 32-byte public key.
returns `true` if the signature is valid, and `false` otherwise.
Note that validation is in strict mode, so non-canonical signatures will be rejected.",
example: "(ed25519-verify 0xaf82
0x6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a
0xfc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025) ;; Returns true
(ed25519-verify 0x00000000000000000000000000000000000000 0x6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a
0xfc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025) ;; Returns false"
};

const CONTRACT_CALL_API: SpecialAPI = SpecialAPI {
input_type: "ContractName, PublicFunctionName, Arg0, ...",
snippet: "contract-call? ${1:contract-principal} ${2:func} ${3:arg1}",
Expand Down Expand Up @@ -2970,6 +2987,7 @@ pub fn make_api_reference(function: &NativeFunctions) -> FunctionAPI {
Secp256r1Verify => make_for_special(&SECP256R1VERIFY_API, function),
VerifyMerkleProof => make_for_special(&VERIFY_MERKLE_PROOF_API, function),
GetBitcoinTxOutput => make_for_special(&GET_BITCOIN_TX_OUTPUT_API, function),
Ed25519Verify => make_for_special(&ED25519VERIFY_API, function),
}
}

Expand Down
19 changes: 4 additions & 15 deletions clarity/src/vm/functions/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use stacks_common::deps_common::bitcoin::network::serialize::deserialize as btc_
use stacks_common::deps_common::bitcoin::util::hash::Sha256dHash;

use crate::vm::errors::{RuntimeCheckErrorKind, VmExecutionError, VmInternalError};
use crate::vm::functions::buff_to_array;
use crate::vm::types::{BuffData, ListData, SequenceData, TupleData, TypeSignature, Value};

/// Maximum supported merkle proof depth for `(verify-merkle-proof ...)`.
Expand Down Expand Up @@ -142,18 +143,6 @@ fn verify_merkle(
cur == root
}

/// Helper to coerce a Clarity buffer value into a fixed-size byte array.
fn buff_to_array_32(value: &Value) -> Option<[u8; 32]> {
match value {
Value::Sequence(SequenceData::Buffer(BuffData { data })) if data.len() == 32 => {
let mut out = [0u8; 32];
out.copy_from_slice(data);
Some(out)
}
_ => None,
}
}

/// Cost-input function for `verify-merkle-proof`: the number of siblings in
/// the proof, which is what `ClarityCostFunction::VerifyMerkleProof` scales
/// on. Ignore and default around type errors here since they are already
Expand Down Expand Up @@ -188,13 +177,13 @@ pub fn native_verify_merkle_proof(args: Vec<Value>) -> Result<Value, VmExecution
.try_into()
.map_err(|_| VmInternalError::Expect("verify-merkle-proof received wrong arity".into()))?;

let leaf = buff_to_array_32(&leaf_value).ok_or_else(|| {
let leaf = buff_to_array::<32>(&leaf_value).ok_or_else(|| {
RuntimeCheckErrorKind::TypeValueError(
Box::new(TypeSignature::BUFFER_32),
leaf_value.to_error_string(),
)
})?;
let root = buff_to_array_32(&root_value).ok_or_else(|| {
let root = buff_to_array::<32>(&root_value).ok_or_else(|| {
RuntimeCheckErrorKind::TypeValueError(
Box::new(TypeSignature::BUFFER_32),
root_value.to_error_string(),
Expand Down Expand Up @@ -240,7 +229,7 @@ pub fn native_verify_merkle_proof(args: Vec<Value>) -> Result<Value, VmExecution

let mut siblings: Vec<[u8; 32]> = Vec::with_capacity(siblings_data.len());
for v in &siblings_data {
match buff_to_array_32(v) {
match buff_to_array::<32>(v) {
Some(b) => siblings.push(b),
// A list element that isn't a 32-byte buff is structurally invalid
// — return false rather than a runtime error so that callers can
Expand Down
43 changes: 43 additions & 0 deletions clarity/src/vm/functions/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use clarity_types::types::MAX_VALUE_SIZE;
use stacks_common::address::{
AddressHashMode, C32_ADDRESS_VERSION_MAINNET_SINGLESIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
};
use stacks_common::types::chainstate::StacksAddress;
use stacks_common::util::ed25519::ed25519_verify;
use stacks_common::util::hash;
use stacks_common::util::secp256k1::{Secp256k1PublicKey, secp256k1_recover, secp256k1_verify};
use stacks_common::util::secp256r1::{secp256r1_verify, secp256r1_verify_digest};
Expand All @@ -28,6 +30,7 @@ use crate::vm::costs::runtime_cost;
use crate::vm::errors::{
RuntimeCheckErrorKind, VmExecutionError, VmInternalError, check_argument_count,
};
use crate::vm::functions::{buff_to_array, buff_to_vec};
use crate::vm::representations::SymbolicExpression;
use crate::vm::types::{BuffData, SequenceData, TypeSignature, Value};
use crate::vm::{ClarityVersion, LocalContext, eval};
Expand Down Expand Up @@ -331,3 +334,43 @@ pub fn special_secp256r1_verify(

Ok(Value::Bool(verify_result.is_ok()))
}

pub fn native_ed25519_verify(args: Vec<Value>) -> Result<Value, VmExecutionError> {
// (ed25519-verify message signature public-key)
// message: (buff MAX_VALUE_SIZE), signature: (buff 64), public-key: (buff 32)

let [message_value, signature_value, public_key_value]: [Value; 3] = args
.try_into()
.map_err(|_| VmInternalError::Expect("ed25519-verify received wrong arity".into()))?;

let message = buff_to_vec(&message_value, MAX_VALUE_SIZE as usize).ok_or_else(|| {
RuntimeCheckErrorKind::TypeValueError(
Box::new(TypeSignature::BUFFER_MAX),
message_value.to_error_string(),
)
})?;
let signature = buff_to_array::<64>(&signature_value).ok_or_else(|| {
RuntimeCheckErrorKind::TypeValueError(
Box::new(TypeSignature::BUFFER_64),
signature_value.to_error_string(),
)
})?;
let public_key = buff_to_array::<32>(&public_key_value).ok_or_else(|| {
RuntimeCheckErrorKind::TypeValueError(
Box::new(TypeSignature::BUFFER_32),
public_key_value.to_error_string(),
)
})?;

let verify_result = ed25519_verify(&message, &signature, &public_key);

Ok(Value::Bool(verify_result.is_ok()))
}

pub fn cost_input_ed25519_verify(args: &[Value]) -> Result<u64, VmExecutionError> {
let len = match args.first() {
Some(Value::Sequence(SequenceData::Buffer(BuffData { data }))) => data.len(),
_ => 0,
};
Ok(u64::try_from(len).unwrap_or(u64::MAX))
}
30 changes: 29 additions & 1 deletion clarity/src/vm/functions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::vm::errors::{
};
pub use crate::vm::functions::assets::stx_transfer_consolidated;
use crate::vm::representations::{ClarityName, SymbolicExpression, SymbolicExpressionType};
use crate::vm::types::{PrincipalData, TypeSignature, Value};
use crate::vm::types::{BuffData, PrincipalData, SequenceData, TypeSignature, Value};
use crate::vm::{LocalContext, eval, is_reserved};

macro_rules! switch_on_global_epoch {
Expand Down Expand Up @@ -190,6 +190,7 @@ define_versioned_named_enum_with_max!(NativeFunctions(ClarityVersion) {
Secp256r1Verify("secp256r1-verify", ClarityVersion::Clarity4, None),
VerifyMerkleProof("verify-merkle-proof", ClarityVersion::Clarity6, None),
GetBitcoinTxOutput("get-bitcoin-tx-output?", ClarityVersion::Clarity6, None),
Ed25519Verify("ed25519-verify", ClarityVersion::Clarity6, None),
});

///
Expand Down Expand Up @@ -591,6 +592,12 @@ pub fn lookup_reserved_functions(name: &str, version: &ClarityVersion) -> Option
ClarityCostFunction::GetBitcoinTxOutput,
&bitcoin::cost_input_get_bitcoin_tx_output,
),
Ed25519Verify => NativeFunction205(
"native_ed25519-verify",
NativeHandle::MoreArg(&crypto::native_ed25519_verify),
ClarityCostFunction::Ed25519verify,
&crypto::cost_input_ed25519_verify,
),
};
Some(callable)
} else {
Expand Down Expand Up @@ -916,6 +923,27 @@ fn special_contract_of(
Ok(contract_principal)
}

/// Helper to coerce a Clarity buffer value into a fixed-size byte array.
pub fn buff_to_array<const N: usize>(value: &Value) -> Option<[u8; N]> {
match value {
Value::Sequence(SequenceData::Buffer(BuffData { data })) if data.len() == N => {
let mut out = [0u8; N];
out.copy_from_slice(data);
Some(out)
}
_ => None,
}
}

pub fn buff_to_vec(value: &Value, max_size: usize) -> Option<Vec<u8>> {
Comment thread
rob-stacks marked this conversation as resolved.
match value {
Value::Sequence(SequenceData::Buffer(BuffData { data })) if data.len() <= max_size => {
Some(data.clone())
}
_ => None,
}
}

#[cfg(test)]
mod test {
use clarity_types::ClarityName;
Expand Down
Loading
Loading