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

feat: Implement fixed length hash functions and optimize existing imlementations #646

Merged
merged 7 commits into from
Dec 16, 2021
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
- `near_sdk::ONE_NEAR`, `near_sdk::ONE_YOCTO`, `near_sdk::Gas::ONE_TERA`
- Update SDK dependencies for `nearcore` crates used for mocking (`0.10`) and `borsh` (`0.9`)
- store: Implement caching `LookupSet` type. This is the new iteration of the previous version of `near_sdk::collections::LookupSet` that has an updated API, and is located at `near_sdk::store::LookupSet`. [PR 654](https://github.com/near/near-sdk-rs/pull/654).
- Added `_array` suffix versions of `sha256`, `keccak256`, and `keccak512` hash functions in `env` [PR 646](https://github.com/near/near-sdk-rs/pull/646)
- These return a fixed length array instead of heap allocating with `Vec<u8>`

## `4.0.0-pre.4` [10-15-2021]
- Unpin `syn` dependency in macros from `=1.0.57` to be more composable with other crates. [PR 605](https://github.com/near/near-sdk-rs/pull/605)
Expand Down
Binary file modified examples/callback-results/res/callback_results.wasm
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified examples/fungible-token/res/defi.wasm
Binary file not shown.
Binary file modified examples/fungible-token/res/fungible_token.wasm
Binary file not shown.
Binary file not shown.
Binary file modified examples/mission-control/res/mission_control.wasm
Binary file not shown.
Binary file modified examples/non-fungible-token/res/approval_receiver.wasm
Binary file not shown.
Binary file modified examples/non-fungible-token/res/non_fungible_token.wasm
Binary file not shown.
Binary file modified examples/non-fungible-token/res/token_receiver.wasm
Binary file not shown.
Binary file not shown.
Binary file modified examples/status-message/res/status_message.wasm
Binary file not shown.
Binary file modified examples/test-contract/res/test_contract.wasm
Binary file not shown.
83 changes: 76 additions & 7 deletions near-sdk/src/environment/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
//! whenever possible. In case of cross-contract calls prefer using even higher-level API available
//! through `callback_args`, `callback_args_vec`, `ext_contract`, `Promise`, and `PromiseOrValue`.

use std::convert::TryFrom;
use std::mem::size_of;
use std::panic as std_panic;
use std::{convert::TryFrom, mem::MaybeUninit};

#[cfg(not(target_arch = "wasm32"))]
use crate::mock::MockedBlockchain;
Expand Down Expand Up @@ -51,6 +51,21 @@ macro_rules! method_into_register {
}};
}

//* Note: need specific length functions because const generics don't work with mem::transmute
//* https://github.com/rust-lang/rust/issues/61956

pub(crate) unsafe fn read_register_fixed_32(register_id: u64) -> [u8; 32] {
let mut hash = [MaybeUninit::<u8>::uninit(); 32];
sys::read_register(register_id, hash.as_mut_ptr() as _);
std::mem::transmute(hash)
}

pub(crate) unsafe fn read_register_fixed_64(register_id: u64) -> [u8; 64] {
let mut hash = [MaybeUninit::<u8>::uninit(); 64];
sys::read_register(register_id, hash.as_mut_ptr() as _);
std::mem::transmute(hash)
}

/// Replaces the current low-level blockchain interface accessible through `env::*` with another
/// low-level blockchain interfacr that implements `BlockchainInterface` trait. In most cases you
/// want to use `testing_env!` macro to set it.
Expand Down Expand Up @@ -218,20 +233,50 @@ pub fn random_seed() -> Vec<u8> {

/// Hashes the random sequence of bytes using sha256.
pub fn sha256(value: &[u8]) -> Vec<u8> {
unsafe { sys::sha256(value.len() as _, value.as_ptr() as _, ATOMIC_OP_REGISTER) };
expect_register(read_register(ATOMIC_OP_REGISTER))
sha256_array(value).to_vec()
}

/// Hashes the random sequence of bytes using keccak256.
pub fn keccak256(value: &[u8]) -> Vec<u8> {
unsafe { sys::keccak256(value.len() as _, value.as_ptr() as _, ATOMIC_OP_REGISTER) };
expect_register(read_register(ATOMIC_OP_REGISTER))
keccak256_array(value).to_vec()
}

/// Hashes the random sequence of bytes using keccak512.
pub fn keccak512(value: &[u8]) -> Vec<u8> {
unsafe { sys::keccak512(value.len() as _, value.as_ptr() as _, ATOMIC_OP_REGISTER) };
expect_register(read_register(ATOMIC_OP_REGISTER))
keccak512_array(value).to_vec()
}

/// Hashes the bytes using the SHA-256 hash function. This returns a 32 byte hash.
pub fn sha256_array(value: &[u8]) -> [u8; 32] {
//* SAFETY: sha256 syscall will always generate 32 bytes inside of the atomic op register
//* so the read will have a sufficient buffer of 32, and can transmute from uninit
//* because all bytes are filled. This assumes a valid sha256 implementation.
unsafe {
sys::sha256(value.len() as _, value.as_ptr() as _, ATOMIC_OP_REGISTER);
read_register_fixed_32(ATOMIC_OP_REGISTER)
}
}

/// Hashes the bytes using the Keccak-256 hash function. This returns a 32 byte hash.
pub fn keccak256_array(value: &[u8]) -> [u8; 32] {
//* SAFETY: keccak256 syscall will always generate 32 bytes inside of the atomic op register
//* so the read will have a sufficient buffer of 32, and can transmute from uninit
//* because all bytes are filled. This assumes a valid keccak256 implementation.
unsafe {
sys::keccak256(value.len() as _, value.as_ptr() as _, ATOMIC_OP_REGISTER);
read_register_fixed_32(ATOMIC_OP_REGISTER)
}
}

/// Hashes the bytes using the Keccak-512 hash function. This returns a 64 byte hash.
pub fn keccak512_array(value: &[u8]) -> [u8; 64] {
//* SAFETY: keccak512 syscall will always generate 64 bytes inside of the atomic op register
//* so the read will have a sufficient buffer of 64, and can transmute from uninit
//* because all bytes are filled. This assumes a valid keccak512 implementation.
unsafe {
sys::keccak512(value.len() as _, value.as_ptr() as _, ATOMIC_OP_REGISTER);
read_register_fixed_64(ATOMIC_OP_REGISTER)
}
}

// ################
Expand Down Expand Up @@ -719,4 +764,28 @@ mod tests {
assert!(!is_valid_account_id(&[0, 1, 2]));
assert!(is_valid_account_id(b"near"));
}

#[cfg(not(target_arch = "wasm32"))]
#[test]
fn hash_smoke_tests() {
assert_eq!(
&super::sha256_array(b"some value"),
base64::decode("qz0H8xacy9DtbEtF3iFRn5+TjHLSQSSZiquUnOg7tRs").unwrap().as_slice()
);

assert_eq!(
&super::keccak256_array(b"some value"),
base64::decode("+Sjftfxys7v7mlzLDumEOye0rB68Jab294PiPr1H7x8=").unwrap().as_slice()
);

assert_eq!(
&super::keccak512_array(b"some value"),
base64::decode(
"PjjRQKhRIzdO5j7CCJc6o5uHNJ0XzKyUiiST4YsYtZEyIM0XS09RGql5dwCeFr5IX8\
lPXidDy5uwV501q0EFgw=="
)
.unwrap()
.as_slice()
);
}
}
28 changes: 3 additions & 25 deletions near-sdk/src/environment/hash.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use std::mem::MaybeUninit;

use crate::sys;

const ATOMIC_OP_REGISTER: u64 = u64::MAX - 2;
use crate::env;

mod private {
/// Seal `CryptoHasher` implementations to limit usage to the builtin implementations
Expand Down Expand Up @@ -30,16 +26,7 @@ impl CryptoHasher for Sha256 {
type Digest = [u8; 32];

fn hash(ingest: &[u8]) -> Self::Digest {
//* SAFETY: sha256 syscall will always generate 32 bytes inside of the atomic op register
//* so the read will have a sufficient buffer of 32, and can transmute from uninit
//* because all bytes are filled. This assumes a valid sha256 implementation.
unsafe {
sys::sha256(ingest.len() as _, ingest.as_ptr() as _, ATOMIC_OP_REGISTER);

let mut hash = [MaybeUninit::<u8>::uninit(); 32];
sys::read_register(ATOMIC_OP_REGISTER, hash.as_mut_ptr() as _);
std::mem::transmute(hash)
}
env::sha256_array(ingest)
}
}

Expand All @@ -52,15 +39,6 @@ impl CryptoHasher for Keccak256 {
type Digest = [u8; 32];

fn hash(ingest: &[u8]) -> Self::Digest {
//* SAFETY: keccak256 syscall will always generate 32 bytes inside of the atomic op register
//* so the read will have a sufficient buffer of 32, and can transmute from uninit
//* because all bytes are filled. This assumes a valid keccak256 implementation.
unsafe {
sys::keccak256(ingest.len() as _, ingest.as_ptr() as _, ATOMIC_OP_REGISTER);

let mut hash = [MaybeUninit::<u8>::uninit(); 32];
sys::read_register(ATOMIC_OP_REGISTER, hash.as_mut_ptr() as _);
std::mem::transmute(hash)
}
env::keccak256_array(ingest)
}
}