Repository: paritytech/ink
Pull request #360 (2020-03-21 10:33:46)

Implement crypto hash functions supported by contracts pallet
Implements https://github.com/paritytech/ink/issues/358.

We intentionally do not implement those hash functions on the `EnvAccess` and thus do not allow the `self.hash_fn` syntax in contracts. The intended use case for invoking those hash functions is either directly through the environment or through hash abstractions that we are going to add in the future.

## Screenshots

![2020-03-21-150154_1613x596_scrot](https://user-images.githubusercontent.com/8193155/77228052-ed575480-6b84-11ea-8b1e-fdb737fcf688.png)
![2020-03-21-150242_1442x304_scrot](https://user-images.githubusercontent.com/8193155/77228074-06f89c00-6b85-11ea-9cbd-8f1d42874ce6.png)


## Future Tasks

We might want to add new example contracts that demonstrate the usage of those hash functions.

codecov-io: # [Codecov](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=h1) Report
> Merging [#360](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=desc) into [master](https://codecov.io/gh/paritytech/ink/commit/aaf42ed4494163d0ef65d20b649ecb3425e70948?src=pr&el=desc) will **increase** coverage by `6.31%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/paritytech/ink/pull/360/graphs/tree.svg?width=650&token=tYyVaCZb7o&height=150&src=pr)](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #360      +/-   ##
==========================================
+ Coverage   13.46%   19.78%   +6.31%     
==========================================
  Files           4       27      +23     
  Lines          52     1830    +1778     
==========================================
+ Hits            7      362     +355     
- Misses         45     1468    +1423
```


| [Impacted Files](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [core/derive/tests/compile\_tests.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-Y29yZS9kZXJpdmUvdGVzdHMvY29tcGlsZV90ZXN0cy5ycw==) | | |
| [lang/src/dispatcher.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9zcmMvZGlzcGF0Y2hlci5ycw==) | | |
| [lang/macro/src/codegen/abi.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby9zcmMvY29kZWdlbi9hYmkucnM=) | `0% <0%> (ø)` | |
| [lang/macro/src/codegen/contract.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby9zcmMvY29kZWdlbi9jb250cmFjdC5ycw==) | `0% <0%> (ø)` | |
| [lang/macro/tests/compile\_tests.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby90ZXN0cy9jb21waWxlX3Rlc3RzLnJz) | `100% <0%> (ø)` | |
| [lang/macro/src/codegen/env\_types.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby9zcmMvY29kZWdlbi9lbnZfdHlwZXMucnM=) | `0% <0%> (ø)` | |
| [lang/macro/src/ir/data.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby9zcmMvaXIvZGF0YS5ycw==) | `7.91% <0%> (ø)` | |
| [lang/macro/src/codegen/testable.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby9zcmMvY29kZWdlbi90ZXN0YWJsZS5ycw==) | `0% <0%> (ø)` | |
| [primitives/src/byte\_utils.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-cHJpbWl0aXZlcy9zcmMvYnl0ZV91dGlscy5ycw==) | `100% <0%> (ø)` | |
| [lang/macro/src/codegen/mod.rs](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree#diff-bGFuZy9tYWNyby9zcmMvY29kZWdlbi9tb2QucnM=) | `0% <0%> (ø)` | |
| ... and [17 more](https://codecov.io/gh/paritytech/ink/pull/360/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=footer). Last update [aaf42ed...e6f39e2](https://codecov.io/gh/paritytech/ink/pull/360?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/core/Cargo.toml b/core/Cargo.toml
index ada6b44dae..36f4cf59db 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -26,6 +26,14 @@ derive_more = { version = "0.99", default-features = false, features = ["from",
 smallvec = { version = "1.2", default-features = false, features = ["union"] }
 cfg-if = "0.1"
 num-traits = { version = "0.2", default-features = false, features = ["i128"] }
+paste = "0.1"
+
+# Hashes for the off-chain environment.
+byteorder = { version = "1.3", optional = true }
+blake2-rfc = { version = "0.2", optional = true }
+sha2 = { version = "0.8", optional = true }
+tiny-keccak = { version = "2.0", optional = true }
+twox-hash = { version = "1.5", optional = true }
 
 # Only used in the off-chain environment.
 #
@@ -55,6 +63,12 @@ std = [
     "rand",
     "rand/std",
     "num-traits/std",
+    # Enables hashing crates for off-chain environment.
+    "byteorder",
+    "blake2-rfc",
+    "sha2",
+    "tiny-keccak",
+    "twox-hash",
 ]
 ink-generate-abi = [
     "ink_abi",
diff --git a/core/src/env/api.rs b/core/src/env/api.rs
index 5ce8cd0cad..29c34e643b 100644
--- a/core/src/env/api.rs
+++ b/core/src/env/api.rs
@@ -506,3 +506,56 @@ where
         Env::get_runtime_storage::<R>(instance, runtime_key)
     })
 }
+
+/// Built-in efficient cryptographic hash functions.
+pub mod hash {
+    use super::*;
+
+    macro_rules! impl_hash_fn {
+        ( $(#[$doc:meta])* fn $name:ident($output_len:literal) ) => {
+            paste::item! {
+                $( #[$doc] )*
+                pub fn $name(input: &[u8], output: &mut [u8; $output_len]) {
+                    // No need to actually access the environmental instance
+                    // if we only call one of its inherent methods.
+                    <EnvInstance as Env>::[<hash_ $name>](input, output)
+                }
+            }
+        };
+    }
+    impl_hash_fn!(
+        /// Conducts the SHA2 256-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn sha2_256(32)
+    );
+    impl_hash_fn!(
+        /// Conducts the KECCAK 256-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn keccak_256(32)
+    );
+    impl_hash_fn!(
+        /// Conducts the BLAKE2 256-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn blake2_256(32)
+    );
+    impl_hash_fn!(
+        /// Conducts the BLAKE2 128-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn blake2_128(16)
+    );
+    impl_hash_fn!(
+        /// Conducts the TWOX 256-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn twox_256(32)
+    );
+    impl_hash_fn!(
+        /// Conducts the TWOX 128-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn twox_128(16)
+    );
+    impl_hash_fn!(
+        /// Conducts the TWOX 64-bit hash of the input and
+        /// puts the result into the output buffer.
+        fn twox_64(8)
+    );
+}
diff --git a/core/src/env/backend.rs b/core/src/env/backend.rs
index f8ba6a3da8..a8e356655e 100644
--- a/core/src/env/backend.rs
+++ b/core/src/env/backend.rs
@@ -77,6 +77,34 @@ pub trait Env {
 
     /// Prints the given contents to the console log.
     fn println(&mut self, content: &str);
+
+    /// Conducts the SHA2 256-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]);
+
+    /// Conducts the KECCAK 256-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]);
+
+    /// Conducts the BLAKE2 256-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]);
+
+    /// Conducts the BLAKE2 128-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]);
+
+    /// Conducts the TWOX 256-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_twox_256(input: &[u8], output: &mut [u8; 32]);
+
+    /// Conducts the TWOX 128-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_twox_128(input: &[u8], output: &mut [u8; 16]);
+
+    /// Conducts the TWOX 64-bit hash of the input
+    /// puts the result into the output buffer.
+    fn hash_twox_64(input: &[u8], output: &mut [u8; 8]);
 }
 
 /// Environmental contract functionality.
diff --git a/core/src/env/engine/off_chain/hashing.rs b/core/src/env/engine/off_chain/hashing.rs
new file mode 100644
index 0000000000..53a6cd48d7
--- /dev/null
+++ b/core/src/env/engine/off_chain/hashing.rs
@@ -0,0 +1,102 @@
+// Copyright 2019-2020 Parity Technologies (UK) Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Implementations of supported cryptographic hash functions.
+
+/// Conduct the BLAKE2 256-bit hash and place the result into `output`.
+pub fn blake2_256(input: &[u8], output: &mut [u8; 32]) {
+    output.copy_from_slice(blake2_rfc::blake2b::blake2b(32, &[], input).as_bytes());
+}
+
+/// Conduct the BLAKE2 128-bit hash and place the result into `output`.
+pub fn blake2_128(input: &[u8], output: &mut [u8; 16]) {
+    output.copy_from_slice(blake2_rfc::blake2b::blake2b(16, &[], input).as_bytes());
+}
+
+/// Conduct the TWOX (XX) 64-bit hash and place the result into `output`.
+pub fn twox_64(input: &[u8], output: &mut [u8; 8]) {
+    use ::core::hash::Hasher;
+    let mut h0 = twox_hash::XxHash::with_seed(0);
+    h0.write(input);
+    let r0 = h0.finish();
+    use byteorder::{
+        ByteOrder,
+        LittleEndian,
+    };
+    LittleEndian::write_u64(&mut output[0..8], r0);
+}
+
+/// Conduct the TWOX (XX) 128-bit hash and place the result into `output`.
+pub fn twox_128(input: &[u8], output: &mut [u8; 16]) {
+    use ::core::hash::Hasher;
+    let mut h0 = twox_hash::XxHash::with_seed(0);
+    let mut h1 = twox_hash::XxHash::with_seed(1);
+    h0.write(input);
+    h1.write(input);
+    let r0 = h0.finish();
+    let r1 = h1.finish();
+    use byteorder::{
+        ByteOrder,
+        LittleEndian,
+    };
+    LittleEndian::write_u64(&mut output[0..8], r0);
+    LittleEndian::write_u64(&mut output[8..16], r1);
+}
+
+/// Conduct the TWOX (XX) 256-bit hash and place the result into `output`.
+pub fn twox_256(input: &[u8], output: &mut [u8; 32]) {
+    use ::core::hash::Hasher;
+    use byteorder::{
+        ByteOrder,
+        LittleEndian,
+    };
+    let mut h0 = twox_hash::XxHash::with_seed(0);
+    let mut h1 = twox_hash::XxHash::with_seed(1);
+    let mut h2 = twox_hash::XxHash::with_seed(2);
+    let mut h3 = twox_hash::XxHash::with_seed(3);
+    h0.write(input);
+    h1.write(input);
+    h2.write(input);
+    h3.write(input);
+    let r0 = h0.finish();
+    let r1 = h1.finish();
+    let r2 = h2.finish();
+    let r3 = h3.finish();
+    LittleEndian::write_u64(&mut output[0..8], r0);
+    LittleEndian::write_u64(&mut output[8..16], r1);
+    LittleEndian::write_u64(&mut output[16..24], r2);
+    LittleEndian::write_u64(&mut output[24..32], r3);
+}
+
+/// Conduct the KECCAK 256-bit hash and place the result into `output`.
+pub fn keccak_256(input: &[u8], output: &mut [u8; 32]) {
+    use ::tiny_keccak::{
+        Hasher,
+        Keccak,
+    };
+    let mut keccak = Keccak::v256();
+    keccak.update(input);
+    keccak.finalize(output)
+}
+
+/// Conduct the SHA2 256-bit hash and place the result into `output`.
+pub fn sha2_256(input: &[u8], output: &mut [u8; 32]) {
+    use ::sha2::{
+        Digest,
+        Sha256,
+    };
+    let mut hasher = Sha256::new();
+    hasher.input(input);
+    output.copy_from_slice(&hasher.result());
+}
diff --git a/core/src/env/engine/off_chain/impls.rs b/core/src/env/engine/off_chain/impls.rs
index 58da8668e3..0d20698169 100644
--- a/core/src/env/engine/off_chain/impls.rs
+++ b/core/src/env/engine/off_chain/impls.rs
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 use super::{
+    hashing,
     Account,
     EnvInstance,
 };
@@ -111,6 +112,34 @@ impl Env for EnvInstance {
     fn println(&mut self, content: &str) {
         self.console.println(content)
     }
+
+    fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) {
+        hashing::keccak_256(input, output)
+    }
+
+    fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) {
+        hashing::blake2_256(input, output)
+    }
+
+    fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]) {
+        hashing::blake2_128(input, output)
+    }
+
+    fn hash_twox_256(input: &[u8], output: &mut [u8; 32]) {
+        hashing::twox_256(input, output)
+    }
+
+    fn hash_twox_128(input: &[u8], output: &mut [u8; 16]) {
+        hashing::twox_128(input, output)
+    }
+
+    fn hash_twox_64(input: &[u8], output: &mut [u8; 8]) {
+        hashing::twox_64(input, output)
+    }
+
+    fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) {
+        hashing::sha2_256(input, output)
+    }
 }
 
 impl TypedEnv for EnvInstance {
diff --git a/core/src/env/engine/off_chain/mod.rs b/core/src/env/engine/off_chain/mod.rs
index 29945e7807..ab91134431 100644
--- a/core/src/env/engine/off_chain/mod.rs
+++ b/core/src/env/engine/off_chain/mod.rs
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 mod db;
+mod hashing;
 mod impls;
 mod runtime_calls;
 mod runtime_storage;
diff --git a/core/src/env/engine/on_chain/ext.rs b/core/src/env/engine/on_chain/ext.rs
index c168e10a69..cca7b75888 100644
--- a/core/src/env/engine/on_chain/ext.rs
+++ b/core/src/env/engine/on_chain/ext.rs
@@ -101,6 +101,14 @@ mod sys {
 
         pub fn ext_random_seed(subject_ptr: u32, subject_len: u32);
         pub fn ext_println(str_ptr: u32, str_len: u32);
+
+        pub fn ext_hash_keccak_256(input_ptr: u32, input_len: u32, output_ptr: u32);
+        pub fn ext_hash_blake2_256(input_ptr: u32, input_len: u32, output_ptr: u32);
+        pub fn ext_hash_blake2_128(input_ptr: u32, input_len: u32, output_ptr: u32);
+        pub fn ext_hash_twox_256(input_ptr: u32, input_len: u32, output_ptr: u32);
+        pub fn ext_hash_twox_128(input_ptr: u32, input_len: u32, output_ptr: u32);
+        pub fn ext_hash_twox_64(input_ptr: u32, input_len: u32, output_ptr: u32);
+        pub fn ext_hash_sha2_256(input_ptr: u32, input_len: u32, output_ptr: u32);
     }
 }
 
@@ -301,3 +309,26 @@ pub fn println(content: &str) {
     let bytes = content.as_bytes();
     unsafe { sys::ext_println(bytes.as_ptr() as u32, bytes.len() as u32) }
 }
+
+macro_rules! impl_hash_fn {
+    ( $name:ident, $bytes_result:literal ) => {
+        paste::item! {
+            pub fn [<hash_ $name>](input: &[u8], output: &mut [u8; $bytes_result]) {
+                unsafe {
+                    sys::[<ext_hash_ $name>](
+                        input.as_ptr() as u32,
+                        input.len() as u32,
+                        output.as_ptr() as u32,
+                    )
+                }
+            }
+        }
+    };
+}
+impl_hash_fn!(sha2_256, 32);
+impl_hash_fn!(keccak_256, 32);
+impl_hash_fn!(blake2_256, 32);
+impl_hash_fn!(blake2_128, 16);
+impl_hash_fn!(twox_256, 32);
+impl_hash_fn!(twox_128, 16);
+impl_hash_fn!(twox_64, 8);
diff --git a/core/src/env/engine/on_chain/impls.rs b/core/src/env/engine/on_chain/impls.rs
index 748ca7c75f..5ff5ea827f 100644
--- a/core/src/env/engine/on_chain/impls.rs
+++ b/core/src/env/engine/on_chain/impls.rs
@@ -178,6 +178,34 @@ impl Env for EnvInstance {
     fn println(&mut self, content: &str) {
         ext::println(content)
     }
+
+    fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) {
+        ext::hash_keccak_256(input, output)
+    }
+
+    fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) {
+        ext::hash_blake2_256(input, output)
+    }
+
+    fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]) {
+        ext::hash_blake2_128(input, output)
+    }
+
+    fn hash_twox_256(input: &[u8], output: &mut [u8; 32]) {
+        ext::hash_twox_256(input, output)
+    }
+
+    fn hash_twox_128(input: &[u8], output: &mut [u8; 16]) {
+        ext::hash_twox_128(input, output)
+    }
+
+    fn hash_twox_64(input: &[u8], output: &mut [u8; 8]) {
+        ext::hash_twox_64(input, output)
+    }
+
+    fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) {
+        ext::hash_sha2_256(input, output)
+    }
 }
 
 impl TypedEnv for EnvInstance {
diff --git a/core/src/env/mod.rs b/core/src/env/mod.rs
index 38ee67dbf6..98000466d9 100644
--- a/core/src/env/mod.rs
+++ b/core/src/env/mod.rs
@@ -24,6 +24,9 @@ mod engine;
 mod error;
 mod types;
 
+#[cfg(test)]
+mod tests;
+
 #[cfg(any(feature = "std", test, doc))]
 #[doc(inline)]
 pub use self::engine::off_chain::test_api as test;


Test patch:
diff --git a/core/src/env/tests.rs b/core/src/env/tests.rs
new file mode 100644
index 0000000000..aaa47568aa
--- /dev/null
+++ b/core/src/env/tests.rs
@@ -0,0 +1,92 @@
+// Copyright 2019-2020 Parity Technologies (UK) Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::env;
+
+static TEST_INPUT: &[u8] = b"DEAD_BEEF";
+
+#[test]
+fn test_hash_keccak_256() {
+    let mut output = [0x00_u8; 32];
+    env::hash::keccak_256(TEST_INPUT, &mut output);
+    assert_eq!(
+        output,
+        [
+            24, 230, 209, 59, 127, 30, 158, 244, 60, 177, 132, 150, 167, 244, 64, 69,
+            184, 123, 185, 44, 211, 199, 208, 179, 14, 64, 126, 140, 217, 69, 36, 216
+        ]
+    );
+}
+
+#[test]
+fn test_hash_sha2_256() {
+    let mut output = [0x00_u8; 32];
+    env::hash::sha2_256(TEST_INPUT, &mut output);
+    assert_eq!(
+        output,
+        [
+            136, 15, 25, 218, 88, 54, 49, 152, 115, 168, 147, 189, 207, 171, 243, 129,
+            161, 76, 15, 141, 197, 106, 111, 213, 19, 197, 133, 219, 181, 233, 195, 120
+        ]
+    );
+}
+
+#[test]
+fn test_hash_blake2_256() {
+    let mut output = [0x00_u8; 32];
+    env::hash::blake2_256(TEST_INPUT, &mut output);
+    assert_eq!(
+        output,
+        [
+            244, 247, 235, 182, 194, 161, 28, 69, 34, 106, 237, 7, 57, 87, 190, 12, 92,
+            171, 91, 176, 135, 52, 247, 94, 8, 112, 94, 183, 140, 101, 208, 120
+        ]
+    );
+}
+
+#[test]
+fn test_hash_blake2_128() {
+    let mut output = [0x00_u8; 16];
+    env::hash::blake2_128(TEST_INPUT, &mut output);
+    assert_eq!(
+        output,
+        [180, 158, 48, 21, 171, 163, 217, 175, 145, 160, 25, 159, 213, 142, 103, 242]
+    );
+}
+
+const EXPECTED_TWOX_256_HASH: [u8; 32] = [
+    184, 90, 166, 82, 206, 121, 53, 220, 214, 51, 21, 244, 158, 99, 210, 59, 173, 79,
+    253, 143, 224, 57, 69, 25, 254, 88, 31, 187, 27, 139, 238, 91,
+];
+
+#[test]
+fn test_hash_twox_256() {
+    let mut output = [0x00_u8; 32];
+    env::hash::twox_256(TEST_INPUT, &mut output);
+    assert_eq!(&output, &EXPECTED_TWOX_256_HASH[..],);
+}
+
+#[test]
+fn test_hash_twox_128() {
+    let mut output = [0x00_u8; 16];
+    env::hash::twox_128(TEST_INPUT, &mut output);
+    assert_eq!(&output, &EXPECTED_TWOX_256_HASH[..16],);
+}
+
+#[test]
+fn test_hash_twox_64() {
+    let mut output = [0x00_u8; 8];
+    env::hash::twox_64(TEST_INPUT, &mut output);
+    assert_eq!(&output, &EXPECTED_TWOX_256_HASH[..8],);
+}