gmsm/slhdsa/dsa.go

147 lines
4.6 KiB
Go

// 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) {
signatureHead := make([]byte, sk.params.sigLen)
// 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, signatureHead)
R := signatureHead[:sk.params.n]
signature := signatureHead[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())
// The address adrs must have the layer address set to zero (since the XMSS tree that signs a FORS key is always at layer 0),
// the tree address set to the index of the WOTS+ key within the XMSS tree that signs the FORS key.
adrs := sk.addressCreator()
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 signatureHead, 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
}