Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding P256 Signature Support in TVM #236

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 = [ ]
Expand Down
4 changes: 3 additions & 1 deletion doc/tvm.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
87 changes: 87 additions & 0 deletions src/executor/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<UnsignedIntegerBigEndianEncoding>(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)
}
2 changes: 2 additions & 0 deletions src/executor/engine/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down