diff --git a/.vscode/launch.json b/.vscode/launch.json index ecd6fbdaf..716c68690 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,8 @@ }, "args": [], "cwd": "${workspaceFolder}", - "sourceLanguages": ["rust"] + "sourceLanguages": ["rust"], + "terminal": "integrated" }, { "type": "lldb", diff --git a/Cargo.toml b/Cargo.toml index 45fa5550f..ea286db69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ serde_derive = "1" serde_json = "1.0" rust-crypto = "0.2" sha2 = "0.8.0" +sha3 = "0.8.2" dirs = "1.0.4" regex = "1" linefeed = "0.6" diff --git a/src/util/hash.rs b/src/util/hash.rs index 93dac9e36..f9a25b6a4 100644 --- a/src/util/hash.rs +++ b/src/util/hash.rs @@ -21,8 +21,39 @@ use util::pair::*; use crypto::ripemd160::Ripemd160; use crypto::sha2::Sha256; +use crypto::sha3::Sha3; use crypto::digest::Digest; +#[derive(Serialize, Deserialize)] +pub struct Keccak256Hash(pub [u8; 32]); +impl Keccak256Hash { + pub fn from_data(data: &[u8]) -> Keccak256Hash { + let mut ret = [0u8; 32]; + let mut sha3 = Sha3::keccak256(); + sha3.input(data); + sha3.result(&mut ret); + Keccak256Hash(ret) + } +} +impl_array_newtype!(Keccak256Hash, u8, 32); +impl_array_hexstring_fmt!(Keccak256Hash); +impl_byte_array_newtype!(Keccak256Hash, u8, 32); + +#[derive(Serialize, Deserialize)] +pub struct Sha256Hash(pub [u8; 32]); +impl Sha256Hash { + pub fn from_data(data: &[u8]) -> Sha256Hash { + let mut ret = [0u8; 32]; + let mut sha2 = Sha256::new(); + sha2.input(data); + sha2.result(&mut ret); + Sha256Hash(ret) + } +} +impl_array_newtype!(Sha256Hash, u8, 32); +impl_array_hexstring_fmt!(Sha256Hash); +impl_byte_array_newtype!(Sha256Hash, u8, 32); + #[derive(Serialize, Deserialize)] pub struct Hash160(pub [u8; 20]); impl_array_newtype!(Hash160, u8, 20); diff --git a/src/vm/checker/typecheck/natives/mod.rs b/src/vm/checker/typecheck/natives/mod.rs index 0639dd25e..4a481af6a 100644 --- a/src/vm/checker/typecheck/natives/mod.rs +++ b/src/vm/checker/typecheck/natives/mod.rs @@ -296,6 +296,12 @@ impl TypedNativeFunction { Hash160 => Simple(SimpleNativeFunction(FunctionType::Fixed(vec![TypeSignature::new_atom( AtomTypeIdentifier::AnyType )], TypeSignature::new_atom( AtomTypeIdentifier::BufferType(20) )))), + Sha256 => + Simple(SimpleNativeFunction(FunctionType::Fixed(vec![TypeSignature::new_atom( AtomTypeIdentifier::AnyType )], + TypeSignature::new_atom( AtomTypeIdentifier::BufferType(32) )))), + Keccak256 => + Simple(SimpleNativeFunction(FunctionType::Fixed(vec![TypeSignature::new_atom( AtomTypeIdentifier::AnyType )], + TypeSignature::new_atom( AtomTypeIdentifier::BufferType(32) )))), Equals => Simple(SimpleNativeFunction(FunctionType::Variadic(TypeSignature::new_atom( AtomTypeIdentifier::AnyType ), TypeSignature::new_atom( AtomTypeIdentifier::BoolType )))), diff --git a/src/vm/docs/mod.rs b/src/vm/docs/mod.rs index 5a790b184..70ab44c0e 100644 --- a/src/vm/docs/mod.rs +++ b/src/vm/docs/mod.rs @@ -343,9 +343,9 @@ If a Void value is supplied as the inputted tuple, `get` returns Void.", " }; -const HASH_160_API: SpecialAPI = SpecialAPI { +const HASH160_API: SpecialAPI = SpecialAPI { input_type: "buff|int", - output_type: "(buff 160)", + output_type: "(buff 20)", signature: "(hash160 value)", description: "The `hash160` function computes RIPEMD160(SHA256(x)) of the inputted value. If an integer (128 bit) is supplied the hash is computed over the little endian representation of the @@ -353,6 +353,26 @@ integer.", example: "(hash160 0) => 0xe4352f72356db555721651aa612e00379167b30f" }; +const SHA256_API: SpecialAPI = SpecialAPI { + input_type: "buff|int", + output_type: "(buff 32)", + signature: "(sha256 value)", + description: "The `sha256` function computes SHA256(x) of the inputted value. +If an integer (128 bit) is supplied the hash is computer over the little endian representation of the +integer.", + example: "(sha256 0) => 0x374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb" +}; + +const KECCAK256_API: SpecialAPI = SpecialAPI { + input_type: "buff|int", + output_type: "(buff 32)", + signature: "(keccak256 value)", + description: "The `keccak256` function computes KECCAK256(value) of the inputted value. +Note that this differs from the NIST SHA-3 (i.e. FIPS 202) standard. If an integer (128 bit) +is supplied the hash is computer over the little endian representation of the integer.", + example: "(keccak256 0) => 0xf490de2920c8a35fabeb13208852aa28c76f9be9b03a4dd2b3c075f7a26923b4" +}; + const CONTRACT_CALL_API: SpecialAPI = SpecialAPI { input_type: "ContractName, PublicFunctionName, Arg0, ...", output_type: "BoolType", @@ -417,7 +437,9 @@ fn make_api_reference(function: &NativeFunctions) -> FunctionAPI { TupleCons => make_for_special(&TUPLE_CONS_API), TupleGet => make_for_special(&TUPLE_GET_API), Begin => make_for_special(&BEGIN_API), - Hash160 => make_for_special(&HASH_160_API), + Hash160 => make_for_special(&HASH160_API), + Sha256 => make_for_special(&SHA256_API), + Keccak256 => make_for_special(&KECCAK256_API), Print => make_for_special(&PRINT_API), ContractCall => make_for_special(&CONTRACT_CALL_API), AsContract => make_for_special(&AS_CONTRACT_API) diff --git a/src/vm/functions/mod.rs b/src/vm/functions/mod.rs index ddae27894..44f7038ed 100644 --- a/src/vm/functions/mod.rs +++ b/src/vm/functions/mod.rs @@ -43,6 +43,8 @@ pub enum NativeFunctions { TupleGet, Begin, Hash160, + Sha256, + Keccak256, Print, ContractCall, AsContract @@ -82,6 +84,8 @@ impl NativeFunctions { "get" => Some(TupleGet), "begin" => Some(Begin), "hash160" => Some(Hash160), + "sha256" => Some(Sha256), + "keccak256" => Some(Keccak256), "print" => Some(Print), "contract-call!" => Some(ContractCall), "as-contract" => Some(AsContract), @@ -124,6 +128,8 @@ pub fn lookup_reserved_functions(name: &str) -> Option { TupleGet => CallableType::SpecialFunction("native_get-tuple", &tuples::tuple_get), Begin => CallableType::NativeFunction("native_begin", &native_begin), Hash160 => CallableType::NativeFunction("native_hash160", &native_hash160), + Sha256 => CallableType::NativeFunction("native_sha256", &native_sha256), + Keccak256 => CallableType::NativeFunction("native_keccak256", &native_keccak256), Print => CallableType::NativeFunction("native_print", &native_print), ContractCall => CallableType::SpecialFunction("native_contract-call", &database::special_contract_call), AsContract => CallableType::SpecialFunction("native_as-contract", &special_as_contract), @@ -179,6 +185,38 @@ fn native_hash160(args: &[Value]) -> Result { Value::buff_from(hash160.as_bytes().to_vec()) } +fn native_sha256(args: &[Value]) -> Result { + use util::hash::Sha256Hash; + + if !(args.len() == 1) { + return Err(Error::new(ErrType::InvalidArguments("Wrong number of arguments to sha256 (expects 1)".to_string()))) + } + let input = &args[0]; + let bytes = match input { + Value::Int(value) => Ok(value.to_le_bytes().to_vec()), + Value::Buffer(value) => Ok(value.data.clone()), + _ => Err(Error::new(ErrType::NotImplemented)) + }?; + let sha256 = Sha256Hash::from_data(&bytes); + Value::buff_from(sha256.as_bytes().to_vec()) +} + +fn native_keccak256(args: &[Value]) -> Result { + use util::hash::Keccak256Hash; + + if !(args.len() == 1) { + return Err(Error::new(ErrType::InvalidArguments("Wrong number of arguments to keccak256 (expects 1)".to_string()))) + } + let input = &args[0]; + let bytes = match input { + Value::Int(value) => Ok(value.to_le_bytes().to_vec()), + Value::Buffer(value) => Ok(value.data.clone()), + _ => Err(Error::new(ErrType::NotImplemented)) + }?; + let keccak256 = Keccak256Hash::from_data(&bytes); + Value::buff_from(keccak256.as_bytes().to_vec()) +} + fn native_begin(args: &[Value]) -> Result { match args.last() { Some(v) => Ok(v.clone()), diff --git a/src/vm/tests/simple_apply_eval.rs b/src/vm/tests/simple_apply_eval.rs index 063419633..c4033b0a4 100644 --- a/src/vm/tests/simple_apply_eval.rs +++ b/src/vm/tests/simple_apply_eval.rs @@ -4,8 +4,9 @@ use vm::errors::{ErrType}; use vm::{Value, LocalContext, ContractContext, GlobalContext, Environment, CallStack}; use vm::contexts::{OwnedEnvironment}; use vm::callables::DefinedFunction; -use vm::types::{TypeSignature, AtomTypeIdentifier}; +use vm::types::{TypeSignature, AtomTypeIdentifier, BuffData}; use vm::parser::parse; +use util::hash::hex_bytes; #[test] fn test_simple_let() { @@ -36,6 +37,50 @@ fn test_simple_let() { } +#[test] +fn test_sha256() { + let sha256_evals = [ + "(sha256 \"\")", + "(sha256 0)", + "(sha256 \"The quick brown fox jumps over the lazy dog\")", + ]; + + fn to_buffer(hex: &str) -> Value { + return Value::Buffer(BuffData { data: hex_bytes(hex).unwrap() }); + } + + let expectations = [ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb", + "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592" + ]; + + sha256_evals.iter().zip(expectations.iter()) + .for_each(|(program, expectation)| assert_eq!(Ok(to_buffer(expectation)), execute(program))); +} + +#[test] +fn test_keccak256() { + let keccak256_evals = [ + "(keccak256 \"\")", + "(keccak256 0)", + "(keccak256 \"The quick brown fox jumps over the lazy dog\")", + ]; + + fn to_buffer(hex: &str) -> Value { + return Value::Buffer(BuffData { data: hex_bytes(hex).unwrap() }); + } + + let expectations = [ + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "f490de2920c8a35fabeb13208852aa28c76f9be9b03a4dd2b3c075f7a26923b4", + "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15" + ]; + + keccak256_evals.iter().zip(expectations.iter()) + .for_each(|(program, expectation)| assert_eq!(Ok(to_buffer(expectation)), execute(program))); +} + #[test] fn test_simple_is_null() { let null_evals = [