mirror of
https://github.com/emmansun/gmsm.git
synced 2025-06-28 16:27:51 +08:00
slhdsa: SLH-DSA initialize
This commit is contained in:
parent
b634efb6ad
commit
9f0d175f2c
182
slhdsa/adrs.go
Normal file
182
slhdsa/adrs.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package slhdsa
|
||||||
|
|
||||||
|
type addressType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddressTypeWOTSHash addressType = iota
|
||||||
|
AddressTypeWOTSPK
|
||||||
|
AddressTypeTree
|
||||||
|
AddressTypeFORSTree
|
||||||
|
AddressTypeFORSRoots
|
||||||
|
AddressTypeWOTSPRF
|
||||||
|
AddressTypeFORSPRF
|
||||||
|
)
|
||||||
|
|
||||||
|
type adrsOperations interface {
|
||||||
|
setLayerAddress(l uint32)
|
||||||
|
setTreeAddress(t uint64)
|
||||||
|
setTypeAndClear(y addressType)
|
||||||
|
setKeyPairAddress(i uint32)
|
||||||
|
setChainAddress(i uint32)
|
||||||
|
setHashAddress(i uint32)
|
||||||
|
setTreeHeight(i uint32)
|
||||||
|
setTreeIndex(i uint32)
|
||||||
|
getKeyPairAddress() uint32
|
||||||
|
getTreeIndex() uint32
|
||||||
|
bytes() []byte
|
||||||
|
clone(source adrsOperations)
|
||||||
|
copyKeyPairAddress(source adrsOperations)
|
||||||
|
}
|
||||||
|
|
||||||
|
type adrs [32]byte
|
||||||
|
|
||||||
|
func (a *adrs) setLayerAddress(l uint32) {
|
||||||
|
a[0] = byte(l >> 24)
|
||||||
|
a[1] = byte(l >> 16)
|
||||||
|
a[2] = byte(l >> 8)
|
||||||
|
a[3] = byte(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setTreeAddress(t uint64) {
|
||||||
|
a[4+4] = byte(t >> 56)
|
||||||
|
a[4+5] = byte(t >> 48)
|
||||||
|
a[4+6] = byte(t >> 40)
|
||||||
|
a[4+7] = byte(t >> 32)
|
||||||
|
a[4+8] = byte(t >> 24)
|
||||||
|
a[4+9] = byte(t >> 16)
|
||||||
|
a[4+10] = byte(t >> 8)
|
||||||
|
a[4+11] = byte(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setTypeAndClear(y addressType) {
|
||||||
|
a[19] = byte(y)
|
||||||
|
clear(a[20:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setKeyPairAddress(i uint32) {
|
||||||
|
a[20] = byte(i >> 24)
|
||||||
|
a[21] = byte(i >> 16)
|
||||||
|
a[22] = byte(i >> 8)
|
||||||
|
a[23] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setChainAddress(i uint32) {
|
||||||
|
a[24] = byte(i >> 24)
|
||||||
|
a[25] = byte(i >> 16)
|
||||||
|
a[26] = byte(i >> 8)
|
||||||
|
a[27] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setHashAddress(i uint32) {
|
||||||
|
a[28] = byte(i >> 24)
|
||||||
|
a[29] = byte(i >> 16)
|
||||||
|
a[30] = byte(i >> 8)
|
||||||
|
a[31] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setTreeHeight(i uint32) {
|
||||||
|
a.setChainAddress(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) setTreeIndex(i uint32) {
|
||||||
|
a.setHashAddress(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) getKeyPairAddress() uint32 {
|
||||||
|
return uint32(a[20])<<24 | uint32(a[21])<<16 | uint32(a[22])<<8 | uint32(a[23])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) getTreeIndex() uint32 {
|
||||||
|
return uint32(a[28])<<24 | uint32(a[29])<<16 | uint32(a[30])<<8 | uint32(a[31])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) bytes() []byte {
|
||||||
|
return a[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) clone(b adrsOperations) {
|
||||||
|
copy(a[:], b.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrs) copyKeyPairAddress(b adrsOperations) {
|
||||||
|
copy(a[20:24], b.bytes()[20:24])
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAdrs() adrsOperations {
|
||||||
|
return &adrs{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type adrsc [22]byte
|
||||||
|
|
||||||
|
func (a *adrsc) setLayerAddress(l uint32) {
|
||||||
|
a[0] = byte(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setTreeAddress(t uint64) {
|
||||||
|
a[1] = byte(t >> 56)
|
||||||
|
a[2] = byte(t >> 48)
|
||||||
|
a[3] = byte(t >> 40)
|
||||||
|
a[4] = byte(t >> 32)
|
||||||
|
a[5] = byte(t >> 24)
|
||||||
|
a[6] = byte(t >> 16)
|
||||||
|
a[7] = byte(t >> 8)
|
||||||
|
a[8] = byte(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setTypeAndClear(y addressType) {
|
||||||
|
a[9] = byte(y)
|
||||||
|
clear(a[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setKeyPairAddress(i uint32) {
|
||||||
|
a[10] = byte(i >> 24)
|
||||||
|
a[11] = byte(i >> 16)
|
||||||
|
a[12] = byte(i >> 8)
|
||||||
|
a[13] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setChainAddress(i uint32) {
|
||||||
|
a[14] = byte(i >> 24)
|
||||||
|
a[15] = byte(i >> 16)
|
||||||
|
a[16] = byte(i >> 8)
|
||||||
|
a[17] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setHashAddress(i uint32) {
|
||||||
|
a[18] = byte(i >> 24)
|
||||||
|
a[19] = byte(i >> 16)
|
||||||
|
a[20] = byte(i >> 8)
|
||||||
|
a[21] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setTreeHeight(i uint32) {
|
||||||
|
a.setChainAddress(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) setTreeIndex(i uint32) {
|
||||||
|
a.setHashAddress(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) getKeyPairAddress() uint32 {
|
||||||
|
return uint32(a[10])<<24 | uint32(a[11])<<16 | uint32(a[12])<<8 | uint32(a[13])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) getTreeIndex() uint32 {
|
||||||
|
return uint32(a[18])<<24 | uint32(a[19])<<16 | uint32(a[20])<<8 | uint32(a[21])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) bytes() []byte {
|
||||||
|
return a[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) clone(b adrsOperations) {
|
||||||
|
copy(a[:], b.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adrsc) copyKeyPairAddress(b adrsOperations) {
|
||||||
|
copy(a[10:14], b.bytes()[10:14])
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAdrsC() adrsOperations {
|
||||||
|
return &adrsc{}
|
||||||
|
}
|
144
slhdsa/dsa.go
Normal file
144
slhdsa/dsa.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sign generates a pure SLH-DSA signature for the given message.
|
||||||
|
// The signature is deterministic if the addRand parameter is nil.
|
||||||
|
// If addRand is not nil, it must be of the same length as n.
|
||||||
|
//
|
||||||
|
// See FIPS 205 Algorithm 22 slh_sign
|
||||||
|
func (sk *PrivateKey) Sign(message, context, addRand []byte) ([]byte, error) {
|
||||||
|
if len(message) == 0 {
|
||||||
|
return nil, errors.New("slhdsa: empty message")
|
||||||
|
}
|
||||||
|
if len(addRand) > 0 && len(addRand) != int(sk.params.n) {
|
||||||
|
return nil, errors.New("slhdsa: addrnd should be nil (deterministic variant) or of length n")
|
||||||
|
}
|
||||||
|
ctxLen := len(context)
|
||||||
|
if ctxLen > MAX_CONTEXT_LEN {
|
||||||
|
return nil, errors.New("slhdsa: context too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
var mPrefix [MAX_CONTEXT_LEN + 2]byte
|
||||||
|
|
||||||
|
mPrefix[1] = byte(ctxLen)
|
||||||
|
if ctxLen > 0 {
|
||||||
|
copy(mPrefix[2:], context)
|
||||||
|
}
|
||||||
|
return sk.signInternal(mPrefix[:2+ctxLen], message, addRand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See FIPS 205 Algorithm 19 slh_sign_internal
|
||||||
|
func (sk *PrivateKey) signInternal(msgPrefix, message, addRand []byte) ([]byte, error) {
|
||||||
|
signatureStart := make([]byte, sk.params.sigLen)
|
||||||
|
adrs := sk.addressCreator()
|
||||||
|
|
||||||
|
// generate randomizer
|
||||||
|
if len(addRand) == 0 {
|
||||||
|
// substitute addRand with sk.PublicKey.seed for the deterministic variant
|
||||||
|
addRand = sk.PublicKey.seed[:sk.params.n]
|
||||||
|
}
|
||||||
|
sk.h.prfMsg(sk, addRand, msgPrefix, message, signatureStart)
|
||||||
|
R := signatureStart[:sk.params.n]
|
||||||
|
signature := signatureStart[sk.params.n:]
|
||||||
|
|
||||||
|
// compute message digest
|
||||||
|
var digest [MAX_M]byte
|
||||||
|
sk.h.hMsg(&sk.PublicKey, R, msgPrefix, message, digest[:])
|
||||||
|
// Grab the first mdLen() bytes of digest to use in fors_sign()
|
||||||
|
mdLen := sk.params.mdLen()
|
||||||
|
md := digest[:mdLen]
|
||||||
|
|
||||||
|
// Grab remaining bytes from digest to select tree and leaf id's
|
||||||
|
remaining := digest[mdLen:]
|
||||||
|
treeIdxLen := sk.params.treeIdxLen()
|
||||||
|
leafIdxLen := sk.params.leafIdxLen()
|
||||||
|
treeIdx := toInt(remaining[:treeIdxLen]) & sk.params.treeIdxMask()
|
||||||
|
remaining = remaining[treeIdxLen:]
|
||||||
|
leafIdx := uint32(toInt(remaining[:leafIdxLen]) & sk.params.leafIdxMask())
|
||||||
|
|
||||||
|
adrs.setTreeAddress(treeIdx)
|
||||||
|
adrs.setTypeAndClear(AddressTypeFORSTree)
|
||||||
|
adrs.setKeyPairAddress(leafIdx)
|
||||||
|
// generate the FORS signature and append it to the SLH-DSA signature
|
||||||
|
sk.forsSign(md, adrs, signature)
|
||||||
|
|
||||||
|
var pkFors [MAX_N]byte
|
||||||
|
// calculate the FORS public key using the generated FORS signature
|
||||||
|
signature = sk.forsPkFromSig(md, signature, adrs, pkFors[:])
|
||||||
|
// generate ht signature and append to the SLH-DSA signature
|
||||||
|
sk.htSign(pkFors[:sk.params.n], treeIdx, leafIdx, signature)
|
||||||
|
|
||||||
|
return signatureStart, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies a pure SLH-DSA signature for the given message.
|
||||||
|
//
|
||||||
|
// See FIPS 205 Algorithm 24 slh_verify
|
||||||
|
func (pk *PublicKey) Verify(signature, message, context []byte) bool {
|
||||||
|
if len(message) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(context) > MAX_CONTEXT_LEN {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxLen := len(context)
|
||||||
|
var msgPrefix [MAX_CONTEXT_LEN + 2]byte
|
||||||
|
msgPrefix[1] = byte(ctxLen)
|
||||||
|
if ctxLen > 0 {
|
||||||
|
copy(msgPrefix[2:], context)
|
||||||
|
}
|
||||||
|
return pk.verifyInternal(signature, msgPrefix[:2+ctxLen], message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See FIPS 205 Algorithm 20 slh_verify_internal
|
||||||
|
func (pk *PublicKey) verifyInternal(signature []byte, msgPrefix []byte, message []byte) bool {
|
||||||
|
if len(signature) != pk.params.sigLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
adrs := pk.addressCreator()
|
||||||
|
R := signature[:pk.params.n]
|
||||||
|
signature = signature[pk.params.n:]
|
||||||
|
|
||||||
|
// compute message digest
|
||||||
|
var digest [MAX_M]byte
|
||||||
|
pk.h.hMsg(pk, R, msgPrefix, message, digest[:])
|
||||||
|
// Grab the first mdLen() bytes of digest to use in fors_sign()
|
||||||
|
mdLen := pk.params.mdLen()
|
||||||
|
md := digest[:mdLen]
|
||||||
|
|
||||||
|
// Grab remaining bytes from digest to select tree and leaf id's
|
||||||
|
remaining := digest[mdLen:]
|
||||||
|
treeIdxLen := pk.params.treeIdxLen()
|
||||||
|
leafIdxLen := pk.params.leafIdxLen()
|
||||||
|
treeIdx := toInt(remaining[:treeIdxLen]) & pk.params.treeIdxMask()
|
||||||
|
remaining = remaining[treeIdxLen:]
|
||||||
|
leafIdx := uint32(toInt(remaining[:leafIdxLen]) & pk.params.leafIdxMask())
|
||||||
|
|
||||||
|
adrs.setTreeAddress(treeIdx)
|
||||||
|
adrs.setTypeAndClear(AddressTypeFORSTree)
|
||||||
|
adrs.setKeyPairAddress(leafIdx)
|
||||||
|
|
||||||
|
var pkFors [MAX_N]byte
|
||||||
|
// calculate the FORS public key using the given FORS signature
|
||||||
|
signature = pk.forsPkFromSig(md, signature, adrs, pkFors[:])
|
||||||
|
|
||||||
|
return pk.htVerify(pkFors[:pk.params.n], signature, treeIdx, leafIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt(b []byte) uint64 {
|
||||||
|
var ret uint64
|
||||||
|
for i := range b {
|
||||||
|
ret = ret<<8 + uint64(b[i])
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
110
slhdsa/dsa_test.go
Normal file
110
slhdsa/dsa_test.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type slhtest struct {
|
||||||
|
ParameterSet string `json:"parameterSet"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
Pk string `json:"pk"`
|
||||||
|
AdditionalRandomness string `json:"additionalRandomness,omitempty"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Context string `json:"context,omitempty"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadData(filename string) (*slhtest, error) {
|
||||||
|
file := filepath.Join("testdata", filename)
|
||||||
|
// read json conentent from file and Unmarshal to slhtest
|
||||||
|
in, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
var data slhtest
|
||||||
|
if err := json.NewDecoder(in).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignFromFiles(t *testing.T) {
|
||||||
|
// list files in testdata
|
||||||
|
files, err := os.ReadDir("testdata")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadDir failed: %v", err)
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, err := loadData(file.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("loadData failed: %v", err)
|
||||||
|
}
|
||||||
|
testData(t, file.Name(), data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testData(t *testing.T, filename string, tc *slhtest) {
|
||||||
|
t.Helper()
|
||||||
|
params, ok := GetParameterSet(tc.ParameterSet)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%v GetParameterSet(%s)", filename, tc.ParameterSet)
|
||||||
|
}
|
||||||
|
skBytes, _ := hex.DecodeString(tc.Sk)
|
||||||
|
pkBytes, _ := hex.DecodeString(tc.Pk)
|
||||||
|
addRand, _ := hex.DecodeString(tc.AdditionalRandomness)
|
||||||
|
message, _ := hex.DecodeString(tc.Message)
|
||||||
|
context, _ := hex.DecodeString(tc.Context)
|
||||||
|
sig, _ := hex.DecodeString(tc.Signature)
|
||||||
|
sigOriginal := sig
|
||||||
|
privKey, err := NewPrivateKey(skBytes, params)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v NewPrivateKey(%x) = %v", filename, skBytes, err)
|
||||||
|
}
|
||||||
|
sig2, err := privKey.Sign(message, context, addRand)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v Sign(%x,%x) = %v", filename, message, context, err)
|
||||||
|
}
|
||||||
|
// check R
|
||||||
|
if !bytes.Equal(sig[:params.n], sig2[:params.n]) {
|
||||||
|
t.Errorf("signature.R = %x, want %x", sig2[:params.n], sig[:params.n])
|
||||||
|
}
|
||||||
|
// check SIGfors
|
||||||
|
sig2 = sig2[params.n:]
|
||||||
|
sig = sig[params.n:]
|
||||||
|
if !bytes.Equal(sig[:privKey.params.n*(privKey.params.a+1)*privKey.params.k], sig2[:privKey.params.n*(privKey.params.a+1)*privKey.params.k]) {
|
||||||
|
t.Errorf("signature.SIGfors = %x, want %x", sig2[:privKey.params.n*(privKey.params.a+1)*privKey.params.k], sig[:privKey.params.n*(privKey.params.a+1)*privKey.params.k])
|
||||||
|
}
|
||||||
|
// check SIGht
|
||||||
|
sig2 = sig2[privKey.params.n*(privKey.params.a+1)*privKey.params.k:]
|
||||||
|
sig = sig[privKey.params.n*(privKey.params.a+1)*privKey.params.k:]
|
||||||
|
for i := range int(privKey.params.d) {
|
||||||
|
if !bytes.Equal(sig[:privKey.params.n*(privKey.params.len+privKey.params.hm)], sig2[:privKey.params.n*(privKey.params.len+privKey.params.hm)]) {
|
||||||
|
t.Errorf("signature.SIGht = %v %x, want %x", i, sig2[:privKey.params.n*(privKey.params.len+privKey.params.hm)], sig[:privKey.params.n*(privKey.params.len+privKey.params.hm)])
|
||||||
|
}
|
||||||
|
sig2 = sig2[privKey.params.n*(privKey.params.len+privKey.params.hm):]
|
||||||
|
sig = sig[privKey.params.n*(privKey.params.len+privKey.params.hm):]
|
||||||
|
}
|
||||||
|
// test verify
|
||||||
|
pub, err := NewPublicKey(pkBytes, params)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v NewPublicKey(%x) = %v", filename, pkBytes, err)
|
||||||
|
}
|
||||||
|
if !pub.Verify(sigOriginal, message, context) {
|
||||||
|
t.Errorf("%v Verify() = false, want true", filename)
|
||||||
|
}
|
||||||
|
}
|
134
slhdsa/fors.go
Normal file
134
slhdsa/fors.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
// forsSign generates a FORS signature.
|
||||||
|
// See FIPS 205 Algorithm 16 fors_sign
|
||||||
|
func (sk *PrivateKey) forsSign(md []byte, adrs adrsOperations, sigFors []byte) {
|
||||||
|
var indices [MAX_K]uint32
|
||||||
|
// split md into k a-bits values
|
||||||
|
base2b(md, sk.params.a, indices[:sk.params.k])
|
||||||
|
|
||||||
|
twoPowerA := uint32(1 << sk.params.a)
|
||||||
|
var treeIDTimeTwoPowerA uint32
|
||||||
|
|
||||||
|
for treeID := range sk.params.k {
|
||||||
|
nodeID := indices[treeID]
|
||||||
|
sk.forsGenPrivateKey(nodeID+treeIDTimeTwoPowerA, adrs, sigFors)
|
||||||
|
sigFors = sigFors[sk.params.n:]
|
||||||
|
|
||||||
|
treeOffset := treeIDTimeTwoPowerA
|
||||||
|
for layer := range sk.params.a {
|
||||||
|
s := nodeID ^ 1
|
||||||
|
sk.forsNode(s+treeOffset, layer, adrs, sigFors)
|
||||||
|
|
||||||
|
nodeID >>= 1
|
||||||
|
treeOffset >>= 1
|
||||||
|
sigFors = sigFors[sk.params.n:]
|
||||||
|
}
|
||||||
|
treeIDTimeTwoPowerA += twoPowerA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forsPkFromSig computes a FORS public key from a FORS signature.
|
||||||
|
// See FIPS 205 Algorithm 17 fors_pkFromSig
|
||||||
|
func (pk *PublicKey) forsPkFromSig(md, signature []byte, adrs adrsOperations, out []byte) []byte {
|
||||||
|
var indices [MAX_K]uint32
|
||||||
|
base2b(md, pk.params.a, indices[:pk.params.k])
|
||||||
|
|
||||||
|
twoPowerA := uint32(1 << pk.params.a)
|
||||||
|
|
||||||
|
var treeIDTimeTwoPowerA uint32
|
||||||
|
// TODO: use array to avoid heap allocation?
|
||||||
|
root := make([]byte, pk.params.n*pk.params.k)
|
||||||
|
rootPt := root
|
||||||
|
for treeID := range pk.params.k {
|
||||||
|
// compute leaf
|
||||||
|
nodeID := indices[treeID]
|
||||||
|
treeIdx := nodeID + treeIDTimeTwoPowerA
|
||||||
|
adrs.setTreeHeight(0)
|
||||||
|
adrs.setTreeIndex(treeIdx)
|
||||||
|
pk.h.f(pk, adrs, signature, rootPt)
|
||||||
|
signature = signature[pk.params.n:]
|
||||||
|
|
||||||
|
// compute root from leaf and AUTH
|
||||||
|
for layer := range pk.params.a {
|
||||||
|
adrs.setTreeHeight(layer + 1)
|
||||||
|
if nodeID&1 == 0 {
|
||||||
|
treeIdx = treeIdx >> 1
|
||||||
|
adrs.setTreeIndex(treeIdx)
|
||||||
|
pk.h.h(pk, adrs, rootPt, signature, rootPt)
|
||||||
|
} else {
|
||||||
|
treeIdx = (treeIdx - 1) >> 1
|
||||||
|
adrs.setTreeIndex(treeIdx)
|
||||||
|
pk.h.h(pk, adrs, signature, rootPt, rootPt)
|
||||||
|
}
|
||||||
|
signature = signature[pk.params.n:]
|
||||||
|
nodeID >>= 1
|
||||||
|
}
|
||||||
|
treeIDTimeTwoPowerA += twoPowerA
|
||||||
|
rootPt = rootPt[pk.params.n:]
|
||||||
|
}
|
||||||
|
// copy address to create a FORS public-key address
|
||||||
|
forspkADRS := pk.addressCreator()
|
||||||
|
forspkADRS.clone(adrs)
|
||||||
|
forspkADRS.setTypeAndClear(AddressTypeFORSRoots)
|
||||||
|
forspkADRS.copyKeyPairAddress(adrs)
|
||||||
|
pk.h.t(pk, forspkADRS, root, out)
|
||||||
|
clear(root)
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// forsNode computes the root of a Merkle subtree of FORS public values.
|
||||||
|
// See FIPS 205 Algorithm 15 fors_node
|
||||||
|
func (sk *PrivateKey) forsNode(nodeID, layer uint32, adrs adrsOperations, out []byte) {
|
||||||
|
if layer == 0 {
|
||||||
|
sk.forsGenPrivateKey(nodeID, adrs, out)
|
||||||
|
adrs.setTreeHeight(0)
|
||||||
|
adrs.setTreeIndex(nodeID)
|
||||||
|
sk.h.f(&sk.PublicKey, adrs, out, out)
|
||||||
|
} else {
|
||||||
|
var lnode, rnode [MAX_N]byte
|
||||||
|
sk.forsNode(nodeID*2, layer-1, adrs, lnode[:])
|
||||||
|
sk.forsNode(nodeID*2+1, layer-1, adrs, rnode[:])
|
||||||
|
adrs.setTreeHeight(layer)
|
||||||
|
adrs.setTreeIndex(nodeID)
|
||||||
|
sk.h.h(&sk.PublicKey, adrs, lnode[:], rnode[:], out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forsGenPrivateKey generates a FORS private key value.
|
||||||
|
// See FIPS 205 Algorithm 14 fors_skGen
|
||||||
|
func (sk *PrivateKey) forsGenPrivateKey(i uint32, adrs adrsOperations, out []byte) {
|
||||||
|
skADRS := sk.addressCreator()
|
||||||
|
skADRS.clone(adrs)
|
||||||
|
skADRS.setTypeAndClear(AddressTypeFORSPRF)
|
||||||
|
skADRS.copyKeyPairAddress(adrs)
|
||||||
|
skADRS.setTreeIndex(i)
|
||||||
|
sk.h.prf(sk, skADRS, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// base2b computes the base-2^b representation of the input byte array.
|
||||||
|
// See FIPS 205 Algorithm 4 base_2^b
|
||||||
|
func base2b(in []byte, base uint32, out []uint32) {
|
||||||
|
var (
|
||||||
|
bits uint32
|
||||||
|
total uint32
|
||||||
|
mask uint32
|
||||||
|
idx int
|
||||||
|
)
|
||||||
|
mask = 1<<base - 1
|
||||||
|
|
||||||
|
for i := range out {
|
||||||
|
for ; bits < base; bits += 8 {
|
||||||
|
total = (total << 8) | uint32(in[idx])
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
bits -= base
|
||||||
|
out[i] = (total >> bits) & mask
|
||||||
|
}
|
||||||
|
}
|
161
slhdsa/hash.go
Normal file
161
slhdsa/hash.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hashOperations interface {
|
||||||
|
f(pk *PublicKey, address adrsOperations, m1, out []byte)
|
||||||
|
h(pk *PublicKey, address adrsOperations, m1, m2, out []byte)
|
||||||
|
t(pk *PublicKey, address adrsOperations, ml, out []byte)
|
||||||
|
hMsg(pk *PublicKey, R, mPrefix, M, out []byte)
|
||||||
|
prf(sk *PrivateKey, address adrsOperations, out []byte)
|
||||||
|
prfMsg(sk *PrivateKey, optRand, mPrefix, m, out []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
type shakeOperations struct{}
|
||||||
|
|
||||||
|
func (shakeOperations) f(pk *PublicKey, address adrsOperations, m1, out []byte) {
|
||||||
|
pk.shake.Reset()
|
||||||
|
pk.shake.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.shake.Write(address.bytes())
|
||||||
|
pk.shake.Write(m1[:pk.params.n])
|
||||||
|
pk.shake.Read(out[:pk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shakeOperations) h(pk *PublicKey, address adrsOperations, m1, m2, out []byte) {
|
||||||
|
pk.shake.Reset()
|
||||||
|
pk.shake.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.shake.Write(address.bytes())
|
||||||
|
pk.shake.Write(m1[:pk.params.n])
|
||||||
|
pk.shake.Write(m2[:pk.params.n])
|
||||||
|
pk.shake.Read(out[:pk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shakeOperations) t(pk *PublicKey, address adrsOperations, ml, out []byte) {
|
||||||
|
pk.shake.Reset()
|
||||||
|
pk.shake.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.shake.Write(address.bytes())
|
||||||
|
pk.shake.Write(ml)
|
||||||
|
pk.shake.Read(out[:pk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shakeOperations) hMsg(pk *PublicKey, R, mPrefix, M, out []byte) {
|
||||||
|
pk.shake.Reset()
|
||||||
|
pk.shake.Write(R[:pk.params.n])
|
||||||
|
pk.shake.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.shake.Write(pk.root[:pk.params.n])
|
||||||
|
pk.shake.Write(mPrefix)
|
||||||
|
pk.shake.Write(M)
|
||||||
|
pk.shake.Read(out[:pk.params.m])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shakeOperations) prf(sk *PrivateKey, address adrsOperations, out []byte) {
|
||||||
|
sk.shake.Reset()
|
||||||
|
sk.shake.Write(sk.PublicKey.seed[:sk.params.n])
|
||||||
|
sk.shake.Write(address.bytes())
|
||||||
|
sk.shake.Write(sk.seed[:sk.params.n])
|
||||||
|
sk.shake.Read(out[:sk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shakeOperations) prfMsg(sk *PrivateKey, optRand, mPrefix, m, out []byte) {
|
||||||
|
sk.shake.Reset()
|
||||||
|
sk.shake.Write(sk.prf[:sk.params.n])
|
||||||
|
sk.shake.Write(optRand)
|
||||||
|
sk.shake.Write(mPrefix)
|
||||||
|
sk.shake.Write(m)
|
||||||
|
sk.shake.Read(out[:sk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
type sha2Operations struct{}
|
||||||
|
|
||||||
|
func (sha2Operations) f(pk *PublicKey, address adrsOperations, m1, out []byte) {
|
||||||
|
var zeros [64]byte
|
||||||
|
pk.md.Reset()
|
||||||
|
pk.md.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.md.Write(zeros[:64-pk.params.n])
|
||||||
|
pk.md.Write(address.bytes())
|
||||||
|
pk.md.Write(m1[:pk.params.n])
|
||||||
|
pk.md.Sum(zeros[:0])
|
||||||
|
copy(out, zeros[:pk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sha2Operations) h(pk *PublicKey, address adrsOperations, m1, m2, out []byte) {
|
||||||
|
var zeros [128]byte
|
||||||
|
pk.mdBig.Reset()
|
||||||
|
pk.mdBig.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.mdBig.Write(zeros[:uint32(pk.mdBig.BlockSize())-pk.params.n])
|
||||||
|
pk.mdBig.Write(address.bytes())
|
||||||
|
pk.mdBig.Write(m1[:pk.params.n])
|
||||||
|
pk.mdBig.Write(m2[:pk.params.n])
|
||||||
|
pk.mdBig.Sum(zeros[:0])
|
||||||
|
copy(out, zeros[:pk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sha2Operations) t(pk *PublicKey, address adrsOperations, ml, out []byte) {
|
||||||
|
var zeros [128]byte
|
||||||
|
pk.mdBig.Reset()
|
||||||
|
pk.mdBig.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.mdBig.Write(zeros[:uint32(pk.mdBig.BlockSize())-pk.params.n])
|
||||||
|
pk.mdBig.Write(address.bytes())
|
||||||
|
pk.mdBig.Write(ml)
|
||||||
|
pk.mdBig.Sum(zeros[:0])
|
||||||
|
copy(out, zeros[:pk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sha2Operations) prfMsg(sk *PrivateKey, optRand, mPrefix, m, out []byte) {
|
||||||
|
var buf [128]byte
|
||||||
|
mac := hmac.New(sk.mdBigFactory, sk.prf[:sk.params.n])
|
||||||
|
mac.Write(optRand)
|
||||||
|
mac.Write(mPrefix)
|
||||||
|
mac.Write(m)
|
||||||
|
mac.Sum(buf[:0])
|
||||||
|
copy(out, buf[:sk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sha2Operations) prf(sk *PrivateKey, address adrsOperations, out []byte) {
|
||||||
|
var zeros [128]byte
|
||||||
|
sk.md.Reset()
|
||||||
|
sk.md.Write(sk.PublicKey.seed[:sk.params.n])
|
||||||
|
sk.md.Write(zeros[:64-sk.params.n])
|
||||||
|
sk.md.Write(address.bytes())
|
||||||
|
sk.md.Write(sk.seed[:sk.params.n])
|
||||||
|
sk.md.Sum(zeros[:0])
|
||||||
|
copy(out, zeros[:sk.params.n])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sha2Operations) hMsg(pk *PublicKey, R, mPrefix, M, out []byte) {
|
||||||
|
var buf [128]byte
|
||||||
|
pk.mdBig.Reset()
|
||||||
|
pk.mdBig.Write(R[:pk.params.n])
|
||||||
|
pk.mdBig.Write(pk.seed[:pk.params.n])
|
||||||
|
pk.mdBig.Write(pk.root[:pk.params.n])
|
||||||
|
pk.mdBig.Write(mPrefix)
|
||||||
|
pk.mdBig.Write(M)
|
||||||
|
pk.mdBig.Sum(buf[:0])
|
||||||
|
mgf1([][]byte{R[:pk.params.n], pk.seed[:pk.params.n], buf[:pk.mdBig.Size()]}, pk.mdBig, out[:pk.params.m])
|
||||||
|
}
|
||||||
|
|
||||||
|
func mgf1(seeds [][]byte, h hash.Hash, out []byte) {
|
||||||
|
var counter uint32
|
||||||
|
var buff [128]byte
|
||||||
|
size := h.Size()
|
||||||
|
maskLen := len(out)
|
||||||
|
for i := 0; i < maskLen; i += size {
|
||||||
|
h.Reset()
|
||||||
|
for _, seed := range seeds {
|
||||||
|
h.Write(seed)
|
||||||
|
}
|
||||||
|
h.Write([]byte{byte(counter >> 24), byte(counter >> 16), byte(counter >> 8), byte(counter)})
|
||||||
|
h.Sum(buff[:0])
|
||||||
|
copy(out[i:], buff[:size])
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
60
slhdsa/hypertree.go
Normal file
60
slhdsa/hypertree.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
import "crypto/subtle"
|
||||||
|
|
||||||
|
// htSign generates a hypertree signature.
|
||||||
|
// See FIPS 205 Algorithm 12 ht_sign
|
||||||
|
func (sk *PrivateKey) htSign(pkFors []byte, treeIdx uint64, leafIdx uint32, signature []byte) {
|
||||||
|
adrs := sk.addressCreator()
|
||||||
|
|
||||||
|
sigLenPerLayer := (sk.params.hm + sk.params.len) * sk.params.n
|
||||||
|
mask := sk.params.leafIdxMask()
|
||||||
|
|
||||||
|
var rootBuf [MAX_N]byte
|
||||||
|
root := rootBuf[:sk.params.n]
|
||||||
|
copy(root, pkFors)
|
||||||
|
for j := range sk.params.d {
|
||||||
|
adrs.setLayerAddress(j)
|
||||||
|
adrs.setTreeAddress(treeIdx)
|
||||||
|
sk.xmssSign(root, leafIdx, adrs, signature)
|
||||||
|
|
||||||
|
if j < sk.params.d-1 {
|
||||||
|
sk.xmssPkFromSig(leafIdx, signature, root, adrs, root)
|
||||||
|
// hm least significant bits of treeIdx
|
||||||
|
leafIdx = uint32(treeIdx & mask)
|
||||||
|
// remove least significant hm bits from treeIdx
|
||||||
|
treeIdx >>= sk.params.hm
|
||||||
|
signature = signature[sigLenPerLayer:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// htVerify verifies a hypertree signature.
|
||||||
|
// See FIPS 205 Algorithm 13 ht_verify
|
||||||
|
func (pk *PublicKey) htVerify(pkFors []byte, signature []byte, treeIdx uint64, leafIdx uint32) bool {
|
||||||
|
adrs := pk.addressCreator()
|
||||||
|
|
||||||
|
sigLenPerLayer := (pk.params.hm + pk.params.len) * pk.params.n
|
||||||
|
mask := pk.params.leafIdxMask()
|
||||||
|
|
||||||
|
var rootBuf [MAX_N]byte
|
||||||
|
root := rootBuf[:pk.params.n]
|
||||||
|
copy(root, pkFors)
|
||||||
|
for j := range pk.params.d {
|
||||||
|
adrs.setLayerAddress(j)
|
||||||
|
adrs.setTreeAddress(treeIdx)
|
||||||
|
pk.xmssPkFromSig(leafIdx, signature, root, adrs, root)
|
||||||
|
// hm least significant bits of treeIdx
|
||||||
|
leafIdx = uint32(treeIdx & mask)
|
||||||
|
// remove least significant hm bits from treeIdx
|
||||||
|
treeIdx >>= pk.params.hm
|
||||||
|
signature = signature[sigLenPerLayer:]
|
||||||
|
}
|
||||||
|
return subtle.ConstantTimeCompare(pk.root[:pk.params.n], root) == 1
|
||||||
|
}
|
196
slhdsa/key.go
Normal file
196
slhdsa/key.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha3"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/emmansun/gmsm/sm3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PublicKey struct {
|
||||||
|
seed [MAX_N]byte
|
||||||
|
root [MAX_N]byte
|
||||||
|
params *params
|
||||||
|
md hash.Hash
|
||||||
|
mdBig hash.Hash
|
||||||
|
mdBigFactory func() hash.Hash
|
||||||
|
shake *sha3.SHAKE
|
||||||
|
addressCreator func() adrsOperations
|
||||||
|
h hashOperations
|
||||||
|
}
|
||||||
|
|
||||||
|
type PrivateKey struct {
|
||||||
|
PublicKey
|
||||||
|
seed [MAX_N]byte
|
||||||
|
prf [MAX_N]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the byte representation of the PublicKey.
|
||||||
|
// It combines the seed and root fields of the PublicKey.
|
||||||
|
func (pk *PublicKey) Bytes() []byte {
|
||||||
|
var key [2 * MAX_N]byte
|
||||||
|
copy(key[:], pk.seed[:pk.params.n])
|
||||||
|
copy(key[pk.params.n:], pk.root[:pk.params.n])
|
||||||
|
return key[:2*pk.params.n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pk *PublicKey) Equal(x any) bool {
|
||||||
|
xx, ok := x.(*PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return pk.params == xx.params && subtle.ConstantTimeCompare(pk.seed[:pk.params.n], xx.seed[:pk.params.n]) == 1 &&
|
||||||
|
subtle.ConstantTimeCompare(pk.root[:pk.params.n], xx.root[:pk.params.n]) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes serializes the PrivateKey into a byte slice.
|
||||||
|
func (sk *PrivateKey) Bytes() []byte {
|
||||||
|
var key [4 * MAX_N]byte
|
||||||
|
keySlice := key[:]
|
||||||
|
copy(keySlice, sk.seed[:sk.params.n])
|
||||||
|
keySlice = keySlice[sk.params.n:]
|
||||||
|
copy(keySlice, sk.prf[:sk.params.n])
|
||||||
|
keySlice = keySlice[sk.params.n:]
|
||||||
|
copy(keySlice, sk.PublicKey.seed[:sk.params.n])
|
||||||
|
keySlice = keySlice[sk.params.n:]
|
||||||
|
copy(keySlice, sk.root[:sk.params.n])
|
||||||
|
return key[:4*sk.params.n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey returns the public key of the private key.
|
||||||
|
func (sk *PrivateKey) Public() *PublicKey {
|
||||||
|
return &sk.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sk *PrivateKey) Equal(x any) bool {
|
||||||
|
xx, ok := x.(*PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sk.params == xx.params && subtle.ConstantTimeCompare(sk.seed[:sk.params.n], xx.seed[:sk.params.n]) == 1 &&
|
||||||
|
subtle.ConstantTimeCompare(sk.prf[:sk.params.n], xx.prf[:sk.params.n]) == 1 &&
|
||||||
|
subtle.ConstantTimeCompare(sk.PublicKey.seed[:sk.params.n], xx.PublicKey.seed[:sk.params.n]) == 1 &&
|
||||||
|
subtle.ConstantTimeCompare(sk.root[:sk.params.n], xx.root[:sk.params.n]) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKey generates a new private key based on the provided parameters.
|
||||||
|
// It initializes the key structure, fills the necessary fields with provided entropy,
|
||||||
|
// and computes the root node for the XMSS tree.
|
||||||
|
func GenerateKey(rand io.Reader, params *params) (*PrivateKey, error) {
|
||||||
|
priv := &PrivateKey{}
|
||||||
|
if err := initKey(params, &priv.PublicKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(rand, priv.seed[:params.n]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(rand, priv.prf[:params.n]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := io.ReadFull(rand, priv.PublicKey.seed[:params.n]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
adrs := priv.addressCreator()
|
||||||
|
adrs.setLayerAddress(params.d - 1)
|
||||||
|
priv.xmssNode(priv.root[:], 0, params.hm, adrs)
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPrivateKey creates a new PrivateKey instance from the provided priv.seed||priv.prf||pub.seed||pub.root and parameters.
|
||||||
|
// The function validates the length of the input byte slice and initializes the PrivateKey structure,
|
||||||
|
// including its PublicKey field. It also verifies the integrity of the key by comparing the root hash
|
||||||
|
// with the expected value. If any validation or initialization step fails, an error is returned.
|
||||||
|
func NewPrivateKey(bytes []byte, params *params) (*PrivateKey, error) {
|
||||||
|
if len(bytes) != 4*int(params.n) {
|
||||||
|
return nil, errors.New("slhdsa: invalid key length")
|
||||||
|
}
|
||||||
|
priv := &PrivateKey{}
|
||||||
|
if err := initKey(params, &priv.PublicKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
copy(priv.seed[:], bytes[:params.n])
|
||||||
|
copy(priv.prf[:], bytes[params.n:2*params.n])
|
||||||
|
copy(priv.PublicKey.seed[:], bytes[2*params.n:3*params.n])
|
||||||
|
|
||||||
|
adrs := priv.addressCreator()
|
||||||
|
adrs.setLayerAddress(params.d - 1)
|
||||||
|
priv.xmssNode(priv.root[:], 0, params.hm, adrs)
|
||||||
|
if subtle.ConstantTimeCompare(priv.root[:params.n], bytes[3*params.n:]) != 1 {
|
||||||
|
return nil, errors.New("slhdsa: invalid key")
|
||||||
|
}
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublicKey creates a new PublicKey instance from the provided seed||root and parameters.
|
||||||
|
// Note this method can NOT verify the validity of the public key.
|
||||||
|
func NewPublicKey(bytes []byte, params *params) (*PublicKey, error) {
|
||||||
|
if len(bytes) != 2*int(params.n) {
|
||||||
|
return nil, errors.New("slhdsa: invalid key length")
|
||||||
|
}
|
||||||
|
pub := &PublicKey{}
|
||||||
|
if err := initKey(params, pub); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
copy(pub.seed[:], bytes[:params.n])
|
||||||
|
copy(pub.root[:], bytes[params.n:2*params.n])
|
||||||
|
return pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateKeyInernal(skSeed, skPRF, pkSeed []byte, params *params) (*PrivateKey, error) {
|
||||||
|
priv := &PrivateKey{}
|
||||||
|
if err := initKey(params, &priv.PublicKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(skSeed) != int(params.n) || len(skPRF) != int(params.n) || len(pkSeed) != int(params.n) {
|
||||||
|
return nil, errors.New("slhdsa: invalid seed/prf length")
|
||||||
|
}
|
||||||
|
copy(priv.seed[:], skSeed)
|
||||||
|
copy(priv.prf[:], skPRF)
|
||||||
|
copy(priv.PublicKey.seed[:], pkSeed)
|
||||||
|
adrs := priv.addressCreator()
|
||||||
|
adrs.setLayerAddress(params.d - 1)
|
||||||
|
priv.xmssNode(priv.root[:], 0, params.hm, adrs)
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initKey(params *params, key *PublicKey) error {
|
||||||
|
switch params {
|
||||||
|
case &SLHDSA128SmallSHA2, &SLHDSA128FastSHA2:
|
||||||
|
key.md = sha256.New()
|
||||||
|
key.mdBig = key.md
|
||||||
|
key.mdBigFactory = sha256.New
|
||||||
|
key.h = sha2Operations{}
|
||||||
|
key.addressCreator = newAdrsC
|
||||||
|
case &SLHDSA128SmallSM3, &SLHDSA128FastSM3:
|
||||||
|
key.md = sm3.New()
|
||||||
|
key.mdBig = key.md
|
||||||
|
key.mdBigFactory = sm3.New
|
||||||
|
key.h = sha2Operations{}
|
||||||
|
key.addressCreator = newAdrsC
|
||||||
|
case &SLHDSA192SmallSHA2, &SLHDSA192FastSHA2, &SLHDSA256SmallSHA2, &SLHDSA256FastSHA2:
|
||||||
|
key.md = sha256.New()
|
||||||
|
key.mdBig = sha512.New()
|
||||||
|
key.mdBigFactory = sha512.New
|
||||||
|
key.h = sha2Operations{}
|
||||||
|
key.addressCreator = newAdrsC
|
||||||
|
case &SLHDSA128SmallSHAKE, &SLHDSA128FastSHAKE, &SLHDSA192SmallSHAKE, &SLHDSA192FastSHAKE, &SLHDSA256SmallSHAKE, &SLHDSA256FastSHAKE:
|
||||||
|
key.shake = sha3.NewSHAKE256()
|
||||||
|
key.h = shakeOperations{}
|
||||||
|
key.addressCreator = newAdrs
|
||||||
|
default:
|
||||||
|
return errors.New("slhdsa: unsupported parameters")
|
||||||
|
}
|
||||||
|
key.params = params
|
||||||
|
return nil
|
||||||
|
}
|
139
slhdsa/key_test.go
Normal file
139
slhdsa/key_test.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyCases = []struct {
|
||||||
|
params *params
|
||||||
|
skSeed string
|
||||||
|
skPRF string
|
||||||
|
pkSeed string
|
||||||
|
pkRoot string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
params: &SLHDSA128SmallSHA2,
|
||||||
|
skSeed: "AC379F047FAAB2004F3AE32350AC9A3D",
|
||||||
|
skPRF: "829FFF0AA59E956A87F3971C4D58E710",
|
||||||
|
pkSeed: "0566D240CC519834322EAFBCC73C79F5",
|
||||||
|
pkRoot: "A4B84F02E8BF0CBD54017B2D3C494B57",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA128SmallSM3,
|
||||||
|
skSeed: "AC379F047FAAB2004F3AE32350AC9A3D",
|
||||||
|
skPRF: "829FFF0AA59E956A87F3971C4D58E710",
|
||||||
|
pkSeed: "0566D240CC519834322EAFBCC73C79F5",
|
||||||
|
pkRoot: "5EECDA31C1C8406AA02F846831D0B7FD",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA128SmallSHAKE,
|
||||||
|
skSeed: "2A2CCF3CD8F9F86E131BE654CFF6C0B4",
|
||||||
|
skPRF: "FDFCEB1AA2F0BA2C3C1388194F6116C7",
|
||||||
|
pkSeed: "890CC7F4A46FE6C34D3F26A62FF962E1",
|
||||||
|
pkRoot: "E8C88D2BDCBA6F66E50403E77FA92EFE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA128FastSHA2,
|
||||||
|
skSeed: "AED6F6F5C5408BBFFA1136BC9049A701",
|
||||||
|
skPRF: "4D4CE0711E176A0C8A023508A692C207",
|
||||||
|
pkSeed: "74D98D5000AF53B98F36389A1292BED3",
|
||||||
|
pkRoot: "F4A650C56C426FCFDB88E3355459440C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA128FastSM3,
|
||||||
|
skSeed: "AED6F6F5C5408BBFFA1136BC9049A701",
|
||||||
|
skPRF: "4D4CE0711E176A0C8A023508A692C207",
|
||||||
|
pkSeed: "74D98D5000AF53B98F36389A1292BED3",
|
||||||
|
pkRoot: "876557FCFD59E660B26D2C607E81BDDA",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA128FastSHAKE,
|
||||||
|
skSeed: "CD4A308C03D970508572C0815D7488B7",
|
||||||
|
skPRF: "F3FD6D2DCC7E5120FA544846AEDDED81",
|
||||||
|
pkSeed: "BC435C3E66E4C2E4FBC09779DA5F74D4",
|
||||||
|
pkRoot: "4EA0E0DF05C2457BCC81F59928433390",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA192SmallSHA2,
|
||||||
|
skSeed: "3BFAED208B7DC795BF3647F86E4B48BF9ADB8D6784C50155",
|
||||||
|
skPRF: "A20311739497C3FCB860EE47E09EDE036F7AE8A939155BC0",
|
||||||
|
pkSeed: "A67856A81A6ADBCED7F1A2780CC48A06681BA5E8C7938506",
|
||||||
|
pkRoot: "BD031BC8124F95F0BAE2BECB2A3FBBAEC453C04A6E918FFB",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA192SmallSHAKE,
|
||||||
|
skSeed: "915173EE0D17F30877E1D463E3DEC914E71F436867AD7615",
|
||||||
|
skPRF: "ED782E7033C4963A7FF0B67181DE0F0EA7EFABB326D40A86",
|
||||||
|
pkSeed: "520660F654D537DA6934F96E5EE01B24A2F36102F68DCD10",
|
||||||
|
pkRoot: "AA206FC79803E63850DA5E86969569FC8FB021B6C40616E2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA192FastSHA2,
|
||||||
|
skSeed: "45D7131C727DF1CC51DB85B44E37868215DF8AEC5D1B552F",
|
||||||
|
skPRF: "92BC5FC8A2969FE0A522492082E994DE1DDC90FA984F847B",
|
||||||
|
pkSeed: "8330589C20701AA9F11B473B67E1D67E1C6A2EB6C86265ED",
|
||||||
|
pkRoot: "13A3EA895C4EEEADDE8A796BBA5233F0D86EE5CBF2A6F99C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA192FastSHAKE,
|
||||||
|
skSeed: "855000FDFFFBA76962809C69432452F3DC79428F662C59B1",
|
||||||
|
skPRF: "43B1FC381C300B5ECEC7571B5DE2FCA16737E4C14911F683",
|
||||||
|
pkSeed: "124623BA6CA1BC1B0E1A303099E2A608B0AC41715BC788A1",
|
||||||
|
pkRoot: "9873C783378F935794ABC0313243EFC3F4A10A619CB1B1FE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA256SmallSHA2,
|
||||||
|
skSeed: "2FBEAB9A6A80FD817E7EFCDF834EFBD4F0A36195D7598408A6A151E93DE6A557",
|
||||||
|
skPRF: "5D0B37D1ECBC68265B0AFEECBBA783DD27EAFDBDF3143E4AF3E5057FD5C2DADA",
|
||||||
|
pkSeed: "1322F94917AE67D0DB420203178D591283C08BE8A1385A16CE70CD9FBAFD2AC6",
|
||||||
|
pkRoot: "40041EAB68A4A653F89CAB7585F6B410603326DBBAAF733E7E72CB6097A4A452",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA256SmallSHAKE,
|
||||||
|
skSeed: "7D88445A7B0022F12E9E2D74755431505FF6DB1C38A8CE44864D34CFF1A12CE0",
|
||||||
|
skPRF: "FF2CD133AD00728EB29DD0CE881C41C640F2E28861555B59D4E0BAA0447BB542",
|
||||||
|
pkSeed: "87A133B92EB6C81771AE002819B4C0300FA63CD7181C805096BFB16067F52A45",
|
||||||
|
pkRoot: "CC785237C24D9235B6BC3194B79E5A9F953388EA745D7CFB87826A94E5B271D5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA256FastSHA2,
|
||||||
|
skSeed: "B8ABC485122BE003CF36D677BEE7F47EA1017C39D96D0C56A87A7ADAD24F731A",
|
||||||
|
skPRF: "9222684FFACF803D44CB98222C44B3C519698B798D8F7A759FE2FA6EF173CF64",
|
||||||
|
pkSeed: "0D50E82BEDB42E03CC967E7FD24C12777855A946FD49471184330F096A75B561",
|
||||||
|
pkRoot: "7FB65FBD08D05F24F20CB3875E28FAC4A52A2513C7EF447B8E9328632A684CF7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: &SLHDSA256FastSHAKE,
|
||||||
|
skSeed: "3DE4B54A5F5FB98D6638FB3D8899355CC3582E8A397D0990CAD032D78EE9E199",
|
||||||
|
skPRF: "DA7F71D21D0182A99DE34E2796FE5DDE046D9C9E961DCE24C2562728BE7D9632",
|
||||||
|
pkSeed: "B3EF3825A515E0B2E4164DB7EC805B4CF1C7A2DE6E63D7DF359B99B1F3063F25",
|
||||||
|
pkRoot: "AEC38FF53C46AAD930166957CA0DB5C5466D0CBE9A11970987A230EBBB5450A4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateKeyInternal(t *testing.T) {
|
||||||
|
for _, tc := range keyCases {
|
||||||
|
skSeed, _ := hex.DecodeString(tc.skSeed)
|
||||||
|
skPRF, _ := hex.DecodeString(tc.skPRF)
|
||||||
|
pkSeed, _ := hex.DecodeString(tc.pkSeed)
|
||||||
|
pkRoot, _ := hex.DecodeString(tc.pkRoot)
|
||||||
|
|
||||||
|
privKey, err := generateKeyInernal(skSeed, skPRF, pkSeed, tc.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("generateKeyInernal(%x,%x,%x) = %v", skSeed, skPRF, pkSeed, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(privKey.PublicKey.root[:tc.params.n], pkRoot) {
|
||||||
|
t.Errorf("generateKeyInernal(%x,%x,%x) = %x, expected=%x", skSeed, skPRF, pkSeed, privKey.PublicKey.root[:tc.params.n], pkRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
slhdsa/parameterset.go
Normal file
123
slhdsa/parameterset.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package slhdsa
|
||||||
|
|
||||||
|
const (
|
||||||
|
MAX_N = 32
|
||||||
|
MAX_M = 49
|
||||||
|
MAX_K = 35
|
||||||
|
MAX_A = 9
|
||||||
|
MAX_K_TIMES_A = MAX_K * MAX_A
|
||||||
|
MAX_WOTS_LEN = 2*MAX_N + 3
|
||||||
|
|
||||||
|
MAX_CONTEXT_LEN = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
type params struct {
|
||||||
|
alg string
|
||||||
|
isShake int
|
||||||
|
isSM3 int
|
||||||
|
n uint32 // Security parameter (Hash output size in bytes) (16, 24, 32)
|
||||||
|
len uint32 // 2*n + 3
|
||||||
|
h uint32 // The total height of the tree (63, 64, 66, 68). #keypairs = 2^h
|
||||||
|
d uint32 // The number of tree layers (7, 8, 17, 22)
|
||||||
|
hm uint32 // The height (h') of each merkle tree. (h = hm * d )
|
||||||
|
a uint32 // Height of a FORS tree
|
||||||
|
k uint32 // The number of FORS trees
|
||||||
|
m uint32 // The size of H_MSG() output
|
||||||
|
securityCategory uint32
|
||||||
|
sigLen int
|
||||||
|
pkLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
// sigLen = (1+k*(1+a)+d*(hm+len))*n
|
||||||
|
var (
|
||||||
|
SLHDSA128SmallSHA2 = params{alg: "SLH-DSA-SHA2-128s", isShake: 0, isSM3: 0, n: 16, len: 35, h: 63, d: 7, hm: 9, a: 12, k: 14, m: 30,
|
||||||
|
securityCategory: 1, sigLen: 7856, pkLen: 32}
|
||||||
|
SLHDSA128SmallSM3 = params{alg: "SLH-DSA-SM3-128s", isShake: 0, isSM3: 1, n: 16, len: 35, h: 63, d: 7, hm: 9, a: 12, k: 14, m: 30,
|
||||||
|
securityCategory: 1, sigLen: 7856, pkLen: 32}
|
||||||
|
SLHDSA128SmallSHAKE = params{alg: "SLH-DSA-SHAKE-128s", isShake: 1, isSM3: 0, n: 16, len: 35, h: 63, d: 7, hm: 9, a: 12, k: 14, m: 30,
|
||||||
|
securityCategory: 1, sigLen: 7856, pkLen: 32}
|
||||||
|
SLHDSA128FastSHA2 = params{alg: "SLH-DSA-SHA2-128f", isShake: 0, isSM3: 0, n: 16, len: 35, h: 66, d: 22, hm: 3, a: 6, k: 33, m: 34,
|
||||||
|
securityCategory: 1, sigLen: 17088, pkLen: 32}
|
||||||
|
SLHDSA128FastSM3 = params{alg: "SLH-DSA-SM3-128f", isShake: 0, isSM3: 10, n: 16, len: 35, h: 66, d: 22, hm: 3, a: 6, k: 33, m: 34,
|
||||||
|
securityCategory: 1, sigLen: 17088, pkLen: 32}
|
||||||
|
SLHDSA128FastSHAKE = params{alg: "SLH-DSA-SHAKE-128f", isShake: 1, isSM3: 0, n: 16, len: 35, h: 66, d: 22, hm: 3, a: 6, k: 33, m: 34,
|
||||||
|
securityCategory: 1, sigLen: 17088, pkLen: 32}
|
||||||
|
SLHDSA192SmallSHA2 = params{alg: "SLH-DSA-SHA2-192s", isShake: 0, isSM3: 0, n: 24, len: 51, h: 63, d: 7, hm: 9, a: 14, k: 17, m: 39,
|
||||||
|
securityCategory: 3, sigLen: 16224, pkLen: 48}
|
||||||
|
SLHDSA192SmallSHAKE = params{alg: "SLH-DSA-SHAKE-192s", isShake: 1, isSM3: 0, n: 24, len: 51, h: 63, d: 7, hm: 9, a: 14, k: 17, m: 39,
|
||||||
|
securityCategory: 3, sigLen: 16224, pkLen: 48}
|
||||||
|
SLHDSA192FastSHA2 = params{alg: "SLH-DSA-SHA2-192f", isShake: 0, isSM3: 0, n: 24, len: 51, h: 66, d: 22, hm: 3, a: 8, k: 33, m: 42,
|
||||||
|
securityCategory: 3, sigLen: 35664, pkLen: 48}
|
||||||
|
SLHDSA192FastSHAKE = params{alg: "SLH-DSA-SHAKE-192f", isShake: 1, isSM3: 0, n: 24, len: 51, h: 66, d: 22, hm: 3, a: 8, k: 33, m: 42,
|
||||||
|
securityCategory: 3, sigLen: 35664, pkLen: 48}
|
||||||
|
SLHDSA256SmallSHA2 = params{alg: "SLH-DSA-SHA2-256s", isShake: 0, isSM3: 0, n: 32, len: 67, h: 64, d: 8, hm: 8, a: 14, k: 22, m: 47,
|
||||||
|
securityCategory: 5, sigLen: 29792, pkLen: 64}
|
||||||
|
SLHDSA256SmallSHAKE = params{alg: "SLH-DSA-SHAKE-256s", isShake: 1, isSM3: 0, n: 32, len: 67, h: 64, d: 8, hm: 8, a: 14, k: 22, m: 47,
|
||||||
|
securityCategory: 5, sigLen: 29792, pkLen: 64}
|
||||||
|
SLHDSA256FastSHA2 = params{alg: "SLH-DSA-SHA2-256f", isShake: 0, isSM3: 0, n: 32, len: 67, h: 68, d: 17, hm: 4, a: 9, k: 35, m: 49,
|
||||||
|
securityCategory: 5, sigLen: 49856, pkLen: 64}
|
||||||
|
SLHDSA256FastSHAKE = params{alg: "SLH-DSA-SHAKE-256f", isShake: 1, isSM3: 0, n: 32, len: 67, h: 68, d: 17, hm: 4, a: 9, k: 35, m: 49,
|
||||||
|
securityCategory: 5, sigLen: 49856, pkLen: 64}
|
||||||
|
)
|
||||||
|
|
||||||
|
var parameterSets = map[string]*params{
|
||||||
|
"SLH-DSA-SHA2-128s": &SLHDSA128SmallSHA2,
|
||||||
|
"SLH-DSA-SHA2-128f": &SLHDSA128FastSHA2,
|
||||||
|
"SLH-DSA-SHA2-192s": &SLHDSA192SmallSHA2,
|
||||||
|
"SLH-DSA-SHA2-192f": &SLHDSA192FastSHA2,
|
||||||
|
"SLH-DSA-SHA2-256s": &SLHDSA256SmallSHA2,
|
||||||
|
"SLH-DSA-SHA2-256f": &SLHDSA256FastSHA2,
|
||||||
|
"SLH-DSA-SM3-128s": &SLHDSA128SmallSM3,
|
||||||
|
"SLH-DSA-SM3-128f": &SLHDSA128FastSM3,
|
||||||
|
"SLH-DSA-SHAKE-128s": &SLHDSA128SmallSHAKE,
|
||||||
|
"SLH-DSA-SHAKE-128f": &SLHDSA128FastSHAKE,
|
||||||
|
"SLH-DSA-SHAKE-192s": &SLHDSA192SmallSHAKE,
|
||||||
|
"SLH-DSA-SHAKE-192f": &SLHDSA192FastSHAKE,
|
||||||
|
"SLH-DSA-SHAKE-256s": &SLHDSA256SmallSHAKE,
|
||||||
|
"SLH-DSA-SHAKE-256f": &SLHDSA256FastSHAKE,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParameterSet(name string) (*params, bool) {
|
||||||
|
if p, ok := parameterSets[name]; ok {
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) Equal(x any) bool {
|
||||||
|
if x == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if p2, ok := x.(*params); ok {
|
||||||
|
return p.alg == p2.alg && p.isShake == p2.isShake && p.isSM3 == p2.isSM3 &&
|
||||||
|
p.n == p2.n && p.h == p2.h && p.d == p2.d && p.hm == p2.hm &&
|
||||||
|
p.a == p2.a && p.k == p2.k && p.m == p2.m &&
|
||||||
|
p.securityCategory == p2.securityCategory &&
|
||||||
|
p.sigLen == p2.sigLen && p.pkLen == p2.pkLen
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) mdLen() int {
|
||||||
|
return int(p.k*p.a+7) >> 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) treeIdxLen() int {
|
||||||
|
return int(p.h-p.hm+7) >> 3 // 7 or 8 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) treeIdxMask() uint64 {
|
||||||
|
return (1 << (p.h - p.hm)) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) leafIdxLen() int {
|
||||||
|
return int(p.hm+7) >> 3 // 1 or 2 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) leafIdxMask() uint64 {
|
||||||
|
return (1 << p.hm) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *params) String() string {
|
||||||
|
return p.alg
|
||||||
|
}
|
8
slhdsa/testdata/SLHDSA128FastSHA2_1.json
vendored
Normal file
8
slhdsa/testdata/SLHDSA128FastSHA2_1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
9
slhdsa/testdata/SLHDSA128FastSHA2_2.json
vendored
Normal file
9
slhdsa/testdata/SLHDSA128FastSHA2_2.json
vendored
Normal file
File diff suppressed because one or more lines are too long
115
slhdsa/wots.go
Normal file
115
slhdsa/wots.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
// Chaining function used in WOTS
|
||||||
|
// See FIPS 205 Algorithm 5 wots_chain
|
||||||
|
func (pk *PublicKey) wotsChain(inout []byte, start, steps byte, addr adrsOperations) {
|
||||||
|
for i := start; i < start+steps; i++ {
|
||||||
|
addr.setHashAddress(uint32(i))
|
||||||
|
pk.h.f(pk, addr, inout, inout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wotsPkGen generates a WOTS public key.
|
||||||
|
// See FIPS 205 Algorithm 6 wots_pkGen
|
||||||
|
func (sk *PrivateKey) wotsPkGen(out []byte, addr adrsOperations) {
|
||||||
|
skADRS := sk.addressCreator()
|
||||||
|
skADRS.clone(addr)
|
||||||
|
skADRS.setTypeAndClear(AddressTypeWOTSPRF)
|
||||||
|
skADRS.copyKeyPairAddress(addr)
|
||||||
|
// TODO: use array to avoid heap allocation?
|
||||||
|
tmpBuf := make([]byte, sk.params.n*sk.params.len)
|
||||||
|
tmp := tmpBuf
|
||||||
|
for i := uint32(0); i < sk.params.len; i++ {
|
||||||
|
skADRS.setChainAddress(i)
|
||||||
|
sk.h.prf(sk, skADRS, tmp)
|
||||||
|
addr.setChainAddress(i)
|
||||||
|
sk.wotsChain(tmp, 0, 15, addr) // w = 16
|
||||||
|
tmp = tmp[sk.params.n:]
|
||||||
|
}
|
||||||
|
wotspkADRS := sk.addressCreator()
|
||||||
|
wotspkADRS.clone(addr)
|
||||||
|
wotspkADRS.setTypeAndClear(AddressTypeWOTSPK)
|
||||||
|
wotspkADRS.copyKeyPairAddress(addr)
|
||||||
|
sk.h.t(&sk.PublicKey, wotspkADRS, tmpBuf, out)
|
||||||
|
clear(tmpBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wotsSign generates a WOTS signature on an n-byte message.
|
||||||
|
// See FIPS 205 Algorithm 10 wots_sign
|
||||||
|
func (sk *PrivateKey) wotsSign(m []byte, adrs adrsOperations, sigWots []byte) {
|
||||||
|
var msgAndCsum [MAX_WOTS_LEN]byte
|
||||||
|
// convert message to base w=16
|
||||||
|
bytes2nibbles(m, msgAndCsum[:])
|
||||||
|
// compute checksum
|
||||||
|
// checksum = 15 * len1 - sum(msgAndCsum)
|
||||||
|
var csum uint16
|
||||||
|
len1 := sk.params.n * 2
|
||||||
|
for i := range len1 {
|
||||||
|
csum += uint16(msgAndCsum[i])
|
||||||
|
}
|
||||||
|
csum = uint16(15*len1) - csum
|
||||||
|
msgAndCsum[len1] = byte(csum>>8) & 0x0F
|
||||||
|
msgAndCsum[len1+1] = byte(csum>>4) & 0x0F
|
||||||
|
msgAndCsum[len1+2] = byte(csum) & 0x0F
|
||||||
|
|
||||||
|
skADRS := sk.addressCreator()
|
||||||
|
skADRS.clone(adrs)
|
||||||
|
skADRS.setTypeAndClear(AddressTypeWOTSPRF)
|
||||||
|
skADRS.copyKeyPairAddress(adrs)
|
||||||
|
|
||||||
|
for i := range sk.params.len {
|
||||||
|
skADRS.setChainAddress(i)
|
||||||
|
sk.h.prf(sk, skADRS, sigWots)
|
||||||
|
adrs.setChainAddress(i)
|
||||||
|
sk.wotsChain(sigWots, 0, msgAndCsum[i], adrs)
|
||||||
|
sigWots = sigWots[sk.params.n:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wotsPkFromSig computes a WOTS public key from a message and its signature
|
||||||
|
// See FIPS 205 Algorithm 8 wots_pkFromSig
|
||||||
|
func (pk *PublicKey) wotsPkFromSig(signature, m []byte, adrs adrsOperations, out []byte) {
|
||||||
|
var msgAndCsum [MAX_WOTS_LEN]byte
|
||||||
|
// convert message to base w=16
|
||||||
|
bytes2nibbles(m, msgAndCsum[:])
|
||||||
|
// compute checksum
|
||||||
|
// checksum = 15 * len1 - sum(msgAndCsum)
|
||||||
|
var csum uint16
|
||||||
|
len1 := pk.params.n * 2
|
||||||
|
for i := range len1 {
|
||||||
|
csum += uint16(msgAndCsum[i])
|
||||||
|
}
|
||||||
|
csum = uint16(15*len1) - csum
|
||||||
|
// convert checksum to base w=16 (left shift by 4 first)
|
||||||
|
msgAndCsum[len1] = byte(csum>>8) & 0x0F
|
||||||
|
msgAndCsum[len1+1] = byte(csum>>4) & 0x0F
|
||||||
|
msgAndCsum[len1+2] = byte(csum) & 0x0F
|
||||||
|
|
||||||
|
tmpBuf := make([]byte, pk.params.n*pk.params.len)
|
||||||
|
copy(tmpBuf, signature)
|
||||||
|
tmp := tmpBuf
|
||||||
|
for i := range pk.params.len {
|
||||||
|
adrs.setChainAddress(i)
|
||||||
|
pk.wotsChain(tmp, msgAndCsum[i], 15-msgAndCsum[i], adrs)
|
||||||
|
tmp = tmp[pk.params.n:]
|
||||||
|
}
|
||||||
|
wotspkADRS := pk.addressCreator()
|
||||||
|
wotspkADRS.clone(adrs)
|
||||||
|
wotspkADRS.setTypeAndClear(AddressTypeWOTSPK)
|
||||||
|
wotspkADRS.copyKeyPairAddress(adrs)
|
||||||
|
pk.h.t(pk, wotspkADRS, tmpBuf, out)
|
||||||
|
clear(tmpBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytes2nibbles(in, out []byte) {
|
||||||
|
for i := range in {
|
||||||
|
out[i*2] = in[i] >> 4
|
||||||
|
out[i*2+1] = in[i] & 0x0F
|
||||||
|
}
|
||||||
|
}
|
67
slhdsa/xmss.go
Normal file
67
slhdsa/xmss.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2025 Sun Yimin. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.24
|
||||||
|
|
||||||
|
package slhdsa
|
||||||
|
|
||||||
|
// xmssNode computes the root of a Merkle subtree of WOTS public keys.
|
||||||
|
// See FIPS 205 Algorithm 9 xmss_node
|
||||||
|
func (sk *PrivateKey) xmssNode(out []byte, i, z uint32, adrs adrsOperations) {
|
||||||
|
if z == 0 { // height 0
|
||||||
|
adrs.setTypeAndClear(AddressTypeWOTSHash)
|
||||||
|
adrs.setKeyPairAddress(i)
|
||||||
|
sk.wotsPkGen(out, adrs)
|
||||||
|
} else {
|
||||||
|
var lnode, rnode [MAX_N]byte
|
||||||
|
sk.xmssNode(lnode[:], 2*i, z-1, adrs)
|
||||||
|
sk.xmssNode(rnode[:], 2*i+1, z-1, adrs)
|
||||||
|
adrs.setTypeAndClear(AddressTypeTree)
|
||||||
|
adrs.setTreeHeight(z)
|
||||||
|
adrs.setTreeIndex(i)
|
||||||
|
sk.h.h(&sk.PublicKey, adrs, lnode[:], rnode[:], out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xmssSign generates an XMSS signature.
|
||||||
|
// See FIPS 205 Algorithm 10 xmss_sign
|
||||||
|
func (sk *PrivateKey) xmssSign(pkFors []byte, leafIdx uint32, adrs adrsOperations, signature []byte) {
|
||||||
|
authStart := sk.params.n * sk.params.len
|
||||||
|
authPath := signature[authStart:]
|
||||||
|
leafIdxCopy := leafIdx
|
||||||
|
for j := range sk.params.hm {
|
||||||
|
sk.xmssNode(authPath, leafIdx^1, j, adrs)
|
||||||
|
authPath = authPath[sk.params.n:]
|
||||||
|
leafIdx >>= 1
|
||||||
|
}
|
||||||
|
adrs.setTypeAndClear(AddressTypeWOTSHash)
|
||||||
|
adrs.setKeyPairAddress(leafIdxCopy)
|
||||||
|
sk.wotsSign(pkFors, adrs, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xmssPkFromSig computes an XMSS public key from an XMSS signature.
|
||||||
|
// See FIPS 205 Algorithm 11 xmss_pkFromSig
|
||||||
|
func (pk *PublicKey) xmssPkFromSig(leafIdx uint32, signature, m []byte, adrs adrsOperations, out []byte) {
|
||||||
|
// compute WOTS pk from WOTS signature
|
||||||
|
adrs.setTypeAndClear(AddressTypeWOTSHash)
|
||||||
|
adrs.setKeyPairAddress(leafIdx)
|
||||||
|
pk.wotsPkFromSig(signature, m, adrs, out)
|
||||||
|
|
||||||
|
// compute root from WOTS pk and AUTH
|
||||||
|
adrs.setTypeAndClear(AddressTypeTree)
|
||||||
|
signature = signature[pk.params.len*pk.params.n:] // auth path
|
||||||
|
for k := range pk.params.hm {
|
||||||
|
adrs.setTreeHeight(k + 1)
|
||||||
|
if leafIdx&1 == 0 { // even
|
||||||
|
leafIdx >>= 1
|
||||||
|
adrs.setTreeIndex(leafIdx)
|
||||||
|
pk.h.h(pk, adrs, out, signature, out)
|
||||||
|
} else { // odd
|
||||||
|
leafIdx = (leafIdx - 1) >> 1
|
||||||
|
adrs.setTreeIndex(leafIdx)
|
||||||
|
pk.h.h(pk, adrs, signature, out, out)
|
||||||
|
}
|
||||||
|
signature = signature[pk.params.n:]
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user