mirror of
https://github.com/vlang/v.git
synced 2025-09-13 22:42:26 +03:00
x.crypto.ascon: improves one-shot of ascon hasing functions
This commit is contained in:
parent
2a4749d4e8
commit
152001ef81
8 changed files with 361 additions and 23 deletions
75
vlib/x/crypto/ascon/bench/aead.v
Normal file
75
vlib/x/crypto/ascon/bench/aead.v
Normal file
|
@ -0,0 +1,75 @@
|
|||
// This benchmark is for Ascon-AEAD128 in `x.crypto.ascon` compared to
|
||||
// already stocked `x.crypto.cacha20poly1305 for AEAD functionalities.
|
||||
//
|
||||
// Here is output in my tests, first item was `x.crypto.ascon` and the later
|
||||
// for `x.crypto.chacha20poly1305` on encryption or decryption part.
|
||||
//
|
||||
// Encryption..
|
||||
// -----------
|
||||
// Iterations: 10000 Total Duration: 26.008ms ns/op: 2600 B/op: 16 allocs/op: 17
|
||||
// Iterations: 10000 Total Duration: 158.865ms ns/op: 15886 B/op: 16 allocs/op: 16
|
||||
//
|
||||
// Decryption..
|
||||
// -----------
|
||||
// Iterations: 10000 Total Duration: 29.091ms ns/op: 2909 B/op: 6 allocs/op: 8
|
||||
// Iterations: 10000 Total Duration: 158.373ms ns/op: 15837 B/op: 8 allocs/op: 12
|
||||
//
|
||||
import encoding.hex
|
||||
import x.benchmark
|
||||
import x.crypto.ascon
|
||||
import x.crypto.chacha20poly1305
|
||||
|
||||
// randomly generated key and nonce, 16-bytes of ascon key and 32-bytes of chacha20poly1305 key.
|
||||
const key_ascon = hex.decode('7857bfb462c654d1d1b02971be021235')!
|
||||
const key_cpoly = hex.decode('9d9603f4fc460e273b80795ea50eab5873c04f589226c7d591b5336feb32fcba')!
|
||||
|
||||
// 16-bytes ascon-nonce
|
||||
const ascon_nonce = hex.decode('8b521028fb54591472d8d8ee14430835')!
|
||||
|
||||
// 12-bytes chacha20poly1305 nonce
|
||||
const cpoly_nonce = hex.decode('9a3c83e4236ea9a2c4e482da')!
|
||||
|
||||
const ad = 'Ascon-AEAD128 additional data'.bytes()
|
||||
const msg = 'Ascon-AEAD128 benchmarking message'.bytes()
|
||||
|
||||
// expected ciphertext for aead128 := 4b21a18cbca65b11aaf73dc74241c89bfcec96a4c8973ae696a938e0a591e846c4eb7b2906664f2318c0fd6ec1c56424aa9b
|
||||
const ciphertext_aead128 = hex.decode('4b21a18cbca65b11aaf73dc74241c89bfcec96a4c8973ae696a938e0a591e846c4eb7b2906664f2318c0fd6ec1c56424aa9b')!
|
||||
|
||||
fn bench_ascon_aead128_encrypt() ! {
|
||||
_ := ascon.encrypt(key_ascon, ascon_nonce, ad, msg)!
|
||||
}
|
||||
|
||||
fn bench_ascon_aead128_decrypt() ! {
|
||||
_ := ascon.decrypt(key_ascon, ascon_nonce, ad, ciphertext_aead128)!
|
||||
}
|
||||
|
||||
// expected ciphertext for chacha20poly1305
|
||||
const ciphertext_chachapoly1305 = hex.decode('67dea3c65f0f326bcf587f024140a85d9535790d9b16129210a2289eda43bb9b62746450026fc1baf466bcb8a181843cd424')!
|
||||
|
||||
fn bench_chacha20poly1305_encrypt() ! {
|
||||
_ := chacha20poly1305.encrypt(msg, key_cpoly, cpoly_nonce, ad)!
|
||||
}
|
||||
|
||||
fn bench_chacha20poly1305_decrypt() ! {
|
||||
_ := chacha20poly1305.decrypt(ciphertext_chachapoly1305, key_cpoly, cpoly_nonce, ad)!
|
||||
}
|
||||
|
||||
fn main() {
|
||||
cf := benchmark.BenchmarkDefaults{
|
||||
n: 10000
|
||||
}
|
||||
println('Encryption..')
|
||||
println('-----------')
|
||||
mut b0 := benchmark.setup(bench_ascon_aead128_encrypt, cf)!
|
||||
b0.run()
|
||||
mut b1 := benchmark.setup(bench_chacha20poly1305_encrypt, cf)!
|
||||
b1.run()
|
||||
|
||||
println('')
|
||||
println('Decryption..')
|
||||
println('-----------')
|
||||
mut b2 := benchmark.setup(bench_ascon_aead128_decrypt, cf)!
|
||||
b2.run()
|
||||
mut b3 := benchmark.setup(bench_chacha20poly1305_decrypt, cf)!
|
||||
b3.run()
|
||||
}
|
197
vlib/x/crypto/ascon/bench/hashxof.v
Normal file
197
vlib/x/crypto/ascon/bench/hashxof.v
Normal file
|
@ -0,0 +1,197 @@
|
|||
// Ascon-Hash256 (and Ascon-XOF128) benchmark compared to builtin
|
||||
// crypto.sha256 (for sum256) and sha3.shake256 (for xof outputing 256-bits)
|
||||
//
|
||||
// This benchmark code was adapted from argon2 benchmark by @fleximus, the creator argon2 module.
|
||||
// Credit tributed to @fleximus
|
||||
// See https://gist.github.com/fleximus/db5b867a9a37da46340db61bdac6e696
|
||||
//
|
||||
// Output
|
||||
// ======
|
||||
// Sum and Xof 256-bits output performance comparison
|
||||
// ============================================================
|
||||
// Iterations per test: 10000
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
// Data Size | Ascon256 | Sha256 | Ratio 256 || AsconXof128 | Shake256 | Ratio (Xof) |
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
// 4 B | 24.00ms | 33.00ms | 0.73x || 24.00ms | 208.00ms | 0.12x |
|
||||
// 6 B | 23.00ms | 53.00ms | 0.45x || 25.00ms | 287.00ms | 0.08x |
|
||||
// 8 B | 35.00ms | 37.00ms | 0.95x || 26.00ms | 202.00ms | 0.18x |
|
||||
// 16 B | 30.00ms | 37.00ms | 0.83x || 30.00ms | 205.00ms | 0.15x |
|
||||
// 64 B | 55.00ms | 61.00ms | 0.89x || 53.00ms | 241.00ms | 0.23x |
|
||||
// 75 B | 61.00ms | 57.00ms | 1.07x || 58.00ms | 182.00ms | 0.34x |
|
||||
// 256 B | 154.00ms | 123.00ms | 1.25x || 144.00ms | 398.00ms | 0.39x |
|
||||
// 512 B | 273.00ms | 216.00ms | 1.26x || 265.00ms | 779.00ms | 0.35x |
|
||||
// 1025 B | 610.00ms | 401.00ms | 1.52x || 509.00ms | 1.37s | 0.45x |
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
// Total | 1.27s | 1.02s | 1.24x || 1.14s | 3.87s | 0.294x|
|
||||
// --------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Per-operation averages:
|
||||
// Ascon256: 14108 ns per hash
|
||||
// Sha256: 11360 ns per hash
|
||||
// AsconXof128: 12648 ns per hash
|
||||
// Shake256: 43036 ns per hash
|
||||
//
|
||||
module main
|
||||
|
||||
import time
|
||||
import crypto.sha3
|
||||
import crypto.sha256
|
||||
import x.crypto.ascon
|
||||
|
||||
const benchmark_iterations = 10000
|
||||
|
||||
// We include more small size because, Ascon-Hash256 working with more smaller block size.
|
||||
const test_data_sizes = [
|
||||
4, // below Ascon-Hash256 block size
|
||||
6, // Still below Ascon-Hash256 block size
|
||||
8, // align with Ascon-Hash256 block size
|
||||
16, // Small data
|
||||
64, // Medium data
|
||||
75, // above 64-bytes block
|
||||
256, // Large data
|
||||
512,
|
||||
1025,
|
||||
]
|
||||
|
||||
fn generate_test_data(size int) []u8 {
|
||||
mut data := []u8{len: size}
|
||||
for i in 0 .. size {
|
||||
data[i] = u8(i % 256)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
fn benchmark_ascon_sha256(data []u8, iterations int) time.Duration {
|
||||
start := time.now()
|
||||
for _ in 0 .. iterations {
|
||||
_ := ascon.sum256(data)
|
||||
}
|
||||
return time.since(start)
|
||||
}
|
||||
|
||||
fn benchmark_sha256_sum256(data []u8, iterations int) time.Duration {
|
||||
start := time.now()
|
||||
for _ in 0 .. iterations {
|
||||
_ := sha256.sum256(data)
|
||||
}
|
||||
return time.since(start)
|
||||
}
|
||||
|
||||
// for eXtendable output functions (XOF)
|
||||
fn benchmark_ascon_xof128_32(data []u8, iterations int) time.Duration {
|
||||
start := time.now()
|
||||
for _ in 0 .. iterations {
|
||||
_ := ascon.xof128(data, 32) or { panic(err) }
|
||||
}
|
||||
return time.since(start)
|
||||
}
|
||||
|
||||
fn benchmark_sha3_shake256(data []u8, iterations int) time.Duration {
|
||||
start := time.now()
|
||||
for _ in 0 .. iterations {
|
||||
_ := sha3.shake256(data, 32)
|
||||
}
|
||||
return time.since(start)
|
||||
}
|
||||
|
||||
fn format_duration(d time.Duration) string {
|
||||
if d.microseconds() < 1000 {
|
||||
return '${d.microseconds():6}μs'
|
||||
} else if d.milliseconds() < 1000 {
|
||||
return '${f64(d.milliseconds()):6.2f}ms'
|
||||
} else {
|
||||
return '${f64(d.seconds()):6.2f}s'
|
||||
}
|
||||
}
|
||||
|
||||
const data_title = 'Data Size'
|
||||
const ascon_sum256_title = 'Ascon256'
|
||||
const sha256_title = 'Sha256'
|
||||
const ascon_xof128_title = 'AsconXof128'
|
||||
const sha3_shake256_title = 'Shake256'
|
||||
const ratio_ascon256_w_sha256 = 'Ratio 256'
|
||||
const ratio_asconxof128_w_shake256 = 'Ratio (Xof)'
|
||||
|
||||
fn main() {
|
||||
println('')
|
||||
println('Sum and Xof 256-bits output performance comparison')
|
||||
println('============================================================')
|
||||
println('Iterations per test: ${benchmark_iterations}')
|
||||
|
||||
println('${'-'.repeat(98)}')
|
||||
println('${data_title:12} | ${ascon_sum256_title:10} | ${sha256_title:10} | ${ratio_ascon256_w_sha256:12} || ${ascon_xof128_title:10} | ${sha3_shake256_title:10} | ${ratio_asconxof128_w_shake256:12} |')
|
||||
println('${'-'.repeat(98)}')
|
||||
|
||||
mut total_ascon256 := time.Duration(0)
|
||||
mut total_sha256 := time.Duration(0)
|
||||
mut total_shake256 := time.Duration(0)
|
||||
mut total_asconxof128 := time.Duration(0)
|
||||
|
||||
for size in test_data_sizes {
|
||||
test_data := generate_test_data(size)
|
||||
|
||||
// Warm up
|
||||
_ := ascon.sum256(test_data)
|
||||
_ := sha256.sum256(test_data)
|
||||
|
||||
_ := ascon.xof128(test_data, 32)!
|
||||
_ := sha3.shake256(test_data, 32)
|
||||
|
||||
// Benchmark Ascon-HASH256
|
||||
ascon256_time := benchmark_ascon_sha256(test_data, benchmark_iterations)
|
||||
|
||||
// Benchmark Sha256 implementation
|
||||
sha256_time := benchmark_sha256_sum256(test_data, benchmark_iterations)
|
||||
|
||||
// Benchmark Sha3 shake256 implementation
|
||||
shake256_time := benchmark_sha3_shake256(test_data, benchmark_iterations)
|
||||
|
||||
// Benchmark AsconXof128 256-bits output
|
||||
asconxof128_time := benchmark_ascon_xof128_32(test_data, benchmark_iterations)
|
||||
|
||||
// Calculate ratio ascon256 / sha256
|
||||
ratio_ascon256_sha256 := f64(ascon256_time.nanoseconds()) / f64(sha256_time.nanoseconds())
|
||||
|
||||
// Calculate ratio asconxof128 / shake256
|
||||
ratio_asconxof128_shake256 := f64(asconxof128_time.nanoseconds()) / f64(shake256_time.nanoseconds())
|
||||
|
||||
ascon256_str := format_duration(ascon256_time)
|
||||
sha256_str := format_duration(sha256_time)
|
||||
asconxof128_str := format_duration(asconxof128_time)
|
||||
shake256_str := format_duration(shake256_time)
|
||||
|
||||
ratio_ascon256_sha256_str := '${ratio_ascon256_sha256:6.2f}x'
|
||||
ratio_asconxof128_shake256_str := '${ratio_asconxof128_shake256:6.2f}x'
|
||||
|
||||
println('${size:10} B | ${ascon256_str:10} | ${sha256_str:10} | ${ratio_ascon256_sha256_str:12} || ${asconxof128_str:11} | ${shake256_str:10} | ${ratio_asconxof128_shake256_str:12} |')
|
||||
|
||||
total_ascon256 += ascon256_time
|
||||
total_sha256 += sha256_time
|
||||
|
||||
total_asconxof128 += asconxof128_time
|
||||
total_shake256 += shake256_time
|
||||
}
|
||||
|
||||
println('${'-'.repeat(98)}')
|
||||
|
||||
// Overall performance comparison
|
||||
overall_ascon256_w_sha256_ratio := f64(total_ascon256.nanoseconds()) / f64(total_sha256.nanoseconds())
|
||||
overall_asconxof128_w_shake256_ratio := f64(total_asconxof128.nanoseconds()) / f64(total_shake256.nanoseconds())
|
||||
total_title := 'Total'
|
||||
println('${total_title:12} | ${format_duration(total_ascon256):10} | ${format_duration(total_sha256):10} | ${overall_ascon256_w_sha256_ratio:11.2f}x || ${format_duration(total_asconxof128):11} | ${format_duration(total_shake256):10} | ${overall_asconxof128_w_shake256_ratio:12.2f}x|')
|
||||
println('${'-'.repeat(98)}')
|
||||
|
||||
println('')
|
||||
println('Per-operation averages:')
|
||||
avg_ascon256 := total_ascon256.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
|
||||
avg_sha256 := total_sha256.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
|
||||
avg_shake256 := total_shake256.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
|
||||
avg_asconxof128 := total_asconxof128.nanoseconds() / (benchmark_iterations * test_data_sizes.len)
|
||||
|
||||
println(' Ascon256:\t ${avg_ascon256:8} ns per hash')
|
||||
println(' Sha256:\t ${avg_sha256:8} ns per hash')
|
||||
println(' AsconXof128:\t ${avg_asconxof128:8} ns per hash')
|
||||
println(' Shake256:\t ${avg_shake256:8} ns per hash')
|
||||
println('')
|
||||
}
|
36
vlib/x/crypto/ascon/bench/sum.v
Normal file
36
vlib/x/crypto/ascon/bench/sum.v
Normal file
|
@ -0,0 +1,36 @@
|
|||
import time
|
||||
import x.crypto.ascon
|
||||
|
||||
// Before:
|
||||
// Benchmarking ascon.sum256 ...
|
||||
// Average ascon.sum256 time: 8 µs
|
||||
// Benchmarking ascon.sum256 ...
|
||||
// Average ascon.sum256 time: 6 µs
|
||||
|
||||
// For xof128 (32 bytes)
|
||||
// Benchmarking ascon.xof128 ...
|
||||
// Average ascon.xof128 time: 7 µs
|
||||
// Benchmarking ascon.xof128 ...
|
||||
// Average ascon.xof128 time: 6 µs
|
||||
|
||||
// For cxof128 32 bytes
|
||||
// Benchmarking ascon.cxof128 ...
|
||||
// Average ascon.cxof128 time: 9 µs
|
||||
// Benchmarking ascon.sum256 ...
|
||||
// Average ascon.cxof128 time: 7 µs
|
||||
//
|
||||
fn main() {
|
||||
iterations := 1000
|
||||
msg := [u8(0xff)].repeat(100)
|
||||
|
||||
println('Benchmarking ascon.sum256 ...')
|
||||
mut total_sum_time := i64(0)
|
||||
for _ in 0 .. iterations {
|
||||
sw := time.new_stopwatch()
|
||||
_ := ascon.sum256(msg)
|
||||
elapsed := sw.elapsed().microseconds()
|
||||
total_sum_time += elapsed
|
||||
}
|
||||
avg_sum_time := total_sum_time / iterations
|
||||
println('Average ascon.sum256 time: ${avg_sum_time} µs')
|
||||
}
|
|
@ -133,3 +133,39 @@ fn (mut d Digest) squeeze(mut dst []u8) int {
|
|||
|
||||
return pos
|
||||
}
|
||||
|
||||
@[direct_array_access; inline]
|
||||
fn ascon_generic_hash(mut s State, msg_ []u8, size int) []u8 {
|
||||
// Assumed state was correctly initialized
|
||||
// Absorbing the message
|
||||
mut msg := msg_.clone()
|
||||
for msg.len >= block_size {
|
||||
s.e0 ^= binary.little_endian_u64(msg[0..block_size])
|
||||
unsafe {
|
||||
msg = msg[block_size..]
|
||||
}
|
||||
ascon_pnr(mut s, ascon_prnd_12)
|
||||
}
|
||||
// Absorb the last partial message block
|
||||
s.e0 ^= load_bytes(msg, msg.len)
|
||||
s.e0 ^= pad(msg.len)
|
||||
|
||||
// Squeezing phase
|
||||
//
|
||||
// The squeezing phase begins after msg is absorbed with an
|
||||
// permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state:
|
||||
ascon_pnr(mut s, ascon_prnd_12)
|
||||
mut out := []u8{len: size}
|
||||
mut pos := 0
|
||||
mut clen := out.len
|
||||
for clen >= block_size {
|
||||
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
|
||||
ascon_pnr(mut s, ascon_prnd_12)
|
||||
pos += block_size
|
||||
clen -= block_size
|
||||
}
|
||||
// final output, the resulting 256-bit digest is the concatenation of hash blocks
|
||||
store_bytes(mut out[pos..], s.e0, clen)
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -42,13 +42,13 @@ const hash256_initial_state = State{
|
|||
}
|
||||
|
||||
// sum256 creates an Ascon-Hash256 checksum for bytes on msg and produces a 256-bit hash.
|
||||
pub fn sum256(msg []u8) []u8 {
|
||||
mut h := new_hash256()
|
||||
_ := h.write(msg) or { panic(err) }
|
||||
h.Digest.finish()
|
||||
mut dst := []u8{len: hash256_size}
|
||||
_ := h.Digest.squeeze(mut dst)
|
||||
return dst
|
||||
pub fn sum256(msg_ []u8) []u8 {
|
||||
// This is single-shot function, so, no need to use Hash256 opaque that process
|
||||
// message in streaming way. To reduce this overhead, use raw processing instead.
|
||||
//
|
||||
// Initialize state
|
||||
mut s := hash256_initial_state
|
||||
return ascon_generic_hash(mut s, msg_, hash256_size)
|
||||
}
|
||||
|
||||
// Hash256 is an opaque provides an implementation of Ascon-Hash256 from NIST.SP.800-232 standard.
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// Utility helpers used across the module
|
||||
module ascon
|
||||
|
||||
import math.bits
|
||||
import encoding.binary
|
||||
|
||||
// clear_bytes clears the bytes of x in n byte
|
||||
|
|
|
@ -30,13 +30,11 @@ const xof128_initial_state = State{
|
|||
|
||||
// xof128 creates an Ascon-XOF128 checksum of msg with specified desired size of output.
|
||||
pub fn xof128(msg []u8, size int) ![]u8 {
|
||||
mut x := new_xof128(size)
|
||||
_ := x.write(msg)!
|
||||
x.Digest.finish()
|
||||
mut out := []u8{len: size}
|
||||
_ := x.Digest.squeeze(mut out)
|
||||
x.reset()
|
||||
return out
|
||||
if size > max_hash_size {
|
||||
return error('xof128: invalid size')
|
||||
}
|
||||
mut s := xof128_initial_state
|
||||
return ascon_generic_hash(mut s, msg, size)
|
||||
}
|
||||
|
||||
// xof128_64 creates a 64-bytes of Ascon-XOF128 checksum of msg.
|
||||
|
@ -170,13 +168,10 @@ const cxof128_initial_state = State{
|
|||
|
||||
// cxof128 creates an Ascon-CXOF128 checksum of msg with supplied size and custom string cs.
|
||||
pub fn cxof128(msg []u8, size int, cs []u8) ![]u8 {
|
||||
mut cx := new_cxof128(size, cs)!
|
||||
_ := cx.write(msg)!
|
||||
cx.Digest.finish()
|
||||
mut out := []u8{len: size}
|
||||
_ := cx.Digest.squeeze(mut out)
|
||||
cx.reset()
|
||||
return out
|
||||
// Initialize CXof128 state with precomputed-value and absorb the customization string
|
||||
mut s := cxof128_initial_state
|
||||
cxof128_absorb_custom_string(mut s, cs)
|
||||
return ascon_generic_hash(mut s, msg, size)
|
||||
}
|
||||
|
||||
// cxof128_64 creates a 64-bytes of Ascon-CXOF128 checksum of msg with supplied custom string in cs.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue