diff --git a/Cargo.toml b/Cargo.toml index de50d22..c9bcba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ build = 'common/build/build.rs' edition = '2021' name = 'ton_vm' -version = '1.8.191' +version = '1.8.192' [dependencies] ed25519 = '1.2' @@ -19,6 +19,8 @@ similar = { features = [ 'bytes' ], optional = true, version = '2.2.0' } ton_block = { git = 'https://github.com/tonlabs/ever-block.git', tag = '1.9.89' } ton_types = { git = 'https://github.com/tonlabs/ever-types.git', tag = '2.0.18' } zstd = { default-features = false, optional = true, version = '0.11' } +openssl = "0.10" +openssl-sys = "0.9" [features] fift_check = [ ] diff --git a/doc/tvm.tex b/doc/tvm.tex index 0fb71e9..4c47e76 100644 --- a/doc/tvm.tex +++ b/doc/tvm.tex @@ -2277,7 +2277,9 @@ \section*{Introduction} \item {\tt F902} --- {\tt SHA256U} ($s$ -- $x$), computes $\Sha$ of the data bits of~{\em Slice\/}~$s$. If the bit length of $s$ is not divisible by eight, throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer~$x$. \item {\tt F910} --- {\tt CHKSIGNU} ($h$ $s$ $k$ -- $?$), checks the Ed25519-signature $s$ of a hash $h$ (a 256-bit unsigned integer, usually computed as the hash of some data) using public key $k$ (also represented by a 256-bit unsigned integer). The signature $s$ must be a {\em Slice\/} containing at least 512 data bits; only the first 512 bits are used. The result is $-1$ if the signature is valid, $0$ otherwise. Notice that {\tt CHKSIGNU} is equivalent to {\tt ROT}; {\tt NEWB}; {\tt STU 256}; {\tt ENDB}; {\tt NEWC}; {\tt ROTREV}; {\tt CHKSIGNS}, i.e., to {\tt CHKSIGNS} with the first argument $d$ set to 256-bit {\em Slice} containing~$h$. Therefore, if $h$ is computed as the hash of some data, these data are hashed {\em twice}, the second hashing occurring inside {\tt CHKSIGNS}. \item {\tt F911} --- {\tt CHKSIGNS} ($d$ $s$ $k$ -- $?$), checks whether $s$ is a valid Ed25519-signature of the data portion of {\em Slice\/}~$d$ using public key~$k$, similarly to {\tt CHKSIGNU}. If the bit length of {\em Slice\/}~$d$ is not divisible by eight, throws a cell underflow exception. The verification of Ed25519 signatures is the standard one, with $\Sha$ used to reduce $d$ to the 256-bit number that is actually signed. -\item {\tt F912}--{\tt F93F} --- Reserved for hashing and cryptography primitives. +\item {\tt F912} --- {\tt P256\_CHKSIGNU} ($h$, $s$, $k$ -- $?$), checks the P256-signature $s$ of a hash $h$ (a 256-bit unsigned integer, usually computed as the hash of some data) using public key $k$ (represented as a Slice). The signature $s$ must be a Slice containing at least 512 data bits; only the first 512 bits are used. The result is $-1$ if the signature is valid, $0$ otherwise. +\item {\tt F913} --- {\tt P256\_CHKSIGNS} ($d$, $s$, $k$ -- $?$), checks whether $s$ is a valid P256-signature of the data portion of Slice~$d$ using public key~$k$ (represented as a Slice), similarly to {\tt P256\_CHKSIGNS}. If the bit length of Slice~$d$ is not divisible by eight, throws a cell underflow exception. The verification of P256 signatures is the standard one, with $\Sha$ used to reduce $d$ to the 256-bit number that is actually signed. +\item {\tt F914}--{\tt F93F} --- Reserved for hashing and cryptography primitives. \end{itemize} \nxsubpoint\emb{Miscellaneous primitives} diff --git a/src/executor/crypto.rs b/src/executor/crypto.rs index 358e88c..dee5fb2 100644 --- a/src/executor/crypto.rs +++ b/src/executor/crypto.rs @@ -29,6 +29,11 @@ use ed25519::signature::Verifier; use std::borrow::Cow; use ton_block::GlobalCapabilities; use ton_types::{BuilderData, error, GasConsumer, ExceptionCode, UInt256}; +use openssl::ec::{EcGroup, EcKey, EcPoint}; +use openssl::nid::Nid; +use openssl::bn::BigNum; +use openssl::hash::MessageDigest; +use openssl::ecdsa::EcdsaSig; const PUBLIC_KEY_BITS: usize = PUBLIC_KEY_BYTES * 8; const SIGNATURE_BITS: usize = SIGNATURE_BYTES * 8; @@ -180,3 +185,85 @@ pub(super) fn execute_chksigns(engine: &mut Engine) -> Status { pub(super) fn execute_chksignu(engine: &mut Engine) -> Status { check_signature(engine, "CHKSIGNU", true) } + +fn check_p256_signature(engine: &mut Engine, name: &'static str, hash: bool) -> Status { + engine.load_instruction(Instruction::new(name))?; + fetch_stack(engine, 3)?; + + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let pub_key_bytes = engine.cmd.var(0).as_slice()?.get_bytestring(0); + let pub_key_point = match EcPoint::from_bytes(&group, &pub_key_bytes, &mut openssl::bn::BigNumContext::new().unwrap()) { + Ok(pub_key_point) => pub_key_point, + Err(err) => if engine.check_capabilities(GlobalCapabilities::CapsTvmBugfixes2022 as u64) { + engine.cc.stack.push(boolean!(false)); + return Ok(()); + } else { + return err!(ExceptionCode::FatalError, "cannot decode public key into EcPoint {}", err); + } + }; + + let pub_key = match EcKey::from_public_key(&group, &pub_key_point) { + Ok(pub_key) => pub_key, + Err(err) => if engine.check_capabilities(GlobalCapabilities::CapsTvmBugfixes2022 as u64) { + engine.cc.stack.push(boolean!(false)); + return Ok(()); + } else { + return err!(ExceptionCode::FatalError, "cannot load public key {}", err); + } + }; + + if hash { + engine.cmd.var(2).as_integer()?; + } else { + engine.cmd.var(2).as_slice()?; + } + + if engine.cmd.var(1).as_slice()?.remaining_bits() < SIGNATURE_BITS { + return err!(ExceptionCode::CellUnderflow) + } + + let data = if hash { + DataForSignature::Hash(engine.cmd.var(2).as_integer()? + .as_builder::(256)?) + } else { + if engine.cmd.var(2).as_slice()?.remaining_bits() % 8 != 0 { + return err!(ExceptionCode::CellUnderflow) + } + DataForSignature::Slice(engine.cmd.var(2).as_slice()?.get_bytestring(0)) + }; + let signature_bytes = engine.cmd.var(1).as_slice()?.get_bytestring(0); + if signature_bytes.len() != 64 { + return err!(ExceptionCode::FatalError, "Invalid signature length"); + } + + let r_bytes = &signature_bytes[0..32]; + let s_bytes = &signature_bytes[32..64]; + + let r = BigNum::from_slice(r_bytes).unwrap(); + let s = BigNum::from_slice(s_bytes).unwrap(); + + let signature = EcdsaSig::from_private_components(r, s).unwrap(); + + + let data = preprocess_signed_data(engine, data.as_ref()); + let md = openssl::hash::hash(MessageDigest::sha256(), &data).unwrap(); + + #[cfg(feature = "signature_no_check")] + let result = engine.modifiers.chksig_always_succeed || signature.verify(&md, &pub_key).is_ok(); + #[cfg(not(feature = "signature_no_check"))] + let result = match signature.verify(&md, &pub_key) { + Ok(true) => true, + Ok(false) => false, + Err(_) => false, + }; + engine.cc.stack.push(boolean!(result)); + Ok(()) +} + +pub(super) fn execute_p256_chksignu(engine: &mut Engine) -> Status { + check_p256_signature(engine, "P256_CHKSIGNU", true) +} + +pub(super) fn execute_p256_chksigns(engine: &mut Engine) -> Status { + check_p256_signature(engine, "P256_CHKSIGNS", false) +} diff --git a/src/executor/engine/handlers.rs b/src/executor/engine/handlers.rs index 48e786d..8a8c2d9 100644 --- a/src/executor/engine/handlers.rs +++ b/src/executor/engine/handlers.rs @@ -894,6 +894,8 @@ impl Handlers { .set(0x02, execute_sha256u) .set(0x10, execute_chksignu) .set(0x11, execute_chksigns) + .set(0x12, execute_p256_chksignu) + .set(0x13, execute_p256_chksigns) .set(0x40, execute_cdatasizeq) .set(0x41, execute_cdatasize) .set(0x42, execute_sdatasizeq)