Compare commits

...

6 commits

Author SHA1 Message Date
blackshirt
f073169177
x.crypto: add a new ascon cryptographic module, based on https://doi.org/10.6028/NIST.SP.800-232 (Lightweight Cryptography Standards for Constrained Devices) (#25260)
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
2025-09-10 10:03:35 +03:00
kbkpbot
e650d6b7f0
cgen: fix big IntegerLiteral LL postfix (fix #25269) (#25275) 2025-09-10 09:45:40 +03:00
Swastik Baranwal
447994543b
cgen: allow generic alias enum comptime (fix #25249) (#25251) 2025-09-10 09:37:48 +03:00
xieke
015a85daf4
builtin: fix thread naming issue on Windows by ignoring RaiseException (0x406D1388) (#25270) 2025-09-10 09:34:45 +03:00
Delyan Angelov
01f108e349
builtin: fix v -W -Wimpure-v examples/hello_world.v (move ctovstring_impl to builtin.c.v) 2025-09-10 09:11:17 +03:00
Felipe Pena
49e77546c9
markused: fix CI (for failing compilation of vlib/v/tests/skip_unused/array_init_from_sumtype.vv) (#25274) 2025-09-10 09:00:34 +03:00
25 changed files with 2258 additions and 34 deletions

View file

@ -90,6 +90,11 @@ fn get_all_commands() []Command {
okmsg: 'V can compile hello world.' okmsg: 'V can compile hello world.'
rmfile: 'examples/hello_world' rmfile: 'examples/hello_world'
} }
res << Command{
line: '${vexe} -W -Wimpure-v run examples/hello_world.v'
okmsg: 'V can compile hello world with the stricter `-W -Wimpure-v` mode .'
rmfile: 'examples/hello_world'
}
$if linux { $if linux {
if l2w_crosscc != '' { if l2w_crosscc != '' {
res << Command{ res << Command{

View file

@ -944,3 +944,16 @@ pub fn arguments() []string {
} }
return res return res
} }
// ctovstring_impl is a temporary API, to enable clean CI runs for https://github.com/vlang/v/pull/25264 .
// It will be deleted after the migration to the new `builtin` naming scheme is finished.
@[export: 'builtin__ctovstring']
pub fn ctovstring_impl(s &u8) string {
unsafe {
len := C.strlen(voidptr(s))
return string{
str: memdup(voidptr(s), isize(len))
len: len
}
}
}

View file

@ -125,7 +125,7 @@ fn unhandled_exception_handler(e &ExceptionPointers) int {
match e.exception_record.code { match e.exception_record.code {
// These are 'used' by the backtrace printer // These are 'used' by the backtrace printer
// so we dont want to catch them... // so we dont want to catch them...
0x4001000A, 0x40010006, 0xE06D7363 { 0x4001000A, 0x40010006, 0x406D1388, 0xE06D7363 {
return 0 return 0
} }
else { else {

View file

@ -268,3 +268,12 @@ fn test_int_max() {
assert int_max(-5, 5) == 5 assert int_max(-5, 5) == 5
assert int_max(5, -5) == 5 assert int_max(5, -5) == 5
} }
fn test_big_int() {
x := i64(2147483647)
if x > -2147483649 && x < 2147483648 {
assert true
} else {
assert false
}
}

View file

@ -3052,16 +3052,3 @@ pub fn (mut ri RunesIterator) next() ?rune {
} }
return rune(impl_utf8_to_utf32(start, len)) return rune(impl_utf8_to_utf32(start, len))
} }
// ctovstring_impl is a temporary API, to enable clean CI runs for https://github.com/vlang/v/pull/25264 .
// It will be deleted after the migration to the new `builtin` naming scheme is finished.
@[export: 'builtin__ctovstring']
pub fn ctovstring_impl(s &u8) string {
unsafe {
len := C.strlen(voidptr(s))
return string{
str: memdup(voidptr(s), isize(len))
len: len
}
}
}

View file

@ -3184,13 +3184,6 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ
g.write('*') g.write('*')
} }
} }
if expr is ast.IntegerLiteral {
if expected_type in [ast.u64_type, ast.u32_type, ast.u16_type] && expr.val[0] != `-` {
g.expr(expr)
g.write('U')
return
}
}
if (exp_sym.kind == .function && !expected_type.has_option_or_result()) if (exp_sym.kind == .function && !expected_type.has_option_or_result())
|| (g.inside_struct_init && expected_type == ast.voidptr_type || (g.inside_struct_init && expected_type == ast.voidptr_type
&& expected_type != got_type_raw && expr !is ast.StructInit) { && expected_type != got_type_raw && expr !is ast.StructInit) {
@ -3814,6 +3807,13 @@ fn (mut g Gen) expr(node_ ast.Expr) {
g.write2('-0', node.val[3..]) g.write2('-0', node.val[3..])
} else { } else {
g.write(node.val) g.write(node.val)
val_type := determine_integer_literal_type(node)
if val_type in [ast.u32_type, ast.u64_type] {
g.write('U')
}
if val_type in [ast.i64_type, ast.u64_type] {
g.write('LL')
}
} }
} }
ast.IsRefType { ast.IsRefType {
@ -5670,13 +5670,6 @@ fn (mut g Gen) cast_expr(node ast.CastExpr) {
} }
} }
g.expr(node.expr) g.expr(node.expr)
if node.expr is ast.IntegerLiteral {
if node_typ in [ast.u64_type, ast.u32_type, ast.u16_type] {
if !node.expr.val.starts_with('-') {
g.write('U')
}
}
}
g.write('))') g.write('))')
} }
} }
@ -8519,3 +8512,43 @@ fn (mut g Gen) check_noscan(elem_typ ast.Type) string {
} }
return '' return ''
} }
const hi_32_mask = u64(0xFFFFFFFF00000000)
const lo_32_mask = u64(0x00000000FFFFFFFF)
const sign_bit_32 = u32(0x80000000)
const sign_bit_64 = u64(0x8000000000000000)
fn determine_integer_literal_type(node ast.IntegerLiteral) ast.Type {
is_negative := node.val.starts_with('-')
if is_negative {
uval := node.val.i64()
high32 := u32(uval >> 32)
low32 := u32(uval)
// Check if high 32 bits are all ones (0xFFFFFFFF)
// This indicates a 32-bit negative number in two's complement
high32_all_ones := high32 == u32(0xFFFFFFFF)
// Check if the sign bit (bit 31) is set in low 32 bits
// This confirms the number is negative in 32-bit context
low32_sign_bit_set := (low32 & sign_bit_32) != 0
// If both conditions are true, it's a 32-bit signed integer (i32)
// Otherwise, it requires 64-bit representation (i64)
return if high32_all_ones && low32_sign_bit_set { ast.i32_type } else { ast.i64_type }
} else {
uval := node.val.u64()
high32_all_zero := (uval & hi_32_mask) == 0
if high32_all_zero {
low32 := u32(uval)
// Check if sign bit (bit 31) is clear (0)
// This indicates the number fits in 31 bits (signed 32-bit positive)
low32_sign_bit_clear := (low32 & sign_bit_32) == 0
// If sign bit is clear, it's a signed 32-bit integer (i32)
// Otherwise, it's an unsigned 32-bit integer (u32)
return if low32_sign_bit_clear { ast.i32_type } else { ast.u32_type }
} else {
sign_bit_clear := (uval & sign_bit_64) == 0
// If sign bit is clear, it's a signed 64-bit integer (i64)
// Otherwise, it's an unsigned 64-bit integer (u64)
return if sign_bit_clear { ast.i64_type } else { ast.u64_type }
}
}
}

View file

@ -781,7 +781,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) {
if g.pref.translated && node.typ.is_number() { if g.pref.translated && node.typ.is_number() {
g.writeln('_const_main__${val};') g.writeln('_const_main__${val};')
} else { } else {
node_sym := g.table.sym(node.typ) node_sym := g.table.sym(g.unwrap_generic(node.typ))
if node_sym.info is ast.Alias { if node_sym.info is ast.Alias {
g.writeln('${g.styp(node_sym.info.parent_type)}__${val};') g.writeln('${g.styp(node_sym.info.parent_type)}__${val};')
} else { } else {

View file

@ -1,7 +1,7 @@
u64 VWEAK abc = ((u64)(1U)); // global u64 VWEAK abc = ((u64)(1)); // global
u64 xyz = ((u64)(2U)); // global u64 xyz = ((u64)(2)); // global
u64 VWEAK weak_1 = ((u64)(4U)); // global u64 VWEAK weak_1 = ((u64)(4)); // global
u64 VWEAK weak_2 = ((u64)(5U)); // global u64 VWEAK weak_2 = ((u64)(5)); // global
VV_LOC int main__a_weak_function(void); VV_LOC int main__a_weak_function(void);
VV_LOC void main__main(void); VV_LOC void main__main(void);

View file

@ -42,6 +42,7 @@ mut:
// dependencies finding flags // dependencies finding flags
uses_atomic bool // has atomic uses_atomic bool // has atomic
uses_array bool // has array uses_array bool // has array
uses_array_sumtype bool // has array on sumtype init
uses_channel bool // has chan dep uses_channel bool // has chan dep
uses_lock bool // has mutex dep uses_lock bool // has mutex dep
uses_ct_fields bool // $for .fields uses_ct_fields bool // $for .fields
@ -1178,7 +1179,9 @@ pub fn (mut w Walker) mark_by_sym(isym ast.TypeSymbol) {
if typ.has_flag(.option) { if typ.has_flag(.option) {
w.used_option++ w.used_option++
} }
w.mark_by_type(typ) sym := w.table.sym(typ)
w.mark_by_sym(sym)
w.uses_array_sumtype = w.uses_array_sumtype || sym.kind == .array
} }
} }
ast.Map { ast.Map {
@ -1477,6 +1480,9 @@ fn (mut w Walker) mark_resource_dependencies() {
w.fn_by_name(int(ast.array_type.ref()).str() + '.set') w.fn_by_name(int(ast.array_type.ref()).str() + '.set')
w.fn_by_name('clone_static_to_depth') w.fn_by_name('clone_static_to_depth')
} }
if w.uses_array_sumtype {
w.fn_by_name('__new_array')
}
if w.uses_fixed_arr_int { if w.uses_fixed_arr_int {
w.fn_by_name('v_fixed_index') w.fn_by_name('v_fixed_index')
} }

View file

@ -24,6 +24,14 @@ fn CharacterGroup.values() []CharacterGroup {
return res return res
} }
fn do_generic[T](t T) []EnumData {
mut vals := []EnumData{}
$for value in T.values {
vals << value
}
return vals
}
fn test_main() { fn test_main() {
values := CharacterGroup.values() values := CharacterGroup.values()
println('Char group values: ${values}') println('Char group values: ${values}')
@ -53,3 +61,19 @@ fn test_alias_enum() {
assert values[3].value == int(CharacterGroup.special) assert values[3].value == int(CharacterGroup.special)
assert values[3].name == CharacterGroup.special.str() assert values[3].name == CharacterGroup.special.str()
} }
fn test_generic_alias_enum() {
values := do_generic(AnotherCharGroup.chars)
assert values[0].value == int(CharacterGroup.chars)
assert values[0].name == CharacterGroup.chars.str()
assert values[1].value == int(CharacterGroup.alphanumerics)
assert values[1].name == CharacterGroup.alphanumerics.str()
assert values[2].value == int(CharacterGroup.numeric)
assert values[2].name == CharacterGroup.numeric.str()
assert values[3].value == int(CharacterGroup.special)
assert values[3].name == CharacterGroup.special.str()
}

View file

@ -0,0 +1,15 @@
# ascon
`ascon` is a implementation of Ascon-Based Cryptography module implemented in pure V language.
This module was mostly based on NIST Special Publication of 800 NIST SP 800-232 document.
Its describes an Ascon-Based Lightweight Cryptography Standards for Constrained Devices
thats provides Authenticated Encryption, Hash, and Extendable Output Functions.
See the [NIST.SP.800-232 Standard](https://doi.org/10.6028/NIST.SP.800-232) for more detail.
This module does not fully implements all the features availables on the document.
Its currently implements:
- `Ascon-Hash256`, Ascon-based hashing implementation that produces 256-bits output.
- `Ascon-XOF128`, Ascon-based eXtendible Output Function (XOF) where the output size of
the hash of the message can be selected by the user.
- `Ascon-CXOF128`, a customized XOF that allows users to specify a customization
string and choose the output size of the message hash.

View file

@ -0,0 +1,464 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// This file implements an Authenticated Encryption with Associated Data based on Ascon-AEAD128,
// AEAD Scheme defined in NIST.SP.800-232 standard.
module ascon
import encoding.binary
import crypto.internal.subtle
// The constants for Ascon-AEAD128
//
// key_size is 128-bit size of Ascon-AEAD128 key
pub const key_size = 16
// nonce_size is 128-bit size of Ascon-AEAD128 nonce
pub const nonce_size = 16
// tag_size is 128-bit size of Ascon-AEAD128 authentication tag
pub const tag_size = 16
// aead128_iv is a precomputed initialization phase values for Ascon-AEAD128
// See Table 14 of NIST SP 800-232 doc.
const aead128_iv = u64(0x0000_1000_808c_0001)
// aead128_data_limit is a limit amount of data processed during encryption and decryption,
// including the nonce, shall not exceed 2⁵⁴ bytes for a given key.
const aead128_data_limit = u64(1) << 54 - 1
// aead128_block_size is a number (rate) of input bytes processed per invocation of the underlying of state.
// Ascon-AEAD128 working with 128-bit rate
const aead128_block_size = 16
// encrypt encrypts the message under provided key and nonce and supplied additional data in ad.
// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag
// was stored within the end of ciphertext.
// Note: The Ascon-AEAD128 key shall be kept secret,
pub fn encrypt(key []u8, nonce []u8, ad []u8, msg []u8) ![]u8 {
// Preliminary check
if key.len != key_size {
return error('encrypt: invalid key size')
}
if nonce.len != nonce_size {
return error('encrypt: invalid nonce size')
}
// The key shall be updated to a new key once the total amount of input data reaches the limit
data_length := u64(nonce.len) + u64(msg.len) + u64(ad.len)
if data_length > aead128_data_limit {
return error('encrypt: exceed data limit')
}
mut s := State{}
mut out := []u8{len: msg.len + tag_size}
// Ascon-AEAD128 comprises four phases:
// - initialization of the state,
// - associated data processing,
// - plaintext processing,
// - and finalization (includes writing the tag).
k0, k1 := aead128_init(mut s, key, nonce)
aead128_process_ad(mut s, ad)
loc := aead128_process_msg(mut out, mut s, msg)
aead128_finalize(mut s, k0, k1)
aead128_write_tag(mut out, s, loc)
// clean out the intermediate Ascon state
reset_state(mut s)
return out
}
// decrypt decrypts authenticated encrypted messages in ciphertext that encrypted under
// provided key and nonce with additional data in `ad`.
// It would check if authentication tag mas matching and return decrypted message
// if success or error on fails.
@[direct_array_access]
pub fn decrypt(key []u8, nonce []u8, ad []u8, ciphertext []u8) ![]u8 {
// Preliminary check
if key.len != key_size {
return error('decrypt: invalid key size')
}
if nonce.len != nonce_size {
return error('decrypt: invalid nonce size')
}
if ciphertext.len < tag_size {
return error('decrypt: invalid ciphertext size')
}
data_length := u64(nonce.len) + u64(ciphertext.len) + u64(ad.len)
if data_length > aead128_data_limit {
return error('decrypt: exceed data limit')
}
mut s := State{}
// Initialization phase and additional data processing
k0, k1 := aead128_init(mut s, key, nonce)
aead128_process_ad(mut s, ad)
// Decryption phase, start by slicing the ciphertext
cmsg := ciphertext[0..ciphertext.len - tag_size]
stag := ciphertext[ciphertext.len - tag_size..ciphertext.len]
mut msg := []u8{len: ciphertext.len - tag_size}
// Partially decrypt the cmsg and stored into msg buffer
aead128_partial_dec(mut msg, mut s, cmsg)
// Finalizes the state and calc the tag and compares with expected tag.
// It would return error if the tag was unmatching.
aead128_finalize(mut s, k0, k1)
mut ctag := []u8{len: tag_size}
aead128_write_tag(mut ctag, s, 0)
if subtle.constant_time_compare(ctag, stag) != 1 {
// clean up
unsafe {
msg.reset()
ctag.reset()
}
reset_state(mut s)
return error('decrypt: unmatching tag')
}
return msg
}
// Aead128 is an opaque provides an implementation of Ascon-AEAD128 from NIST.SP.800-232 standard.
// Its implements `x.crypto.chacha20poly1305.AEAD` interfaces.
@[noinit]
pub struct Aead128 {
State
mut:
// 32-bytes of underlying key
key [2]u64
}
// new_aead128 creates a new Aead128 instance and initialized with supplied key.
@[direct_array_access]
pub fn new_aead128(key []u8) !&Aead128 {
if key.len != key_size {
return error('invalid cipher key size')
}
k0 := binary.little_endian_u64(key[0..8])
k1 := binary.little_endian_u64(key[8..16])
mut c := &Aead128{}
// Partially initializes state
c.State.e0 = aead128_iv
c.State.e1 = k0
c.State.e2 = k1
// stores the key
c.key[0] = k0
c.key[1] = k1
return c
}
// nonce_size returns the nonce size of Ascon-AEAD128 Aead128.
pub fn (c &Aead128) nonce_size() int {
return nonce_size
}
// overhead returns the maximum difference between the lengths of a plaintext and its ciphertext.
pub fn (c &Aead128) overhead() int {
return tag_size
}
// encrypt encrypts the message under provided key and nonce and supplied additional data in ad.
// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag
// was stored within the end of ciphertext.
@[direct_array_access]
pub fn (mut c Aead128) encrypt(msg []u8, nonce []u8, ad []u8) ![]u8 {
// Check for the nonce
if nonce.len != nonce_size {
return error('encrypt: invalid nonce size')
}
data_length := u64(msg.len) + u64(ad.len) + 32
if data_length > aead128_data_limit {
return error('encrypt: exceed data limit')
}
// Initialization phase
n0 := binary.little_endian_u64(nonce[0..8])
n1 := binary.little_endian_u64(nonce[8..16])
// setup state
c.State.e0 = aead128_iv
c.State.e1 = c.key[0]
c.State.e2 = c.key[1]
c.State.e3 = n0
c.State.e4 = n1
// Update state by permutation
ascon_pnr(mut c.State, 12)
// XOR-ing with the cipher's key
c.State.e3 ^= c.key[0]
c.State.e4 ^= c.key[1]
// Associated data processing
aead128_process_ad(mut c.State, ad)
// Message processing
mut dst := []u8{len: msg.len + tag_size}
n := aead128_process_msg(mut dst, mut c.State, msg)
// Finalization and writes out the tag into dst
aead128_finalize(mut c.State, c.key[0], c.key[1])
aead128_write_tag(mut dst, c.State, n)
return dst
}
// decrypt decrypts the ciphertext and validates authentication tag with
// provided nonce and additional data ad. It returns error on fails or tag unmatching.
@[direct_array_access]
pub fn (mut c Aead128) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 {
// Check for nonce
if nonce.len != nonce_size {
return error('bad nonce size')
}
// Check for ciphertext length, its ahould have length >= tag_size
if ciphertext.len < tag_size {
return error('bad ciphertext size')
}
// check for data limit overflow
data_length := u64(ciphertext.len) + u64(ad.len) + 32
if data_length > aead128_data_limit {
return error('decrypt: exceed data limit')
}
// load nonce
n0 := binary.little_endian_u64(nonce[0..8])
n1 := binary.little_endian_u64(nonce[8..16])
// Reinitialize internal state
c.State.e0 = aead128_iv
c.State.e1 = c.key[0]
c.State.e2 = c.key[1]
c.State.e3 = n0
c.State.e4 = n1
// scrambled with permutation routine
ascon_pnr(mut c.State, 12)
// xor-ing with the cipher's key
c.State.e3 ^= c.key[0]
c.State.e4 ^= c.key[1]
// Associated data processing
//
aead128_process_ad(mut c.State, ad)
// As we know, ciphertext length was sum of encrypted mesage length plus tag_size
// Lets slicing it
cxt_len := ciphertext.len
cmsg := ciphertext[0..cxt_len - tag_size]
ctag := ciphertext[cxt_len - tag_size..cxt_len]
mut out := []u8{len: cxt_len - tag_size}
aead128_partial_dec(mut out, mut c.State, cmsg)
aead128_finalize(mut c.State, c.key[0], c.key[1])
// tag verification
mut tag := []u8{len: tag_size}
aead128_write_tag(mut tag, c.State, 0)
if subtle.constant_time_compare(ctag, tag) != 1 {
// Cleans up previously produces bytes (state)
reset_state(mut c.State)
unsafe {
tag.free()
out.free()
}
return error('Aead128.decrypt: unmatching tag')
}
return out
}
// Helpers for Ascon-AEAD128
//
// aead128_init initializes Ascon-AEAD128 state by provided key and nonce.
// Its return two's u64 values from deserialized key bytes in little-endian form.
@[direct_array_access; inline]
fn aead128_init(mut s State, key []u8, nonce []u8) (u64, u64) {
// load key and nonce into state in little-endian form,
// The endianness has been switched from big endian to little endian
k0 := binary.little_endian_u64(key[0..8])
k1 := binary.little_endian_u64(key[8..16])
n0 := binary.little_endian_u64(nonce[0..8])
n1 := binary.little_endian_u64(nonce[8..16])
// Given a 128-bit 𝐾 and a 128-bit 𝑁, the 320-bit internal
// state S is initialized as the concatenation of 𝐼𝑉, 𝐾, and 𝑁:
// S ← 𝐼𝑉 || 𝐾 || 𝑁,
s.e0 = aead128_iv
s.e1 = k0
s.e2 = k1
s.e3 = n0
s.e4 = n1
// updates State using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12], S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](S)
ascon_pnr(mut s, 12)
// Then XORing the secret key 𝐾 into the last 128 bits of internal state:
// S ← S ⊕ (0¹⁹² ∥ 𝐾).
s.e3 ^= k0
s.e4 ^= k1
return k0, k1
}
// aead128_process_ad absorbs associated data into Ascon-AEAD128 state.
@[direct_array_access; inline]
fn aead128_process_ad(mut s State, ad []u8) {
mut ad_length := ad.len
mut ad_idx := 0
if ad_length > 0 {
for ad_length >= aead128_block_size {
// Each associated data block 𝐴𝑖 (0 ≤ 𝑖 ≤ 𝑚) is absorbed into the first 128 bits of
// state as S[0127] ← S[0127] ⊕ 𝐴𝑖,
block := unsafe { ad[ad_idx..ad_idx + aead128_block_size] }
s.e0 ^= binary.little_endian_u64(block[0..8])
s.e1 ^= binary.little_endian_u64(block[8..16])
// Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
ascon_pnr(mut s, 8)
// Updates index
ad_length -= aead128_block_size
ad_idx += aead128_block_size
}
// process partial block if it exists
if ad_length >= 8 {
first_block := unsafe { ad[ad_idx..ad_idx + 8] }
s.e0 ^= binary.little_endian_u64(first_block)
// Is there more bytes to process on?
last_block := unsafe { ad[ad_idx + 8..] }
s.e1 ^= pad(last_block.len)
if last_block.len > 0 {
s.e1 ^= u64_from_partial_bytes(last_block)
}
// update index
ad_length -= first_block.len + last_block.len
ad_idx += first_block.len + last_block.len
} else {
last_block := unsafe { ad[ad_idx..] }
s.e0 ^= pad(last_block.len)
if last_block.len > 0 {
s.e0 ^= u64_from_partial_bytes(last_block)
}
}
// Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
ascon_pnr(mut s, 8)
}
// The final step of processing associated data is to update the state
// with a constant that provides domain separation.
s.e4 ^= u64(0x8000_0000_0000_0000)
}
// aead128_process_msg process (encrypt) the messages msg and asborb it into Ascon-AEAD128 state
// Its written the result into out buffer and return the number of bytes has been written.
@[direct_array_access; inline]
fn aead128_process_msg(mut out []u8, mut s State, msg []u8) int {
mut pos := 0
mut mlen := msg.len
mut midx := 0
for mlen >= aead128_block_size {
block := unsafe { msg[midx..midx + aead128_block_size] }
s.e0 ^= binary.little_endian_u64(block[0..8])
s.e1 ^= binary.little_endian_u64(block[8..16])
// stores
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
binary.little_endian_put_u64(mut out[pos + 8..], s.e1)
// apply permutation
ascon_pnr(mut s, 8)
// updates index
mlen -= aead128_block_size
pos += aead128_block_size
midx += aead128_block_size
}
// process partial block if it exists
if mlen >= 8 {
mut first_block := unsafe { msg[midx..] }
s.e0 ^= load_bytes(first_block[0..8], 8)
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
// Is there are reminder bytes to process on?
last_block := unsafe { msg[midx + 8..] }
if last_block.len > 0 {
// We use `load_bytes` to handle length < 8
s.e1 ^= load_bytes(last_block, last_block.len)
store_bytes(mut out[pos + 8..], s.e1, last_block.len)
s.e1 ^= pad(last_block.len)
}
} else {
last_block := unsafe { msg[midx..] }
s.e0 ^= load_bytes(last_block, last_block.len)
store_bytes(mut out[pos..], s.e0, last_block.len)
s.e0 ^= pad(last_block.len)
}
// how much we have written
pos += mlen
return pos
}
// aead128_partial_dec partially decrypts the encrypted part of the message in the cmsg,
// and stored into out buffer.
// Note: The output buffer should have the same length with cmsg, ie, out.len == cmsg.len
@[direct_array_access]
fn aead128_partial_dec(mut out []u8, mut s State, cmsg []u8) {
mut cmsg_len := cmsg.len
mut pos := 0
// assert out.len == cmsg.len
for cmsg_len >= aead128_block_size {
block := unsafe { cmsg[pos..pos + aead128_block_size] }
c0 := binary.little_endian_u64(block[0..8])
c1 := binary.little_endian_u64(block[8..16])
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0 ^ c0)
binary.little_endian_put_u64(mut out[pos + 8..pos + 16], s.e1 ^ c1)
s.e0 = c0
s.e1 = c1
ascon_pnr(mut s, 8)
// updates index
pos += aead128_block_size
cmsg_len -= aead128_block_size
}
// partial block
if cmsg_len >= 8 {
mut first_block := unsafe { cmsg[pos..pos + 8] }
c0 := binary.little_endian_u64(first_block)
binary.little_endian_put_u64(mut out[pos..pos + 8], c0 ^ s.e0)
last_block := unsafe { cmsg[pos + 8..] }
c1 := load_bytes(last_block, last_block.len)
store_bytes(mut out[pos + 8..], c1 ^ s.e1, last_block.len)
s.e0 = c0
s.e1 = clear_bytes(s.e1, last_block.len)
s.e1 |= c1
s.e0 ^= pad(last_block.len)
} else {
last_block := unsafe { cmsg[pos..] }
c0 := load_bytes(last_block, last_block.len)
store_bytes(mut out[pos..], s.e0 ^ c0, last_block.len)
s.e0 = clear_bytes(s.e0, last_block.len)
s.e0 |= c0
s.e0 ^= pad(last_block.len)
}
}
// aead128_finalize does finalization step and generates tag value.
fn aead128_finalize(mut s State, k0 u64, k1 u64) {
// Load the key into state, S ← S ⊕ (0¹²⁸ ∥ 𝐾 ∥ 0⁶⁴),
s.e2 ^= k0
s.e3 ^= k1
// then updated using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12]
ascon_pnr(mut s, 12)
// Finally, the tag 𝑇 is generated by XORing the key with the last 128 bits of the state:
// 𝑇𝑆[192319] ⊕ 𝐾.
s.e3 ^= k0
s.e4 ^= k1
}
// aead128_write_tag writes tag from state into out at loc offset.
@[direct_array_access; inline]
fn aead128_write_tag(mut out []u8, s State, loc int) {
binary.little_endian_put_u64(mut out[loc..loc + 8], s.e3)
binary.little_endian_put_u64(mut out[loc + 8..loc + 16], s.e4)
}

View file

@ -0,0 +1,247 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
import encoding.hex
import x.crypto.ascon
// This test materials was taken and adapted into v from references implementation of Ascon-aead128
// especially for the known answer test data, but, its not all fully-taken, just randomly choosen item.
// See at https://github.com/ascon/ascon-c/blob/main/crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt
struct KatTest {
cnt int
key string
nonce string
pt string
ad string
ct string
}
// testing for Ascon-AEAD128 encryption and decryption.
fn test_ascon_aead128_enc_dec() ! {
for item in aead128_kat_tests_data {
key := hex.decode(item.key)!
nonce := hex.decode(item.nonce)!
pt := hex.decode(item.pt)!
ad := hex.decode(item.ad)!
ct := hex.decode(item.ct)!
out := ascon.encrypt(key, nonce, ad, pt)!
assert out == ct
msg := ascon.decrypt(key, nonce, ad, ct)!
assert msg == pt
// Work with object-based Cipher
mut c := ascon.new_aead128(key)!
// Lets encrypt the message
exp_ct := c.encrypt(msg, nonce, ad)!
assert exp_ct == ct
// Lets decrypt it back
exp_msg := c.decrypt(exp_ct, nonce, ad)!
assert exp_msg == msg
}
}
const aead128_kat_tests_data = [
KatTest{
cnt: 1
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: ''
ct: '4F9C278211BEC9316BF68F46EE8B2EC6'
},
KatTest{
cnt: 2
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '30'
ct: 'CCCB674FE18A09A285D6AB11B35675C0'
},
KatTest{
cnt: 3
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '3031'
ct: 'F65B191550C4DF9CFDD4460EBBCCA782'
},
KatTest{
cnt: 4
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132'
ct: 'D127CF7D2CD4DA8930616C70B3619F42'
},
KatTest{
cnt: 5
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '30313233'
ct: '000BA92E52B5ED6B97C9D913CC4C82DF'
},
KatTest{
cnt: 6
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '3031323334'
ct: 'F7CC167F8FED3AEEA99B385B8622157E'
},
KatTest{
cnt: 7
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435'
ct: '51CCBC46D56E93B89B1A3BFDAD0AA4D5'
},
KatTest{
cnt: 8
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '30313233343536'
ct: 'B38ABBD573E071C6265EEAC4A68F65AB'
},
KatTest{
cnt: 9
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '3031323334353637'
ct: '865C594093A9EDEE2C1D6384CCB4939E'
},
KatTest{
cnt: 10
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738'
ct: '24F13284A0F90F906B18C7E4061C0896'
},
KatTest{
cnt: 27
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F40414243444546474849'
ct: '4ED362C4407B1D3BE17A51465659DECF'
},
KatTest{
cnt: 28
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A'
ct: 'A35C52EC6E7C78C051B23D03F691916F'
},
KatTest{
cnt: 29
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B'
ct: 'F1C946363A21CCFFE291A289202FC64C'
},
KatTest{
cnt: 30
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C'
ct: 'F1D453E933904578EEC3EA8E85550CE5'
},
KatTest{
cnt: 31
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D'
ct: '82E22C860881C0485EC5F5E8CEA42CEA'
},
KatTest{
cnt: 32
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E'
ct: 'C6306F1F154C78833984173360AAE874'
},
KatTest{
cnt: 33
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: ''
ad: '303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F'
ct: 'EFC3E78B02AD9A80A6F0548C5B0BB5BA'
},
KatTest{
cnt: 34
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: '20'
ad: ''
ct: 'E8DD576ABA1CD3E6FC704DE02AEDB79588'
},
KatTest{
cnt: 35
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: '20'
ad: '30'
ct: '962B8016836C75A7D86866588CA245D886'
},
KatTest{
cnt: 49
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: '20'
ad: '303132333435363738393A3B3C3D3E'
ct: '2089CB1DE2AE7D3E45BA7E9CC293548546'
},
KatTest{
cnt: 599
key: '000102030405060708090a0b0c0d0e0f'
nonce: '101112131415161718191a1b1c1d1e1f'
pt: '202122232425262728292a2b2c2d2e2f3031'
ad: '30313233'
ct: 'cf5337fcb70ec45d179e0c3f51bb25ac967a2e7062ee9bd80da6c72e3a9b43aed9e0'
},
KatTest{
cnt: 600
key: '000102030405060708090a0b0c0d0e0f'
nonce: '101112131415161718191a1b1c1d1e1f'
pt: '202122232425262728292a2b2c2d2e2f3031'
ad: '3031323334'
ct: '3076658cba8bf3bb6dccaa2f1255ee2e7db6f6493c7698f65f6860a7433a0f561e6c'
},
KatTest{
cnt: 601
key: '000102030405060708090a0b0c0d0e0f'
nonce: '101112131415161718191a1b1c1d1e1f'
pt: '202122232425262728292a2b2c2d2e2f3031'
ad: '303132333435'
ct: '9310c6dd8e9cbc3e406c0ebfbea312435f2c6975faf3b6b2b17ef1ea2503c3d31ef5'
},
KatTest{
cnt: 602
key: '000102030405060708090a0b0c0d0e0f'
nonce: '101112131415161718191a1b1c1d1e1f'
pt: '202122232425262728292a2b2c2d2e2f3031'
ad: '30313233343536'
ct: '6e024bd403f386eb9d1c56f459cfdcde1b2fdf8fd8be2faf0576c81e8d21c0dd8f8a'
},
KatTest{
cnt: 603
key: '000102030405060708090A0B0C0D0E0F'
nonce: '101112131415161718191A1B1C1D1E1F'
pt: '202122232425262728292A2B2C2D2E2F3031'
ad: '3031323334353637'
ct: 'fabe2cb1e7eba6329a30080f26e7dc72503dfc57f4de06a334b7ebadca03b44b73e9'
},
]

127
vlib/x/crypto/ascon/ascon.v Normal file
View file

@ -0,0 +1,127 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
module ascon
// max_nr_perm is the maximum number of permutations round supported on this module.
// The previous Ascon submission defined three Ascon permutations with 6, 8, and 12 rounds.
// The NIST SP 800-232 standard specifies additional Ascon permutations by providing round
// constants for up to 16 rounds to accommodate potential functionality extensions in the future.
const max_nr_perm = 16
// The constants to derive round constants of the Ascon permutations
// See Table 5. of NIST SP 800-232 docs
//
// 0 0x000000000000003c 8 0x00000000000000b4
// 1 0x000000000000002d 9 0x00000000000000a5
// 2 0x000000000000001e 10 0x0000000000000096
// 3 0x000000000000000f 11 0x0000000000000087
// 4 0x00000000000000f0 12 0x0000000000000078
// 5 0x00000000000000e1 13 0x0000000000000069
// 6 0x00000000000000d2 14 0x000000000000005a
// 7 0x00000000000000c3 15 0x000000000000004b
//
// We use u8 instead, since the first 56 bits of the constants are zero
const rnc = [u8(0x3c), 0x2d, 0x1e, 0x0f, 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78,
0x69, 0x5a, 0x4b]
// ascon_pnr is ascon permutation routine with specified numbers of round nr, where 1 ≤ nr ≤ 16
@[direct_array_access]
fn ascon_pnr(mut s State, nr int) {
// We dont allow nr == 0
if nr < 1 || nr > 16 {
panic('Invalid round number')
}
for i := max_nr_perm - nr; i < max_nr_perm; i++ {
ascon_perm(mut s, rnc[i])
}
}
// ascon_perm was the main permutations routine in Ascon-family crypto. Its consist of
// iterations of the round function that is defined as the composition of three steps, ie:
// 1. the constant-addition layer (see Sec. 3.2),
// 2. the substitution layer (see Sec.3.3), and,
// 3. the linear diffusion layer
fn ascon_perm(mut s State, c u8) {
// 3.2 Constant-Addition Layer step
//
// The constant-addition layer adds a 64-bit round constant 𝑐𝑖
// to 𝑆₂ in round 𝑖, for 𝑖 ≥ 0, ie, this is equivalent to applying
// the constant to only the least significant eight bits of 𝑆₂
s.e2 ^= c
// 3.3. Substitution Layer
// The substitution layer updates the state S with 64 parallel applications of the 5-bit
// substitution box SBOX
s.e0 ^= s.e4
s.e4 ^= s.e3
s.e2 ^= s.e1
t0 := s.e4 ^ (~s.e0 & s.e1)
t1 := s.e0 ^ (~s.e1 & s.e2)
t2 := s.e1 ^ (~s.e2 & s.e3)
t3 := s.e2 ^ (~s.e3 & s.e4)
t4 := s.e3 ^ (~s.e4 & s.e0)
s.e0 = t1
s.e1 = t2
s.e2 = t3
s.e3 = t4
s.e4 = t0
s.e1 ^= s.e0
s.e0 ^= s.e4
s.e3 ^= s.e2
s.e2 = ~(s.e2)
// 3.4. Linear Diffusion Layer
//
// The linear diffusion layer provides diffusion within each 64-bit word S,
// defined as :
// Σ0(𝑆0) = 𝑆0 ⊕ (𝑆0 ⋙ 19) ⊕ (𝑆0 ⋙ 28)
// Σ1(𝑆1) = 𝑆1 ⊕ (𝑆1 ⋙ 61) ⊕ (𝑆1 ⋙ 39)
// Σ2(𝑆2) = 𝑆2 ⊕ (𝑆2 ⋙ 1) ⊕ (𝑆2 ⋙ 6)
// Σ3(𝑆3) = 𝑆3 ⊕ (𝑆3 ⋙ 10) ⊕ (𝑆3 ⋙ 17)
// Σ4(𝑆4) = 𝑆4 ⊕ (𝑆4 ⋙ 7) ⊕ (𝑆4 ⋙ 41)
s.e0 ^= ascon_rotate_right(s.e0, 19) ^ ascon_rotate_right(s.e0, 28)
s.e1 ^= ascon_rotate_right(s.e1, 61) ^ ascon_rotate_right(s.e1, 39)
s.e2 ^= ascon_rotate_right(s.e2, 1) ^ ascon_rotate_right(s.e2, 6)
s.e3 ^= ascon_rotate_right(s.e3, 10) ^ ascon_rotate_right(s.e3, 17)
s.e4 ^= ascon_rotate_right(s.e4, 7) ^ ascon_rotate_right(s.e4, 41)
}
// State is structure represents Ascon state. Its operates on the 320-bit opaque,
// which is represented as five of 64-bit words.
@[noinit]
struct State {
mut:
e0 u64
e1 u64
e2 u64
e3 u64
e4 u64
}
// clone returns a clone of current state.
@[inline]
fn clone_state(s State) State {
return State{
e0: s.e0
e1: s.e1
e2: s.e2
e3: s.e3
e4: s.e4
}
}
// reset this state with default value
@[inline]
fn reset_state(mut s State) {
s.e0 = 0
s.e1 = 0
s.e2 = 0
s.e3 = 0
s.e4 = 0
}

View file

@ -0,0 +1,71 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
module ascon
// This test mostly taken from https://docs.rs/ascon/latest/src/ascon/lib.rs.html
fn test_ascon_round_one() {
mut s := State{
e0: u64(0x0123456789abcdef)
e1: 0x23456789abcdef01
e2: 0x456789abcdef0123
e3: 0x6789abcdef012345
e4: 0x89abcde01234567f
}
ascon_perm(mut s, 0x1f)
assert s.e0 == u64(0x3c1748c9be2892ce)
assert s.e1 == u64(0x5eafb305cd26164f)
assert s.e2 == u64(0xf9470254bb3a4213)
assert s.e3 == u64(0xf0428daf0c5d3948)
assert s.e4 == u64(0x281375af0b294899)
}
fn test_ascon_round_p6() {
mut s := State{
e0: u64(0x0123456789abcdef)
e1: 0xef0123456789abcd
e2: 0xcdef0123456789ab
e3: 0xabcdef0123456789
e4: 0x89abcdef01234567
}
ascon_pnr(mut s, 6)
assert s.e0 == u64(0xc27b505c635eb07f)
assert s.e1 == u64(0xd388f5d2a72046fa)
assert s.e2 == u64(0x9e415c204d7b15e7)
assert s.e3 == u64(0xce0d71450fe44581)
assert s.e4 == u64(0xdd7c5fef57befe48)
}
fn test_ascon_round_p8() {
mut s := State{
e0: u64(0x0123456789abcdef)
e1: 0xef0123456789abcd
e2: 0xcdef0123456789ab
e3: 0xabcdef0123456789
e4: 0x89abcdef01234567
}
ascon_pnr(mut s, 8)
assert s.e0 == u64(0x67ed228272f46eee)
assert s.e1 == u64(0x80bc0b097aad7944)
assert s.e2 == u64(0x2fa599382c6db215)
assert s.e3 == u64(0x368133fae2f7667a)
assert s.e4 == u64(0x28cefb195a7c651c)
}
fn test_ascon_round_p12() {
mut s := State{
e0: u64(0x0123456789abcdef)
e1: 0xef0123456789abcd
e2: 0xcdef0123456789ab
e3: 0xabcdef0123456789
e4: 0x89abcdef01234567
}
ascon_pnr(mut s, 12)
assert s.e0 == u64(0x206416dfc624bb14)
assert s.e1 == u64(0x1b0c47a601058aab)
assert s.e2 == u64(0x8934cfc93814cddd)
assert s.e3 == u64(0xa9738d287a748e4b)
assert s.e4 == u64(0xddd934f058afc7e1)
}

View file

@ -0,0 +1,145 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
import arrays
import encoding.hex
import x.crypto.ascon
struct CxofTest {
count int
msg string
z string
md string
}
// Known Answer Tests (KAT) with 512-bits output for Ascon-CXOF128
fn test_cxof128() ! {
for item in cxof128_test_data {
msg := hex.decode(item.msg)!
z := hex.decode(item.z)!
md := hex.decode(item.md)!
out := ascon.cxof128_64(msg, z)!
assert md == out
// With Xof128Digest opaque
mut cx := ascon.new_cxof128(ascon.default_xof_size, z)!
md0 := cx.sum(msg)
assert md0 == md
// test .read()
cx.reset()
mut dst := []u8{len: 64}
_ := cx.write(msg)!
nr := cx.read(mut dst)!
assert nr == 64
assert dst == md
// with chunked messages
cx.reset()
chunks := arrays.chunk[u8](msg, 20)
mut tot := 0
for chunk in chunks {
n := cx.write(chunk)!
tot += n
}
assert msg.len == tot
chunked_md := cx.sum([]u8{})
assert chunked_md == md
}
}
const cxof128_test_data = [
CxofTest{
count: 1
msg: ''
z: ''
md: '4f50159ef70bb3dad8807e034eaebd44c4fa2cbbc8cf1f05511ab66cdcc529905ca12083fc186ad899b270b1473dc5f7ec88d1052082dcdfe69fb75d269e7b74'
},
CxofTest{
count: 2
msg: ''
z: '10'
md: '0c93a483e7d574d49fe52cce03ee646117977d57a8aa57704ab4daf44b501430ff6ac11a5d1fd6f2154b5c65728268270c8bb578508487b8965718ada6272fd6'
},
CxofTest{
count: 3
msg: ''
z: '1011'
md: 'd1106c7622e79fe955bd9d79e03b918e770fe0e0cddde28beb924b02c5fc936b33acca299c89eca5d71886cbbfa4d54a21c55fde2b679f5e2488063a1719dc32'
},
CxofTest{
count: 4
msg: ''
z: '101112'
md: '6a53a6dbf1bec15a79ce1214ff76a4d6bb16f60cfa56bf2c218aec5e160372117d2a2e647b128624e9b1d2259faf083f2bedd0fc751a2e2ff268d0ee026b6449'
},
CxofTest{
count: 5
msg: ''
z: '10111213'
md: 'cc333dd5b4ea61abe4376d61058b16df5eda7056299865ed7d25f43ac5b8541574608bd95ab0a3c3b74abd4abf9e50e63be6efe1b836b58595d8c47705c4dffb'
},
CxofTest{
count: 6
msg: ''
z: '1011121314'
md: '8ee69d28b1bf3eafacf1e169fd10b6b7b72a7e2aaf0625e8e7c00153833b7224ed8c8c127b9808352c5647f9e862958d6de9eb93c4a236d59ecd84665e7164d9'
},
CxofTest{
count: 7
msg: ''
z: '101112131415'
md: '3681695c40d83f60b401ecfa14bc03780ad474438f74b823eec9f0d5a375c13488803d3b4b8c8d4acd03186039f905fa15c7860dd0e9d566f31cd9e5822a937c'
},
CxofTest{
count: 8
msg: ''
z: '10111213141516'
md: '717a9ba3b3c00f4078572b2d3fb3f0a86d45f70bc4e1cd89cb7a952bfa64162383735534ecbe0a7e62e7592cf447404db0361d98c2237245688ead15c05ae59b'
},
CxofTest{
count: 9
msg: ''
z: '1011121314151617'
md: '61324766441dd6c11e1736bad1d2185820885ed76fe2ce537775a6e855eeafd2a6651b5e862a44982765f8b4c7cbe9c8b354f569ead6abc62cc9b7cdd72e0cb3'
},
CxofTest{
count: 10
msg: ''
z: '101112131415161718'
md: '32fde6b9d290f56fc74aac9368f32c69973e1bab35d96118db7181aae577687673c01a9e35327aded556987eed3441d4f42ec36b0c198498d9e7f357b948d560'
},
CxofTest{
count: 11
msg: ''
z: '10111213141516171819'
md: '690fc893055910d7d1d38055cf5589bbbe6b82bf175847ab3e0fd9a578b044dcb42be2932067eaa563a09e634581f34c2b4cfb38e1b06841b45b7b34c746d6dd'
},
CxofTest{
count: 12
msg: ''
z: '101112131415161718191a'
md: 'ecdab5b15324f99a1709be26fc329d305bd475e5f39bc2b63788792166ad08fe720ccd14e0a4de7d83ede1c7744929dc509c73748d6661a3d3215995357d3f88'
},
CxofTest{
count: 13
msg: ''
z: '101112131415161718191a1b'
md: 'ec2ba3309cfa6d6d0b581374e7c020ad17c330ea2b76d48724a415dceca3859c11146c2f64e52e44d27b1c44fd27476990a2e959b9998827a527a7e69089895f'
},
CxofTest{
count: 14
msg: ''
z: '101112131415161718191a1b1c'
md: '659a59bc7fdece2f1bafa9f1bfb4c262043f74da550f85c902c9c4302adfcf898fbcd74c92d67bded153137e0d32ccba88767354be99103dfeb59c686ca98dea'
},
CxofTest{
count: 15
msg: ''
z: '101112131415161718191a1b1c1d'
md: '17756044b742028b508d797c2c75a0722dde763c59d3fe5f70435b82faca5a80fe9c5ec9f3c59072ae48f37a241281c25d2e903c9d9290128265f1fe92b80bed'
},
]

View file

@ -0,0 +1,135 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// Common helpers used by Ascon-Hash256, Ascon-XOF128 and Ascon-CXOF128
module ascon
import encoding.binary
// The Digest is an internal structure used by Ascon-hashing variants.
@[noinit]
struct Digest {
State
mut:
// buffer for storing unprocessed data for streaming-way
buf []u8 = []u8{len: block_size}
// length of leftover unprocessed bytes on the digest buffer
length int
// internal flag
done bool
}
// finish finalizes state by writing last block in the Digest internal buffer.
// and consequently updates Digest state.
@[direct_array_access; inline]
fn (mut d Digest) finish() {
if d.length >= d.buf.len {
panic('Digest.finish: internal error')
}
// Process for the last block stored on the internal buffer
d.State.e0 ^= pad(d.length)
d.State.e0 ^= load_bytes(d.buf[..d.length], d.length)
// Permutation step was done in squeezing-phase
// ascon_pnr(mut d.State, 12)
// zeroing Digest buffer
d.length = 0
unsafe { d.buf.reset() }
// After finishing phase, we can't write data anymore into state
d.done = true
}
// absorb absorbs message msg_ into Digest state.
@[direct_array_access]
fn (mut d Digest) absorb(msg_ []u8) int {
// nothing to absorb, just return
if msg_.len == 0 {
return 0
}
// Absorbing messages into Digest state working in streaming-way.
// Its continuesly updates internal state until you call `.finish` or `.free` it.
// Firstly, it would checking unprocessed bytes on internal buffer, and append it
// with bytes from messasge to align with block_size.
// And then absorb this buffered message into state.
// The process continues until the last partial block thats maybe below the block_size.
// If its happens, it will be stored on the Digest internal buffer for later processing.
mut msg := msg_.clone()
unsafe {
// Check if internal buffer has previous unprocessed bytes.
// If its on there, try to empty the buffer.
if d.length > 0 {
// There are bytes in the d.buf, append it with bytes taken from msg
if d.length + msg.len >= block_size {
n := copy(mut d.buf[d.length..], msg)
msg = msg[n..]
d.length += n
// If this d.buf length has reached block_size bytes, absorb it.
if d.length == block_size {
d.State.e0 ^= binary.little_endian_u64(d.buf)
ascon_pnr(mut d.State, 12)
// reset the internal buffer
d.length = 0
d.buf.reset()
}
} else {
// Otherwise, still fit to buffer, but nof fully fills the d.buf
// just stores into buffer without processing
n := copy(mut d.buf[d.length..], msg)
msg = msg[n..]
d.length += n
}
}
// process for full block
for msg.len >= block_size {
d.State.e0 ^= binary.little_endian_u64(msg[0..block_size])
msg = msg[block_size..]
ascon_pnr(mut d.State, 12)
}
// If there are partial block, just stored into buffer.
if msg.len > 0 {
n := copy(mut d.buf[d.length..], msg)
msg = msg[n..]
d.length += n
}
return msg_.len - msg.len
}
}
// squeeze squeezes the state and calculates checksum output for the current state.
// It accepts destination buffer with desired buffer length you want to output.
@[direct_array_access; inline]
fn (mut d Digest) squeeze(mut dst []u8) int {
// nothing to store, just return unchanged
if dst.len == 0 {
return 0
}
// check
if dst.len > max_hash_size {
panic('Digest.squeeze: invalid dst.len')
}
// The squeezing phase begins after msg is absorbed with an
// permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state:
ascon_pnr(mut d.State, 12)
mut pos := 0
mut clen := dst.len
// process for full block size
for clen >= block_size {
binary.little_endian_put_u64(mut dst[pos..pos + 8], d.State.e0)
ascon_pnr(mut d.State, 12)
pos += block_size
clen -= block_size
}
// final output, the resulting hash is the concatenation of hash blocks
store_bytes(mut dst[pos..], d.State.e0, clen)
pos += clen
// for make sure, assert it here
assert pos == dst.len
return pos
}

View file

@ -0,0 +1,42 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
import encoding.hex
import x.crypto.ascon
// The material was generated from https://hashing.tools/ascon/ascon-hash
fn main() {
msg := 'Example of CXof128 message'.bytes()
cs := 'custom-string-cxof128'.bytes()
// expected output generated from the tool, with 32, 64 dan 75-bytes output
digest32 := hex.decode('d71492b816b1ac27f53f9c13be45c1d2d0530b8dde7fde8d34cb563f79b3d3d3')!
digest64 := hex.decode('d71492b816b1ac27f53f9c13be45c1d2d0530b8dde7fde8d34cb563f79b3d3d3601d03474ec6fe1f6b8dc5dd79bea20aff4c95ca3549202b1aaeb9e66b5df398')!
digest75 := hex.decode('d71492b816b1ac27f53f9c13be45c1d2d0530b8dde7fde8d34cb563f79b3d3d3601d03474ec6fe1f6b8dc5dd79bea20aff4c95ca3549202b1aaeb9e66b5df3985a88fd8bce0f9570962321')!
out32 := ascon.cxof128(msg, 32, cs)!
out64 := ascon.cxof128(msg, 64, cs)!
out75 := ascon.cxof128(msg, 75, cs)!
dump(out32 == digest32) // out32 == digest32: true
dump(out64 == digest64) // out64 == digest64: true
dump(out75 == digest75) // out75 == digest75: true
// With object based
mut x := ascon.new_cxof128(32, cs)!
s32 := x.sum(msg)
dump(s32 == digest32) // s32 == digest32: true
// with sized output
x.reset()
_ := x.write(msg)!
mut b64 := []u8{len: 64}
_ := x.read(mut b64)!
dump(b64 == digest64) // b64 == digest64: true
x.reset()
_ := x.write(msg)!
mut b75 := []u8{len: 75}
_ := x.read(mut b75)!
dump(b75 == digest75) // b75 == digest75: true
}

View file

@ -0,0 +1,16 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
import encoding.hex
import x.crypto.ascon
// The material was generated from https://hashing.tools/ascon/ascon-hash
fn main() {
msg := 'Example of hash256 message'.bytes()
// expected output generated from the tool
digest := hex.decode('0889515a9cfe28ab3a43882884d5933bb74aa09f3c767f8c699b5d7114811340')!
out := ascon.sum256(msg)
dump(out == digest) // out == digest: true
}

View file

@ -0,0 +1,40 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
import encoding.hex
import x.crypto.ascon
// The material was generated from https://hashing.tools/ascon/ascon-hash
fn main() {
msg := 'Example of Xof128 message'.bytes()
// expected output generated from the tool, with 32, 64 dan 75-bytes output
digest32 := hex.decode('424caaf68eb94aa251536bb6b565c0695b8944f932d011b1049df85b7f27d2f3')!
digest64 := hex.decode('424caaf68eb94aa251536bb6b565c0695b8944f932d011b1049df85b7f27d2f3bf704643a643c3f2dcfb1e0bc73ec55781b5283966d2d1da85d89794ca5c292e')!
digest75 := hex.decode('424caaf68eb94aa251536bb6b565c0695b8944f932d011b1049df85b7f27d2f3bf704643a643c3f2dcfb1e0bc73ec55781b5283966d2d1da85d89794ca5c292e36260815a8f10088e3804c')!
out32 := ascon.xof128(msg, 32)!
out64 := ascon.xof128(msg, 64)!
out75 := ascon.xof128(msg, 75)!
dump(out32 == digest32) // out32 == digest32: true
dump(out64 == digest64) // out64 == digest64: true
dump(out75 == digest75) // out75 == digest75: true
// With object based
mut x := ascon.new_xof128(32)
s32 := x.sum(msg)
dump(s32 == digest32) // s32 == digest32: true
// with sized output
x.reset()
_ := x.write(msg)!
mut b64 := []u8{len: 64}
_ := x.read(mut b64)!
dump(b64 == digest64) // b64 == digest64: true
x.reset()
_ := x.write(msg)!
mut b75 := []u8{len: 75}
_ := x.read(mut b75)!
dump(b75 == digest75) // b75 == digest75: true
}

130
vlib/x/crypto/ascon/hash.v Normal file
View file

@ -0,0 +1,130 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// This file implement hashing routines based on Ascon-Hash256 schema defined in NIST SP 800-232 standard,
// Ascon-Hash256 hashing produces 256-bits output.
module ascon
// block_size is the size (rate) of Ascon-Hash256, Ascon-XOF128 and Ascon-CXOF128 operates on.
const block_size = 8
// hash256_size is the length of the Ascon-Hash256 checksum output, in bytes
const hash256_size = 32
// hash256_initial_state is a precomputed value for Ascon-Hash256 state.
//
// The 320-bit internal state of Ascon-Hash256 is initialized with the
// concatenation of the 64-bit 𝐼𝑉 = 0x0000080100cc0002 and 256 zeroes, followed
// by the 𝐴𝑠𝑐𝑜𝑛-𝑝[12] permutation as S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](𝐼𝑉 ∥0256).
//
// s.e0 = 0x0000080100cc0002
// s.e1 = 0
// s.e2 = 0
// s.e3 = 0
// s.e4 = 0
// ascon_pnr(mut s, 12)
//
// Above step can be replaced with precomputed value to reduce runtime computations.
// See the detail on the NIST SP 800-232 standard on Sec A.3. Precomputation
// 𝑆0 ← 0x9b1e5494e934d681
// 𝑆1 ← 0x4bc3a01e333751d2
// 𝑆2 ← 0xae65396c6b34b81a
// 𝑆3 ← 0x3c7fd4a4d56a4db3
// 𝑆4 ← 0x1a5c464906c5976d
//
const hash256_initial_state = State{
e0: u64(0x9b1e5494e934d681)
e1: 0x4bc3a01e333751d2
e2: 0xae65396c6b34b81a
e3: 0x3c7fd4a4d56a4db3
e4: 0x1a5c464906c5976d
}
// 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
}
// Hash256 is an opaque provides an implementation of Ascon-Hash256 from NIST.SP.800-232 standard.
// Its implements `hash.Hash` interface.
@[noinit]
pub struct Hash256 {
Digest
}
// new_hash256 creates a new Ascon-Hash256 instance.
pub fn new_hash256() &Hash256 {
return &Hash256{
Digest: Digest{
State: hash256_initial_state
}
}
}
// size returns an underlying size of Hash256 checksum, ie, 32-bytes
pub fn (h &Hash256) size() int {
return hash256_size
}
// block_size returns an underlying Hash256 block size operates on, ie, 8-bytes
pub fn (h &Hash256) block_size() int {
return block_size
}
// reset resets and reinit internal Hash256 state into default initialized state.
pub fn (mut h Hash256) reset() {
h.Digest.State = hash256_initial_state
unsafe { h.Digest.buf.reset() }
h.Digest.length = 0
h.Digest.done = false
}
// free releases out the resources taken by the `h`. Dont use x after .free call.
@[unsafe]
pub fn (mut h Hash256) free() {
$if prealloc {
return
}
unsafe {
h.Digest.buf.free()
}
// Mark it as unusable
h.Digest.done = true
}
// write writes out the content of message and updates internal Hash256 state.
pub fn (mut h Hash256) write(msg []u8) !int {
if h.Digest.done {
panic('Digest: writing after done ')
}
return h.absorb(msg)
}
// clone returns the clone of the current Hash256
fn (h &Hash256) clone() &Hash256 {
digest := Digest{
State: h.Digest.State
buf: h.Digest.buf.clone()
length: h.Digest.length
done: h.Digest.done
}
return &Hash256{digest}
}
// sum returns an Ascon-Hash256 checksum of the bytes in data.
pub fn (mut h Hash256) sum(data []u8) []u8 {
// working on the clone of the h, so we can keep writing
mut h0 := h.clone()
_ := h0.write(data) or { panic(err) }
h0.Digest.finish()
mut dst := []u8{len: hash256_size}
_ := h0.Digest.squeeze(mut dst)
h0.reset()
return dst
}

View file

@ -0,0 +1,169 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
module ascon
import arrays
import encoding.hex
struct HashTest {
count int
msg string
md string
}
// This test material mostly taken and adapted from Known-Answer-Test (KAT) of reference implementation.
// See at https://github.com/ascon/ascon-c/blob/main/crypto_hash/asconhash256/LWC_HASH_KAT_128_256.txt
fn test_hash256_sum_chunked() ! {
item := HashTest{
count: 500
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2'
md: '8174F5E8DFC9441DBB7A183D56F431B1AEF9513E747A3878BB806BAE27655E3E'
}
msg := hex.decode(item.msg)!
md := hex.decode(item.md)!
// One shoot sum256 function
md0 := sum256(msg)
assert md0 == md
// Digest based
mut h := new_hash256()
expected_md := h.sum(msg)
assert expected_md == md
// with multistep
h.reset()
_ := h.Digest.absorb(msg)
h.Digest.finish()
mut dst := []u8{len: hash256_size}
h.Digest.squeeze(mut dst)
assert dst == md
// with splitted message
msg0 := msg[0..200]
msg1 := msg[200..400]
msg2 := msg[400..]
h.reset()
_ := h.Digest.absorb(msg0)
_ := h.Digest.absorb(msg1)
_ := h.Digest.absorb(msg2)
h.Digest.finish()
h.Digest.squeeze(mut dst)
assert dst == md
// with arrays chunk
h.reset()
chunks := arrays.chunk[u8](msg, 200)
mut n := 0
for chunk in chunks {
n += h.Digest.absorb(chunk)
}
assert n == msg.len
h.Digest.finish()
h.Digest.squeeze(mut dst)
assert dst == md
// with sum
h.reset()
for chunk in chunks {
n += h.write(chunk)!
}
chunked_md := h.sum([]u8{})
assert chunked_md == md
}
fn test_hash256_sum_kat() ! {
for item in ascon_hash256_test_data {
msg := hex.decode(item.msg)!
md := hex.decode(item.md)!
out := sum256(msg)
assert out == md
// work with Digest opaque
mut h := new_hash256()
exp_md := h.sum(msg)
assert exp_md == md
// Lets work in streaming-way
chunks := arrays.chunk[u8](msg, 7)
h.reset()
mut tot := 0
for chunk in chunks {
n := h.write(chunk)!
tot += n
}
assert msg.len == tot
chunked_md := h.sum([]u8{})
assert chunked_md == md
}
}
const ascon_hash256_test_data = [
HashTest{
count: 1
msg: ''
md: '0b3be5850f2f6b98caf29f8fdea89b64a1fa70aa249b8f839bd53baa304d92b2'
},
HashTest{
count: 2
msg: '00'
md: '0728621035af3ed2bca03bf6fde900f9456f5330e4b5ee23e7f6a1e70291bc80'
},
HashTest{
count: 3
msg: '0001'
md: '6115e7c9c4081c2797fc8fe1bc57a836afa1c5381e556dd583860ca2dfb48dd2'
},
HashTest{
count: 4
msg: '000102'
md: '265ab89a609f5a05dca57e83fbba700f9a2d2c4211ba4cc9f0a1a369e17b915c'
},
HashTest{
count: 5
msg: '00010203'
md: 'd7e4c7ed9b8a325cd08b9ef259f8877054ecd8304fe1b2d7fd847137df6727ee'
},
HashTest{
count: 6
msg: '0001020304'
md: 'c7b28962d4f5c2211f466f83d3c57ae1504387e2a326949747a8376447a6bb51'
},
HashTest{
count: 7
msg: '000102030405'
md: 'dc0c6748af8ffe63e1084aa3e5786a194685c88c21348b29e184fb50409703bc'
},
HashTest{
count: 8
msg: '00010203040506'
md: '3e4d273ba69b3b9c53216107e88b75cdbeedbcbf8faf0219c3928ab62b116577'
},
HashTest{
count: 9
msg: '0001020304050607'
md: 'b88e497ae8e6fb641b87ef622eb8f2fca0ed95383f7ffebe167acf1099ba764f'
},
HashTest{
count: 10
msg: '000102030405060708'
md: '94269C30E0296E1EC86655041841823EFA1927F520FD58C8E9BCE6197878C1A6'
},
HashTest{
count: 500
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2'
md: '8174F5E8DFC9441DBB7A183D56F431B1AEF9513E747A3878BB806BAE27655E3E'
},
HashTest{
count: 501
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3'
md: 'E73D4DDB9D248BF2C0F8D49892D7455A4C3053153DE7F79BA4487C7D823F605C'
},
HashTest{
count: 502
msg: '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4'
md: 'B73FA04079263F6A733B67466552784B436138F41F80B72C4D5D03934B72207D'
},
]

107
vlib/x/crypto/ascon/util.v Normal file
View file

@ -0,0 +1,107 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// Utility helpers used across the module
module ascon
import math.bits
import encoding.binary
// rotate_right_64 rotates x right by k bits
fn rotate_right_64(x u64, k int) u64 {
// call rotate_left_64(x, -k).
return bits.rotate_left_64(x, -k)
}
// clear_bytes clears the bytes of x in n byte
@[inline]
fn clear_bytes(x u64, n int) u64 {
mut c := x
for i := 0; i < n; i++ {
c &= ~set_byte(0xff, i)
}
return c
}
// pad appends a one followed by one or more zeroes to data
@[inline]
fn pad(n int) u64 {
return u64(0x01) << (8 * n)
}
// assume input.len < 8
fn u64_from_partial_bytes(input []u8) u64 {
mut tmp := []u8{len: 8}
ct_copy_first(mut tmp, input)
return binary.little_endian_u64(tmp)
}
// ct_copy_at copies of y into x start from at position in constant-time manner.
fn ct_copy_at(mut x []u8, y []u8, at int) {
ct_copy_internal(1, mut x, y, at)
}
// ct_copy_first copies of y into first y.len of x in constant-time manner.
fn ct_copy_first(mut x []u8, y []u8) {
ct_copy_internal(1, mut x, y, 0)
}
@[direct_array_access]
fn ct_copy_internal(v int, mut x []u8, y []u8, at int) {
if at > x.len {
panic('at > x.len')
}
if i64(x.len) < i64(y.len) + i64(at) {
panic('invalid pos')
}
if x.len < y.len {
panic('length x < y')
}
xmask := u8(v - 1)
ymask := u8(~(v - 1))
for i := 0; i < y.len; i++ {
x[i + at] = x[i + at] & xmask | y[i] & ymask
}
}
// portable little-endian helper
@[inline]
fn u64le(x u64) u64 {
$if little_endian {
return x
}
// otherwise, change into little-endian format
return ((u64(0x00000000000000FF) & x) << 56) | ((u64(0x000000000000FF00) & x) << 40) | ((u64(0x0000000000FF0000) & x) << 24) | ((u64(0x00000000FF000000) & x) << 8) | ((u64(0x000000FF00000000) & x) >> 8) | ((u64(0x0000FF0000000000) & x) >> 24) | ((u64(0x00FF000000000000) & x) >> 40) | ((u64(0xFF00000000000000) & x) >> 56)
}
@[inline]
fn get_byte(x u64, i int) u8 {
return u8(x >> (8 * i))
}
@[inline]
fn set_byte(b u8, i int) u64 {
return u64(b) << (8 * i)
}
// load_bytes load partial bytes with length n, used internally.
@[direct_array_access]
fn load_bytes(bytes []u8, n int) u64 {
mut x := u64(0)
for i := 0; i < n; i++ {
x |= set_byte(bytes[i], i)
}
return u64le(x)
}
fn store_bytes(mut out []u8, x u64, n int) {
for i := 0; i < n; i++ {
out[i] = get_byte(x, i)
}
}
@[inline]
fn ascon_rotate_right(x u64, n int) u64 {
return (x >> n) | x << (64 - n)
}

327
vlib/x/crypto/ascon/xof.v Normal file
View file

@ -0,0 +1,327 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// This file implements Ascon-XOF128, an Ascon-based Extendable-output Function (XOF)
// and their variant, Ascon-CXOF128 from NIST.SP.800-232 standard.
// Ascon-XOF128 is similar to Ascon-Hash256 where Ascon-XOF128 accepts an additional input
// which specifies the desired output length in bits.
module ascon
import encoding.binary
// max_hash_size is a maximum size of Ascon-XOF128 (and Ascon-CXOF128) checksum output.
// Its very rare where checksum output bigger than 512-bits, So, we limiting it to prevent unconditional thing.
// This limitation was only occurs on this module, wee can change it later.
pub const max_hash_size = 4096 // in bytes
// default_xof_size is the size of Ascon-XOF128 (and Ascon-CXOF128) checksum that has 512-bits length.
pub const default_xof_size = 64
// xof128_initial_state is a precomputed value for Ascon-XOF128 state.
// See the comment on `hash256_initial_state` about the values
const xof128_initial_state = State{
e0: 0xda82ce768d9447eb
e1: 0xcc7ce6c75f1ef969
e2: 0xe7508fd780085631
e3: 0x0ee0ea53416b58cc
e4: 0xe0547524db6f0bde
}
// 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
}
// xof128_64 creates a 64-bytes of Ascon-XOF128 checksum of msg.
pub fn xof128_64(msg []u8) ![]u8 {
return xof128(msg, default_xof_size)!
}
// Xof128 is an opaque provides an implementation of Ascon-XOF128 from NIST.SP.800-232 standard.
// Its implements `hash.Hash` interface with checksum size stored on instance creation.
@[noinit]
pub struct Xof128 {
Digest
mut:
// The size of Xof128 cheksum, when you dont specify it
size int = default_xof_size
}
// new_xof128 creates a new Ascon-XOF128 instance with provided size parameter.
pub fn new_xof128(size int) &Xof128 {
if size < 1 || size > max_hash_size {
panic('new_xof128: invalid size')
}
return &Xof128{
Digest: Digest{
State: xof128_initial_state
}
size: size
}
}
// size returns an underlying size of Xof128 checksum in fixed-sized manner.
// Internally, its return underlying size stored on current Xof128 instance.
pub fn (x &Xof128) size() int {
return x.size
}
// block_size returns an underlying Xof128 block size operates on, ie, 8-bytes
pub fn (x &Xof128) block_size() int {
return block_size
}
// clone returns a clone of x on the current state.
fn (x &Xof128) clone() &Xof128 {
return &Xof128{
Digest: x.Digest
size: x.size
}
}
// write writes out the content of message and updates internal Xof128 state.
pub fn (mut x Xof128) write(msg []u8) !int {
if x.Digest.done {
panic('Digest: writing after done ')
}
return x.Digest.absorb(msg)
}
// sum returns an Ascon-XOF128 checksum of the bytes in msg.
// Its produces `x.size` of checksum bytes. If you want to more
// extendible output, use `.read` call instead.
pub fn (mut x Xof128) sum(msg []u8) []u8 {
// working on the clone of the h, so we can keep writing
mut x0 := x.clone()
_ := x0.write(msg) or { panic(err) }
x0.Digest.finish()
mut dst := []u8{len: x.size}
x0.Digest.squeeze(mut dst)
x0.reset()
return dst
}
// read tries to read `dst.len` bytes of hash output from current Xof128 state and stored into dst.
// Note: 1 ≤ dst.len ≤ max_hash_size.
pub fn (mut x Xof128) read(mut dst []u8) !int {
// Left unchanged
if dst.len == 0 {
return 0
}
// Check output size
if dst.len > max_hash_size {
panic('Xof128.read: invalid size')
}
mut x0 := x.clone()
x0.Digest.finish()
n := x0.Digest.squeeze(mut dst)
x0.reset()
return n
}
// reset resets internal Xof128 state into default initialized state.
pub fn (mut x Xof128) reset() {
// we dont reset the x.size
unsafe { x.Digest.buf.reset() }
x.Digest.length = 0
x.Digest.done = false
x.Digest.State = xof128_initial_state
}
// free releases out the resources taken by the `x`. Dont use x after .free call.
@[unsafe]
pub fn (mut x Xof128) free() {
$if prealloc {
return
}
unsafe {
x.Digest.buf.free()
}
// Mark it as unusable
x.Digest.done = true
}
// Ascon-CXOF128
//
// Ascon-CXOF128 is customized variant of Ascon-XOF128 that extends its
// functionality by incorporating a user-defined customization string.
// In addition to the message 𝑀 and output length 𝐿, Ascon-CXOF128 takes
// the customization string 𝑍 as input.
// The length of the customization string shall be at most 2048 bits (i.e., 256 bytes)
const max_cxof128_cstring = 256
// cxof128_initial_state is a precomputed value for Ascon-CXOF128 state.
// See the comment on `hash256_initial_state` about the values
const cxof128_initial_state = State{
e0: 0x675527c2a0e8de03
e1: 0x43d12d7dc0377bbc
e2: 0xe9901dec426e81b5
e3: 0x2ab14907720780b6
e4: 0x8f3f1d02d432bc46
}
// 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
}
// cxof128_64 creates a 64-bytes of Ascon-CXOF128 checksum of msg with supplied custom string in cs.
pub fn cxof128_64(msg []u8, cs []u8) ![]u8 {
return cxof128(msg, default_xof_size, cs)!
}
// CXof128 is an opaque provides an implementation of Ascon-CXOF128 from NIST.SP.800-232 standard.
// Its implements `hash.Hash` interface with checksum-size stored on instance creation.
@[noinit]
pub struct CXof128 {
Digest
mut:
// Customization string
cs []u8
// checksum size, for fixed-output
size int = default_xof_size
}
// new_cxof128 creates a new Ascon-CXOF128 instanace with cheksum size
// was set to size and custom string in cs. It returns error on fails.
pub fn new_cxof128(size int, cs []u8) !&CXof128 {
if cs.len > max_cxof128_cstring {
return error('CXof128: custom string length exceed limit')
}
if size < 1 || size > max_hash_size {
return error('CXof128: invalid size')
}
// Initialize CXof128 state with precomputed-value and absorb the customization string
mut s := cxof128_initial_state
cxof128_absorb_custom_string(mut s, cs)
return &CXof128{
Digest: Digest{
State: s
}
cs: cs
size: size
}
}
// size returns an underlying size of CXof128 checksum in fixed-sized manner.
// Internally, its return underlying size stored on current CXof128 instance.
pub fn (x &CXof128) size() int {
return x.size
}
// block_size returns an underlying CXof128 block size operates on, ie, 8-bytes
pub fn (x &CXof128) block_size() int {
return block_size
}
// write writes out the content of message and updates internal CXof128 state.
pub fn (mut x CXof128) write(msg []u8) !int {
if x.Digest.done {
panic('CXof128: writing after done ')
}
return x.Digest.absorb(msg)
}
// sum returns an Ascon-CXOF128 checksum of the bytes in msg.
// Its produces `x.size` of checksum bytes. If you want to more
// extendible output, use `.read` call instead.
pub fn (mut x CXof128) sum(msg []u8) []u8 {
// working on the clone of the h, so we can keep writing
mut x0 := x.clone()
_ := x0.write(msg) or { panic(err) }
x0.Digest.finish()
mut dst := []u8{len: x.size}
x0.Digest.squeeze(mut dst)
x0.reset()
return dst
}
// read tries to read `dst.len` bytes of hash output from current CXof128 state and stored into dst.
pub fn (mut x CXof128) read(mut dst []u8) !int {
// Left unchanged, nothing space to store checksum
if dst.len == 0 {
return 0
}
if dst.len > max_hash_size {
panic('CXof128.read: invalid size')
}
mut x0 := x.clone()
x0.Digest.finish()
n := x0.Digest.squeeze(mut dst)
x0.reset()
return n
}
// clone returns a clone of x on the current state.
fn (x &CXof128) clone() &CXof128 {
return &CXof128{
Digest: x.Digest
size: x.size
cs: x.cs
}
}
// reset resets internal CXof128 state into default initialized state.
pub fn (mut x CXof128) reset() {
// we dont reset the x.size and custom string
unsafe { x.Digest.buf.reset() }
x.Digest.length = 0
x.Digest.done = false
x.Digest.State = cxof128_initial_state
// reabsorbs custom string
cxof128_absorb_custom_string(mut x.Digest.State, x.cs)
}
// free releases out the resources taken by the `x`. Dont use x after .free call.
@[unsafe]
pub fn (mut x CXof128) free() {
$if prealloc {
return
}
unsafe {
x.Digest.buf.free()
}
// Mark it as unusable
x.Digest.done = true
}
// cxof128_absorb_custom_string performs absorbing phase of custom string in cs for Ascon-CXOF128.
@[direct_array_access; inline]
fn cxof128_absorb_custom_string(mut s State, cs []u8) {
// absorb Z0, the length of the customization string (in bits) encoded as a u64
s.e0 ^= u64(cs.len) << 3
ascon_pnr(mut s, 12)
// absorb the customization string
mut zlen := cs.len
mut zidx := 0
for zlen >= block_size {
block := unsafe { cs[zidx..zidx + block_size] }
s.e0 ^= binary.little_endian_u64(block)
ascon_pnr(mut s, 12)
// updates a index
zlen -= block_size
zidx += block_size
}
// absorb final customization string
last_block := unsafe { cs[zidx..] }
s.e0 ^= load_bytes(last_block, last_block.len)
s.e0 ^= pad(last_block.len)
ascon_pnr(mut s, 12)
}

View file

@ -0,0 +1,112 @@
// Copyright ©2025 blackshirt.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
import arrays
import encoding.hex
import x.crypto.ascon
// This test for Xof128 with 512-bits outputs
struct XofTest {
count int
msg string
md string
}
fn test_xof128() ! {
for item in xof128_test_data {
msg := hex.decode(item.msg)!
md := hex.decode(item.md)!
out := ascon.xof128_64(msg)!
assert out == md
// With Xof128Digest opaque
mut x := ascon.new_xof128(ascon.default_xof_size)
md0 := x.sum(msg)
assert md0 == md
// test .read()
x.reset()
mut dst := []u8{len: 64}
_ := x.write(msg)!
nr := x.read(mut dst)!
assert nr == 64
assert dst == md
// with chunked messages
x.reset()
chunks := arrays.chunk[u8](msg, 20)
mut tot := 0
for chunk in chunks {
n := x.write(chunk)!
tot += n
}
assert msg.len == tot
chunked_md := x.sum([]u8{})
assert chunked_md == md
}
}
const xof128_test_data = [
XofTest{
count: 1
msg: ''
md: '473d5e6164f58b39dfd84aacdb8ae42ec2d91fed33388ee0d960d9b3993295c6ad77855a5d3b13fe6ad9e6098988373af7d0956d05a8f1665d2c67d1a3ad10ff'
},
XofTest{
count: 2
msg: '00'
md: '51430e0438ecdf642b393630d977625f5f337656ba58ab1e960784ac32a16e0d446405551f5469384f8ea283cf12e64fa72c426bfebaea3aa1529e2c4ab23a2f'
},
XofTest{
count: 3
msg: '0001'
md: 'a05383077af971d3830bd37e7b981497a773d441db077c6494cc73125953846eb6427fba4cd308ff90a11385d51101341bf5379249217bfdace9cca1148cc966'
},
XofTest{
count: 4
msg: '000102'
md: '9c96f31c3e7bdfdc5ef6ba836f760a0d6548d94dd0a512033022c9242e8ba916c30c3961d37d7dd7282e2191494d60dc5058588b276c60c90be2aaa7e7013d96'
},
XofTest{
count: 5
msg: '00010203'
md: '21f7fd74588e244af45f9016b8db19b857ec5e6208978cfc1b4611ed91fb38f87e8f82a6409fb2b77acfbba8862aa22a7b0c98c1c01d5a4fdf64827b450fa1eb'
},
XofTest{
count: 6
msg: '0001020304'
md: 'd647cc91aafff06a486f00a33fdfe9222f08b94da3b17804da9aaae167b4285dd6395e2a61fded3cf73c99774aff7066f74f7698f4824ba538602087d7c267fa'
},
XofTest{
count: 7
msg: '000102030405'
md: '4793fbe6aa7688e52cd3a97a2685c68b218e0ca8754307956509974ab107d8ba19070424d5dfd336c3fc1250a273b9146f9f26d7658b9e213c37aebbe74abc6e'
},
XofTest{
count: 8
msg: '00010203040506'
md: '7ae562db37212a9acd2673ecfd5b4f1c5cb2e6f64ebf00aa7f6ef8dc82c448d5fe11cd91f4368c37690d79e5de0ca8ad419e1918ce8dab2d42363e9476638a7b'
},
XofTest{
count: 9
msg: '0001020304050607'
md: '8d1886f5d3ec4af8d15b44bc62b74da6ea91bc28fb82f9c34079b5ed6e38b6c951803d7dfb3c5e512a0ef5e4060062a6fd067f9c73ef9bee527411bda67fc896'
},
XofTest{
count: 10
msg: '000102030405060708'
md: 'db3013bfbbd132dc1d3152fd955ed48f7cbb675e9ad2a2fecf92b74c957592e0c89959e81c16fd07ead9eeb8e40359c497aa20258b43d87ec69ad0bb0993fd38'
},
XofTest{
count: 601
msg: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f5051525354555657'
md: '8fbbce48a72faf257e06df9408d4b6e11d35a86dfc29c0f106cb86128a2fe94208dcc8df4a439978cdb77f53317cbe5507b7f33da590be10a424d4ef60b37036'
},
XofTest{
count: 602
msg: '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758'
md: 'b4ca30047dc94ed770e45f4fa07b25a049d4a149a78227e25625cec2cbee7cfdd7d3cfb2635a38770e04009de348726364f648984ef47e9bc90c2a3c0526b227'
},
]