v/vlib/x/crypto/ascon/xof.v

322 lines
9.3 KiB
V
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 {
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.
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 {
// 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.
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, ascon_prnd_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, ascon_prnd_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, ascon_prnd_12)
}