// Copyright (c) blackshirt. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module slhdsa
// Configuration options used in SLH-DSA key generation.
@[params]
pub struct KeyOpts {
pub mut:
// An opaque represents the kind of SLH-DSA keys want to built.
// See `enum Kind` for available options.
kind Kind = .sha2_128s
// flag, 0=random (default), 1= use seed bytes, 2 = use priv bytes, otherwise error
flag int
// when you set flag=1, builder will use seed bytes as a params,
// so, make sure to pass seed bytes length != 0
seed []u8
// when flag=2, you should ensure private bytes length != 0
priv []u8
// This option below was not supported yet.
//
// Sets properties to be used when fetching algorithm implementations
// used for SLH-DSA hashing operations.
propq string
}
// Configurations parameters used for signing (and or verifying)
@[params]
pub struct SignerOpts {
pub mut:
// optional context string up to 255 length, used in signing (verifying)
context string
// "message-encoding"
// The default value of 1 uses 'Pure SLH-DSA Signature Generation'.
// Setting it to 0 does not encode the message, which is used for testing,
// but can also be used for 'Pre Hash SLH-DSA Signature Generation'.
// If you set encoding to 0, you should provide the entropy.
encoding int = 1
// "test-entropy" used for testing to pass a optional random value.
entropy []u8 // octet-string
// "deterministic" integer option.
// The default value of 0 generates a random value (using a DRBG) this is used when
// processing the message. Setting this to 1 causes the private key seed to be used instead.
// This value is ignored if "test-entropy" is set.
deterministic int
}
// from_seed creates a new SLH-DSA PrivateKey from seed bytes and kind options.
// If the seed length was zero, it will create a key based on randomly generated seed.
// You should make sure, the seed bytes comes from trusted cryptographic secure source.
// The seed size was 3 times of `𝑛` parameter defined in the standard.
// The `𝑛` size maybe 16, 24 or 32 bytes length, depend on the chosen type.
fn PrivateKey.from_seed(seed []u8, kind Kind) !PrivateKey {
// when seed bytes length was zero, build the key based on the random ones
if seed.len == 0 {
return PrivateKey.new(kind: kind)!
}
// Get the n size parameter set from the options
nsize := kind.nsize()
// The length of the seed bytes supplied must be 3 * nsize.
if seed.len != 3 * nsize {
return error('Unmatching seed length with kind supplied, need ${3 * nsize} bytes')
}
param_bld := C.OSSL_PARAM_BLD_new()
assert param_bld != 0
m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'seed', seed.data, seed.len)
if m <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
return error('OSSL_PARAM_BLD_push failed')
}
// build params
params := C.OSSL_PARAM_BLD_to_param(param_bld)
// create a desired context
pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0)
if params == 0 || pctx == 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
}
pkey := C.EVP_PKEY_new()
assert pkey != 0
ke := C.EVP_PKEY_keygen_init(pctx)
if ke <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_keygen_init failed')
}
// Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init().
s := C.EVP_PKEY_CTX_set_params(pctx, params)
if s != 1 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_CTX_set_params failed')
}
ss := C.EVP_PKEY_keygen(pctx, &pkey)
if ss <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_keygen failed')
}
// TODO: right way to check the key
pvkey := PrivateKey{
key: pkey
}
// Cleans up
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return pvkey
}
fn PrivateKey.from_bytes(bytes []u8, kind Kind) !PrivateKey {
// when bytes length was zero, build the key based on the random ones
if bytes.len == 0 {
return PrivateKey.new(kind: kind)!
}
// Get the n size parameter set from the options
nsize := kind.nsize()
// The private key has a size of 4 * n bytes.
if bytes.len != 4 * nsize {
return error('Unmatching private bytes length with kind supplied, need ${4 * nsize} bytes')
}
param_bld := C.OSSL_PARAM_BLD_new()
assert param_bld != 0
m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'priv', bytes.data, bytes.len)
if m <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
return error('OSSL_PARAM_BLD_push FAILED')
}
// build params
params := C.OSSL_PARAM_BLD_to_param(param_bld)
// create a desired context
pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0)
if params == 0 || pctx == 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
}
pkey := C.EVP_PKEY_new()
assert pkey != 0
s := C.EVP_PKEY_fromdata_init(pctx)
if s <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_fromdata_init failed')
}
ss := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_keypair, params)
if ss <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_fromdata failed')
}
pvkey := PrivateKey{
key: pkey
}
// Cleans up
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return pvkey
}
// from_bytes creates a new PublicKey with type of supported key and bytes array.
// If you dont provide the bytes, ie, supplied with zero-length bytes,
// it will be generated with random bytes for you.
pub fn PublicKey.from_bytes(bytes []u8, kind Kind) !PublicKey {
// when bytes length was zero, build the key based on the random ones
if bytes.len == 0 {
pv := PrivateKey.new(kind: kind)!
pbk := pv.public_key()!
return pbk
}
// Get the n size parameter set from the options
nsize := kind.nsize()
// The public key has a size of 2 * n bytes.
if bytes.len != 2 * nsize {
return error('Unmatching public bytes length with kind supplied, need ${2 * nsize} bytes')
}
param_bld := C.OSSL_PARAM_BLD_new()
assert param_bld != 0
m := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'pub', bytes.data, bytes.len)
if m <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
return error('OSSL_PARAM_BLD_push FAILED')
}
// build params
params := C.OSSL_PARAM_BLD_to_param(param_bld)
// create a desired context
pctx := C.EVP_PKEY_CTX_new_from_name(0, kind.long_name(), 0)
if params == 0 || pctx == 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return error('EVP_PKEY_CTX_new or OSSL_PARAM_BLD_to_param failed')
}
pkey := C.EVP_PKEY_new()
assert pkey != 0
s := C.EVP_PKEY_fromdata_init(pctx)
if s <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_fromdata failed')
}
ss := C.EVP_PKEY_fromdata(pctx, &pkey, evp_pkey_public_key, params)
if ss <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
C.EVP_PKEY_free(pkey)
return error('EVP_PKEY_fromdata failed')
}
pbkey := PublicKey{
key: pkey
}
// Cleans up
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_PKEY_CTX_free(pctx)
return pbkey
}
// The enumeration of NID of SLHDSA parameters set.
// See Table 2. SLH-DSA parameter sets of the Chapter 11. Parameter Sets
// Each sets name indicates:
//
// - the hash function family (SHA2 or SHAKE) that is used to instantiate the hash functions.
// - the length in bits of the security parameter, in the 128, 192, and 256 respectives number.
// - the mnemonic name indicates parameter to create relatively small signatures (`s`)
// or to have relatively fast signature generation (`f`).
pub enum Kind {
// SHA2-based family
sha2_128s = C.NID_SLH_DSA_SHA2_128s
sha2_128f = C.NID_SLH_DSA_SHA2_128f
sha2_192s = C.NID_SLH_DSA_SHA2_192s
sha2_192f = C.NID_SLH_DSA_SHA2_192f
sha2_256s = C.NID_SLH_DSA_SHA2_256s
sha2_256f = C.NID_SLH_DSA_SHA2_256f
// SHAKE-based family
shake_128s = C.NID_SLH_DSA_SHAKE_128s
shake_128f = C.NID_SLH_DSA_SHAKE_128f
shake_192s = C.NID_SLH_DSA_SHAKE_192s
shake_192f = C.NID_SLH_DSA_SHAKE_192f
shake_256s = C.NID_SLH_DSA_SHAKE_256s
shake_256f = C.NID_SLH_DSA_SHAKE_256f
}
// nsize returns the size of underlying n parameter from current type.
@[inline]
fn (n Kind) nsize() int {
match n {
.sha2_128s, .sha2_128f, .shake_128s, .shake_128f { return 16 }
.sha2_192s, .sha2_192f, .shake_192s, .shake_192f { return 24 }
.sha2_256s, .sha2_256f, .shake_256s, .shake_256f { return 32 }
}
}
fn (n Kind) str() string {
match n {
// vfmt off
// SHA2-based family
.sha2_128s { return "sha2_128s" }
.sha2_128f { return "sha2_128f" }
.sha2_192s { return "sha2_192s" }
.sha2_192f { return "sha2_192f" }
.sha2_256s { return "sha2_256s" }
.sha2_256f { return "sha2_256f" }
// SHAKE-based family
.shake_128s { return "shake_128s" }
.shake_128f { return "shake_128f" }
.shake_192s { return "shake_192s" }
.shake_192f { return "shake_192f" }
.shake_256s { return "shake_256s" }
.shake_256f { return "shake_256f" }
// vfmt on
}
}
// Kind long name as v string
@[inline]
fn (n Kind) ln_to_vstr() string {
out := unsafe { n.long_name().vstring() }
return out
}
// Kind long name as c-style string
@[inline]
fn (n Kind) long_name() &char {
match n {
// vfmt off
// SHA2-based family
.sha2_128s { return ln_slhdsa_sha2_128s }
.sha2_128f { return ln_slhdsa_sha2_128f }
.sha2_192s { return ln_slhdsa_sha2_192s }
.sha2_192f { return ln_slhdsa_sha2_192f }
.sha2_256s { return ln_slhdsa_sha2_256s }
.sha2_256f { return ln_slhdsa_sha2_256f }
// SHAKE-based family
.shake_128s { return ln_slhdsa_shake_128s }
.shake_128f { return ln_slhdsa_shake_128f }
.shake_192s { return ln_slhdsa_shake_192s }
.shake_192f { return ln_slhdsa_shake_192f }
.shake_256s { return ln_slhdsa_shake_256s }
.shake_256f { return ln_slhdsa_shake_256f }
// vfmt on
}
}
// Chapter 11. Parameters Set
struct ParamSet {
// Algorithm name
id Kind
n int
h int
d int
hp int
a int
k int
lgw int = 4
m int
sc int
pkb int
sig int
}
// Table 2. SLH-DSA parameter sets
const paramset = {
// id 𝑛 ℎ 𝑑 ℎ′ 𝑎 𝑘 𝑙𝑔𝑤 𝑚 sc pkb sig
'sha2_128s': ParamSet{.sha2_128s, 16, 63, 7, 9, 12, 14, 4, 30, 1, 32, 7856}
'sha2_128f': ParamSet{.sha2_128f, 16, 66, 22, 3, 6, 33, 4, 34, 1, 32, 17088}
'sha2_192s': ParamSet{.sha2_192s, 24, 63, 7, 9, 14, 17, 4, 39, 3, 48, 16224}
'sha2_192f': ParamSet{.sha2_192f, 24, 66, 22, 3, 8, 33, 4, 42, 3, 48, 35664}
'sha2_256s': ParamSet{.sha2_256s, 32, 64, 8, 8, 14, 22, 4, 47, 5, 64, 29792}
'sha2_256f': ParamSet{.sha2_256f, 32, 68, 17, 4, 9, 35, 4, 49, 5, 64, 49856}
// SHAKE family
'shake_128s': ParamSet{.shake_128s, 16, 63, 7, 9, 12, 14, 4, 30, 1, 32, 7856}
'shake_128f': ParamSet{.shake_128f, 16, 66, 22, 3, 6, 33, 4, 34, 1, 32, 17088}
'shake_192s': ParamSet{.shake_192s, 24, 63, 7, 9, 14, 17, 4, 39, 3, 48, 16224}
'shake_192f': ParamSet{.shake_192f, 24, 66, 22, 3, 8, 33, 4, 42, 3, 48, 35664}
'shake_256s': ParamSet{.shake_256s, 32, 64, 8, 8, 14, 22, 4, 47, 5, 64, 29792}
'shake_256f': ParamSet{.shake_256f, 32, 68, 17, 4, 9, 35, 4, 49, 5, 64, 49856}
}
fn ParamSet.from_kind(k Kind) ParamSet {
return paramset[k.str()]
}
// Some helpers
//
fn key_algo_name(key &C.EVP_PKEY) &char {
name := voidptr(C.EVP_PKEY_get0_type_name(key))
assert name != 0
return name
}
fn key_type_name(key &C.EVP_PKEY) &char {
kn := voidptr(C.EVP_PKEY_get0_type_name(key))
assert kn != 0
return kn
}
fn key_description(key &C.EVP_PKEY) &char {
kd := voidptr(C.EVP_PKEY_get0_description(key))
assert kd != 0
return kd
}