-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement EIP-152 Blake2 compression function.
- Loading branch information
Showing
3 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "ewasm-precompile-blake2f" | ||
version = "0.2.0" | ||
authors = ["Grant Wuerker <[email protected]>"] | ||
license = "Apache-2.0" | ||
repository = "https://github.com/ewasm/ewasm-precompiles" | ||
description = "Ethereum Blake2 compression function precompile in Rust" | ||
publish = false | ||
edition = "2018" | ||
|
||
[dependencies] | ||
byteorder = "1.3.4" | ||
ewasm_api = "0.9" | ||
arr_macro = "0.1.3" | ||
|
||
[dev-dependencies] | ||
hex = "0.3.1" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/// Implementation of blake2 compression function F. | ||
/// | ||
/// This was copied from https://gist.github.com/seunlanlege/fa848401d316c52919f6e554fba6870b | ||
/// with some modifications. It was initially written by Seun Lanlege and has no explicit license. | ||
/// Message word schedule permutations for each round of both BLAKE2b and BLAKE2s are defined by | ||
/// SIGMA. For BLAKE2b, the two extra permutations for rounds 10 and 11 are | ||
/// SIGMA[10..11] = SIGMA[0..1]. | ||
/// | ||
/// https://tools.ietf.org/html/rfc7693#section-2.7 | ||
const SIGMA: [[usize; 16]; 10] = [ | ||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], | ||
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], | ||
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], | ||
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], | ||
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], | ||
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], | ||
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], | ||
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], | ||
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], | ||
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], | ||
]; | ||
|
||
/// IV[i] = floor(2**w * frac(sqrt(prime(i+1)))), where prime(i) is the i:th prime number | ||
/// ( 2, 3, 5, 7, 11, 13, 17, 19 ) and sqrt(x) is the square root of x. | ||
/// | ||
/// https://tools.ietf.org/html/rfc7693#section-2.6 | ||
const IV: [u64; 8] = [ | ||
0x6a09e667f3bcc908, | ||
0xbb67ae8584caa73b, | ||
0x3c6ef372fe94f82b, | ||
0xa54ff53a5f1d36f1, | ||
0x510e527fade682d1, | ||
0x9b05688c2b3e6c1f, | ||
0x1f83d9abfb41bd6b, | ||
0x5be0cd19137e2179, | ||
]; | ||
|
||
/// Compression function F takes as an argument the state vector "h", message block vector "m" | ||
/// (last block is padded with zeros to full block size, if required), 2w-bit offset counter "t", | ||
/// and final block indicator flag "f". Local vector v[0..15] is used in processing. F returns a | ||
/// new state vector. The number of rounds, "r", is 12 for BLAKE2b and 10 for BLAKE2s. | ||
/// Rounds are numbered from 0 to r - 1. | ||
/// | ||
/// https://tools.ietf.org/html/rfc7693#section-3.2 | ||
pub fn compress(rounds: u32, h: &mut [u64; 8], m: [u64; 16], t: [u64; 2], f: bool) { | ||
let mut v = Vec::new(); | ||
v.extend(h.iter().cloned()); | ||
v.extend_from_slice(&IV); | ||
|
||
v[12] ^= t[0]; | ||
v[13] ^= t[1]; | ||
|
||
if f { | ||
v[14] = !v[14] | ||
} | ||
|
||
for i in 0..rounds as usize { | ||
let s = &SIGMA[i % 10]; | ||
v[0] = v[0].wrapping_add(v[4]).wrapping_add(m[s[0]]); | ||
v[12] = (v[12] ^ v[0]).rotate_right(32); | ||
v[8] = v[8].wrapping_add(v[12]); | ||
v[4] = (v[4] ^ v[8]).rotate_right(24); | ||
v[0] = v[0].wrapping_add(v[4]).wrapping_add(m[s[1]]); | ||
v[12] = (v[12] ^ v[0]).rotate_right(16); | ||
v[8] = v[8].wrapping_add(v[12]); | ||
v[4] = (v[4] ^ v[8]).rotate_right(63); | ||
v[1] = v[1].wrapping_add(v[5]).wrapping_add(m[s[2]]); | ||
v[13] = (v[13] ^ v[1]).rotate_right(32); | ||
v[9] = v[9].wrapping_add(v[13]); | ||
v[5] = (v[5] ^ v[9]).rotate_right(24); | ||
v[1] = v[1].wrapping_add(v[5]).wrapping_add(m[s[3]]); | ||
v[13] = (v[13] ^ v[1]).rotate_right(16); | ||
v[9] = v[9].wrapping_add(v[13]); | ||
v[5] = (v[5] ^ v[9]).rotate_right(63); | ||
v[2] = v[2].wrapping_add(v[6]).wrapping_add(m[s[4]]); | ||
v[14] = (v[14] ^ v[2]).rotate_right(32); | ||
v[10] = v[10].wrapping_add(v[14]); | ||
v[6] = (v[6] ^ v[10]).rotate_right(24); | ||
v[2] = v[2].wrapping_add(v[6]).wrapping_add(m[s[5]]); | ||
v[14] = (v[14] ^ v[2]).rotate_right(16); | ||
v[10] = v[10].wrapping_add(v[14]); | ||
v[6] = (v[6] ^ v[10]).rotate_right(63); | ||
v[3] = v[3].wrapping_add(v[7]).wrapping_add(m[s[6]]); | ||
v[15] = (v[15] ^ v[3]).rotate_right(32); | ||
v[11] = v[11].wrapping_add(v[15]); | ||
v[7] = (v[7] ^ v[11]).rotate_right(24); | ||
v[3] = v[3].wrapping_add(v[7]).wrapping_add(m[s[7]]); | ||
v[15] = (v[15] ^ v[3]).rotate_right(16); | ||
v[11] = v[11].wrapping_add(v[15]); | ||
v[7] = (v[7] ^ v[11]).rotate_right(63); | ||
v[0] = v[0].wrapping_add(v[5]).wrapping_add(m[s[8]]); | ||
v[15] = (v[15] ^ v[0]).rotate_right(32); | ||
v[10] = v[10].wrapping_add(v[15]); | ||
v[5] = (v[5] ^ v[10]).rotate_right(24); | ||
v[0] = v[0].wrapping_add(v[5]).wrapping_add(m[s[9]]); | ||
v[15] = (v[15] ^ v[0]).rotate_right(16); | ||
v[10] = v[10].wrapping_add(v[15]); | ||
v[5] = (v[5] ^ v[10]).rotate_right(63); | ||
v[1] = v[1].wrapping_add(v[6]).wrapping_add(m[s[10]]); | ||
v[12] = (v[12] ^ v[1]).rotate_right(32); | ||
v[11] = v[11].wrapping_add(v[12]); | ||
v[6] = (v[6] ^ v[11]).rotate_right(24); | ||
v[1] = v[1].wrapping_add(v[6]).wrapping_add(m[s[11]]); | ||
v[12] = (v[12] ^ v[1]).rotate_right(16); | ||
v[11] = v[11].wrapping_add(v[12]); | ||
v[6] = (v[6] ^ v[11]).rotate_right(63); | ||
v[2] = v[2].wrapping_add(v[7]).wrapping_add(m[s[12]]); | ||
v[13] = (v[13] ^ v[2]).rotate_right(32); | ||
v[8] = v[8].wrapping_add(v[13]); | ||
v[7] = (v[7] ^ v[8]).rotate_right(24); | ||
v[2] = v[2].wrapping_add(v[7]).wrapping_add(m[s[13]]); | ||
v[13] = (v[13] ^ v[2]).rotate_right(16); | ||
v[8] = v[8].wrapping_add(v[13]); | ||
v[7] = (v[7] ^ v[8]).rotate_right(63); | ||
v[3] = v[3].wrapping_add(v[4]).wrapping_add(m[s[14]]); | ||
v[14] = (v[14] ^ v[3]).rotate_right(32); | ||
v[9] = v[9].wrapping_add(v[14]); | ||
v[4] = (v[4] ^ v[9]).rotate_right(24); | ||
v[3] = v[3].wrapping_add(v[4]).wrapping_add(m[s[15]]); | ||
v[14] = (v[14] ^ v[3]).rotate_right(16); | ||
v[9] = v[9].wrapping_add(v[14]); | ||
v[4] = (v[4] ^ v[9]).rotate_right(63); | ||
} | ||
|
||
for i in 0..8 { | ||
h[i] ^= v[i] ^ v[i + 8]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
extern crate ewasm_api; | ||
|
||
mod f; | ||
|
||
use arr_macro::arr; | ||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; | ||
use std::io::Cursor; | ||
|
||
#[cfg(not(test))] | ||
#[no_mangle] | ||
pub extern "C" fn main() { | ||
match blake2_f(ewasm_api::calldata_acquire()) { | ||
Ok((gas_used, data)) => { | ||
ewasm_api::consume_gas(gas_used); | ||
ewasm_api::finish_data(data.as_slice()) | ||
} | ||
Err(err) => ewasm_api::revert_data(err.as_bytes()), | ||
} | ||
} | ||
|
||
/// The Blake2F precompile written as Result returning function. | ||
pub fn blake2_f(data: Vec<u8>) -> Result<(u64, Vec<u8>), &'static str> { | ||
if data.len() == 213 { | ||
let (rounds, mut h, m, t, f) = read_data(data)?; | ||
f::compress(rounds, &mut h, m, t, f); | ||
|
||
return Ok((rounds as u64, write_data(h))); | ||
} | ||
|
||
Err("input length for BLAKE2 F precompile should be exactly 213 bytes") | ||
} | ||
|
||
fn read_data(data: Vec<u8>) -> Result<(u32, [u64; 8], [u64; 16], [u64; 2], bool), &'static str> { | ||
let mut reader = Cursor::new(data); | ||
|
||
let rounds = reader | ||
.read_u32::<BigEndian>() | ||
.expect("Unable to read data."); | ||
let h = arr![reader.read_u64::<LittleEndian>().expect("Unable to read data."); 8]; | ||
let m = arr![reader.read_u64::<LittleEndian>().expect("Unable to read data."); 16]; | ||
let t = arr![reader.read_u64::<LittleEndian>().expect("Unable to read data."); 2]; | ||
let f = match reader.read_u8().expect("Unable to read data.") { | ||
0 => false, | ||
1 => true, | ||
_ => return Err("incorrect final block indicator flag"), | ||
}; | ||
|
||
Ok((rounds, h, m, t, f)) | ||
} | ||
|
||
fn write_data(h: [u64; 8]) -> Vec<u8> { | ||
let mut data = vec![]; | ||
|
||
for i in 0..8 { | ||
data.write_u64::<LittleEndian>(h[i]) | ||
.expect("Unable to write data.") | ||
} | ||
|
||
data | ||
} | ||
|
||
/// See test cases at https://eips.ethereum.org/EIPS/eip-152. | ||
/// | ||
/// You will most likely need to override the default target like so: | ||
/// Example: `cargo test --target x86_64-unknown-linux-gnu --release` | ||
#[cfg(test)] | ||
mod tests { | ||
extern crate hex; | ||
|
||
use crate::blake2_f; | ||
use std::time::Instant; | ||
|
||
const INPUTS: [&str; 9] = [ | ||
"", | ||
"00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", | ||
"000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", | ||
"0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002", | ||
"0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", | ||
"0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", | ||
"0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", | ||
"0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", | ||
"ffffffff48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", | ||
]; | ||
|
||
const OUTPUTS: [&str; 9] = [ | ||
"", | ||
"", | ||
"", | ||
"", | ||
"08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", | ||
"ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", | ||
"75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", | ||
"b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", | ||
"fc59093aafa9ab43daae0e914c57635c5402d8e3d2130eb9b3cc181de7f0ecf9b22bf99a7815ce16419e200e01846e6b5df8cc7703041bbceb571de6631d2615", | ||
]; | ||
|
||
/// 1200 rounds | ||
const BENCH_INPUT: &str = "000004B048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"; | ||
|
||
#[test] | ||
fn case0() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[0]).unwrap()), | ||
Err("input length for BLAKE2 F precompile should be exactly 213 bytes"), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case1() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[1]).unwrap()), | ||
Err("input length for BLAKE2 F precompile should be exactly 213 bytes"), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case2() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[2]).unwrap()), | ||
Err("input length for BLAKE2 F precompile should be exactly 213 bytes"), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case3() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[3]).unwrap()), | ||
Err("incorrect final block indicator flag"), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case4() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[4]).unwrap()), | ||
Ok((0, hex::decode(OUTPUTS[4]).unwrap())), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case5() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[5]).unwrap()), | ||
Ok((12, hex::decode(OUTPUTS[5]).unwrap())), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case6() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[6]).unwrap()), | ||
Ok((12, hex::decode(OUTPUTS[6]).unwrap())), | ||
) | ||
} | ||
|
||
#[test] | ||
fn case7() { | ||
assert_eq!( | ||
blake2_f(hex::decode(INPUTS[7]).unwrap()), | ||
Ok((1, hex::decode(OUTPUTS[7]).unwrap())), | ||
) | ||
} | ||
|
||
// This test takes excessively long. | ||
// | ||
// #[test] | ||
// fn case8() { | ||
// assert_eq!( | ||
// blake2_f(hex::decode(INPUTS[8]).unwrap()), | ||
// Ok((4294967295, hex::decode(OUTPUTS[8]).unwrap())), | ||
// ); | ||
// } | ||
|
||
/// run with `-- --nocapture` | ||
#[test] | ||
fn benchmark() { | ||
let n = 1000; | ||
let mut sum = 0; | ||
|
||
for _ in 0..n { | ||
let now = Instant::now(); | ||
blake2_f(hex::decode(BENCH_INPUT).unwrap()).expect("Couldn't compress BENCH_INPUT."); | ||
sum += now.elapsed().as_nanos(); | ||
} | ||
|
||
println!("Average elapsed time for 1200 rounds: {}ns", sum / n) | ||
} | ||
} |