mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 20:31:13 +00:00
* Implement ext_ hashes for contracts (issue #5258) * load cryto hash .wat from raw string literal instead of file * update .wat contents for testing crypto hashes * remove unnecessary 'static * fix bug in input (call_indirect required 1+ at least it seems) * no longer use scratch buffer for crypto hash functions * improve doc comments of ext_ hash functions * remove unnecessary comment in .wat test file * add return value (const 0) to contract test to hopefully enable result buffer * fix bug in contract assertion * implement proper output_len in contract * implement proper test for crypto hashes * bump spec_version 238 -> 239 * fix COMPLEXITY description * remove final invalid instances of scratch buffer from docs
This commit is contained in:
@@ -82,7 +82,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
// and set impl_version to 0. If only runtime
|
||||
// implementation changes and behavior does not, then leave spec_version as
|
||||
// is and increment impl_version.
|
||||
spec_version: 238,
|
||||
spec_version: 239,
|
||||
impl_version: 0,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
@@ -454,3 +454,28 @@ function performs a DB read.
|
||||
This function serializes the current block's number into the scratch buffer.
|
||||
|
||||
**complexity**: Assuming that the block number is of constant size, this function has constant complexity.
|
||||
|
||||
## Built-in hashing functions
|
||||
|
||||
This paragraph concerns the following supported built-in hash functions:
|
||||
|
||||
- `SHA2` with 256-bit width
|
||||
- `KECCAK` with 256-bit width
|
||||
- `BLAKE2` with 128-bit and 256-bit widths
|
||||
- `TWOX` with 64-bit, 128-bit and 256-bit widths
|
||||
|
||||
These functions compute a cryptographic hash on the given inputs and copy the
|
||||
resulting hash directly back into the sandboxed Wasm contract output buffer.
|
||||
|
||||
Execution of the function consists of the following steps:
|
||||
|
||||
1. Load data stored in the input buffer into an intermediate buffer.
|
||||
2. Compute the cryptographic hash `H` on the intermediate buffer.
|
||||
3. Copy back the bytes of `H` into the contract side output buffer.
|
||||
|
||||
**complexity**: Complexity is proportional to the size of the input buffer in bytes
|
||||
as well as to the size of the output buffer in bytes. Also different cryptographic
|
||||
algorithms have different inherent complexity so users must expect the above
|
||||
mentioned crypto hashes to have varying gas costs.
|
||||
The complexity of each cryptographic hash function highly depends on the underlying
|
||||
implementation.
|
||||
|
||||
@@ -2736,3 +2736,149 @@ fn get_runtime_storage() {
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
const CODE_CRYPTO_HASHES: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_scratch_size" (func $ext_scratch_size (result i32)))
|
||||
(import "env" "ext_scratch_read" (func $ext_scratch_read (param i32 i32 i32)))
|
||||
(import "env" "ext_scratch_write" (func $ext_scratch_write (param i32 i32)))
|
||||
|
||||
(import "env" "ext_hash_sha2_256" (func $ext_hash_sha2_256 (param i32 i32 i32)))
|
||||
(import "env" "ext_hash_keccak_256" (func $ext_hash_keccak_256 (param i32 i32 i32)))
|
||||
(import "env" "ext_hash_blake2_256" (func $ext_hash_blake2_256 (param i32 i32 i32)))
|
||||
(import "env" "ext_hash_blake2_128" (func $ext_hash_blake2_128 (param i32 i32 i32)))
|
||||
(import "env" "ext_hash_twox_256" (func $ext_hash_twox_256 (param i32 i32 i32)))
|
||||
(import "env" "ext_hash_twox_128" (func $ext_hash_twox_128 (param i32 i32 i32)))
|
||||
(import "env" "ext_hash_twox_64" (func $ext_hash_twox_64 (param i32 i32 i32)))
|
||||
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(type $hash_fn_sig (func (param i32 i32 i32)))
|
||||
(table 8 funcref)
|
||||
(elem (i32.const 1)
|
||||
$ext_hash_sha2_256
|
||||
$ext_hash_keccak_256
|
||||
$ext_hash_blake2_256
|
||||
$ext_hash_blake2_128
|
||||
$ext_hash_twox_256
|
||||
$ext_hash_twox_128
|
||||
$ext_hash_twox_64
|
||||
)
|
||||
(data (i32.const 1) "20202010201008") ;; Output sizes of the hashes in order in hex.
|
||||
|
||||
;; Not in use by the tests besides instantiating the contract.
|
||||
(func (export "deploy"))
|
||||
|
||||
;; Called by the tests.
|
||||
;;
|
||||
;; The `call` function expects data in a certain format in the scratch
|
||||
;; buffer.
|
||||
;;
|
||||
;; 1. The first byte encodes an identifier for the crypto hash function
|
||||
;; under test. (*)
|
||||
;; 2. The rest encodes the input data that is directly fed into the
|
||||
;; crypto hash function chosen in 1.
|
||||
;;
|
||||
;; The `deploy` function then computes the chosen crypto hash function
|
||||
;; given the input and puts the result back into the scratch buffer.
|
||||
;; After contract execution the test driver then asserts that the returned
|
||||
;; values are equal to the expected bytes for the input and chosen hash
|
||||
;; function.
|
||||
;;
|
||||
;; (*) The possible value for the crypto hash identifiers can be found below:
|
||||
;;
|
||||
;; | value | Algorithm | Bit Width |
|
||||
;; |-------|-----------|-----------|
|
||||
;; | 0 | SHA2 | 256 |
|
||||
;; | 1 | KECCAK | 256 |
|
||||
;; | 2 | BLAKE2 | 256 |
|
||||
;; | 3 | BLAKE2 | 128 |
|
||||
;; | 4 | TWOX | 256 |
|
||||
;; | 5 | TWOX | 128 |
|
||||
;; | 6 | TWOX | 64 |
|
||||
;; ---------------------------------
|
||||
(func (export "call") (result i32)
|
||||
(local $chosen_hash_fn i32)
|
||||
(local $input_ptr i32)
|
||||
(local $input_len i32)
|
||||
(local $output_ptr i32)
|
||||
(local $output_len i32)
|
||||
(local.set $input_ptr (i32.const 10))
|
||||
(call $ext_scratch_read (local.get $input_ptr) (i32.const 0) (call $ext_scratch_size))
|
||||
(local.set $chosen_hash_fn (i32.load8_u (local.get $input_ptr)))
|
||||
(if (i32.gt_u (local.get $chosen_hash_fn) (i32.const 7))
|
||||
;; We check that the chosen hash fn identifier is within bounds: [0,7]
|
||||
(unreachable)
|
||||
)
|
||||
(local.set $input_ptr (i32.add (local.get $input_ptr) (i32.const 1)))
|
||||
(local.set $input_len (i32.sub (call $ext_scratch_size) (i32.const 1)))
|
||||
(local.set $output_ptr (i32.const 100))
|
||||
(local.set $output_len (i32.load8_u (local.get $chosen_hash_fn)))
|
||||
(call_indirect (type $hash_fn_sig)
|
||||
(local.get $input_ptr)
|
||||
(local.get $input_len)
|
||||
(local.get $output_ptr)
|
||||
(local.get $chosen_hash_fn) ;; Which crypto hash function to execute.
|
||||
)
|
||||
(call $ext_scratch_write
|
||||
(local.get $output_ptr) ;; Linear memory location of the output buffer.
|
||||
(local.get $output_len) ;; Number of output buffer bytes.
|
||||
)
|
||||
(i32.const 0)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn crypto_hashes() {
|
||||
let (wasm, code_hash) = compile_module::<Test>(&CODE_CRYPTO_HASHES).unwrap();
|
||||
|
||||
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
|
||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
assert_ok!(Contracts::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||
|
||||
// Instantiate the CRYPTO_HASHES contract.
|
||||
assert_ok!(Contracts::instantiate(
|
||||
Origin::signed(ALICE),
|
||||
100_000,
|
||||
100_000,
|
||||
code_hash.into(),
|
||||
vec![],
|
||||
));
|
||||
// Perform the call.
|
||||
let input = b"_DEAD_BEEF";
|
||||
use sp_io::hashing::*;
|
||||
// Wraps a hash function into a more dynamic form usable for testing.
|
||||
macro_rules! dyn_hash_fn {
|
||||
($name:ident) => {
|
||||
Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice())
|
||||
};
|
||||
}
|
||||
// All hash functions and their associated output byte lengths.
|
||||
let test_cases: &[(Box<dyn Fn(&[u8]) -> Box<[u8]>>, usize)] = &[
|
||||
(dyn_hash_fn!(sha2_256), 32),
|
||||
(dyn_hash_fn!(keccak_256), 32),
|
||||
(dyn_hash_fn!(blake2_256), 32),
|
||||
(dyn_hash_fn!(blake2_128), 16),
|
||||
(dyn_hash_fn!(twox_256), 32),
|
||||
(dyn_hash_fn!(twox_128), 16),
|
||||
(dyn_hash_fn!(twox_64), 8),
|
||||
];
|
||||
// Test the given hash functions for the input: "_DEAD_BEEF"
|
||||
for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() {
|
||||
// We offset data in the contract tables by 1.
|
||||
let mut params = vec![(n + 1) as u8];
|
||||
params.extend_from_slice(input);
|
||||
let result = <Module<Test>>::bare_call(
|
||||
ALICE,
|
||||
BOB,
|
||||
0,
|
||||
100_000,
|
||||
params,
|
||||
).unwrap();
|
||||
assert_eq!(result.status, 0);
|
||||
let expected = hash_fn(input.as_ref());
|
||||
assert_eq!(&result.data[..*expected_size], &*expected);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ use frame_system;
|
||||
use sp_std::{prelude::*, mem, convert::TryInto};
|
||||
use codec::{Decode, Encode};
|
||||
use sp_runtime::traits::{Bounded, SaturatedConversion};
|
||||
use sp_io::hashing::{
|
||||
keccak_256,
|
||||
blake2_256,
|
||||
blake2_128,
|
||||
twox_256,
|
||||
twox_128,
|
||||
twox_64,
|
||||
sha2_256,
|
||||
};
|
||||
|
||||
/// The value returned from ext_call and ext_instantiate contract external functions if the call or
|
||||
/// instantiation traps. This value is chosen as if the execution does not trap, the return value
|
||||
@@ -1013,8 +1022,217 @@ define_env!(Env, <E: Ext>,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Computes the SHA2 256-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 32 bytes (256 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, sha2_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
// Computes the KECCAK 256-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 32 bytes (256 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, keccak_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
// Computes the BLAKE2 256-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 32 bytes (256 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, blake2_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
// Computes the BLAKE2 128-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 16 bytes (128 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, blake2_128, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
// Computes the TWOX 256-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 32 bytes (256 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_twox_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, twox_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
// Computes the TWOX 128-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 16 bytes (128 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_twox_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, twox_128, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
// Computes the TWOX 64-bit hash on the given input buffer.
|
||||
//
|
||||
// Returns the result directly into the given output buffer.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// - The `input` and `output` buffer may overlap.
|
||||
// - The output buffer is expected to hold at least 8 bytes (64 bits).
|
||||
// - It is the callers responsibility to provide an output buffer that
|
||||
// is large enough to hold the expected amount of bytes returned by the
|
||||
// chosen hash function.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `input_ptr`: the pointer into the linear memory where the input
|
||||
// data is placed.
|
||||
// - `input_len`: the length of the input data in bytes.
|
||||
// - `output_ptr`: the pointer into the linear memory where the output
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
ext_hash_twox_64(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
compute_hash_on_intermediate_buffer(ctx, twox_64, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
);
|
||||
|
||||
/// Computes the given hash function on the scratch buffer.
|
||||
///
|
||||
/// Reads from the sandboxed input buffer into an intermediate buffer.
|
||||
/// Returns the result directly to the output buffer of the sandboxed memory.
|
||||
///
|
||||
/// It is the callers responsibility to provide an output buffer that
|
||||
/// is large enough to hold the expected amount of bytes returned by the
|
||||
/// chosen hash function.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The `input` and `output` buffers may overlap.
|
||||
fn compute_hash_on_intermediate_buffer<E, F, R>(
|
||||
ctx: &mut Runtime<E>,
|
||||
hash_fn: F,
|
||||
input_ptr: u32,
|
||||
input_len: u32,
|
||||
output_ptr: u32,
|
||||
) -> Result<(), sp_sandbox::HostError>
|
||||
where
|
||||
E: Ext,
|
||||
F: FnOnce(&[u8]) -> R,
|
||||
R: AsRef<[u8]>,
|
||||
{
|
||||
// Copy the input buffer directly into the scratch buffer to avoid
|
||||
// heap allocations.
|
||||
let input = read_sandbox_memory(ctx, input_ptr, input_len)?;
|
||||
// Compute the hash on the scratch buffer using the given hash function.
|
||||
let hash = hash_fn(&input);
|
||||
// Write the resulting hash back into the sandboxed output buffer.
|
||||
write_sandbox_memory(
|
||||
ctx.schedule,
|
||||
&mut ctx.special_trap,
|
||||
ctx.gas_meter,
|
||||
&ctx.memory,
|
||||
output_ptr,
|
||||
hash.as_ref(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Finds duplicates in a given vector.
|
||||
///
|
||||
/// This function has complexity of O(n log n) and no additional memory is required, although
|
||||
|
||||
Reference in New Issue
Block a user