core/crypto/hash: Make the low level interface allocator-less

Just (ab)using reflect to victory is probably fine.
This commit is contained in:
Yawning Angel
2024-02-07 00:37:18 +09:00
parent 7a8b1669b0
commit 2357293e05
3 changed files with 150 additions and 181 deletions
+2 -2
View File
@@ -39,7 +39,7 @@ hash_string_to_buffer :: proc(algorithm: Algorithm, data: string, hash: []byte)
hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) { hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) {
ctx: Context ctx: Context
init(&ctx, algorithm, context.temp_allocator) init(&ctx, algorithm)
update(&ctx, data) update(&ctx, data)
final(&ctx, hash) final(&ctx, hash)
} }
@@ -56,7 +56,7 @@ hash_stream :: proc(
) { ) {
ctx: Context ctx: Context
init(&ctx, algorithm, context.temp_allocator) init(&ctx, algorithm)
buffer_size := block_size(&ctx) * 4 buffer_size := block_size(&ctx) * 4
buf := make([]byte, buffer_size, context.temp_allocator) buf := make([]byte, buffer_size, context.temp_allocator)
+138 -175
View File
@@ -9,7 +9,7 @@ import "core:crypto/legacy/keccak"
import "core:crypto/legacy/md5" import "core:crypto/legacy/md5"
import "core:crypto/legacy/sha1" import "core:crypto/legacy/sha1"
import "core:mem" import "core:reflect"
// Algorithm is the algorithm identifier associated with a given Context. // Algorithm is the algorithm identifier associated with a given Context.
Algorithm :: enum { Algorithm :: enum {
@@ -107,101 +107,89 @@ BLOCK_SIZES := [Algorithm]int {
Context :: struct { Context :: struct {
_algo: Algorithm, _algo: Algorithm,
_impl: union { _impl: union {
^blake2b.Context, blake2b.Context,
^blake2s.Context, blake2s.Context,
^sha2.Context_256, sha2.Context_256,
^sha2.Context_512, sha2.Context_512,
^sha3.Context, sha3.Context,
^sm3.Context, sm3.Context,
^keccak.Context, keccak.Context,
^md5.Context, md5.Context,
^sha1.Context, sha1.Context,
}, },
_allocator: mem.Allocator, }
@(private)
_IMPL_IDS := [Algorithm]typeid {
.Invalid = nil,
.BLAKE2B = typeid_of(blake2b.Context),
.BLAKE2S = typeid_of(blake2s.Context),
.SHA224 = typeid_of(sha2.Context_256),
.SHA256 = typeid_of(sha2.Context_256),
.SHA384 = typeid_of(sha2.Context_512),
.SHA512 = typeid_of(sha2.Context_512),
.SHA512_256 = typeid_of(sha2.Context_512),
.SHA3_224 = typeid_of(sha3.Context),
.SHA3_256 = typeid_of(sha3.Context),
.SHA3_384 = typeid_of(sha3.Context),
.SHA3_512 = typeid_of(sha3.Context),
.SM3 = typeid_of(sm3.Context),
.Legacy_KECCAK_224 = typeid_of(keccak.Context),
.Legacy_KECCAK_256 = typeid_of(keccak.Context),
.Legacy_KECCAK_384 = typeid_of(keccak.Context),
.Legacy_KECCAK_512 = typeid_of(keccak.Context),
.Insecure_MD5 = typeid_of(md5.Context),
.Insecure_SHA1 = typeid_of(sha1.Context),
} }
// init initializes a Context with a specific hash Algorithm. // init initializes a Context with a specific hash Algorithm.
// init :: proc(ctx: ^Context, algorithm: Algorithm) {
// Warning: Internal state is allocated, and resources must be freed
// either implicitly via a call to final, or explicitly via calling reset.
init :: proc(ctx: ^Context, algorithm: Algorithm, allocator := context.allocator) {
if ctx._impl != nil { if ctx._impl != nil {
reset(ctx) reset(ctx)
} }
// Directly specialize the union by setting the type ID (save a copy).
reflect.set_union_variant_typeid(
ctx._impl,
_IMPL_IDS[algorithm],
)
switch algorithm { switch algorithm {
case .BLAKE2B: case .BLAKE2B:
impl := new(blake2b.Context, allocator) blake2b.init(&ctx._impl.(blake2b.Context))
blake2b.init(impl)
ctx._impl = impl
case .BLAKE2S: case .BLAKE2S:
impl := new(blake2s.Context, allocator) blake2s.init(&ctx._impl.(blake2s.Context))
blake2s.init(impl)
ctx._impl = impl
case .SHA224: case .SHA224:
impl := new(sha2.Context_256, allocator) sha2.init_224(&ctx._impl.(sha2.Context_256))
sha2.init_224(impl)
ctx._impl = impl
case .SHA256: case .SHA256:
impl := new(sha2.Context_256, allocator) sha2.init_256(&ctx._impl.(sha2.Context_256))
sha2.init_256(impl)
ctx._impl = impl
case .SHA384: case .SHA384:
impl := new(sha2.Context_512, allocator) sha2.init_384(&ctx._impl.(sha2.Context_512))
sha2.init_384(impl)
ctx._impl = impl
case .SHA512: case .SHA512:
impl := new(sha2.Context_512, allocator) sha2.init_512(&ctx._impl.(sha2.Context_512))
sha2.init_512(impl)
ctx._impl = impl
case .SHA512_256: case .SHA512_256:
impl := new(sha2.Context_512, allocator) sha2.init_512_256(&ctx._impl.(sha2.Context_512))
sha2.init_512_256(impl)
ctx._impl = impl
case .SHA3_224: case .SHA3_224:
impl := new(sha3.Context, allocator) sha3.init_224(&ctx._impl.(sha3.Context))
sha3.init_224(impl)
ctx._impl = impl
case .SHA3_256: case .SHA3_256:
impl := new(sha3.Context, allocator) sha3.init_256(&ctx._impl.(sha3.Context))
sha3.init_256(impl)
ctx._impl = impl
case .SHA3_384: case .SHA3_384:
impl := new(sha3.Context, allocator) sha3.init_384(&ctx._impl.(sha3.Context))
sha3.init_384(impl)
ctx._impl = impl
case .SHA3_512: case .SHA3_512:
impl := new(sha3.Context, allocator) sha3.init_512(&ctx._impl.(sha3.Context))
sha3.init_512(impl)
ctx._impl = impl
case .SM3: case .SM3:
impl := new(sm3.Context, allocator) sm3.init(&ctx._impl.(sm3.Context))
sm3.init(impl)
ctx._impl = impl
case .Legacy_KECCAK_224: case .Legacy_KECCAK_224:
impl := new(keccak.Context, allocator) keccak.init_224(&ctx._impl.(keccak.Context))
keccak.init_224(impl)
ctx._impl = impl
case .Legacy_KECCAK_256: case .Legacy_KECCAK_256:
impl := new(keccak.Context, allocator) keccak.init_256(&ctx._impl.(keccak.Context))
keccak.init_256(impl)
ctx._impl = impl
case .Legacy_KECCAK_384: case .Legacy_KECCAK_384:
impl := new(keccak.Context, allocator) keccak.init_384(&ctx._impl.(keccak.Context))
keccak.init_384(impl)
ctx._impl = impl
case .Legacy_KECCAK_512: case .Legacy_KECCAK_512:
impl := new(keccak.Context, allocator) keccak.init_512(&ctx._impl.(keccak.Context))
keccak.init_512(impl)
ctx._impl = impl
case .Insecure_MD5: case .Insecure_MD5:
impl := new(md5.Context, allocator) md5.init(&ctx._impl.(md5.Context))
md5.init(impl)
ctx._impl = impl
case .Insecure_SHA1: case .Insecure_SHA1:
impl := new(sha1.Context, allocator) sha1.init(&ctx._impl.(sha1.Context))
sha1.init(impl)
ctx._impl = impl
case .Invalid: case .Invalid:
panic("crypto/hash: uninitialized algorithm") panic("crypto/hash: uninitialized algorithm")
case: case:
@@ -209,30 +197,29 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, allocator := context.allocator
} }
ctx._algo = algorithm ctx._algo = algorithm
ctx._allocator = allocator
} }
// update adds more data to the Context. // update adds more data to the Context.
update :: proc(ctx: ^Context, data: []byte) { update :: proc(ctx: ^Context, data: []byte) {
switch impl in ctx._impl { switch &impl in ctx._impl {
case ^blake2b.Context: case blake2b.Context:
blake2b.update(impl, data) blake2b.update(&impl, data)
case ^blake2s.Context: case blake2s.Context:
blake2s.update(impl, data) blake2s.update(&impl, data)
case ^sha2.Context_256: case sha2.Context_256:
sha2.update(impl, data) sha2.update(&impl, data)
case ^sha2.Context_512: case sha2.Context_512:
sha2.update(impl, data) sha2.update(&impl, data)
case ^sha3.Context: case sha3.Context:
sha3.update(impl, data) sha3.update(&impl, data)
case ^sm3.Context: case sm3.Context:
sm3.update(impl, data) sm3.update(&impl, data)
case ^keccak.Context: case keccak.Context:
keccak.update(impl, data) keccak.update(&impl, data)
case ^md5.Context: case md5.Context:
md5.update(impl, data) md5.update(&impl, data)
case ^sha1.Context: case sha1.Context:
sha1.update(impl, data) sha1.update(&impl, data)
case: case:
panic("crypto/hash: uninitialized algorithm") panic("crypto/hash: uninitialized algorithm")
} }
@@ -244,25 +231,25 @@ update :: proc(ctx: ^Context, data: []byte) {
// Iff finalize_clone is set, final will work on a copy of the Context, // Iff finalize_clone is set, final will work on a copy of the Context,
// which is useful for for calculating rolling digests. // which is useful for for calculating rolling digests.
final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
switch impl in ctx._impl { switch &impl in ctx._impl {
case ^blake2b.Context: case blake2b.Context:
blake2b.final(impl, hash, finalize_clone) blake2b.final(&impl, hash, finalize_clone)
case ^blake2s.Context: case blake2s.Context:
blake2s.final(impl, hash, finalize_clone) blake2s.final(&impl, hash, finalize_clone)
case ^sha2.Context_256: case sha2.Context_256:
sha2.final(impl, hash, finalize_clone) sha2.final(&impl, hash, finalize_clone)
case ^sha2.Context_512: case sha2.Context_512:
sha2.final(impl, hash, finalize_clone) sha2.final(&impl, hash, finalize_clone)
case ^sha3.Context: case sha3.Context:
sha3.final(impl, hash, finalize_clone) sha3.final(&impl, hash, finalize_clone)
case ^sm3.Context: case sm3.Context:
sm3.final(impl, hash, finalize_clone) sm3.final(&impl, hash, finalize_clone)
case ^keccak.Context: case keccak.Context:
keccak.final(impl, hash, finalize_clone) keccak.final(&impl, hash, finalize_clone)
case ^md5.Context: case md5.Context:
md5.final(impl, hash, finalize_clone) md5.final(&impl, hash, finalize_clone)
case ^sha1.Context: case sha1.Context:
sha1.final(impl, hash, finalize_clone) sha1.final(&impl, hash, finalize_clone)
case: case:
panic("crypto/hash: uninitialized algorithm") panic("crypto/hash: uninitialized algorithm")
} }
@@ -273,7 +260,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) {
} }
// clone clones the Context other into ctx. // clone clones the Context other into ctx.
clone :: proc(ctx, other: ^Context, allocator := context.allocator) { clone :: proc(ctx, other: ^Context) {
// XXX/yawning: Maybe these cases should panic, because both cases, // XXX/yawning: Maybe these cases should panic, because both cases,
// are probably bugs. // are probably bugs.
if ctx == other { if ctx == other {
@@ -284,45 +271,30 @@ clone :: proc(ctx, other: ^Context, allocator := context.allocator) {
} }
ctx._algo = other._algo ctx._algo = other._algo
ctx._allocator = allocator
switch src_impl in other._impl { reflect.set_union_variant_typeid(
case ^blake2b.Context: ctx._impl,
impl := new(blake2b.Context, allocator) reflect.union_variant_typeid(other._impl),
blake2b.clone(impl, src_impl) )
ctx._impl = impl switch &src_impl in other._impl {
case ^blake2s.Context: case blake2b.Context:
impl := new(blake2s.Context, allocator) blake2b.clone(&ctx._impl.(blake2b.Context), &src_impl)
blake2s.clone(impl, src_impl) case blake2s.Context:
ctx._impl = impl blake2s.clone(&ctx._impl.(blake2s.Context), &src_impl)
case ^sha2.Context_256: case sha2.Context_256:
impl := new(sha2.Context_256, allocator) sha2.clone(&ctx._impl.(sha2.Context_256), &src_impl)
sha2.clone(impl, src_impl) case sha2.Context_512:
ctx._impl = impl sha2.clone(&ctx._impl.(sha2.Context_512), &src_impl)
case ^sha2.Context_512: case sha3.Context:
impl := new(sha2.Context_512, allocator) sha3.clone(&ctx._impl.(sha3.Context), &src_impl)
sha2.clone(impl, src_impl) case sm3.Context:
ctx._impl = impl sm3.clone(&ctx._impl.(sm3.Context), &src_impl)
case ^sha3.Context: case keccak.Context:
impl := new(sha3.Context, allocator) keccak.clone(&ctx._impl.(keccak.Context), &src_impl)
sha3.clone(impl, src_impl) case md5.Context:
ctx._impl = impl md5.clone(&ctx._impl.(md5.Context), &src_impl)
case ^sm3.Context: case sha1.Context:
impl := new(sm3.Context, allocator) sha1.clone(&ctx._impl.(sha1.Context), &src_impl)
sm3.clone(impl, src_impl)
ctx._impl = impl
case ^keccak.Context:
impl := new(keccak.Context, allocator)
keccak.clone(impl, src_impl)
ctx._impl = impl
case ^md5.Context:
impl := new(md5.Context, allocator)
md5.clone(impl, src_impl)
ctx._impl = impl
case ^sha1.Context:
impl := new(sha1.Context, allocator)
sha1.clone(impl, src_impl)
ctx._impl = impl
case: case:
panic("crypto/hash: uninitialized algorithm") panic("crypto/hash: uninitialized algorithm")
} }
@@ -331,34 +303,25 @@ clone :: proc(ctx, other: ^Context, allocator := context.allocator) {
// reset sanitizes the Context. The Context must be re-initialized to // reset sanitizes the Context. The Context must be re-initialized to
// be used again. // be used again.
reset :: proc(ctx: ^Context) { reset :: proc(ctx: ^Context) {
switch impl in ctx._impl { switch &impl in ctx._impl {
case ^blake2b.Context: case blake2b.Context:
blake2b.reset(impl) blake2b.reset(&impl)
free(impl, ctx._allocator) case blake2s.Context:
case ^blake2s.Context: blake2s.reset(&impl)
blake2s.reset(impl) case sha2.Context_256:
free(impl, ctx._allocator) sha2.reset(&impl)
case ^sha2.Context_256: case sha2.Context_512:
sha2.reset(impl) sha2.reset(&impl)
free(impl, ctx._allocator) case sha3.Context:
case ^sha2.Context_512: sha3.reset(&impl)
sha2.reset(impl) case sm3.Context:
free(impl, ctx._allocator) sm3.reset(&impl)
case ^sha3.Context: case keccak.Context:
sha3.reset(impl) keccak.reset(&impl)
free(impl, ctx._allocator) case md5.Context:
case ^sm3.Context: md5.reset(&impl)
sm3.reset(impl) case sha1.Context:
free(impl, ctx._allocator) sha1.reset(&impl)
case ^keccak.Context:
keccak.reset(impl)
free(impl, ctx._allocator)
case ^md5.Context:
md5.reset(impl)
free(impl, ctx._allocator)
case ^sha1.Context:
sha1.reset(impl)
free(impl, ctx._allocator)
case: case:
// Unlike clone, calling reset repeatedly is fine. // Unlike clone, calling reset repeatedly is fine.
} }
+10 -4
View File
@@ -538,8 +538,8 @@ test_hash :: proc(t: ^testing.T) {
// Exercise the rolling digest functionality, which also covers // Exercise the rolling digest functionality, which also covers
// each implementation's clone routine. // each implementation's clone routine.
ctx: hash.Context ctx, ctx_clone: hash.Context
hash.init(&ctx, algo, context.temp_allocator) hash.init(&ctx, algo)
api_algo := hash.algorithm(&ctx) api_algo := hash.algorithm(&ctx)
api_digest_size := hash.digest_size(&ctx) api_digest_size := hash.digest_size(&ctx)
@@ -565,20 +565,26 @@ test_hash :: proc(t: ^testing.T) {
) )
hash.update(&ctx, digest_a) hash.update(&ctx, digest_a)
hash.clone(&ctx_clone, &ctx)
hash.final(&ctx, digest_a, true) hash.final(&ctx, digest_a, true)
hash.final(&ctx, digest_b) hash.final(&ctx, digest_b)
digest_c := make([]byte, hash.digest_size(&ctx_clone), context.temp_allocator)
hash.final(&ctx_clone, digest_c)
a_str = string(hex.encode(digest_a, context.temp_allocator)) a_str = string(hex.encode(digest_a, context.temp_allocator))
b_str = string(hex.encode(digest_b, context.temp_allocator)) b_str = string(hex.encode(digest_b, context.temp_allocator))
c_str := string(hex.encode(digest_c, context.temp_allocator))
expect( expect(
t, t,
a_str == b_str, a_str == b_str && b_str == c_str,
fmt.tprintf( fmt.tprintf(
"%s/rolling: Expected: %s (first) == %s (second)", "%s/rolling: Expected: %s (first) == %s (second) == %s (third)",
algo_name, algo_name,
a_str, a_str,
b_str, b_str,
c_str,
), ),
) )
} }