x.crypto.slhdsa: add a SLH-DSA implementation, a stateless hash-based DSA, a post quantum cryptographic module (#24086)

This commit is contained in:
blackshirt 2025-04-01 18:01:26 +07:00 committed by GitHub
parent 12afd051bb
commit a8a4507237
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1513 additions and 0 deletions

View File

@ -0,0 +1,75 @@
# slhdsa
Experimental module of NIST FIPS-205 Stateless Hash-Based Digital Signature Standard (SLH-DSA) in V
## About
SLH-DSA was a quantum resistent cryptographic digital signature standard that was approved and publicly published by NIST at August, 2024. Its availables on [NIST FIPS 205](https://csrc.nist.gov/pubs/fips/205/final). <br>
SLH-DSA allow builds relatively big signaturue size with small key (`16 - 32` bytes key).
The signatures range from `±8K - ±50K` depending on the type chosen.
> [!NOTE]
> This module wraps and written on top of SLH-DSA functionality availables on latest release
> of recent OpenSSL library. Based on the history, this functionality was added in OpenSSL 3.5.
> So, make sure, you have required version of OpenSSL library installed. For simple guides how
> to build and install latest OpenSSL library on unix-like box,
> see [install-latest-ssl.md](https://github.com/vlang/v/blob/master/vlib/x/crypto/slhdsa/install-latest-ssl.md)
## Basic
SLH-DSA signature scheme is constructed using other hash-based signature schemes as components.
SLH-DSA was comes with set of predefined parameter that describes security categories, ie:<br>
- What is underlying hash algorithm used in the mean of process. <br>
The standard defines two's hash algorithm family availables, `sha2` family and `shake` family
- Security bits number of parameter set
- Whether the parameter set was designed to create relatively small signatures ('s') or to have
relatively fast signature generation ('f').<br>
See Table. 2 SLH-DSA parameter sets in the standard document. <br>
This parameter set represented by this opaque on this module:
```codeblock
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
}
```
Example
-------
```v cgen
import x.crypto.slhdsa
fn main() {
// you can choose and pass the kind of the SLH-DSA parameter to the constructor
opt := slhdsa.KeyOpts{
kind: .sha2_128s
// other options was availables
}
mut pv := slhdsa.PrivateKey.new(opt)!
// Example message
msg := 'SLH-DSA example message'.bytes()
// Sign a message using constructed key
sig := pv.sign(msg)!
// Then the public key part can verify this signature
mut pb := pv.public_key()!
verified := pb.verify(sig, msg)!
assert verified // true
// release the resource
pv.free()
pb.free()
}
```

405
vlib/x/crypto/slhdsa/base.v Normal file
View File

@ -0,0 +1,405 @@
// 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. <br>
// See Table 2. SLH-DSA parameter sets of the Chapter 11. Parameter Sets<br>
// 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
}

View File

@ -0,0 +1,187 @@
// 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.
// vtest build: has_modern_openssl?
module slhdsa
import rand
import encoding.hex
fn test_privatekey_from_bytes() ! {
// for sha2_128s
priv := hex.decode('4329f96b412b5d5ca9c730c9d54e52595265f6fc3ea67c183aa7043d90e949b8fd320c8fc0fa6a23462483535f131b9ea265848e7bb5f4d3cfd99de2595382b3')!
mut pv := PrivateKey.from_bytes(priv, .sha2_128s)!
sig := pv.sign('test'.bytes())!
mut pb := pv.public_key()!
status := pb.verify(sig, 'test'.bytes())!
assert status == true
pv.free()
pb.free()
}
fn test_pvkey_from_seed() ! {
kind := Kind.sha2_128s
seed := rand.bytes(3 * kind.nsize())!
mut pv := PrivateKey.from_seed(seed, kind)!
msg := 'Hello'.bytes()
sig := pv.sign(msg)!
mut pb := pv.public_key()!
verified := pb.verify(sig, msg)!
assert verified
pv.free()
pb.free()
}
fn test_privkey_signing() ! {
mut pv := PrivateKey.new(kind: .shake_256f)!
assert pv.bytes()!.len == 128
assert pv.public_bytes()!.len == 64
// type name
chr := key_type_name(pv.key)
assert unsafe { chr.vstring() } == 'SLH-DSA-SHAKE-256f'
msg := 'Hello'.bytes()
sig := slhdsa_do_sign(pv.key, msg)!
assert sig.len == 49856
verified := slhdsa_do_verify(pv.key, sig, msg)!
assert verified
mut pb := pv.public_key()!
sig2 := pv.sign(msg)!
status := pb.verify(sig, msg)!
status2 := pb.verify(sig2, msg)!
assert status
assert status2
pv.free()
pb.free()
}
fn test_basic_properties() ! {
mut pv := PrivateKey.new()!
desc := key_description(pv.key)
unsafe { desc.vstring() } == 'OpenSSL SLH-DSA-SHA2-128f implementation'
n := C.EVP_PKEY_is_a(pv.key, ln_slhdsa_sha2_128s)
assert n == 1 // true
assert C.EVP_PKEY_size(pv.key) == 7856
assert C.EVP_PKEY_get_bits(pv.key) == 256
assert C.EVP_PKEY_get_security_bits(pv.key) == 128
mut pv2 := PrivateKey.new(kind: .sha2_128f)!
n2 := C.EVP_PKEY_is_a(pv2.key, ln_slhdsa_sha2_128f)
assert n2 == 1 // true
assert C.EVP_PKEY_size(pv2.key) == 17088
assert C.EVP_PKEY_get_bits(pv2.key) == 256
assert C.EVP_PKEY_get_security_bits(pv2.key) == 128
mut pv3 := PrivateKey.new(kind: .sha2_192s)!
n3 := C.EVP_PKEY_is_a(pv3.key, ln_slhdsa_sha2_192s)
assert n3 == 1 // true
assert C.EVP_PKEY_size(pv3.key) == 16224
assert C.EVP_PKEY_get_bits(pv3.key) == 384
assert C.EVP_PKEY_get_security_bits(pv3.key) == 192
mut pv4 := PrivateKey.new(kind: .shake_256f)!
n4 := C.EVP_PKEY_is_a(pv4.key, ln_slhdsa_shake_256f)
assert n4 == 1 // true
assert C.EVP_PKEY_size(pv4.key) == 49856
assert C.EVP_PKEY_get_bits(pv4.key) == 512
assert C.EVP_PKEY_get_security_bits(pv4.key) == 256
pv.free()
pv2.free()
pv3.free()
pv4.free()
}
struct Slh128sSeed {
mut:
seed string
pubk string
priv string
}
// test SLH-DSA-SHA2_128s based on samples data
fn test_privatekey_slhdsa_128s_from_seed_with_sample_data() ! {
for obj in sample_slhdsa_128s_seed {
seed := hex.decode(obj.seed)!
k_opt := KeyOpts{
kind: .sha2_128s
flag: 1
seed: seed
}
mut pv := PrivateKey.new(k_opt)!
assert pv.bytes()! == hex.decode(obj.priv)!
assert pv.public_bytes()! == hex.decode(obj.pubk)!
pv.free()
}
}
// This sample data was copied and adapted from boringssl test data in slh_keygen.txt
// at https://boringssl.googlesource.com/boringssl.git/+/chromium-stable/crypto/slhdsa/slhdsa_keygen.txt
// Its likely only support for SLH-DSA-SHA2_128s
const sample_slhdsa_128s_seed = [
Slh128sSeed{
seed: '2f896d61d9cd9038ca303394fadaa22a24ac5ec1d86a989ca2196c3c8632419c1a05a42fe300e87b16aee116cb2e2363'
pubk: '1a05a42fe300e87b16aee116cb2e236358e2c3e62632c9de03d08a535a0eb7e7'
priv: '2f896d61d9cd9038ca303394fadaa22a24ac5ec1d86a989ca2196c3c8632419c1a05a42fe300e87b16aee116cb2e236358e2c3e62632c9de03d08a535a0eb7e7'
},
Slh128sSeed{
seed: '87dd614ea188940d93c7df9943caa283cbdf2ccc4940e7007b7b6c799e95e160bbaca7b93fec1a097e115f10d18e6611'
pubk: 'bbaca7b93fec1a097e115f10d18e66112aa087f0eeed95be3ab301ebc812406f'
priv: '87dd614ea188940d93c7df9943caa283cbdf2ccc4940e7007b7b6c799e95e160bbaca7b93fec1a097e115f10d18e66112aa087f0eeed95be3ab301ebc812406f'
},
Slh128sSeed{
seed: '0436b480d04c4c47f5ecd1e4b94f103988c5f804913fac77a96572cdc26f6a92f18d59d50b6b13f71821526f3aa800a8'
pubk: 'f18d59d50b6b13f71821526f3aa800a8cde5ccb604485df0c6f14cd64b60979c'
priv: '0436b480d04c4c47f5ecd1e4b94f103988c5f804913fac77a96572cdc26f6a92f18d59d50b6b13f71821526f3aa800a8cde5ccb604485df0c6f14cd64b60979c'
},
Slh128sSeed{
seed: 'b9341d201d49b337c84e6ac2c26361e1f5dcab582f404fe89739df4ec64eccdd04519d0d7c1ad543cd1b479602e41784'
pubk: '04519d0d7c1ad543cd1b479602e417848aeb88695d05f531a8415a4365b62983'
priv: 'b9341d201d49b337c84e6ac2c26361e1f5dcab582f404fe89739df4ec64eccdd04519d0d7c1ad543cd1b479602e417848aeb88695d05f531a8415a4365b62983'
},
Slh128sSeed{
seed: 'cbcaec354573e8cee1373b7aced67cdc683ed4491e17ef1dff9a9163fe7bcf74d91e0df4756c799ada2a0cf1b1aada1d'
pubk: 'd91e0df4756c799ada2a0cf1b1aada1d1fddc3692e8909f2185e11654dc84def'
priv: 'cbcaec354573e8cee1373b7aced67cdc683ed4491e17ef1dff9a9163fe7bcf74d91e0df4756c799ada2a0cf1b1aada1d1fddc3692e8909f2185e11654dc84def'
},
Slh128sSeed{
seed: '1d92b6cc53e5bb08841b2669f1fb481c62db8f494e1bac12a6c6163142842a33771c182ffb096207da6476874ebdaf15'
pubk: '771c182ffb096207da6476874ebdaf158c0143adbd0f66057a5180c96467d31d'
priv: '1d92b6cc53e5bb08841b2669f1fb481c62db8f494e1bac12a6c6163142842a33771c182ffb096207da6476874ebdaf158c0143adbd0f66057a5180c96467d31d'
},
Slh128sSeed{
seed: '94b11b26e2ec430f413f353fae96c144915e7ec84e9c786c895593b859bdb0edd54cc085cb4f4265f8288a9d67ae5806'
pubk: 'd54cc085cb4f4265f8288a9d67ae5806ac638ad976a1fc8e2c7c5b9db2ea0573'
priv: '94b11b26e2ec430f413f353fae96c144915e7ec84e9c786c895593b859bdb0edd54cc085cb4f4265f8288a9d67ae5806ac638ad976a1fc8e2c7c5b9db2ea0573'
},
Slh128sSeed{
seed: '9078f099c6420c6bb1303977029b1e1bac048a4375f3c48862f74a6ccedb5be4755e624791fbfd27cd195ce3aa32c62c'
pubk: '755e624791fbfd27cd195ce3aa32c62cd9030c131ca25c8c442c4ac0b27ae548'
priv: '9078f099c6420c6bb1303977029b1e1bac048a4375f3c48862f74a6ccedb5be4755e624791fbfd27cd195ce3aa32c62cd9030c131ca25c8c442c4ac0b27ae548'
},
Slh128sSeed{
seed: 'e0c67d9fb2434e27165c861c0f1040ed58c13b7f5e9517d000770bd3e88fbdb48cdf5f1cc1aa770a8bafcf1b8c5c843b'
pubk: '8cdf5f1cc1aa770a8bafcf1b8c5c843ba659b85d99ca18f555b8452f411b519c'
priv: 'e0c67d9fb2434e27165c861c0f1040ed58c13b7f5e9517d000770bd3e88fbdb48cdf5f1cc1aa770a8bafcf1b8c5c843ba659b85d99ca18f555b8452f411b519c'
},
Slh128sSeed{
seed: 'ccae2481cecc4a55aa2afe4874b0ce501984d48423fc2452f24f49b2a68ef9b8d66807ecbfd2dc999d274d0d6414afc3'
pubk: 'd66807ecbfd2dc999d274d0d6414afc3dfba899a4f7799cf74e98dc1c82a26ab'
priv: 'ccae2481cecc4a55aa2afe4874b0ce501984d48423fc2452f24f49b2a68ef9b8d66807ecbfd2dc999d274d0d6414afc3dfba899a4f7799cf74e98dc1c82a26ab'
},
]

View File

@ -0,0 +1,75 @@
// 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
// The SLH-DSA functionality was added in recent OpenSSL 3.5 version.
// So, we would panic if this module run on unmeet version on the current stock.
fn init() {
if C.OpenSSL_version_num() < u64(0x030500000) {
panic('Your OpenSSL version ${C.OpenSSL_version_num().hex()} does not support for SLH-DSA')
}
}
// #define LN_SLH_DSA_SHA2_128s "SLH-DSA-SHA2-128s"
// #define NID_SLH_DSA_SHA2_128s 1460
const nid_slhdsa_sha2_128s = C.NID_SLH_DSA_SHA2_128s
const ln_slhdsa_sha2_128s = &char(C.LN_SLH_DSA_SHA2_128s)
// #define LN_SLH_DSA_SHA2_128f "SLH-DSA-SHA2-128f"
// #define NID_SLH_DSA_SHA2_128f 1461
const nid_slhdsa_sha2_128f = C.NID_SLH_DSA_SHA2_128f
const ln_slhdsa_sha2_128f = &char(C.LN_SLH_DSA_SHA2_128f)
// #define LN_SLH_DSA_SHA2_192s "SLH-DSA-SHA2-192s"
// #define NID_SLH_DSA_SHA2_192s 1462
const ln_slhdsa_sha2_192s = &char(C.LN_SLH_DSA_SHA2_192s)
const nid_slhdsa_sha2_192s = C.NID_SLH_DSA_SHA2_192s
// #define LN_SLH_DSA_SHA2_192f "SLH-DSA-SHA2-192f"
// #define NID_SLH_DSA_SHA2_192f 1463
const ln_slhdsa_sha2_192f = &char(C.LN_SLH_DSA_SHA2_192f)
const nid_slhdsa_sha2_192f = C.NID_SLH_DSA_SHA2_192f
// #define LN_SLH_DSA_SHA2_256s "SLH-DSA-SHA2-256s"
// #define NID_SLH_DSA_SHA2_256s 1464
const ln_slhdsa_sha2_256s = &char(C.LN_SLH_DSA_SHA2_256s)
const nid_slhdsa_sha2_256s = C.NID_SLH_DSA_SHA2_256s
// #define LN_SLH_DSA_SHA2_256f "SLH-DSA-SHA2-256f"
// #define NID_SLH_DSA_SHA2_256f 1465
const ln_slhdsa_sha2_256f = &char(C.LN_SLH_DSA_SHA2_256f)
const nid_slhdsa_sha2_256f = C.NID_SLH_DSA_SHA2_256f
// #define LN_SLH_DSA_SHAKE_128s "SLH-DSA-SHAKE-128s"
// #define NID_SLH_DSA_SHAKE_128s 1466
const ln_slhdsa_shake_128s = &char(C.LN_SLH_DSA_SHAKE_128s)
const nid_slhdsa_shake_128s = C.NID_SLH_DSA_SHAKE_128s
// #define LN_SLH_DSA_SHAKE_128f "SLH-DSA-SHAKE-128f"
// #define NID_SLH_DSA_SHAKE_128f 1467
const ln_slhdsa_shake_128f = &char(C.LN_SLH_DSA_SHAKE_128f)
const nid_slhdsa_shake_128f = C.NID_SLH_DSA_SHAKE_128f
// #define LN_SLH_DSA_SHAKE_192s "SLH-DSA-SHAKE-192s"
// #define NID_SLH_DSA_SHAKE_192s 1468
const ln_slhdsa_shake_192s = &char(C.LN_SLH_DSA_SHAKE_192s)
const nid_slhdsa_shake_192s = C.NID_SLH_DSA_SHAKE_192s
// #define LN_SLH_DSA_SHAKE_192f "SLH-DSA-SHAKE-192f"
// #define NID_SLH_DSA_SHAKE_192f 1469
const ln_slhdsa_shake_192f = &char(C.LN_SLH_DSA_SHAKE_192f)
const nid_slhdsa_shake_192f = C.NID_SLH_DSA_SHAKE_192f
// #define LN_SLH_DSA_SHAKE_256s "SLH-DSA-SHAKE-256s"
// #define NID_SLH_DSA_SHAKE_256s 1470
const ln_slhdsa_shake_256s = &char(C.LN_SLH_DSA_SHAKE_256s)
const nid_slhdsa_shake_256s = C.NID_SLH_DSA_SHAKE_256s
// #define LN_SLH_DSA_SHAKE_256f "SLH-DSA-SHAKE-256f"
// #define NID_SLH_DSA_SHAKE_256f 1471
const ln_slhdsa_shake_256f = &char(C.LN_SLH_DSA_SHAKE_256f)
const nid_slhdsa_shake_256f = C.NID_SLH_DSA_SHAKE_256f
const evp_pkey_keypair = C.EVP_PKEY_KEYPAIR
const evp_pkey_public_key = C.EVP_PKEY_PUBLIC_KEY

View File

@ -0,0 +1,26 @@
module main
import x.crypto.slhdsa
fn main() {
// creates default key
mut pvkey := slhdsa.PrivateKey.new()!
// example of message
xmsg := 'Sample message'.bytes()
// signing the message with the key
sig := pvkey.sign(xmsg)!
// example of public key
mut pbkey := pvkey.public_key()!
// verify signature with the public key
verified := pbkey.verify(sig, xmsg)!
dump(verified) // true
assert verified == true
// release the resources
pvkey.free()
pbkey.free()
}

View File

@ -0,0 +1,35 @@
This `slhdsa` module requires latest OpenSSL library and development headers.
It required minimum of OpenSLL 3.5 version.
Likely most of the linux (unix) distros does not ships with this version on this time
of writing, at March, 2025.
You can manually download and install OpenSSL library by your self. This documents acts as a simple
guide on how to build and install latest OpenSSL library to your box.
This guide was for unix-like system, for others, see the guides more detail goes to
[install.md](https://github.com/openssl/openssl/blob/master/INSTALL.md#building-openssl)<br>
1. Go to https://openssl-library.org/source/ and download the latest version
```bash
$ wget -v https://github.com/openssl/openssl/releases/download/openssl-3.5.0-beta1/openssl-3.5.0-beta1.tar.gz
```
2. Extract your downloaded file
```bash
$ tar -xzvf openssl-3.5.0-beta1.tar.gz
$ cd openssl-3.5.0-beta1
```
3. Configure the build flag, this module for optional build requires to use `/opt/ssl` prefix.
```bash
$ ./Configure --prefix=/opt/ssl --openssldir=/opt/ssl
$ make
$ sudo make install
```
By succesfully this step, the latest OpenSSL has been built and installed onto
configured path. You need to tell the system to add this path. <br>
4. Add `/opt/ssl/lib64` entry onto `/etc/ld.so.conf.d/libcrypto.conf`
5. Run `ldconfig`
```bash
$sudo ldconfig
```
6. Verify if the modules works.

View File

@ -0,0 +1,99 @@
// 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
// TODO: remove this when the 3.5 version was commonly availables on the stock.
#flag linux -I/opt/ssl/include/openssl
#flag linux -I/opt/ssl/include/crypto
#flag linux -I/opt/ssl/include
#flag linux -L/opt/ssl/lib64
// Standard path
#flag linux -I/usr/local/include/openssl
#flag linux -I/usr/local/include/crypto
#flag linux -L/usr/local/lib64
#flag darwin -L /opt/homebrew/opt/openssl/lib -I /opt/homebrew/opt/openssl/include
#flag -I/usr/include/openssl
#flag -lcrypto
#flag darwin -I/usr/local/opt/openssl/include
#flag darwin -L/usr/local/opt/openssl/lib
#include <openssl/obj_mac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/param_build.h>
@[typedef]
struct C.EVP_PKEY {}
fn C.EVP_PKEY_new() &C.EVP_PKEY
fn C.EVP_PKEY_Q_keygen(ctx voidptr, propq &char, tipe &char) &C.EVP_PKEY
fn C.EVP_PKEY_is_a(pkey &C.EVP_PKEY, name &char) int
fn C.EVP_PKEY_size(key &C.EVP_PKEY) int
fn C.EVP_PKEY_dup(key &C.EVP_PKEY) &C.EVP_PKEY
fn C.EVP_PKEY_free(key &C.EVP_PKEY)
fn C.EVP_PKEY_get_octet_string_param(pkey &C.EVP_PKEY, key_name &char, buf &u8, max_buf_sz int, out_len &usize) int
fn C.EVP_PKEY_print_private(out &C.BIO, pkey &C.EVP_PKEY, indent int, pctx voidptr) int
fn C.EVP_PKEY_print_public(out &C.BIO, pkey &C.EVP_PKEY, indent int, pctx voidptr) int
fn C.EVP_PKEY_keygen_init(ctx &C.EVP_PKEY_CTX) int // 1 success
fn C.EVP_PKEY_keygen(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY) int
fn C.EVP_PKEY_CTX_set_params(ctx &C.EVP_PKEY_CTX, params &C.OSSL_PARAM) int
fn C.EVP_PKEY_get0_type_name(key &C.EVP_PKEY) &char
fn C.EVP_PKEY_get0_description(key &C.EVP_PKEY) &char
fn C.EVP_PKEY_get_bits(pkey &C.EVP_PKEY) int
fn C.EVP_PKEY_get_security_bits(pkey &C.EVP_PKEY) int
fn C.EVP_PKEY_sign_init_ex(ctx &C.EVP_PKEY_CTX, params &C.OSSL_PARAM) int
fn C.EVP_PKEY_sign_message_init(ctx &C.EVP_PKEY_CTX, algo &C.EVP_SIGNATURE, params &C.OSSL_PARAM) int
fn C.EVP_PKEY_sign(ctx &C.EVP_PKEY_CTX, sig &char, siglen &usize, tbs &char, tbslen int) int
fn C.EVP_PKEY_verify_message_init(ctx &C.EVP_PKEY_CTX, algo &C.EVP_SIGNATURE, params &C.OSSL_PARAM) int
fn C.EVP_PKEY_verify(ctx &C.EVP_PKEY_CTX, sig &char, siglen &int, tbs &char, tbslen int) int
fn C.EVP_PKEY_set_octet_string_param(key &C.EVP_PKEY, key_name &u8, buf &u8, bsize int) int
fn C.EVP_PKEY_fromdata_init(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_fromdata(ctx &C.EVP_PKEY_CTX, ppkey &&C.EVP_PKEY, selection int, params &C.OSSL_PARAM) int
fn C.EVP_PKEY_check(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_private_check(ctx &C.EVP_PKEY_CTX) int
fn C.EVP_PKEY_public_check(ctx &C.EVP_PKEY_CTX) int
@[typedef]
struct C.EVP_PKEY_CTX {}
fn C.EVP_PKEY_CTX_free(ctx &C.EVP_PKEY_CTX)
fn C.EVP_PKEY_CTX_new_from_name(libctx voidptr, name &char, pq voidptr) &C.EVP_PKEY_CTX
fn C.EVP_PKEY_CTX_new_from_pkey(libctx voidptr, pkey &C.EVP_PKEY, pq voidptr) &C.EVP_PKEY_CTX
fn C.OpenSSL_version_num() u64
@[typedef]
struct C.EVP_SIGNATURE {}
fn C.EVP_SIGNATURE_free(signature &C.EVP_SIGNATURE)
fn C.EVP_SIGNATURE_fetch(ctx voidptr, algorithm &char, properties voidptr) &C.EVP_SIGNATURE
fn C.EVP_SIGNATURE_get0_name(signature &C.EVP_SIGNATURE) &char
@[typedef]
struct C.OSSL_PARAM {}
@[typedef]
struct C.OSSL_PARAM_BLD {}
fn C.OSSL_PARAM_free(params &C.OSSL_PARAM)
fn C.OSSL_PARAM_BLD_free(param_bld &C.OSSL_PARAM_BLD)
fn C.OSSL_PARAM_BLD_new() &C.OSSL_PARAM_BLD
fn C.OSSL_PARAM_BLD_push_int(bld &C.OSSL_PARAM_BLD, key &u8, val int) int
fn C.OSSL_PARAM_BLD_push_octet_string(bld &C.OSSL_PARAM_BLD, key &u8, buf voidptr, bsize int) int
fn C.OSSL_PARAM_BLD_to_param(bld &C.OSSL_PARAM_BLD) &C.OSSL_PARAM
@[typedef]
struct C.BIO_METHOD {}
@[typedef]
struct C.BIO {}
fn C.BIO_new(t &C.BIO_METHOD) &C.BIO
fn C.BIO_read_ex(b &C.BIO, data voidptr, dlen int, readbytes &usize) int
fn C.BIO_free_all(a &C.BIO)
fn C.BIO_s_mem() &C.BIO_METHOD

View File

@ -0,0 +1,380 @@
// 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
// PrivateKey represents SLH-DSA keypair.
pub struct PrivateKey {
key &C.EVP_PKEY
}
// new creates a new SLH-DSA PrivateKey based on the supplied options.
// By default, it will create a `SLH-DSA-SHA2-128s` key with random generator.
// See `enum Kind` for all of availables choice's type (kind) of key.
// Its also support to generate keys based on the seed or private bytes through
// provided options. See available options in `KeyOpts`.
// Example:
// ```
// PrivateKey.new()!
// PrivateKey.new(kind: .sha2_128s)
// ```
pub fn PrivateKey.new(opt KeyOpts) !PrivateKey {
match opt.flag {
// default random generated key, its recommended way
0 {
kind := opt.kind.long_name()
key := C.EVP_PKEY_Q_keygen(0, 0, kind)
if key == 0 {
return error('EVP_PKEY_Q_keygen failed')
}
return PrivateKey{
key: key
}
}
// use seed bytes, usually for testing purposes
1 {
// if you dont provides seed bytes, it will discarded, and use random one
return PrivateKey.from_seed(opt.seed, opt.kind)!
}
// use private bytes, usually for testing purposes
2 {
// if you dont provides private bytes, it will discarded, and use random one
return PrivateKey.from_bytes(opt.priv, opt.kind)!
}
else {
return error('Unsupported flag')
}
}
}
// sign signs the message using this key under desired options in opt.
pub fn (pv PrivateKey) sign(msg []u8, opt SignerOpts) ![]u8 {
if msg.len == 0 {
return error('Zero length of messages')
}
if opt.context.len > 255 {
return error('The context string size was over than 255')
}
out := slhdsa_do_sign(pv.key, msg, opt)!
return out
}
// verify verifies signature for the message msg under provided options.
// Its possible because of under the hood, private key is a key pair.
pub fn (pv PrivateKey) verify(sig []u8, msg []u8, opt SignerOpts) !bool {
if opt.context.len > 255 {
return error('The context string size was over than 255')
}
return slhdsa_do_verify(pv.key, sig, msg, opt)!
}
const default_bioread_bufsize = 4096
// dump_key represents PrivateKey in human readable string.
pub fn (pv PrivateKey) dump_key() !string {
bo := C.BIO_new(C.BIO_s_mem())
if bo == 0 {
C.BIO_free_all(bo)
return error('BIO_new failed')
}
n := C.EVP_PKEY_print_private(bo, pv.key, 2, 0)
if n <= 0 {
C.BIO_free_all(bo)
return error('print private failed')
}
size := usize(0)
buf := []u8{len: default_bioread_bufsize}
m := C.BIO_read_ex(bo, buf.data, buf.len, &size)
if m <= 0 {
unsafe { buf.free() }
C.BIO_free_all(bo)
return error('BIO_read_ex failed')
}
output := buf[..size].bytestr()
// Cleans up and return the result
unsafe { buf.free() }
C.BIO_free_all(bo)
return output
}
// public_key gets the the public part of this private key as a PublicKey.
pub fn (pv PrivateKey) public_key() !PublicKey {
pbkey := C.EVP_PKEY_dup(pv.key)
// we clears out the private bits from the key
// by setting it into null
n := C.EVP_PKEY_set_octet_string_param(pbkey, c'priv', 0, 0)
if n <= 0 {
C.EVP_PKEY_free(pbkey)
return error('EVP_PKEY_set_octet_string_param')
}
return PublicKey{
key: pbkey
}
}
// free releases memory occupied by this key.
pub fn (mut pv PrivateKey) free() {
C.EVP_PKEY_free(pv.key)
}
// PublicKey represents public key part from the key.
pub struct PublicKey {
key &C.EVP_PKEY
}
// verify verifies signature sig for messages msg under options provided.
pub fn (pb PublicKey) verify(sig []u8, msg []u8, opt SignerOpts) !bool {
if opt.context.len > 255 {
return error('The context string size was over than 255')
}
return slhdsa_do_verify(pb.key, sig, msg, opt)!
}
// dump_key dumps this public key as a human readable string.
pub fn (pb PublicKey) dump_key() !string {
bo := C.BIO_new(C.BIO_s_mem())
n := C.EVP_PKEY_print_public(bo, pb.key, 2, 0)
if n <= 0 {
C.BIO_free_all(bo)
return error('EVP_PKEY_print_public failed')
}
size := usize(0)
mut m := C.BIO_read_ex(bo, 0, default_bioread_bufsize, &size)
mut buf := []u8{len: int(size)}
m = C.BIO_read_ex(bo, buf.data, buf.len, &size)
if m <= 0 {
C.BIO_free_all(bo)
return error('BIO_read_ex failed')
}
output := buf[..size].bytestr()
// Cleans up
unsafe { buf.free() }
C.BIO_free_all(bo)
return output
}
// free releases memory occupied by this public key
pub fn (mut pb PublicKey) free() {
C.EVP_PKEY_free(pb.key)
}
// the biggest supported size of seed was 32 bytes length, where private key contains 4*32 bytes
const default_privkey_buffer = 128
fn (pv PrivateKey) bytes() ![]u8 {
priv_len := usize(0)
priv := []u8{len: default_privkey_buffer}
n := C.EVP_PKEY_get_octet_string_param(pv.key, c'priv', priv.data, priv.len, &priv_len)
if n <= 0 {
return error('EVP_PKEY_get_octet_string_param failed')
}
out := priv[..int(priv_len)].clone()
unsafe { priv.free() }
return out
}
// the biggest public key size was 64 bytes length, where public key
// was contains 2*n, so we relaxed it to 2*72 bytes
const default_pubkey_buffer = 144
fn (pv PrivateKey) public_bytes() ![]u8 {
pblen := usize(0)
buf := []u8{len: default_pubkey_buffer}
n := C.EVP_PKEY_get_octet_string_param(pv.key, c'pub', buf.data, buf.len, &pblen)
if n <= 0 {
return error('EVP_PKEY_get_octet_string_param failed')
}
out := buf[..pblen].clone()
unsafe { buf.free() }
return out
}
// Signing and verifying Helpers
//
// slhdsa_do_sign performs signing msg with SLH-DSA key.
@[inline]
fn slhdsa_do_sign(key &C.EVP_PKEY, msg []u8, opt SignerOpts) ![]u8 {
sctx := C.EVP_PKEY_CTX_new_from_pkey(0, key, 0)
if sctx == 0 {
C.EVP_PKEY_CTX_free(sctx)
return error('EVP_PKEY_CTX_new_from_pkey failed')
}
// gets algo name from key
algo_name := key_type_name(key)
sig_alg := C.EVP_SIGNATURE_fetch(0, algo_name, 0)
if sig_alg == 0 {
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('EVP_SIGNATURE_fetch failed')
}
param_bld := C.OSSL_PARAM_BLD_new()
if param_bld == 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_new failed')
}
// if context string was set into non-null string, then we set
// `context-string` params into context key generator.
if opt.context.len > 0 {
// OSSL_PARAM_octet_string("context-string", (unsigned char *)"A context string", 33),
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'context-string', opt.context.str,
opt.context.len)
if o <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push_octet_string FAILED')
}
}
// handle entropy testing
if opt.entropy.len > 0 {
if opt.encoding != 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('encoding need 0 for testing')
}
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'test-entropy', opt.entropy.data,
opt.entropy.len)
if o <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push_octet_string failed')
}
oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
if oo <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push_int FAILED')
}
}
if opt.encoding == 0 {
oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
if oo <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push_int FAILED')
}
}
// build params
params := C.OSSL_PARAM_BLD_to_param(param_bld)
// Then init the context with this params
x := C.EVP_PKEY_sign_message_init(sctx, sig_alg, params)
if x <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('EVP_PKEY_sign_message_init failed')
}
sig_len := usize(C.EVP_PKEY_size(key))
buf := []u8{len: int(sig_len)}
// Do signing the msg and updates the sig_len.
m := C.EVP_PKEY_sign(sctx, buf.data, &sig_len, msg.data, msg.len)
if m <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('EVP_PKEY_sign_message_init failed')
}
// return the copy of the sig
sig := buf[..sig_len].clone()
// cleans up
unsafe { buf.free() }
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return sig
}
// slhdsa_do_verify performs verifying of provided signature in sign for the msg message.
@[inline]
fn slhdsa_do_verify(key &C.EVP_PKEY, sign []u8, msg []u8, opt SignerOpts) !bool {
sctx := C.EVP_PKEY_CTX_new_from_pkey(0, key, 0)
if sctx == 0 {
C.EVP_PKEY_CTX_free(sctx)
return error('EVP_PKEY_CTX_new_from_pkey failed')
}
// get the algo signature name from the key
algo_name := key_type_name(key)
sig_alg := C.EVP_SIGNATURE_fetch(0, algo_name, 0)
if sig_alg == 0 {
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('EVP_SIGNATURE_fetch failed')
}
param_bld := C.OSSL_PARAM_BLD_new()
if param_bld == 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_new failed')
}
// handle context option
if opt.context.len > 0 {
o := C.OSSL_PARAM_BLD_push_octet_string(param_bld, c'context-string', opt.context.str,
opt.context.len)
if o <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push failed')
}
}
// for testing
if opt.encoding == 0 {
oo := C.OSSL_PARAM_BLD_push_int(param_bld, c'message-encoding', opt.encoding)
if oo <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push FAILED')
}
}
// build params
params := C.OSSL_PARAM_BLD_to_param(param_bld)
n := C.EVP_PKEY_verify_message_init(sctx, sig_alg, params)
if n <= 0 {
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
return error('OSSL_PARAM_BLD_push FAILED')
}
// do verifying of the signature.
m := C.EVP_PKEY_verify(sctx, sign.data, sign.len, msg.data, msg.len)
// Cleans up
C.EVP_SIGNATURE_free(sig_alg)
C.EVP_PKEY_CTX_free(sctx)
C.OSSL_PARAM_BLD_free(param_bld)
C.OSSL_PARAM_free(params)
return m == 1
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,28 @@
// 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.
// vtest build: has_modern_openssl?
import x.crypto.slhdsa
fn test_slhdsa_basic_default_functionality() ! {
// creates default key
mut pvkey := slhdsa.PrivateKey.new()!
// example of message
xmsg := 'Sample message'.bytes()
// signing the message with the key
sig := pvkey.sign(xmsg)!
// example of public key
mut pbkey := pvkey.public_key()!
// verify signature with the public key
verified := pbkey.verify(sig, xmsg)!
println(verified)
assert verified == true
// release
pvkey.free()
pbkey.free()
}