echo "gost3410-512C gost3410-256A
ed25519-blake2b ed25519-blake2b
-sphincs+-shake-256s sphincs+-shake-256s" | while read caAlgo eeAlgo ; do
+slh-dsa-shake-256s slh-dsa-shake-256s" | while read caAlgo eeAlgo ; do
sub="-sub CN=CA -sub C=RU"
test_expect_success "$caAlgo: CA load generation" "cmkeytool \
"go.cypherpunks.su/keks/cm/sign"
ed25519blake2b "go.cypherpunks.su/keks/cm/sign/ed25519-blake2b"
"go.cypherpunks.su/keks/cm/sign/gost"
- "go.cypherpunks.su/keks/cm/sign/spx"
+ "go.cypherpunks.su/keks/cm/sign/slhdsa"
)
const (
gost.GOST3410512C,
sntrup4591761x25519.SNTRUP4591761X25519,
mceliece6960119x25519.ClassicMcEliece6960119X25519,
- spx.SPHINCSPlusSHAKE256s,
+ slhdsa.SLHDSASHAKE256s,
}
sort.Strings(algos)
for _, s := range algos {
prvRaw, pub, err = sntrup4591761x25519.NewKeypair()
case mceliece6960119x25519.ClassicMcEliece6960119X25519:
prvRaw, pub, err = mceliece6960119x25519.NewKeypair()
- case spx.SPHINCSPlusSHAKE256s:
- prvRaw, pub, err = spx.NewKeypair(*algo)
+ case slhdsa.SLHDSASHAKE256s:
+ prvRaw, pub, err = slhdsa.NewKeypair(*algo)
default:
err = errors.New("unknown -algo specified")
}
hasher = cmhash.ByName(cmhash.BLAKE2b256)
case gost.GOST3410256A, gost.GOST3410512C:
hasher = cmhash.ByName(cmhash.Streebog256)
- case mceliece6960119x25519.ClassicMcEliece6960119X25519, spx.SPHINCSPlusSHAKE256s:
+ case mceliece6960119x25519.ClassicMcEliece6960119X25519, slhdsa.SLHDSASHAKE256s:
hasher = cmhash.ByName(cmhash.SHAKE128)
default:
log.Fatal("unsupported algorithm")
echo "gost3410-512C
gost3410-256A
ed25519-blake2b
-sphincs+-shake-256s" | while read keyalgo ; do
+slh-dsa-shake-256s" | while read keyalgo ; do
sub="-sub what=ever"
typ="some-different-type"
-Go/Git is unable to fetch (https://github.com/cloudflare/circl.git)
+Go/Git is unable to fetch (https://github.com/cloudflare/circl)
pull-request's commit (7dfc396c96830ed3601ace705e1612b9bcc447f9) to
github.com/cloudflare/circl containing mceliece6960119 implementation
(https://github.com/cloudflare/circl/pull/378). So copy it here.
require (
github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a
github.com/google/uuid v1.6.0
- github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9
go.cypherpunks.su/balloon/v3 v3.0.0
go.cypherpunks.su/gogost/v6 v6.1.0
go.cypherpunks.su/keks v0.0.0-00010101000000-000000000000
github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a/go.mod h1:z/9Ck1EDixEbBbZ2KH2qNHekEmDLTOZ+FyoIPWWSVOI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9 h1:G8fshCtNb60L5IM2tuYD81uh6YQFqJ78MAGUCMks7Bg=
-github.com/kasperdi/SPHINCSPLUS-golang v0.0.0-20231223193046-84468b93f7e9/go.mod h1:XWeSWo+UqzMi1uh/Td/gKlVHaPQjUj92s3omn7eccUM=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
go.cypherpunks.su/balloon/v3 v3.0.0 h1:80JUfOvjEgeuQlZ8biZarbuld0T9L/6gbC2DAZAZncI=
return h
case SHAKE128:
return NewSHAKE128()
- case SHAKE256, SPHINCSPlusSHAKE256s, SPHINCSPlusSHAKE256sPh:
+ case SHAKE256, SLHDSASHAKE256s, SLHDSASHAKE256sPh:
return NewSHAKE256()
case SHAKE128Merkle:
return NewSHAKE128MerkleHasher(
merkle.DefaultChunkLen, DefaultNumCPU)
- case SHAKE256Merkle, SPHINCSPlusSHAKE256sMerkle:
+ case SHAKE256Merkle, SLHDSASHAKE256sMerkle:
return NewSHAKE256MerkleHasher(
merkle.DefaultChunkLen, DefaultNumCPU)
}
SHAKE128Merkle = "shake128-merkle"
SHAKE256Merkle = "shake256-merkle"
- SPHINCSPlusSHAKE256s = "sphincs+-shake-256s"
- SPHINCSPlusSHAKE256sPh = "sphincs+-shake-256s-ph"
- SPHINCSPlusSHAKE256sMerkle = "sphincs+-shake-256s-merkle"
+ SLHDSASHAKE256s = "slh-dsa-shake-256s"
+ SLHDSASHAKE256sPh = "slh-dsa-shake-256s-ph"
+ SLHDSASHAKE256sMerkle = "slh-dsa-shake-256s-merkle"
)
type SHAKE struct {
"go.cypherpunks.su/keks/cm"
ed25519blake2b "go.cypherpunks.su/keks/cm/sign/ed25519-blake2b"
"go.cypherpunks.su/keks/cm/sign/gost"
- "go.cypherpunks.su/keks/cm/sign/spx"
+ "go.cypherpunks.su/keks/cm/sign/slhdsa"
"go.cypherpunks.su/keks/schema"
)
prv, pub, err = ed25519blake2b.NewSigner(av.V)
case gost.GOST3410256A, gost.GOST3410512C:
prv, pub, err = gost.NewSigner(av.V)
- case spx.SPHINCSPlusSHAKE256s:
- prv, pub, err = spx.NewSigner(av.V)
+ case slhdsa.SLHDSASHAKE256s:
+ prv, pub, err = slhdsa.NewSigner(av.V)
default:
err = fmt.Errorf("unknown private key algo: %s", av.A)
}
"go.cypherpunks.su/keks/cm"
ed25519blake2b "go.cypherpunks.su/keks/cm/sign/ed25519-blake2b"
"go.cypherpunks.su/keks/cm/sign/gost"
- "go.cypherpunks.su/keks/cm/sign/spx"
+ "go.cypherpunks.su/keks/cm/sign/slhdsa"
"go.cypherpunks.su/keks/schema"
)
if !valid {
err = ErrSigInvalid
}
- case spx.SPHINCSPlusSHAKE256s:
- if algo != spx.SPHINCSPlusSHAKE256s {
+ case slhdsa.SLHDSASHAKE256s:
+ if algo != slhdsa.SLHDSASHAKE256s {
return ErrBadSigAlgo
}
- valid, err = spx.Verify(key.A, key.V, signed, signature)
+ valid, err = slhdsa.Verify(key.A, key.V, signed, signature)
if !valid {
err = ErrSigInvalid
}
if !valid {
err = ErrSigInvalid
}
- case spx.SPHINCSPlusSHAKE256s:
+ case slhdsa.SLHDSASHAKE256s:
switch algo {
- case spx.SPHINCSPlusSHAKE256sPh:
- case spx.SPHINCSPlusSHAKE256sMerkle:
+ case slhdsa.SLHDSASHAKE256sPh:
+ case slhdsa.SLHDSASHAKE256sMerkle:
default:
return ErrBadSigAlgo
}
- valid, err = spx.VerifyPrehash(key.A, key.V, prehash, signature)
+ valid, err = slhdsa.VerifyPrehash(algo, key.V, prehash, signature)
if !valid {
err = ErrSigInvalid
}
--- /dev/null
+Go/Git is unable to fetch (https://github.com/cloudflare/circl)
+pull-request's commit (5e2385ccad8a0f692caf217682c657481bbff24a) to
+github.com/cloudflare/circl containing SLH-DSA implementation
+(https://github.com/cloudflare/circl/pull/512). So copy it here.
--- /dev/null
+package circl
+
+import "encoding/binary"
+
+// See FIPS 205 -- Section 4.2
+// Functions and Addressing
+
+type addrType = uint32
+
+const (
+ addressWotsHash = addrType(iota)
+ addressWotsPk
+ addressTree
+ addressForsTree
+ addressForsRoots
+ addressWotsPrf
+ addressForsPrf
+)
+
+const (
+ addressSizeCompressed = 22
+ addressSizeNonCompressed = 32
+)
+
+type address struct {
+ b []byte
+ o int
+}
+
+func (p *params) addressSize() uint32 {
+ if p.isSHA2 {
+ return addressSizeCompressed
+ } else {
+ return addressSizeNonCompressed
+ }
+}
+
+func (p *params) addressOffset() int {
+ if p.isSHA2 {
+ return 0
+ } else {
+ return 10
+ }
+}
+
+func (p *params) NewAddress() (a address) {
+ var m [addressSizeNonCompressed]byte
+ a.b = m[:p.addressSize()]
+ a.o = p.addressOffset()
+ return
+}
+
+func (a *address) fromBytes(p *params, c *cursor) {
+ a.b = c.Next(p.addressSize())
+ a.o = p.addressOffset()
+}
+
+func (a *address) Set(x address) { copy(a.b, x.b); a.o = x.o }
+func (a *address) Clear() { clearSlice(&a.b); a.o = 0 }
+func (a *address) SetKeyPairAddress(i uint32) { binary.BigEndian.PutUint32(a.b[a.o+10:], i) }
+func (a *address) SetChainAddress(i uint32) { binary.BigEndian.PutUint32(a.b[a.o+14:], i) }
+func (a *address) SetTreeHeight(i uint32) { binary.BigEndian.PutUint32(a.b[a.o+14:], i) }
+func (a *address) SetHashAddress(i uint32) { binary.BigEndian.PutUint32(a.b[a.o+18:], i) }
+func (a *address) SetTreeIndex(i uint32) { binary.BigEndian.PutUint32(a.b[a.o+18:], i) }
+func (a *address) GetKeyPairAddress() uint32 { return binary.BigEndian.Uint32(a.b[a.o+10:]) }
+func (a *address) SetLayerAddress(l addrType) {
+ if a.o == 0 {
+ a.b[0] = byte(l & 0xFF)
+ } else {
+ binary.BigEndian.PutUint32(a.b[0:], l)
+ }
+}
+
+func (a *address) SetTreeAddress(t [3]uint32) {
+ if a.o == 0 {
+ binary.BigEndian.PutUint32(a.b[1:], t[1])
+ binary.BigEndian.PutUint32(a.b[5:], t[0])
+ } else {
+ binary.BigEndian.PutUint32(a.b[4:], t[2])
+ binary.BigEndian.PutUint32(a.b[8:], t[1])
+ binary.BigEndian.PutUint32(a.b[12:], t[0])
+ }
+}
+
+func (a *address) SetTypeAndClear(t uint32) {
+ if a.o == 0 {
+ a.b[9] = byte(t)
+ } else {
+ binary.BigEndian.PutUint32(a.b[16:], t)
+ }
+ clear(a.b[a.o+10:])
+}
--- /dev/null
+package circl
+
+// See FIPS 205 -- Section 8
+// Forest of Random Subsets (FORS) is a few-time signature scheme that is
+// used to sign the digests of the actual messages.
+
+type (
+ forsPublicKey []byte // n bytes
+ forsPrivateKey []byte // n bytes
+ forsSignature []forsPair // k*forsPairSize() bytes
+ forsPair struct {
+ sk forsPrivateKey // forsSkSize() bytes
+ auth [][]byte // a*n bytes
+ } // forsSkSize() + a*n bytes
+)
+
+func (p *params) forsMsgSize() uint32 { return (p.k*p.a + 7) / 8 }
+func (p *params) forsPkSize() uint32 { return p.n }
+func (p *params) forsSkSize() uint32 { return p.n }
+func (p *params) forsSigSize() uint32 { return p.k * p.forsPairSize() }
+func (p *params) forsPairSize() uint32 { return p.forsSkSize() + p.a*p.n }
+
+func (fs *forsSignature) fromBytes(p *params, c *cursor) {
+ *fs = make([]forsPair, p.k)
+ for i := range *fs {
+ (*fs)[i].fromBytes(p, c)
+ }
+}
+
+func (fp *forsPair) fromBytes(p *params, c *cursor) {
+ fp.sk = c.Next(p.forsSkSize())
+ fp.auth = make([][]byte, p.a)
+ for i := range fp.auth {
+ fp.auth[i] = c.Next(p.n)
+ }
+}
+
+// See FIPS 205 -- Section 8.1 -- Algorithm 14.
+func (s *statePriv) forsSkGen(addr address, idx uint32) forsPrivateKey {
+ s.PRF.address.Set(addr)
+ s.PRF.address.SetTypeAndClear(addressForsPrf)
+ s.PRF.address.SetKeyPairAddress(addr.GetKeyPairAddress())
+ s.PRF.address.SetTreeIndex(idx)
+
+ return s.PRF.Final()
+}
+
+// See FIPS 205 -- Section 8.2 -- Algorithm 15 -- Iterative version.
+//
+// This is a stack-based implementation that computes the tree leaves
+// in order (from the left to the right).
+// Its recursive version can be found at fors_test.go file.
+func (s *statePriv) forsNodeIter(
+ stack stackNode, root []byte, i, z uint32, addr address,
+) {
+ if !(z <= s.a && i < s.k<<(s.a-z)) {
+ panic(ErrTree)
+ }
+
+ s.F.address.Set(addr)
+ s.F.address.SetTreeHeight(0)
+
+ s.H.address.Set(addr)
+
+ twoZ := uint32(1) << z
+ iTwoZ := i << z
+ for k := range twoZ {
+ li := iTwoZ + k
+ lz := uint32(0)
+
+ sk := s.forsSkGen(addr, li)
+ s.F.address.SetTreeIndex(li)
+ s.F.SetMessage(sk)
+ node := s.F.Final()
+
+ for !stack.isEmpty() && stack.top().z == lz {
+ left := stack.pop()
+ li = (li - 1) >> 1
+ lz = lz + 1
+
+ s.H.address.SetTreeHeight(lz)
+ s.H.address.SetTreeIndex(li)
+ s.H.SetMsgs(left.node, node)
+ node = s.H.Final()
+ }
+
+ stack.push(item{node, lz})
+ }
+
+ copy(root, stack.pop().node)
+}
+
+// See FIPS 205 -- Section 8.3 -- Algorithm 16.
+func (s *statePriv) forsSign(sig forsSignature, digest []byte, addr address) {
+ stack := s.NewStack(s.a - 1)
+ defer stack.Clear()
+
+ in, bits, total := 0, uint32(0), uint32(0)
+ maskA := (uint32(1) << s.a) - 1
+
+ for i := range s.k {
+ for bits < s.a {
+ total = (total << 8) + uint32(digest[in])
+ in++
+ bits += 8
+ }
+
+ bits -= s.a
+ indicesI := (total >> bits) & maskA
+ treeIdx := (i << s.a) + indicesI
+ forsSk := s.forsSkGen(addr, treeIdx)
+ copy(sig[i].sk, forsSk)
+
+ for j := range s.a {
+ shift := (indicesI >> j) ^ 1
+ s.forsNodeIter(stack, sig[i].auth[j], (i<<(s.a-j))+shift, j, addr)
+ }
+ }
+}
+
+// See FIPS 205 -- Section 8.4 -- Algorithm 17.
+func (s *state) forsPkFromSig(
+ sig forsSignature, digest []byte, addr address,
+) (pk forsPublicKey) {
+ pk = make([]byte, s.forsPkSize())
+
+ s.F.address.Set(addr)
+ s.F.address.SetTreeHeight(0)
+
+ s.H.address.Set(addr)
+
+ s.T.address.Set(addr)
+ s.T.address.SetTypeAndClear(addressForsRoots)
+ s.T.address.SetKeyPairAddress(addr.GetKeyPairAddress())
+ s.T.Reset()
+
+ in, bits, total := 0, uint32(0), uint32(0)
+ maskA := (uint32(1) << s.a) - 1
+
+ for i := range s.k {
+ for bits < s.a {
+ total = (total << 8) + uint32(digest[in])
+ in++
+ bits += 8
+ }
+
+ bits -= s.a
+ indicesI := (total >> bits) & maskA
+ treeIdx := (i << s.a) + indicesI
+ s.F.address.SetTreeIndex(treeIdx)
+ s.F.SetMessage(sig[i].sk)
+ node := s.F.Final()
+
+ for j := range s.a {
+ if (indicesI>>j)&0x1 == 0 {
+ treeIdx = treeIdx >> 1
+ s.H.SetMsgs(node, sig[i].auth[j])
+ } else {
+ treeIdx = (treeIdx - 1) >> 1
+ s.H.SetMsgs(sig[i].auth[j], node)
+ }
+
+ s.H.address.SetTreeHeight(j + 1)
+ s.H.address.SetTreeIndex(treeIdx)
+ node = s.H.Final()
+ }
+
+ s.T.WriteMessage(node)
+ }
+
+ copy(pk, s.T.Final())
+ return pk
+}
--- /dev/null
+package circl
+
+import "bytes"
+
+// See FIPS 205 -- Section 7
+// SLH-DSA uses a hypertree to sign the FORS keys.
+
+type hyperTreeSignature []xmssSignature // d*xmssSigSize() bytes
+
+func (p *params) hyperTreeSigSize() uint32 { return p.d * p.xmssSigSize() }
+
+func (hts *hyperTreeSignature) fromBytes(p *params, c *cursor) {
+ *hts = make([]xmssSignature, p.d)
+ for i := range *hts {
+ (*hts)[i].fromBytes(p, c)
+ }
+}
+
+func nextIndex(idxTree *[3]uint32, n uint32) (idxLeaf uint32) {
+ idxLeaf = idxTree[0] & ((1 << n) - 1)
+ idxTree[0] = (idxTree[0] >> n) | (idxTree[1] << (32 - n))
+ idxTree[1] = (idxTree[1] >> n) | (idxTree[2] << (32 - n))
+ idxTree[2] = (idxTree[2] >> n)
+
+ return
+}
+
+// See FIPS 205 -- Section 7.1 -- Algorithm 12.
+func (s *statePriv) htSign(
+ sig hyperTreeSignature, msg []byte, idxTree [3]uint32, idxLeaf uint32,
+) {
+ addr := s.NewAddress()
+ addr.SetTreeAddress(idxTree)
+ stack := s.NewStack(s.hPrime - 1)
+ defer stack.Clear()
+
+ s.xmssSign(stack, sig[0], msg, idxLeaf, addr)
+
+ root := make([]byte, s.xmssPkSize())
+ copy(root, msg)
+ for j := uint32(1); j < s.d; j++ {
+ s.xmssPkFromSig(root, root, sig[j-1], idxLeaf, addr)
+ idxLeaf = nextIndex(&idxTree, s.hPrime)
+ addr.SetLayerAddress(j)
+ addr.SetTreeAddress(idxTree)
+ s.xmssSign(stack, sig[j], root, idxLeaf, addr)
+ }
+}
+
+// See FIPS 205 -- Section 7.2 -- Algorithm 13.
+func (s *state) htVerify(
+ msg, root []byte, idxTree [3]uint32, idxLeaf uint32, sig hyperTreeSignature,
+) bool {
+ addr := s.NewAddress()
+ addr.SetTreeAddress(idxTree)
+
+ node := make([]byte, s.xmssPkSize())
+ s.xmssPkFromSig(node, msg, sig[0], idxLeaf, addr)
+
+ for j := uint32(1); j < s.d; j++ {
+ idxLeaf = nextIndex(&idxTree, s.hPrime)
+ addr.SetLayerAddress(j)
+ addr.SetTreeAddress(idxTree)
+ s.xmssPkFromSig(node, node, sig[j], idxLeaf, addr)
+ }
+
+ return bytes.Equal(node, root)
+}
--- /dev/null
+package circl
+
+import "encoding/binary"
+
+// See FIPS 205 -- Section 9
+// SLH-DSA Internal Functions
+
+// See FIPS 205 -- Section 9.1 -- Algorithm 18.
+func slhKeyGenInternal(
+ p *params, skSeed, skPrf, pkSeed []byte,
+) (pub PublicKey, priv PrivateKey) {
+ s := p.NewStatePriv(skSeed, pkSeed)
+ defer s.Clear()
+
+ stack := p.NewStack(p.hPrime)
+ defer stack.Clear()
+
+ addr := p.NewAddress()
+ addr.SetLayerAddress(p.d - 1)
+ pkRoot := make([]byte, p.n)
+ s.xmssNodeIter(stack, pkRoot, 0, p.hPrime, addr)
+
+ pub.ID = p.ID
+ pub.seed = pkSeed
+ pub.root = pkRoot
+
+ priv.ID = p.ID
+ priv.prfKey = skPrf
+ priv.seed = skSeed
+ priv.publicKey = pub
+
+ return
+}
+
+func (p *params) parseMsg(
+ digest []byte,
+) (md []byte, idxTree [3]uint32, idxLeaf uint32) {
+ l1 := (p.k*p.a + 7) / 8
+ l2 := (p.h - p.h/p.d + 7) / 8
+ l3 := (p.h + 8*p.d - 1) / (8 * p.d)
+
+ c := cursor(digest)
+ md = c.Next(l1)
+ s2 := c.Next(l2)
+ s3 := c.Next(l3)
+
+ var b2 [12]byte
+ copy(b2[12-len(s2):], s2)
+ n2 := p.h - p.h/p.d
+ idxTree[0] = binary.BigEndian.Uint32(b2[8:]) & ((1 << n2) - 1)
+ n2 -= 32
+ idxTree[1] = binary.BigEndian.Uint32(b2[4:]) & ((1 << n2) - 1)
+ idxTree[2] = binary.BigEndian.Uint32(b2[0:])
+
+ var b3 [4]byte
+ copy(b3[4-len(s3):], s3)
+ mask32 := (uint32(1) << (p.h / p.d)) - 1
+ idxLeaf = mask32 & binary.BigEndian.Uint32(b3[0:])
+
+ return
+}
+
+// See FIPS 205 -- Section 9.2 -- Algorithm 19.
+func slhSignInternal(sk *PrivateKey, message, addRand []byte) ([]byte, error) {
+ p := sk.ID.params()
+ sigBytes := make([]byte, p.SignatureSize())
+
+ var sig signature
+ curSig := cursor(sigBytes)
+ if !sig.fromBytes(p, &curSig) {
+ return nil, ErrSigParse
+ }
+
+ p.PRFMsg(sig.rnd, sk.prfKey, addRand, message)
+ digest := make([]byte, p.m)
+ p.HashMsg(digest, sig.rnd, message, &sk.publicKey)
+
+ md, idxTree, idxLeaf := p.parseMsg(digest)
+ addr := p.NewAddress()
+ addr.SetTreeAddress(idxTree)
+ addr.SetTypeAndClear(addressForsTree)
+ addr.SetKeyPairAddress(idxLeaf)
+
+ s := p.NewStatePriv(sk.seed, sk.publicKey.seed)
+ defer s.Clear()
+
+ s.forsSign(sig.forsSig, md, addr)
+ pkFors := s.forsPkFromSig(sig.forsSig, md, addr)
+ s.htSign(sig.htSig, pkFors, idxTree, idxLeaf)
+
+ return sigBytes, nil
+}
+
+// See FIPS 205 -- Section 9.3 -- Algorithm 20.
+func slhVerifyInternal(pub *PublicKey, message, sigBytes []byte) bool {
+ p := pub.ID.params()
+ var sig signature
+ curSig := cursor(sigBytes)
+ if len(sigBytes) != p.SignatureSize() || !sig.fromBytes(p, &curSig) {
+ return false
+ }
+
+ digest := make([]byte, p.m)
+ p.HashMsg(digest, sig.rnd, message, pub)
+
+ md, idxTree, idxLeaf := p.parseMsg(digest)
+ addr := p.NewAddress()
+ addr.SetTreeAddress(idxTree)
+ addr.SetTypeAndClear(addressForsTree)
+ addr.SetKeyPairAddress(idxLeaf)
+
+ s := p.NewStatePub(pub.seed)
+ defer s.Clear()
+
+ pkFors := s.forsPkFromSig(sig.forsSig, md, addr)
+ return s.htVerify(pkFors, pub.root, idxTree, idxLeaf, sig.htSig)
+}
+
+// signature represents a SLH-DSA signature.
+type signature struct {
+ rnd []byte // n bytes
+ forsSig forsSignature // forsSigSize() bytes
+ htSig hyperTreeSignature // hyperTreeSigSize() bytes
+}
+
+func (p *params) SignatureSize() int {
+ return int(p.n + p.forsSigSize() + p.hyperTreeSigSize())
+}
+
+func (s *signature) fromBytes(p *params, c *cursor) bool {
+ s.rnd = c.Next(p.n)
+ s.forsSig.fromBytes(p, c)
+ s.htSig.fromBytes(p, c)
+ return len(*c) == 0
+}
--- /dev/null
+package conv
+
+import (
+ "encoding/binary"
+ "fmt"
+ "math/big"
+ "strings"
+
+ "golang.org/x/crypto/cryptobyte"
+)
+
+// BytesLe2Hex returns an hexadecimal string of a number stored in a
+// little-endian order slice x.
+func BytesLe2Hex(x []byte) string {
+ b := &strings.Builder{}
+ b.Grow(2*len(x) + 2)
+ fmt.Fprint(b, "0x")
+ if len(x) == 0 {
+ fmt.Fprint(b, "00")
+ }
+ for i := len(x) - 1; i >= 0; i-- {
+ fmt.Fprintf(b, "%02x", x[i])
+ }
+ return b.String()
+}
+
+// BytesLe2BigInt converts a little-endian slice x into a big-endian
+// math/big.Int.
+func BytesLe2BigInt(x []byte) *big.Int {
+ n := len(x)
+ b := new(big.Int)
+ if len(x) > 0 {
+ y := make([]byte, n)
+ for i := 0; i < n; i++ {
+ y[n-1-i] = x[i]
+ }
+ b.SetBytes(y)
+ }
+ return b
+}
+
+// BytesBe2Uint64Le converts a big-endian slice x to a little-endian slice of uint64.
+func BytesBe2Uint64Le(x []byte) []uint64 {
+ l := len(x)
+ z := make([]uint64, (l+7)/8)
+ blocks := l / 8
+ for i := 0; i < blocks; i++ {
+ z[i] = binary.BigEndian.Uint64(x[l-8*(i+1):])
+ }
+ remBytes := l % 8
+ for i := 0; i < remBytes; i++ {
+ z[blocks] |= uint64(x[l-1-8*blocks-i]) << uint(8*i)
+ }
+ return z
+}
+
+// BigInt2BytesLe stores a positive big.Int number x into a little-endian slice z.
+// The slice is modified if the bitlength of x <= 8*len(z) (padding with zeros).
+// If x does not fit in the slice or is negative, z is not modified.
+func BigInt2BytesLe(z []byte, x *big.Int) {
+ xLen := (x.BitLen() + 7) >> 3
+ zLen := len(z)
+ if zLen >= xLen && x.Sign() >= 0 {
+ y := x.Bytes()
+ for i := 0; i < xLen; i++ {
+ z[i] = y[xLen-1-i]
+ }
+ for i := xLen; i < zLen; i++ {
+ z[i] = 0
+ }
+ }
+}
+
+// Uint64Le2BigInt converts a little-endian slice x into a big number.
+func Uint64Le2BigInt(x []uint64) *big.Int {
+ n := len(x)
+ b := new(big.Int)
+ var bi big.Int
+ for i := n - 1; i >= 0; i-- {
+ bi.SetUint64(x[i])
+ b.Lsh(b, 64)
+ b.Add(b, &bi)
+ }
+ return b
+}
+
+// Uint64Le2BytesLe converts a little-endian slice x to a little-endian slice of bytes.
+func Uint64Le2BytesLe(x []uint64) []byte {
+ b := make([]byte, 8*len(x))
+ n := len(x)
+ for i := 0; i < n; i++ {
+ binary.LittleEndian.PutUint64(b[i*8:], x[i])
+ }
+ return b
+}
+
+// Uint64Le2BytesBe converts a little-endian slice x to a big-endian slice of bytes.
+func Uint64Le2BytesBe(x []uint64) []byte {
+ b := make([]byte, 8*len(x))
+ n := len(x)
+ for i := 0; i < n; i++ {
+ binary.BigEndian.PutUint64(b[i*8:], x[n-1-i])
+ }
+ return b
+}
+
+// Uint64Le2Hex returns an hexadecimal string of a number stored in a
+// little-endian order slice x.
+func Uint64Le2Hex(x []uint64) string {
+ b := new(strings.Builder)
+ b.Grow(16*len(x) + 2)
+ fmt.Fprint(b, "0x")
+ if len(x) == 0 {
+ fmt.Fprint(b, "00")
+ }
+ for i := len(x) - 1; i >= 0; i-- {
+ fmt.Fprintf(b, "%016x", x[i])
+ }
+ return b.String()
+}
+
+// BigInt2Uint64Le stores a positive big.Int number x into a little-endian slice z.
+// The slice is modified if the bitlength of x <= 8*len(z) (padding with zeros).
+// If x does not fit in the slice or is negative, z is not modified.
+func BigInt2Uint64Le(z []uint64, x *big.Int) {
+ xLen := (x.BitLen() + 63) >> 6 // number of 64-bit words
+ zLen := len(z)
+ if zLen >= xLen && x.Sign() > 0 {
+ var y, yi big.Int
+ y.Set(x)
+ two64 := big.NewInt(1)
+ two64.Lsh(two64, 64).Sub(two64, big.NewInt(1))
+ for i := 0; i < xLen; i++ {
+ yi.And(&y, two64)
+ z[i] = yi.Uint64()
+ y.Rsh(&y, 64)
+ }
+ }
+ for i := xLen; i < zLen; i++ {
+ z[i] = 0
+ }
+}
+
+// MarshalBinary encodes a value into a byte array in a format readable by UnmarshalBinary.
+func MarshalBinary(v cryptobyte.MarshalingValue) ([]byte, error) {
+ const DefaultSize = 32
+ b := cryptobyte.NewBuilder(make([]byte, 0, DefaultSize))
+ b.AddValue(v)
+ return b.Bytes()
+}
+
+// MarshalBinaryLen encodes a value into an array of n bytes in a format readable by UnmarshalBinary.
+func MarshalBinaryLen(v cryptobyte.MarshalingValue, length uint) ([]byte, error) {
+ b := cryptobyte.NewFixedBuilder(make([]byte, 0, length))
+ b.AddValue(v)
+ return b.Bytes()
+}
+
+// A UnmarshalingValue decodes itself from a cryptobyte.String and advances the pointer.
+// It reports whether the read was successful.
+type UnmarshalingValue interface {
+ Unmarshal(*cryptobyte.String) bool
+}
+
+// UnmarshalBinary recovers a value from a byte array.
+// It returns an error if the read was unsuccessful.
+func UnmarshalBinary(v UnmarshalingValue, data []byte) (err error) {
+ s := cryptobyte.String(data)
+ if data == nil || !v.Unmarshal(&s) || !s.Empty() {
+ err = fmt.Errorf("cannot read %T from input string", v)
+ }
+ return
+}
--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package sha3 implements the SHA-3 fixed-output-length hash functions and
+// the SHAKE variable-output-length hash functions defined by FIPS-202.
+//
+// Both types of hash function use the "sponge" construction and the Keccak
+// permutation. For a detailed specification see http://keccak.noekeon.org/
+//
+// # Guidance
+//
+// If you aren't sure what function you need, use SHAKE256 with at least 64
+// bytes of output. The SHAKE instances are faster than the SHA3 instances;
+// the latter have to allocate memory to conform to the hash.Hash interface.
+//
+// If you need a secret-key MAC (message authentication code), prepend the
+// secret key to the input, hash with SHAKE256 and read at least 32 bytes of
+// output.
+//
+// # Security strengths
+//
+// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security
+// strength against preimage attacks of x bits. Since they only produce "x"
+// bits of output, their collision-resistance is only "x/2" bits.
+//
+// The SHAKE-256 and -128 functions have a generic security strength of 256 and
+// 128 bits against all attacks, provided that at least 2x bits of their output
+// is used. Requesting more than 64 or 32 bytes of output, respectively, does
+// not increase the collision-resistance of the SHAKE functions.
+//
+// # The sponge construction
+//
+// A sponge builds a pseudo-random function from a public pseudo-random
+// permutation, by applying the permutation to a state of "rate + capacity"
+// bytes, but hiding "capacity" of the bytes.
+//
+// A sponge starts out with a zero state. To hash an input using a sponge, up
+// to "rate" bytes of the input are XORed into the sponge's state. The sponge
+// is then "full" and the permutation is applied to "empty" it. This process is
+// repeated until all the input has been "absorbed". The input is then padded.
+// The digest is "squeezed" from the sponge in the same way, except that output
+// is copied out instead of input being XORed in.
+//
+// A sponge is parameterized by its generic security strength, which is equal
+// to half its capacity; capacity + rate is equal to the permutation's width.
+// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means
+// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2.
+//
+// # Recommendations
+//
+// The SHAKE functions are recommended for most new uses. They can produce
+// output of arbitrary length. SHAKE256, with an output length of at least
+// 64 bytes, provides 256-bit security against all attacks. The Keccak team
+// recommends it for most applications upgrading from SHA2-512. (NIST chose a
+// much stronger, but much slower, sponge instance for SHA3-512.)
+//
+// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions.
+// They produce output of the same length, with the same security strengths
+// against all attacks. This means, in particular, that SHA3-256 only has
+// 128-bit collision resistance, because its output length is 32 bytes.
+package sha3
--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// This file provides functions for creating instances of the SHA-3
+// and SHAKE hash functions, as well as utility functions for hashing
+// bytes.
+
+// New224 creates a new SHA3-224 hash.
+// Its generic security strength is 224 bits against preimage attacks,
+// and 112 bits against collision attacks.
+func New224() State {
+ return State{rate: 144, outputLen: 28, dsbyte: 0x06}
+}
+
+// New256 creates a new SHA3-256 hash.
+// Its generic security strength is 256 bits against preimage attacks,
+// and 128 bits against collision attacks.
+func New256() State {
+ return State{rate: 136, outputLen: 32, dsbyte: 0x06}
+}
+
+// New384 creates a new SHA3-384 hash.
+// Its generic security strength is 384 bits against preimage attacks,
+// and 192 bits against collision attacks.
+func New384() State {
+ return State{rate: 104, outputLen: 48, dsbyte: 0x06}
+}
+
+// New512 creates a new SHA3-512 hash.
+// Its generic security strength is 512 bits against preimage attacks,
+// and 256 bits against collision attacks.
+func New512() State {
+ return State{rate: 72, outputLen: 64, dsbyte: 0x06}
+}
+
+// Sum224 returns the SHA3-224 digest of the data.
+func Sum224(data []byte) (digest [28]byte) {
+ h := New224()
+ _, _ = h.Write(data)
+ h.Sum(digest[:0])
+ return
+}
+
+// Sum256 returns the SHA3-256 digest of the data.
+func Sum256(data []byte) (digest [32]byte) {
+ h := New256()
+ _, _ = h.Write(data)
+ h.Sum(digest[:0])
+ return
+}
+
+// Sum384 returns the SHA3-384 digest of the data.
+func Sum384(data []byte) (digest [48]byte) {
+ h := New384()
+ _, _ = h.Write(data)
+ h.Sum(digest[:0])
+ return
+}
+
+// Sum512 returns the SHA3-512 digest of the data.
+func Sum512(data []byte) (digest [64]byte) {
+ h := New512()
+ _, _ = h.Write(data)
+ h.Sum(digest[:0])
+ return
+}
--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// KeccakF1600 applies the Keccak permutation to a 1600b-wide
+// state represented as a slice of 25 uint64s.
+// If turbo is true, applies the 12-round variant instead of the
+// regular 24-round variant.
+// nolint:funlen
+func KeccakF1600(a *[25]uint64, turbo bool) {
+ // Implementation translated from Keccak-inplace.c
+ // in the keccak reference code.
+ var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64
+
+ i := 0
+
+ if turbo {
+ i = 12
+ }
+
+ for ; i < 24; i += 4 {
+ // Combines the 5 steps in each round into 2 steps.
+ // Unrolls 4 rounds per loop and spreads some steps across rounds.
+
+ // Round 1
+ bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+ bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+ bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+ bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+ bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+ d0 = bc4 ^ (bc1<<1 | bc1>>63)
+ d1 = bc0 ^ (bc2<<1 | bc2>>63)
+ d2 = bc1 ^ (bc3<<1 | bc3>>63)
+ d3 = bc2 ^ (bc4<<1 | bc4>>63)
+ d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+ bc0 = a[0] ^ d0
+ t = a[6] ^ d1
+ bc1 = t<<44 | t>>(64-44)
+ t = a[12] ^ d2
+ bc2 = t<<43 | t>>(64-43)
+ t = a[18] ^ d3
+ bc3 = t<<21 | t>>(64-21)
+ t = a[24] ^ d4
+ bc4 = t<<14 | t>>(64-14)
+ a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i]
+ a[6] = bc1 ^ (bc3 &^ bc2)
+ a[12] = bc2 ^ (bc4 &^ bc3)
+ a[18] = bc3 ^ (bc0 &^ bc4)
+ a[24] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[10] ^ d0
+ bc2 = t<<3 | t>>(64-3)
+ t = a[16] ^ d1
+ bc3 = t<<45 | t>>(64-45)
+ t = a[22] ^ d2
+ bc4 = t<<61 | t>>(64-61)
+ t = a[3] ^ d3
+ bc0 = t<<28 | t>>(64-28)
+ t = a[9] ^ d4
+ bc1 = t<<20 | t>>(64-20)
+ a[10] = bc0 ^ (bc2 &^ bc1)
+ a[16] = bc1 ^ (bc3 &^ bc2)
+ a[22] = bc2 ^ (bc4 &^ bc3)
+ a[3] = bc3 ^ (bc0 &^ bc4)
+ a[9] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[20] ^ d0
+ bc4 = t<<18 | t>>(64-18)
+ t = a[1] ^ d1
+ bc0 = t<<1 | t>>(64-1)
+ t = a[7] ^ d2
+ bc1 = t<<6 | t>>(64-6)
+ t = a[13] ^ d3
+ bc2 = t<<25 | t>>(64-25)
+ t = a[19] ^ d4
+ bc3 = t<<8 | t>>(64-8)
+ a[20] = bc0 ^ (bc2 &^ bc1)
+ a[1] = bc1 ^ (bc3 &^ bc2)
+ a[7] = bc2 ^ (bc4 &^ bc3)
+ a[13] = bc3 ^ (bc0 &^ bc4)
+ a[19] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[5] ^ d0
+ bc1 = t<<36 | t>>(64-36)
+ t = a[11] ^ d1
+ bc2 = t<<10 | t>>(64-10)
+ t = a[17] ^ d2
+ bc3 = t<<15 | t>>(64-15)
+ t = a[23] ^ d3
+ bc4 = t<<56 | t>>(64-56)
+ t = a[4] ^ d4
+ bc0 = t<<27 | t>>(64-27)
+ a[5] = bc0 ^ (bc2 &^ bc1)
+ a[11] = bc1 ^ (bc3 &^ bc2)
+ a[17] = bc2 ^ (bc4 &^ bc3)
+ a[23] = bc3 ^ (bc0 &^ bc4)
+ a[4] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[15] ^ d0
+ bc3 = t<<41 | t>>(64-41)
+ t = a[21] ^ d1
+ bc4 = t<<2 | t>>(64-2)
+ t = a[2] ^ d2
+ bc0 = t<<62 | t>>(64-62)
+ t = a[8] ^ d3
+ bc1 = t<<55 | t>>(64-55)
+ t = a[14] ^ d4
+ bc2 = t<<39 | t>>(64-39)
+ a[15] = bc0 ^ (bc2 &^ bc1)
+ a[21] = bc1 ^ (bc3 &^ bc2)
+ a[2] = bc2 ^ (bc4 &^ bc3)
+ a[8] = bc3 ^ (bc0 &^ bc4)
+ a[14] = bc4 ^ (bc1 &^ bc0)
+
+ // Round 2
+ bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+ bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+ bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+ bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+ bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+ d0 = bc4 ^ (bc1<<1 | bc1>>63)
+ d1 = bc0 ^ (bc2<<1 | bc2>>63)
+ d2 = bc1 ^ (bc3<<1 | bc3>>63)
+ d3 = bc2 ^ (bc4<<1 | bc4>>63)
+ d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+ bc0 = a[0] ^ d0
+ t = a[16] ^ d1
+ bc1 = t<<44 | t>>(64-44)
+ t = a[7] ^ d2
+ bc2 = t<<43 | t>>(64-43)
+ t = a[23] ^ d3
+ bc3 = t<<21 | t>>(64-21)
+ t = a[14] ^ d4
+ bc4 = t<<14 | t>>(64-14)
+ a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i+1]
+ a[16] = bc1 ^ (bc3 &^ bc2)
+ a[7] = bc2 ^ (bc4 &^ bc3)
+ a[23] = bc3 ^ (bc0 &^ bc4)
+ a[14] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[20] ^ d0
+ bc2 = t<<3 | t>>(64-3)
+ t = a[11] ^ d1
+ bc3 = t<<45 | t>>(64-45)
+ t = a[2] ^ d2
+ bc4 = t<<61 | t>>(64-61)
+ t = a[18] ^ d3
+ bc0 = t<<28 | t>>(64-28)
+ t = a[9] ^ d4
+ bc1 = t<<20 | t>>(64-20)
+ a[20] = bc0 ^ (bc2 &^ bc1)
+ a[11] = bc1 ^ (bc3 &^ bc2)
+ a[2] = bc2 ^ (bc4 &^ bc3)
+ a[18] = bc3 ^ (bc0 &^ bc4)
+ a[9] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[15] ^ d0
+ bc4 = t<<18 | t>>(64-18)
+ t = a[6] ^ d1
+ bc0 = t<<1 | t>>(64-1)
+ t = a[22] ^ d2
+ bc1 = t<<6 | t>>(64-6)
+ t = a[13] ^ d3
+ bc2 = t<<25 | t>>(64-25)
+ t = a[4] ^ d4
+ bc3 = t<<8 | t>>(64-8)
+ a[15] = bc0 ^ (bc2 &^ bc1)
+ a[6] = bc1 ^ (bc3 &^ bc2)
+ a[22] = bc2 ^ (bc4 &^ bc3)
+ a[13] = bc3 ^ (bc0 &^ bc4)
+ a[4] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[10] ^ d0
+ bc1 = t<<36 | t>>(64-36)
+ t = a[1] ^ d1
+ bc2 = t<<10 | t>>(64-10)
+ t = a[17] ^ d2
+ bc3 = t<<15 | t>>(64-15)
+ t = a[8] ^ d3
+ bc4 = t<<56 | t>>(64-56)
+ t = a[24] ^ d4
+ bc0 = t<<27 | t>>(64-27)
+ a[10] = bc0 ^ (bc2 &^ bc1)
+ a[1] = bc1 ^ (bc3 &^ bc2)
+ a[17] = bc2 ^ (bc4 &^ bc3)
+ a[8] = bc3 ^ (bc0 &^ bc4)
+ a[24] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[5] ^ d0
+ bc3 = t<<41 | t>>(64-41)
+ t = a[21] ^ d1
+ bc4 = t<<2 | t>>(64-2)
+ t = a[12] ^ d2
+ bc0 = t<<62 | t>>(64-62)
+ t = a[3] ^ d3
+ bc1 = t<<55 | t>>(64-55)
+ t = a[19] ^ d4
+ bc2 = t<<39 | t>>(64-39)
+ a[5] = bc0 ^ (bc2 &^ bc1)
+ a[21] = bc1 ^ (bc3 &^ bc2)
+ a[12] = bc2 ^ (bc4 &^ bc3)
+ a[3] = bc3 ^ (bc0 &^ bc4)
+ a[19] = bc4 ^ (bc1 &^ bc0)
+
+ // Round 3
+ bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+ bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+ bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+ bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+ bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+ d0 = bc4 ^ (bc1<<1 | bc1>>63)
+ d1 = bc0 ^ (bc2<<1 | bc2>>63)
+ d2 = bc1 ^ (bc3<<1 | bc3>>63)
+ d3 = bc2 ^ (bc4<<1 | bc4>>63)
+ d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+ bc0 = a[0] ^ d0
+ t = a[11] ^ d1
+ bc1 = t<<44 | t>>(64-44)
+ t = a[22] ^ d2
+ bc2 = t<<43 | t>>(64-43)
+ t = a[8] ^ d3
+ bc3 = t<<21 | t>>(64-21)
+ t = a[19] ^ d4
+ bc4 = t<<14 | t>>(64-14)
+ a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i+2]
+ a[11] = bc1 ^ (bc3 &^ bc2)
+ a[22] = bc2 ^ (bc4 &^ bc3)
+ a[8] = bc3 ^ (bc0 &^ bc4)
+ a[19] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[15] ^ d0
+ bc2 = t<<3 | t>>(64-3)
+ t = a[1] ^ d1
+ bc3 = t<<45 | t>>(64-45)
+ t = a[12] ^ d2
+ bc4 = t<<61 | t>>(64-61)
+ t = a[23] ^ d3
+ bc0 = t<<28 | t>>(64-28)
+ t = a[9] ^ d4
+ bc1 = t<<20 | t>>(64-20)
+ a[15] = bc0 ^ (bc2 &^ bc1)
+ a[1] = bc1 ^ (bc3 &^ bc2)
+ a[12] = bc2 ^ (bc4 &^ bc3)
+ a[23] = bc3 ^ (bc0 &^ bc4)
+ a[9] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[5] ^ d0
+ bc4 = t<<18 | t>>(64-18)
+ t = a[16] ^ d1
+ bc0 = t<<1 | t>>(64-1)
+ t = a[2] ^ d2
+ bc1 = t<<6 | t>>(64-6)
+ t = a[13] ^ d3
+ bc2 = t<<25 | t>>(64-25)
+ t = a[24] ^ d4
+ bc3 = t<<8 | t>>(64-8)
+ a[5] = bc0 ^ (bc2 &^ bc1)
+ a[16] = bc1 ^ (bc3 &^ bc2)
+ a[2] = bc2 ^ (bc4 &^ bc3)
+ a[13] = bc3 ^ (bc0 &^ bc4)
+ a[24] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[20] ^ d0
+ bc1 = t<<36 | t>>(64-36)
+ t = a[6] ^ d1
+ bc2 = t<<10 | t>>(64-10)
+ t = a[17] ^ d2
+ bc3 = t<<15 | t>>(64-15)
+ t = a[3] ^ d3
+ bc4 = t<<56 | t>>(64-56)
+ t = a[14] ^ d4
+ bc0 = t<<27 | t>>(64-27)
+ a[20] = bc0 ^ (bc2 &^ bc1)
+ a[6] = bc1 ^ (bc3 &^ bc2)
+ a[17] = bc2 ^ (bc4 &^ bc3)
+ a[3] = bc3 ^ (bc0 &^ bc4)
+ a[14] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[10] ^ d0
+ bc3 = t<<41 | t>>(64-41)
+ t = a[21] ^ d1
+ bc4 = t<<2 | t>>(64-2)
+ t = a[7] ^ d2
+ bc0 = t<<62 | t>>(64-62)
+ t = a[18] ^ d3
+ bc1 = t<<55 | t>>(64-55)
+ t = a[4] ^ d4
+ bc2 = t<<39 | t>>(64-39)
+ a[10] = bc0 ^ (bc2 &^ bc1)
+ a[21] = bc1 ^ (bc3 &^ bc2)
+ a[7] = bc2 ^ (bc4 &^ bc3)
+ a[18] = bc3 ^ (bc0 &^ bc4)
+ a[4] = bc4 ^ (bc1 &^ bc0)
+
+ // Round 4
+ bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]
+ bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]
+ bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]
+ bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]
+ bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]
+ d0 = bc4 ^ (bc1<<1 | bc1>>63)
+ d1 = bc0 ^ (bc2<<1 | bc2>>63)
+ d2 = bc1 ^ (bc3<<1 | bc3>>63)
+ d3 = bc2 ^ (bc4<<1 | bc4>>63)
+ d4 = bc3 ^ (bc0<<1 | bc0>>63)
+
+ bc0 = a[0] ^ d0
+ t = a[1] ^ d1
+ bc1 = t<<44 | t>>(64-44)
+ t = a[2] ^ d2
+ bc2 = t<<43 | t>>(64-43)
+ t = a[3] ^ d3
+ bc3 = t<<21 | t>>(64-21)
+ t = a[4] ^ d4
+ bc4 = t<<14 | t>>(64-14)
+ a[0] = bc0 ^ (bc2 &^ bc1) ^ RC[i+3]
+ a[1] = bc1 ^ (bc3 &^ bc2)
+ a[2] = bc2 ^ (bc4 &^ bc3)
+ a[3] = bc3 ^ (bc0 &^ bc4)
+ a[4] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[5] ^ d0
+ bc2 = t<<3 | t>>(64-3)
+ t = a[6] ^ d1
+ bc3 = t<<45 | t>>(64-45)
+ t = a[7] ^ d2
+ bc4 = t<<61 | t>>(64-61)
+ t = a[8] ^ d3
+ bc0 = t<<28 | t>>(64-28)
+ t = a[9] ^ d4
+ bc1 = t<<20 | t>>(64-20)
+ a[5] = bc0 ^ (bc2 &^ bc1)
+ a[6] = bc1 ^ (bc3 &^ bc2)
+ a[7] = bc2 ^ (bc4 &^ bc3)
+ a[8] = bc3 ^ (bc0 &^ bc4)
+ a[9] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[10] ^ d0
+ bc4 = t<<18 | t>>(64-18)
+ t = a[11] ^ d1
+ bc0 = t<<1 | t>>(64-1)
+ t = a[12] ^ d2
+ bc1 = t<<6 | t>>(64-6)
+ t = a[13] ^ d3
+ bc2 = t<<25 | t>>(64-25)
+ t = a[14] ^ d4
+ bc3 = t<<8 | t>>(64-8)
+ a[10] = bc0 ^ (bc2 &^ bc1)
+ a[11] = bc1 ^ (bc3 &^ bc2)
+ a[12] = bc2 ^ (bc4 &^ bc3)
+ a[13] = bc3 ^ (bc0 &^ bc4)
+ a[14] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[15] ^ d0
+ bc1 = t<<36 | t>>(64-36)
+ t = a[16] ^ d1
+ bc2 = t<<10 | t>>(64-10)
+ t = a[17] ^ d2
+ bc3 = t<<15 | t>>(64-15)
+ t = a[18] ^ d3
+ bc4 = t<<56 | t>>(64-56)
+ t = a[19] ^ d4
+ bc0 = t<<27 | t>>(64-27)
+ a[15] = bc0 ^ (bc2 &^ bc1)
+ a[16] = bc1 ^ (bc3 &^ bc2)
+ a[17] = bc2 ^ (bc4 &^ bc3)
+ a[18] = bc3 ^ (bc0 &^ bc4)
+ a[19] = bc4 ^ (bc1 &^ bc0)
+
+ t = a[20] ^ d0
+ bc3 = t<<41 | t>>(64-41)
+ t = a[21] ^ d1
+ bc4 = t<<2 | t>>(64-2)
+ t = a[22] ^ d2
+ bc0 = t<<62 | t>>(64-62)
+ t = a[23] ^ d3
+ bc1 = t<<55 | t>>(64-55)
+ t = a[24] ^ d4
+ bc2 = t<<39 | t>>(64-39)
+ a[20] = bc0 ^ (bc2 &^ bc1)
+ a[21] = bc1 ^ (bc3 &^ bc2)
+ a[22] = bc2 ^ (bc4 &^ bc3)
+ a[23] = bc3 ^ (bc0 &^ bc4)
+ a[24] = bc4 ^ (bc1 &^ bc0)
+ }
+}
--- /dev/null
+package sha3
+
+// RC stores the round constants for use in the ι step.
+var RC = [24]uint64{
+ 0x0000000000000001,
+ 0x0000000000008082,
+ 0x800000000000808A,
+ 0x8000000080008000,
+ 0x000000000000808B,
+ 0x0000000080000001,
+ 0x8000000080008081,
+ 0x8000000000008009,
+ 0x000000000000008A,
+ 0x0000000000000088,
+ 0x0000000080008009,
+ 0x000000008000000A,
+ 0x000000008000808B,
+ 0x800000000000008B,
+ 0x8000000000008089,
+ 0x8000000000008003,
+ 0x8000000000008002,
+ 0x8000000000000080,
+ 0x000000000000800A,
+ 0x800000008000000A,
+ 0x8000000080008081,
+ 0x8000000000008080,
+ 0x0000000080000001,
+ 0x8000000080008008,
+}
--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// spongeDirection indicates the direction bytes are flowing through the sponge.
+type spongeDirection int
+
+const (
+ // spongeAbsorbing indicates that the sponge is absorbing input.
+ spongeAbsorbing spongeDirection = iota
+ // spongeSqueezing indicates that the sponge is being squeezed.
+ spongeSqueezing
+)
+
+const (
+ // maxRate is the maximum size of the internal buffer. SHAKE-256
+ // currently needs the largest buffer.
+ maxRate = 168
+)
+
+func (d *State) buf() []byte {
+ return d.storage.asBytes()[d.bufo:d.bufe]
+}
+
+type State struct {
+ // Generic sponge components.
+ a [25]uint64 // main state of the hash
+ rate int // the number of bytes of state to use
+
+ bufo int // offset of buffer in storage
+ bufe int // end of buffer in storage
+
+ // dsbyte contains the "domain separation" bits and the first bit of
+ // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the
+ // SHA-3 and SHAKE functions by appending bitstrings to the message.
+ // Using a little-endian bit-ordering convention, these are "01" for SHA-3
+ // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the
+ // padding rule from section 5.1 is applied to pad the message to a multiple
+ // of the rate, which involves adding a "1" bit, zero or more "0" bits, and
+ // a final "1" bit. We merge the first "1" bit from the padding into dsbyte,
+ // giving 00000110b (0x06) and 00011111b (0x1f).
+ // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf
+ // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and
+ // Extendable-Output Functions (May 2014)"
+ dsbyte byte
+
+ storage storageBuf
+
+ // Specific to SHA-3 and SHAKE.
+ outputLen int // the default output size in bytes
+ state spongeDirection // whether the sponge is absorbing or squeezing
+ turbo bool // Whether we're using 12 rounds instead of 24
+}
+
+// BlockSize returns the rate of sponge underlying this hash function.
+func (d *State) BlockSize() int { return d.rate }
+
+// Size returns the output size of the hash function in bytes.
+func (d *State) Size() int { return d.outputLen }
+
+// Reset clears the internal state by zeroing the sponge state and
+// the byte buffer, and setting Sponge.state to absorbing.
+func (d *State) Reset() {
+ // Zero the permutation's state.
+ for i := range d.a {
+ d.a[i] = 0
+ }
+ d.state = spongeAbsorbing
+ d.bufo = 0
+ d.bufe = 0
+}
+
+func (d *State) clone() *State {
+ ret := *d
+ return &ret
+}
+
+// permute applies the KeccakF-1600 permutation. It handles
+// any input-output buffering.
+func (d *State) permute() {
+ switch d.state {
+ case spongeAbsorbing:
+ // If we're absorbing, we need to xor the input into the state
+ // before applying the permutation.
+ xorIn(d, d.buf())
+ d.bufe = 0
+ d.bufo = 0
+ KeccakF1600(&d.a, d.turbo)
+ case spongeSqueezing:
+ // If we're squeezing, we need to apply the permutation before
+ // copying more output.
+ KeccakF1600(&d.a, d.turbo)
+ d.bufe = d.rate
+ d.bufo = 0
+ copyOut(d, d.buf())
+ }
+}
+
+// pads appends the domain separation bits in dsbyte, applies
+// the multi-bitrate 10..1 padding rule, and permutes the state.
+func (d *State) padAndPermute(dsbyte byte) {
+ // Pad with this instance's domain-separator bits. We know that there's
+ // at least one byte of space in d.buf() because, if it were full,
+ // permute would have been called to empty it. dsbyte also contains the
+ // first one bit for the padding. See the comment in the state struct.
+ zerosStart := d.bufe + 1
+ d.bufe = d.rate
+ buf := d.buf()
+ buf[zerosStart-1] = dsbyte
+ for i := zerosStart; i < d.rate; i++ {
+ buf[i] = 0
+ }
+ // This adds the final one bit for the padding. Because of the way that
+ // bits are numbered from the LSB upwards, the final bit is the MSB of
+ // the last byte.
+ buf[d.rate-1] ^= 0x80
+ // Apply the permutation
+ d.permute()
+ d.state = spongeSqueezing
+ d.bufe = d.rate
+ copyOut(d, buf)
+}
+
+// Write absorbs more data into the hash's state. It produces an error
+// if more data is written to the ShakeHash after writing
+func (d *State) Write(p []byte) (written int, err error) {
+ if d.state != spongeAbsorbing {
+ panic("sha3: write to sponge after read")
+ }
+ written = len(p)
+
+ for len(p) > 0 {
+ bufl := d.bufe - d.bufo
+ if bufl == 0 && len(p) >= d.rate {
+ // The fast path; absorb a full "rate" bytes of input and apply the permutation.
+ xorIn(d, p[:d.rate])
+ p = p[d.rate:]
+ KeccakF1600(&d.a, d.turbo)
+ } else {
+ // The slow path; buffer the input until we can fill the sponge, and then xor it in.
+ todo := d.rate - bufl
+ if todo > len(p) {
+ todo = len(p)
+ }
+ d.bufe += todo
+ buf := d.buf()
+ copy(buf[bufl:], p[:todo])
+ p = p[todo:]
+
+ // If the sponge is full, apply the permutation.
+ if d.bufe == d.rate {
+ d.permute()
+ }
+ }
+ }
+
+ return written, nil
+}
+
+// Read squeezes an arbitrary number of bytes from the sponge.
+func (d *State) Read(out []byte) (n int, err error) {
+ // If we're still absorbing, pad and apply the permutation.
+ if d.state == spongeAbsorbing {
+ d.padAndPermute(d.dsbyte)
+ }
+
+ n = len(out)
+
+ // Now, do the squeezing.
+ for len(out) > 0 {
+ buf := d.buf()
+ n := copy(out, buf)
+ d.bufo += n
+ out = out[n:]
+
+ // Apply the permutation if we've squeezed the sponge dry.
+ if d.bufo == d.bufe {
+ d.permute()
+ }
+ }
+
+ return
+}
+
+// Sum applies padding to the hash state and then squeezes out the desired
+// number of output bytes.
+func (d *State) Sum(in []byte) []byte {
+ // Make a copy of the original hash so that caller can keep writing
+ // and summing.
+ dup := d.clone()
+ hash := make([]byte, dup.outputLen)
+ _, _ = dup.Read(hash)
+ return append(in, hash...)
+}
+
+func (d *State) IsAbsorbing() bool {
+ return d.state == spongeAbsorbing
+}
--- /dev/null
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sha3
+
+// This file defines the ShakeHash interface, and provides
+// functions for creating SHAKE and cSHAKE instances, as well as utility
+// functions for hashing bytes to arbitrary-length output.
+//
+//
+// SHAKE implementation is based on FIPS PUB 202 [1]
+// cSHAKE implementations is based on NIST SP 800-185 [2]
+//
+// [1] https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
+// [2] https://doi.org/10.6028/NIST.SP.800-185
+
+import (
+ "io"
+)
+
+// ShakeHash defines the interface to hash functions that
+// support arbitrary-length output.
+type ShakeHash interface {
+ // Write absorbs more data into the hash's state. It panics if input is
+ // written to it after output has been read from it.
+ io.Writer
+
+ // Read reads more output from the hash; reading affects the hash's
+ // state. (ShakeHash.Read is thus very different from Hash.Sum)
+ // It never returns an error.
+ io.Reader
+
+ // Clone returns a copy of the ShakeHash in its current state.
+ Clone() ShakeHash
+
+ // Reset resets the ShakeHash to its initial state.
+ Reset()
+}
+
+// Consts for configuring initial SHA-3 state
+const (
+ dsbyteShake = 0x1f
+ rate128 = 168
+ rate256 = 136
+)
+
+// Clone returns copy of SHAKE context within its current state.
+func (d *State) Clone() ShakeHash {
+ return d.clone()
+}
+
+// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
+// Its generic security strength is 128 bits against all attacks if at
+// least 32 bytes of its output are used.
+func NewShake128() State {
+ return State{rate: rate128, dsbyte: dsbyteShake}
+}
+
+// NewTurboShake128 creates a new TurboSHAKE128 variable-output-length ShakeHash.
+// Its generic security strength is 128 bits against all attacks if at
+// least 32 bytes of its output are used.
+// D is the domain separation byte and must be between 0x01 and 0x7f inclusive.
+func NewTurboShake128(D byte) State {
+ if D == 0 || D > 0x7f {
+ panic("turboshake: D out of range")
+ }
+ return State{rate: rate128, dsbyte: D, turbo: true}
+}
+
+// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
+// Its generic security strength is 256 bits against all attacks if
+// at least 64 bytes of its output are used.
+func NewShake256() State {
+ return State{rate: rate256, dsbyte: dsbyteShake}
+}
+
+// NewTurboShake256 creates a new TurboSHAKE256 variable-output-length ShakeHash.
+// Its generic security strength is 256 bits against all attacks if
+// at least 64 bytes of its output are used.
+// D is the domain separation byte and must be between 0x01 and 0x7f inclusive.
+func NewTurboShake256(D byte) State {
+ if D == 0 || D > 0x7f {
+ panic("turboshake: D out of range")
+ }
+ return State{rate: rate256, dsbyte: D, turbo: true}
+}
+
+// ShakeSum128 writes an arbitrary-length digest of data into hash.
+func ShakeSum128(hash, data []byte) {
+ h := NewShake128()
+ _, _ = h.Write(data)
+ _, _ = h.Read(hash)
+}
+
+// ShakeSum256 writes an arbitrary-length digest of data into hash.
+func ShakeSum256(hash, data []byte) {
+ h := NewShake256()
+ _, _ = h.Write(data)
+ _, _ = h.Read(hash)
+}
+
+// TurboShakeSum128 writes an arbitrary-length digest of data into hash.
+func TurboShakeSum128(hash, data []byte, D byte) {
+ h := NewTurboShake128(D)
+ _, _ = h.Write(data)
+ _, _ = h.Read(hash)
+}
+
+// TurboShakeSum256 writes an arbitrary-length digest of data into hash.
+func TurboShakeSum256(hash, data []byte, D byte) {
+ h := NewTurboShake256(D)
+ _, _ = h.Write(data)
+ _, _ = h.Read(hash)
+}
+
+func (d *State) SwitchDS(D byte) {
+ d.dsbyte = D
+}
--- /dev/null
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (!amd64 && !386 && !ppc64le) || appengine
+// +build !amd64,!386,!ppc64le appengine
+
+package sha3
+
+// A storageBuf is an aligned array of maxRate bytes.
+type storageBuf [maxRate]byte
+
+func (b *storageBuf) asBytes() *[maxRate]byte {
+ return (*[maxRate]byte)(b)
+}
--- /dev/null
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (!amd64 || appengine) && (!386 || appengine) && (!ppc64le || appengine)
+// +build !amd64 appengine
+// +build !386 appengine
+// +build !ppc64le appengine
+
+package sha3
+
+import "encoding/binary"
+
+// xorIn xors the bytes in buf into the state; it
+// makes no non-portable assumptions about memory layout
+// or alignment.
+func xorIn(d *State, buf []byte) {
+ n := len(buf) / 8
+
+ for i := 0; i < n; i++ {
+ a := binary.LittleEndian.Uint64(buf)
+ d.a[i] ^= a
+ buf = buf[8:]
+ }
+}
+
+// copyOut copies ulint64s to a byte buffer.
+func copyOut(d *State, b []byte) {
+ for i := 0; len(b) >= 8; i++ {
+ binary.LittleEndian.PutUint64(b, d.a[i])
+ b = b[8:]
+ }
+}
--- /dev/null
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (amd64 || 386 || ppc64le) && !appengine
+// +build amd64 386 ppc64le
+// +build !appengine
+
+package sha3
+
+import "unsafe"
+
+// A storageBuf is an aligned array of maxRate bytes.
+type storageBuf [maxRate / 8]uint64
+
+func (b *storageBuf) asBytes() *[maxRate]byte {
+ return (*[maxRate]byte)(unsafe.Pointer(b))
+}
+
+// xorInuses unaligned reads and writes to update d.a to contain d.a
+// XOR buf.
+func xorIn(d *State, buf []byte) {
+ n := len(buf)
+ bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8]
+ if n >= 72 {
+ d.a[0] ^= bw[0]
+ d.a[1] ^= bw[1]
+ d.a[2] ^= bw[2]
+ d.a[3] ^= bw[3]
+ d.a[4] ^= bw[4]
+ d.a[5] ^= bw[5]
+ d.a[6] ^= bw[6]
+ d.a[7] ^= bw[7]
+ d.a[8] ^= bw[8]
+ }
+ if n >= 104 {
+ d.a[9] ^= bw[9]
+ d.a[10] ^= bw[10]
+ d.a[11] ^= bw[11]
+ d.a[12] ^= bw[12]
+ }
+ if n >= 136 {
+ d.a[13] ^= bw[13]
+ d.a[14] ^= bw[14]
+ d.a[15] ^= bw[15]
+ d.a[16] ^= bw[16]
+ }
+ if n >= 144 {
+ d.a[17] ^= bw[17]
+ }
+ if n >= 168 {
+ d.a[18] ^= bw[18]
+ d.a[19] ^= bw[19]
+ d.a[20] ^= bw[20]
+ }
+}
+
+func copyOut(d *State, buf []byte) {
+ ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
+ copy(buf, ab[:])
+}
--- /dev/null
+package circl
+
+import (
+ "bytes"
+ "crypto"
+ "crypto/subtle"
+
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl/internal/conv"
+ "golang.org/x/crypto/cryptobyte"
+)
+
+// [PrivateKey] stores a private key of the SLH-DSA scheme.
+// It implements the [crypto.Signer] and [crypto.PrivateKey] interfaces.
+// For serialization, it also implements [cryptobyte.MarshalingValue],
+// [encoding.BinaryMarshaler], and [encoding.BinaryUnmarshaler].
+type PrivateKey struct {
+ seed, prfKey []byte
+ publicKey PublicKey
+ ID
+}
+
+func (p *params) PrivateKeySize() int { return int(2*p.n) + p.PublicKeySize() }
+
+// Marshal serializes the key using a [cryptobyte.Builder].
+func (k PrivateKey) Marshal(b *cryptobyte.Builder) error {
+ b.AddBytes(k.seed)
+ b.AddBytes(k.prfKey)
+ b.AddValue(k.publicKey)
+ return nil
+}
+
+// Unmarshal recovers a [PrivateKey] from a [cryptobyte.String].
+// Caller must specify the private key's [ID] in advance.
+// Example:
+//
+// key := PrivateKey{ID: SHA2Small192}
+// key.Unmarshal(str) // returns true
+func (k *PrivateKey) Unmarshal(s *cryptobyte.String) bool {
+ params := k.ID.params()
+ b := make([]byte, params.PrivateKeySize())
+ if !s.CopyBytes(b) {
+ return false
+ }
+
+ c := cursor(b)
+ return k.fromBytes(params, &c)
+}
+
+func (k *PrivateKey) fromBytes(p *params, c *cursor) bool {
+ k.ID = p.ID
+ k.seed = c.Next(p.n)
+ k.prfKey = c.Next(p.n)
+ return k.publicKey.fromBytes(p, c) && k.publicKey.ID == k.ID
+}
+
+// UnmarshalBinary recovers a [PrivateKey] from a slice of bytes.
+// Caller must specify the private key's [ID] in advance.
+// Example:
+//
+// key := PrivateKey{ID: SHA2Small192}
+// key.UnmarshalBinary(bytes) // returns nil
+func (k *PrivateKey) UnmarshalBinary(b []byte) error { return conv.UnmarshalBinary(k, b) }
+func (k PrivateKey) MarshalBinary() ([]byte, error) { return conv.MarshalBinary(k) }
+func (k PrivateKey) Public() crypto.PublicKey { return k.PublicKey() }
+func (k PrivateKey) PublicKey() (pub PublicKey) {
+ params := k.ID.params()
+ c := cursor(make([]byte, params.PublicKeySize()))
+ pub.fromBytes(params, &c)
+ copy(pub.seed, k.publicKey.seed)
+ copy(pub.root, k.publicKey.root)
+ return
+}
+
+func (k PrivateKey) Equal(x crypto.PrivateKey) bool {
+ other, ok := x.(PrivateKey)
+ return ok && k.ID == other.ID &&
+ subtle.ConstantTimeCompare(k.seed, other.seed) == 1 &&
+ subtle.ConstantTimeCompare(k.prfKey, other.prfKey) == 1 &&
+ k.publicKey.Equal(other.publicKey)
+}
+
+// [PublicKey] stores a public key of the SLH-DSA scheme.
+// It implements the [crypto.PublicKey] interface.
+// For serialization, it also implements [cryptobyte.MarshalingValue],
+// [encoding.BinaryMarshaler], and [encoding.BinaryUnmarshaler].
+type PublicKey struct {
+ seed, root []byte
+ ID
+}
+
+func (p *params) PublicKeySize() int { return int(2 * p.n) }
+
+// Marshal serializes the key using a [cryptobyte.Builder].
+func (k PublicKey) Marshal(b *cryptobyte.Builder) error {
+ b.AddBytes(k.seed)
+ b.AddBytes(k.root)
+ return nil
+}
+
+// Unmarshal recovers a [PublicKey] from a [cryptobyte.String].
+// Caller must specify the public key's [ID] in advance.
+// Example:
+//
+// key := PublicKey{ID: SHA2Small192}
+// key.Unmarshal(str) // returns true
+func (k *PublicKey) Unmarshal(s *cryptobyte.String) bool {
+ params := k.ID.params()
+ b := make([]byte, params.PublicKeySize())
+ if !s.CopyBytes(b) {
+ return false
+ }
+
+ c := cursor(b)
+ return k.fromBytes(params, &c)
+}
+
+func (k *PublicKey) fromBytes(p *params, c *cursor) bool {
+ k.ID = p.ID
+ k.seed = c.Next(p.n)
+ k.root = c.Next(p.n)
+ return len(*c) == 0
+}
+
+// UnmarshalBinary recovers a [PublicKey] from a slice of bytes.
+// Caller must specify the public key's [ID] in advance.
+// Example:
+//
+// key := PublicKey{ID: SHA2Small192}
+// key.UnmarshalBinary(bytes) // returns nil
+func (k *PublicKey) UnmarshalBinary(b []byte) error { return conv.UnmarshalBinary(k, b) }
+func (k PublicKey) MarshalBinary() ([]byte, error) { return conv.MarshalBinary(k) }
+func (k PublicKey) Equal(x crypto.PublicKey) bool {
+ other, ok := x.(PublicKey)
+ return ok && k.ID == other.ID &&
+ bytes.Equal(k.seed, other.seed) &&
+ bytes.Equal(k.root, other.root)
+}
--- /dev/null
+package circl
+
+import (
+ _ "golang.org/x/crypto/sha3"
+)
+
+// [Message] wraps a message for signing.
+type Message struct {
+ msg []byte
+ isPreHash byte
+}
+
+// For pure signatures, use [NewMessage] to pass the message to be signed.
+// For pre-hashed signatures, use [PreHash] to hash the message first, and
+// then use [PreHash.BuildMessage] to get a [Message] to be signed.
+func NewMessage(msg []byte) *Message { return &Message{msg, 0} }
+
+func (m *Message) getMsgPrime(context []byte) ([]byte, error) {
+ // See FIPS 205 -- Section 10.2 -- Algorithm 23 and Algorithm 25.
+ const MaxContextSize = 255
+ if len(context) > MaxContextSize {
+ return nil, ErrContext
+ }
+
+ return append(append(
+ []byte{m.isPreHash, byte(len(context))}, context...), m.msg...,
+ ), nil
+}
--- /dev/null
+package circl
+
+import (
+ "crypto"
+ "crypto/hmac"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/binary"
+ "hash"
+ "io"
+ "strings"
+
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl/internal/sha3"
+)
+
+// [ID] identifies the supported parameter sets of SLH-DSA.
+// Note that the zero value is not a valid identifier.
+type ID byte
+
+//nolint:stylecheck
+const (
+ SHA2_128s ID = iota + 1 // SLH-DSA-SHA2-128s
+ SHAKE_128s // SLH-DSA-SHAKE-128s
+ SHA2_128f // SLH-DSA-SHA2-128f
+ SHAKE_128f // SLH-DSA-SHAKE-128f
+ SHA2_192s // SLH-DSA-SHA2-192s
+ SHAKE_192s // SLH-DSA-SHAKE-192s
+ SHA2_192f // SLH-DSA-SHA2-192f
+ SHAKE_192f // SLH-DSA-SHAKE-192f
+ SHA2_256s // SLH-DSA-SHA2-256s
+ SHAKE_256s // SLH-DSA-SHAKE-256s
+ SHA2_256f // SLH-DSA-SHA2-256f
+ SHAKE_256f // SLH-DSA-SHAKE-256f
+ _MaxParams
+)
+
+// [IDByName] returns the [ID] that corresponds to the given name,
+// or an error if no parameter set was found.
+// See [ID] documentation for the specific names of each parameter set.
+// Names are case insensitive.
+//
+// Example:
+//
+// IDByName("SLH-DSA-SHAKE-256s") // returns (SHAKESmall256, nil)
+func IDByName(name string) (ID, error) {
+ v := strings.ToLower(name)
+ for i := range supportedParams {
+ if strings.ToLower(supportedParams[i].name) == v {
+ return supportedParams[i].ID, nil
+ }
+ }
+
+ return ID(0), ErrParam
+}
+
+// IsValid returns true if the parameter set is supported.
+func (id ID) IsValid() bool { return 0 < id && id < _MaxParams }
+
+func (id ID) String() string {
+ if !id.IsValid() {
+ return ErrParam.Error()
+ }
+ return supportedParams[id-1].name
+}
+
+func (id ID) params() *params {
+ if !id.IsValid() {
+ panic(ErrParam)
+ }
+ return &supportedParams[id-1]
+}
+
+// params contains all the relevant constants of a parameter set.
+type params struct {
+ name string // Name of the parameter set.
+ n uint32 // Length of WOTS+ messages.
+ hPrime uint32 // XMSS Merkle tree height.
+ h uint32 // Total height of a hypertree.
+ d uint32 // Hypertree has d layers of XMSS trees.
+ a uint32 // FORS signs a-bit messages.
+ k uint32 // FORS generates k private keys.
+ m uint32 // Used by HashMSG function.
+ isSHA2 bool // True, if the hash function is SHA2, otherwise is SHAKE.
+ ID // Identifier of the parameter set.
+}
+
+// Stores all the supported (read-only) parameter sets.
+var supportedParams = [_MaxParams - 1]params{
+ {ID: SHA2_128s, n: 16, h: 63, d: 7, hPrime: 9, a: 12, k: 14, m: 30, isSHA2: true, name: "SLH-DSA-SHA2-128s"},
+ {ID: SHAKE_128s, n: 16, h: 63, d: 7, hPrime: 9, a: 12, k: 14, m: 30, isSHA2: false, name: "SLH-DSA-SHAKE-128s"},
+ {ID: SHA2_128f, n: 16, h: 66, d: 22, hPrime: 3, a: 6, k: 33, m: 34, isSHA2: true, name: "SLH-DSA-SHA2-128f"},
+ {ID: SHAKE_128f, n: 16, h: 66, d: 22, hPrime: 3, a: 6, k: 33, m: 34, isSHA2: false, name: "SLH-DSA-SHAKE-128f"},
+ {ID: SHA2_192s, n: 24, h: 63, d: 7, hPrime: 9, a: 14, k: 17, m: 39, isSHA2: true, name: "SLH-DSA-SHA2-192s"},
+ {ID: SHAKE_192s, n: 24, h: 63, d: 7, hPrime: 9, a: 14, k: 17, m: 39, isSHA2: false, name: "SLH-DSA-SHAKE-192s"},
+ {ID: SHA2_192f, n: 24, h: 66, d: 22, hPrime: 3, a: 8, k: 33, m: 42, isSHA2: true, name: "SLH-DSA-SHA2-192f"},
+ {ID: SHAKE_192f, n: 24, h: 66, d: 22, hPrime: 3, a: 8, k: 33, m: 42, isSHA2: false, name: "SLH-DSA-SHAKE-192f"},
+ {ID: SHA2_256s, n: 32, h: 64, d: 8, hPrime: 8, a: 14, k: 22, m: 47, isSHA2: true, name: "SLH-DSA-SHA2-256s"},
+ {ID: SHAKE_256s, n: 32, h: 64, d: 8, hPrime: 8, a: 14, k: 22, m: 47, isSHA2: false, name: "SLH-DSA-SHAKE-256s"},
+ {ID: SHA2_256f, n: 32, h: 68, d: 17, hPrime: 4, a: 9, k: 35, m: 49, isSHA2: true, name: "SLH-DSA-SHA2-256f"},
+ {ID: SHAKE_256f, n: 32, h: 68, d: 17, hPrime: 4, a: 9, k: 35, m: 49, isSHA2: false, name: "SLH-DSA-SHAKE-256f"},
+}
+
+// See FIPS-205, Section 11.1 and Section 11.2.
+func (p *params) PRFMsg(out, skPrf, optRand, msg []byte) {
+ if p.isSHA2 {
+ var h crypto.Hash
+ if p.n == 16 {
+ h = crypto.SHA256
+ } else {
+ h = crypto.SHA512
+ }
+
+ mac := hmac.New(h.New, skPrf)
+ concat(mac, optRand, msg)
+ mac.Sum(out[:0])
+ } else {
+ state := sha3.NewShake256()
+ concat(&state, skPrf, optRand, msg)
+ _, _ = state.Read(out)
+ }
+}
+
+// See FIPS-205, Section 11.1 and Section 11.2.
+func (p *params) HashMsg(out, r, msg []byte, pk *PublicKey) {
+ if p.isSHA2 {
+ var hLen uint32
+ var state hash.Hash
+ if p.n == 16 {
+ hLen = sha256.Size
+ state = sha256.New()
+ } else {
+ hLen = sha512.Size
+ state = sha512.New()
+ }
+
+ mgfSeed := make([]byte, 2*p.n+hLen+4)
+ c := cursor(mgfSeed)
+ copy(c.Next(p.n), r)
+ copy(c.Next(p.n), pk.seed)
+ sumInter := c.Next(hLen)
+
+ concat(state, r, pk.seed, pk.root, msg)
+ state.Sum(sumInter[:0])
+ p.mgf1(out, mgfSeed, p.m)
+ } else {
+ state := sha3.NewShake256()
+ concat(&state, r, pk.seed, pk.root, msg)
+ _, _ = state.Read(out)
+ }
+}
+
+// MGF1 described in Appendix B.2.1 of RFC 8017.
+func (p *params) mgf1(out, mgfSeed []byte, maskLen uint32) {
+ var hLen uint32
+ var hashFn func(out, in []byte)
+ if p.n == 16 {
+ hLen = sha256.Size
+ hashFn = sha256sum
+ } else {
+ hLen = sha512.Size
+ hashFn = sha512sum
+ }
+
+ offset := uint32(0)
+ end := (maskLen + hLen - 1) / hLen
+ counterBytes := mgfSeed[len(mgfSeed)-4:]
+
+ for counter := range end {
+ binary.BigEndian.PutUint32(counterBytes, counter)
+ hashFn(out[offset:], mgfSeed)
+ offset += hLen
+ }
+}
+
+func concat(w io.Writer, list ...[]byte) {
+ for _, li := range list {
+ _, err := w.Write(li)
+ if err != nil {
+ panic(ErrWriting)
+ }
+ }
+}
--- /dev/null
+package circl
+
+import (
+ "crypto/rand"
+
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl/internal/sha3"
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl/sign"
+)
+
+func (id ID) Scheme() sign.Scheme { return scheme{id.params()} }
+
+type scheme struct{ *params }
+
+func (s scheme) Name() string { return s.name }
+func (s scheme) SeedSize() int { return s.PrivateKeySize() }
+func (s scheme) SupportsContext() bool { return true }
+
+// GenerateKey is similar to [GenerateKey] function, except it always reads
+// random bytes from [rand.Reader].
+func (s scheme) GenerateKey() (sign.PublicKey, sign.PrivateKey, error) {
+ return GenerateKey(rand.Reader, s.ID)
+}
+
+// Sign returns a randomized pure signature of the message with the context
+// given.
+// If options is nil, an empty context is used.
+// It returns an empty slice if the signature generation fails.
+//
+// Panics if the key is not a [PrivateKey] or when the [ID] mismatches.
+func (s scheme) Sign(
+ priv sign.PrivateKey, message []byte, options *sign.SignatureOpts,
+) []byte {
+ k, ok := priv.(PrivateKey)
+ if !ok || s.ID != k.ID {
+ panic(sign.ErrTypeMismatch)
+ }
+
+ var context []byte
+ if options != nil {
+ context = []byte(options.Context)
+ }
+
+ sig, err := SignRandomized(&k, rand.Reader, NewMessage(message), context)
+ if err != nil {
+ return nil
+ }
+
+ return sig
+}
+
+// Verify returns true if the signature of the message with the specified
+// context is valid.
+// If options is nil, an empty context is used.
+//
+// Panics if the key is not a [PublicKey] or when the [ID] mismatches.
+func (s scheme) Verify(
+ pub sign.PublicKey, message, signature []byte, options *sign.SignatureOpts,
+) bool {
+ k, ok := pub.(PublicKey)
+ if !ok || s.ID != k.ID {
+ panic(sign.ErrTypeMismatch)
+ }
+
+ var context []byte
+ if options != nil {
+ context = []byte(options.Context)
+ }
+
+ return Verify(&k, NewMessage(message), signature, context)
+}
+
+// DeriveKey deterministically generates a pair of keys from a seed.
+//
+// Panics if seed is not of length [sign.Scheme.SeedSize].
+func (s scheme) DeriveKey(seed []byte) (sign.PublicKey, sign.PrivateKey) {
+ if len(seed) != s.SeedSize() {
+ panic(sign.ErrSeedSize)
+ }
+
+ n := s.n
+ buf := make([]byte, 3*n)
+ if s.isSHA2 {
+ s.mgf1(buf, seed, 3*n)
+ } else {
+ sha3.ShakeSum256(buf, seed)
+ }
+
+ c := cursor(buf)
+ skSeed := c.Next(n)
+ skPrf := c.Next(n)
+ pkSeed := c.Next(n)
+
+ return slhKeyGenInternal(s.params, skSeed, skPrf, pkSeed)
+}
+
+func (s scheme) UnmarshalBinaryPublicKey(b []byte) (sign.PublicKey, error) {
+ k := PublicKey{ID: s.ID}
+ err := k.UnmarshalBinary(b)
+ if err != nil {
+ return nil, err
+ }
+
+ return k, nil
+}
+
+func (s scheme) UnmarshalBinaryPrivateKey(b []byte) (sign.PrivateKey, error) {
+ k := PrivateKey{ID: s.ID}
+ err := k.UnmarshalBinary(b)
+ if err != nil {
+ return nil, err
+ }
+
+ return k, nil
+}
--- /dev/null
+// Package sign provides unified interfaces for signature schemes.
+//
+// A register of schemes is available in the package
+//
+// github.com/cloudflare/circl/sign/schemes
+package sign
+
+import (
+ "crypto"
+ "encoding"
+ "errors"
+)
+
+type SignatureOpts struct {
+ // If non-empty, includes the given context in the signature if supported
+ // and will cause an error during signing otherwise.
+ Context string
+}
+
+// A public key is used to verify a signature set by the corresponding private
+// key.
+type PublicKey interface {
+ // Returns the signature scheme for this public key.
+ Scheme() Scheme
+ Equal(crypto.PublicKey) bool
+ encoding.BinaryMarshaler
+ crypto.PublicKey
+}
+
+// A private key allows one to create signatures.
+type PrivateKey interface {
+ // Returns the signature scheme for this private key.
+ Scheme() Scheme
+ Equal(crypto.PrivateKey) bool
+ // For compatibility with Go standard library
+ crypto.Signer
+ crypto.PrivateKey
+ encoding.BinaryMarshaler
+}
+
+// A Scheme represents a specific instance of a signature scheme.
+type Scheme interface {
+ // Name of the scheme.
+ Name() string
+
+ // GenerateKey creates a new key-pair.
+ GenerateKey() (PublicKey, PrivateKey, error)
+
+ // Creates a signature using the PrivateKey on the given message and
+ // returns the signature. opts are additional options which can be nil.
+ //
+ // Panics if key is nil or wrong type or opts context is not supported.
+ Sign(sk PrivateKey, message []byte, opts *SignatureOpts) []byte
+
+ // Checks whether the given signature is a valid signature set by
+ // the private key corresponding to the given public key on the
+ // given message. opts are additional options which can be nil.
+ //
+ // Panics if key is nil or wrong type or opts context is not supported.
+ Verify(pk PublicKey, message, signature []byte, opts *SignatureOpts) bool
+
+ // Deterministically derives a keypair from a seed. If you're unsure,
+ // you're better off using GenerateKey().
+ //
+ // Panics if seed is not of length SeedSize().
+ DeriveKey(seed []byte) (PublicKey, PrivateKey)
+
+ // Unmarshals a PublicKey from the provided buffer.
+ UnmarshalBinaryPublicKey([]byte) (PublicKey, error)
+
+ // Unmarshals a PublicKey from the provided buffer.
+ UnmarshalBinaryPrivateKey([]byte) (PrivateKey, error)
+
+ // Size of binary marshalled public keys.
+ PublicKeySize() int
+
+ // Size of binary marshalled public keys.
+ PrivateKeySize() int
+
+ // Size of signatures.
+ SignatureSize() int
+
+ // Size of seeds.
+ SeedSize() int
+
+ // Returns whether contexts are supported.
+ SupportsContext() bool
+}
+
+var (
+ // ErrTypeMismatch is the error used if types of, for instance, private
+ // and public keys don't match.
+ ErrTypeMismatch = errors.New("types mismatch")
+
+ // ErrSeedSize is the error used if the provided seed is of the wrong
+ // size.
+ ErrSeedSize = errors.New("wrong seed size")
+
+ // ErrPubKeySize is the error used if the provided public key is of
+ // the wrong size.
+ ErrPubKeySize = errors.New("wrong size for public key")
+
+ // ErrPrivKeySize is the error used if the provided private key is of
+ // the wrong size.
+ ErrPrivKeySize = errors.New("wrong size for private key")
+
+ // ErrContextNotSupported is the error used if a context is not
+ // supported.
+ ErrContextNotSupported = errors.New("context not supported")
+
+ // ErrContextTooLong is the error used if the context string is too long.
+ ErrContextTooLong = errors.New("context string too long")
+)
--- /dev/null
+// Package slhdsa provides Stateless Hash-based Digital Signature Algorithm.
+//
+// This package is compliant with [FIPS 205] and the [ID] represents
+// the following parameter sets:
+//
+// Category 1
+// - Based on SHA2: [SHA2_128s] and [SHA2_128f].
+// - Based on SHAKE: [SHAKE_128s] and [SHAKE_128f].
+//
+// Category 3
+// - Based on SHA2: [SHA2_192s] and [SHA2_192f]
+// - Based on SHAKE: [SHAKE_192s] and [SHAKE_192f]
+//
+// Category 5
+// - Based on SHA2: [SHA2_256s] and [SHA2_256f].
+// - Based on SHAKE: [SHAKE_256s] and [SHAKE_256f].
+//
+// [FIPS 205]: https://doi.org/10.6028/NIST.FIPS.205
+package circl
+
+import (
+ "crypto"
+ "crypto/rand"
+ "errors"
+ "io"
+)
+
+// [GenerateKey] returns a pair of keys using the parameter set specified.
+// It returns an error if it fails reading from the random source.
+func GenerateKey(
+ random io.Reader, id ID,
+) (pub PublicKey, priv PrivateKey, err error) {
+ // See FIPS 205 -- Section 10.1 -- Algorithm 21.
+ params := id.params()
+
+ var skSeed, skPrf, pkSeed []byte
+ skSeed, err = readRandom(random, params.n)
+ if err != nil {
+ return
+ }
+
+ skPrf, err = readRandom(random, params.n)
+ if err != nil {
+ return
+ }
+
+ pkSeed, err = readRandom(random, params.n)
+ if err != nil {
+ return
+ }
+
+ pub, priv = slhKeyGenInternal(params, skSeed, skPrf, pkSeed)
+
+ return
+}
+
+// [SignDeterministic] returns the signature of the message with the
+// specified context.
+func SignDeterministic(
+ priv *PrivateKey, message *Message, context []byte,
+) (signature []byte, err error) {
+ return priv.doSign(message, context, priv.publicKey.seed)
+}
+
+// [SignRandomized] returns a random signature of the message with the
+// specified context.
+// It returns an error if it fails reading from the random source.
+func SignRandomized(
+ priv *PrivateKey, random io.Reader, message *Message, context []byte,
+) (signature []byte, err error) {
+ params := priv.ID.params()
+ addRand, err := readRandom(random, params.n)
+ if err != nil {
+ return nil, err
+ }
+
+ return priv.doSign(message, context, addRand)
+}
+
+// [PrivateKey.Sign] returns a randomized signature of the message with an
+// empty context.
+// Any parameter passed in [crypto.SignerOpts] is discarded.
+// It returns an error if it fails reading from the random source.
+func (k PrivateKey) Sign(
+ random io.Reader, message []byte, _ crypto.SignerOpts,
+) (signature []byte, err error) {
+ return SignRandomized(&k, random, NewMessage(message), nil)
+}
+
+func (k *PrivateKey) doSign(
+ message *Message, context, addRand []byte,
+) ([]byte, error) {
+ // See FIPS 205 -- Section 10.2 -- Algorithm 22 and Algorithm 23.
+ msgPrime, err := message.getMsgPrime(context)
+ if err != nil {
+ return nil, err
+ }
+
+ return slhSignInternal(k, msgPrime, addRand)
+}
+
+// [Verify] returns true if the signature of the message with the specified
+// context is valid.
+func Verify(key *PublicKey, message *Message, signature, context []byte) bool {
+ // See FIPS 205 -- Section 10.3 -- Algorithm 24.
+ msgPrime, err := message.getMsgPrime(context)
+ if err != nil {
+ return false
+ }
+
+ return slhVerifyInternal(key, msgPrime, signature)
+}
+
+func readRandom(random io.Reader, size uint32) (out []byte, err error) {
+ out = make([]byte, size)
+ if random == nil {
+ random = rand.Reader
+ }
+ _, err = random.Read(out)
+ return
+}
+
+var (
+ ErrContext = errors.New("sign/slhdsa: context is larger than 255 bytes")
+ ErrMsgLen = errors.New("sign/slhdsa: invalid message length")
+ ErrParam = errors.New("sign/slhdsa: invalid SLH-DSA parameter")
+ ErrPreHash = errors.New("sign/slhdsa: invalid prehash function")
+ ErrSigParse = errors.New("sign/slhdsa: failed to decode the signature")
+ ErrTree = errors.New("sign/slhdsa: invalid tree height or tree index")
+ ErrWriting = errors.New("sign/slhdsa: failed to write to a hash function")
+)
--- /dev/null
+package circl
+
+import (
+ "crypto/sha256"
+ "crypto/sha512"
+ "hash"
+ "io"
+
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl/internal/sha3"
+)
+
+// statePriv encapsulates common data for performing a private operation.
+type statePriv struct {
+ state
+ PRF statePRF
+}
+
+func (s *statePriv) Size(p *params) uint32 {
+ return s.state.Size(p) + s.PRF.Size(p)
+}
+
+func (p *params) NewStatePriv(skSeed, pkSeed []byte) (s statePriv) {
+ c := cursor(make([]byte, s.Size(p)))
+ s.state.init(p, &c, pkSeed)
+ s.PRF.Init(p, &c, skSeed, pkSeed)
+
+ return
+}
+
+func (s *statePriv) Clear() {
+ s.PRF.Clear()
+ s.state.Clear()
+}
+
+// state encapsulates common data for performing a public operation.
+type state struct {
+ *params
+
+ F stateF
+ H stateH
+ T stateT
+}
+
+func (s *state) Size(p *params) uint32 {
+ return s.F.Size(p) + s.H.Size(p) + s.T.Size(p)
+}
+
+func (p *params) NewStatePub(pkSeed []byte) (s state) {
+ c := cursor(make([]byte, s.Size(p)))
+ s.init(p, &c, pkSeed)
+
+ return
+}
+
+func (s *state) init(p *params, c *cursor, pkSeed []byte) {
+ s.params = p
+ s.F.Init(p, c, pkSeed)
+ s.H.Init(p, c, pkSeed)
+ s.T.Init(p, c, pkSeed)
+}
+
+func (s *state) Clear() {
+ s.F.Clear()
+ s.H.Clear()
+ s.T.Clear()
+ s.params = nil
+}
+
+func sha256sum(out, in []byte) { s := sha256.Sum256(in); copy(out, s[:]) }
+func sha512sum(out, in []byte) { s := sha512.Sum512(in); copy(out, s[:]) }
+
+type baseHasher struct {
+ hash func(out, in []byte)
+ input, output []byte
+ address
+}
+
+func (b *baseHasher) Size(p *params) uint32 {
+ return p.n + p.addressSize()
+}
+
+func (b *baseHasher) Clear() {
+ clearSlice(&b.input)
+ clearSlice(&b.output)
+ b.address.Clear()
+}
+
+func (b *baseHasher) Final() []byte {
+ b.hash(b.output, b.input)
+ return b.output
+}
+
+type statePRF struct{ baseHasher }
+
+func (s *statePRF) Init(p *params, cur *cursor, skSeed, pkSeed []byte) {
+ c := cursor(cur.Next(s.Size(p)))
+ s.output = c.Next(p.n)
+ s.input = c.Rest()
+ copy(c.Next(p.n), pkSeed)
+ _ = c.Next(s.padSize(p))
+ s.address.fromBytes(p, &c)
+ copy(c.Next(p.n), skSeed)
+
+ if p.isSHA2 {
+ s.hash = sha256sum
+ } else {
+ s.hash = sha3.ShakeSum256
+ }
+}
+
+func (s *statePRF) Size(p *params) uint32 {
+ return 2*p.n + s.padSize(p) + s.baseHasher.Size(p)
+}
+
+func (s *statePRF) padSize(p *params) uint32 {
+ if p.isSHA2 {
+ return 64 - p.n
+ } else {
+ return 0
+ }
+}
+
+type stateF struct {
+ msg []byte
+ baseHasher
+}
+
+func (s *stateF) Init(p *params, cur *cursor, pkSeed []byte) {
+ c := cursor(cur.Next(s.Size(p)))
+ s.output = c.Next(p.n)
+ s.input = c.Rest()
+ copy(c.Next(p.n), pkSeed)
+ _ = c.Next(s.padSize(p))
+ s.address.fromBytes(p, &c)
+ s.msg = c.Next(p.n)
+
+ if p.isSHA2 {
+ s.hash = sha256sum
+ } else {
+ s.hash = sha3.ShakeSum256
+ }
+}
+
+func (s *stateF) SetMessage(msg []byte) { copy(s.msg, msg) }
+
+func (s *stateF) Clear() {
+ s.baseHasher.Clear()
+ clearSlice(&s.msg)
+}
+
+func (s *stateF) Size(p *params) uint32 {
+ return 2*p.n + s.padSize(p) + s.baseHasher.Size(p)
+}
+
+func (s *stateF) padSize(p *params) uint32 {
+ if p.isSHA2 {
+ return 64 - p.n
+ } else {
+ return 0
+ }
+}
+
+type stateH struct {
+ msg0, msg1 []byte
+ baseHasher
+}
+
+func (s *stateH) Init(p *params, cur *cursor, pkSeed []byte) {
+ c := cursor(cur.Next(s.Size(p)))
+ s.output = c.Next(p.n)
+ s.input = c.Rest()
+ copy(c.Next(p.n), pkSeed)
+ _ = c.Next(s.padSize(p))
+ s.address.fromBytes(p, &c)
+ s.msg0 = c.Next(p.n)
+ s.msg1 = c.Next(p.n)
+
+ if p.isSHA2 {
+ if p.n == 16 {
+ s.hash = sha256sum
+ } else {
+ s.hash = sha512sum
+ }
+ } else {
+ s.hash = sha3.ShakeSum256
+ }
+}
+
+func (s *stateH) SetMsgs(m0, m1 []byte) {
+ copy(s.msg0, m0)
+ copy(s.msg1, m1)
+}
+
+func (s *stateH) Clear() {
+ s.baseHasher.Clear()
+ clearSlice(&s.msg0)
+ clearSlice(&s.msg1)
+}
+
+func (s *stateH) Size(p *params) uint32 {
+ return 3*p.n + s.padSize(p) + s.baseHasher.Size(p)
+}
+
+func (s *stateH) padSize(p *params) uint32 {
+ if p.isSHA2 {
+ if p.n == 16 {
+ return 64 - p.n
+ } else {
+ return 128 - p.n
+ }
+ } else {
+ return 0
+ }
+}
+
+type stateT struct {
+ hash interface {
+ io.Writer
+ Reset()
+ Final([]byte)
+ }
+ input, output []byte
+ address
+}
+
+func (s *stateT) Init(p *params, cur *cursor, pkSeed []byte) {
+ c := cursor(cur.Next(s.Size(p)))
+ s.output = c.Next(s.outputSize(p))[:p.n]
+ s.input = c.Rest()
+ copy(c.Next(p.n), pkSeed)
+ _ = c.Next(s.padSize(p))
+ s.address.fromBytes(p, &c)
+
+ if p.isSHA2 {
+ if p.n == 16 {
+ s.hash = &sha2rw{sha256.New()}
+ } else {
+ s.hash = &sha2rw{sha512.New()}
+ }
+ } else {
+ s.hash = &sha3rw{sha3.NewShake256()}
+ }
+}
+
+func (s *stateT) Clear() {
+ clearSlice(&s.input)
+ clearSlice(&s.output)
+ s.address.Clear()
+ s.hash.Reset()
+}
+
+func (s *stateT) Reset() {
+ s.hash.Reset()
+ _, _ = s.hash.Write(s.input)
+}
+
+func (s *stateT) WriteMessage(msg []byte) { _, _ = s.hash.Write(msg) }
+
+func (s *stateT) Final() []byte {
+ s.hash.Final(s.output)
+ return s.output
+}
+
+func (s *stateT) Size(p *params) uint32 {
+ return s.outputSize(p) + s.padSize(p) + p.n + p.addressSize()
+}
+
+func (s *stateT) outputSize(p *params) uint32 {
+ if p.isSHA2 {
+ if p.n == 16 {
+ return sha256.Size
+ } else {
+ return sha512.Size
+ }
+ } else {
+ return p.n
+ }
+}
+
+func (s *stateT) padSize(p *params) uint32 {
+ if p.isSHA2 {
+ if p.n == 16 {
+ return 64 - p.n
+ } else {
+ return 128 - p.n
+ }
+ } else {
+ return 0
+ }
+}
+
+type sha2rw struct{ hash.Hash }
+
+func (s *sha2rw) Final(out []byte) { s.Sum(out[:0]) }
+func (s *sha2rw) SumIdempotent(out []byte) { s.Sum(out[:0]) }
+
+type sha3rw struct{ sha3.State }
+
+func (s *sha3rw) Final(out []byte) { _, _ = s.Read(out) }
+func (s *sha3rw) SumIdempotent(out []byte) { _, _ = s.Clone().Read(out) }
+
+type (
+ item struct {
+ node []byte
+ z uint32
+ }
+ stackNode []item
+)
+
+func (p *params) NewStack(z uint32) stackNode {
+ s := make([]item, z)
+ c := cursor(make([]byte, z*p.n))
+ for i := range s {
+ s[i].node = c.Next(p.n)
+ }
+
+ return s[:0]
+}
+
+func (s stackNode) isEmpty() bool { return len(s) == 0 }
+func (s stackNode) top() item { return s[len(s)-1] }
+func (s *stackNode) push(v item) {
+ next := len(*s)
+ *s = (*s)[:next+1]
+ (*s)[next].z = v.z
+ copy((*s)[next].node, v.node)
+}
+
+func (s *stackNode) pop() (v item) {
+ last := len(*s) - 1
+ if last >= 0 {
+ v = (*s)[last]
+ *s = (*s)[:last]
+ }
+ return
+}
+
+func (s *stackNode) Clear() {
+ *s = (*s)[:cap(*s)]
+ for i := range *s {
+ clearSlice(&(*s)[i].node)
+ }
+ clear((*s)[:])
+}
+
+type cursor []byte
+
+func (c *cursor) Rest() []byte { return (*c)[:] }
+func (c *cursor) Next(n uint32) (out []byte) {
+ if len(*c) >= int(n) {
+ out = (*c)[:n]
+ *c = (*c)[n:]
+ }
+ return
+}
+
+func clearSlice(s *[]byte) { clear(*s); *s = nil }
--- /dev/null
+package circl
+
+// See FIPS 205 -- Section 5
+// Winternitz One-Time Signature Plus Scheme
+
+const (
+ wotsW uint32 = 16 // wotsW is w = 2^lg_w, where lg_w = 4.
+ wotsLen2 uint32 = 3 // wotsLen2 is len_2 fixed to 3.
+)
+
+type (
+ wotsPublicKey []byte // n bytes
+ wotsSignature []byte // wotsLen()*n bytes
+)
+
+func (p *params) wotsSigSize() uint32 { return p.wotsLen() * p.n }
+func (p *params) wotsLen() uint32 { return p.wotsLen1() + wotsLen2 }
+func (p *params) wotsLen1() uint32 { return 2 * p.n }
+
+func (ws *wotsSignature) fromBytes(p *params, c *cursor) {
+ *ws = c.Next(p.wotsSigSize())
+}
+
+// See FIPS 205 -- Section 5 -- Algorithm 5.
+func (s *state) chain(
+ x []byte, index, steps uint32, addr address,
+) (out []byte) {
+ out = x
+ s.F.address.Set(addr)
+ for j := index; j < index+steps; j++ {
+ s.F.address.SetHashAddress(j)
+ s.F.SetMessage(out)
+ out = s.F.Final()
+ }
+ return
+}
+
+// See FIPS 205 -- Section 5.1 -- Algorithm 6.
+func (s *statePriv) wotsPkGen(addr address) wotsPublicKey {
+ s.PRF.address.Set(addr)
+ s.PRF.address.SetTypeAndClear(addressWotsPrf)
+ s.PRF.address.SetKeyPairAddress(addr.GetKeyPairAddress())
+
+ s.T.address.Set(addr)
+ s.T.address.SetTypeAndClear(addressWotsPk)
+ s.T.address.SetKeyPairAddress(addr.GetKeyPairAddress())
+
+ s.T.Reset()
+ wotsLen := s.wotsLen()
+ for i := range wotsLen {
+ s.PRF.address.SetChainAddress(i)
+ sk := s.PRF.Final()
+
+ addr.SetChainAddress(i)
+ tmpi := s.chain(sk, 0, wotsW-1, addr)
+
+ s.T.WriteMessage(tmpi)
+ }
+
+ return s.T.Final()
+}
+
+// See FIPS 205 -- Section 5.2 -- Algorithm 7.
+func (s *statePriv) wotsSign(sig wotsSignature, msg []byte, addr address) {
+ if len(msg) != int(s.wotsLen1()/2) {
+ panic(ErrMsgLen)
+ }
+
+ curSig := cursor(sig)
+ wotsLen1 := s.wotsLen1()
+ csum := wotsLen1 * (wotsW - 1)
+
+ s.PRF.address.Set(addr)
+ s.PRF.address.SetTypeAndClear(addressWotsPrf)
+ s.PRF.address.SetKeyPairAddress(addr.GetKeyPairAddress())
+
+ // Signs every nibble of the message and computes the checksum.
+ for i := range wotsLen1 {
+ s.PRF.address.SetChainAddress(i)
+ sk := s.PRF.Final()
+
+ addr.SetChainAddress(i)
+ msgi := uint32((msg[i/2] >> ((1 - (i & 1)) << 2)) & 0xF)
+ sigi := s.chain(sk, 0, msgi, addr)
+ copy(curSig.Next(s.n), sigi)
+ csum -= msgi
+ }
+
+ // Lastly, every nibble of the checksum is also signed.
+ for i := range wotsLen2 {
+ s.PRF.address.SetChainAddress(wotsLen1 + i)
+ sk := s.PRF.Final()
+
+ addr.SetChainAddress(wotsLen1 + i)
+ csumi := (csum >> (8 - 4*i)) & 0xF
+ sigi := s.chain(sk, 0, csumi, addr)
+ copy(curSig.Next(s.n), sigi)
+ }
+}
+
+// See FIPS 205 -- Section 5.3 -- Algorithm 8.
+func (s *state) wotsPkFromSig(
+ sig wotsSignature, msg []byte, addr address,
+) wotsPublicKey {
+ if len(msg) != int(s.wotsLen1()/2) {
+ panic(ErrMsgLen)
+ }
+
+ wotsLen1 := s.wotsLen1()
+ csum := wotsLen1 * (wotsW - 1)
+
+ s.T.address.Set(addr)
+ s.T.address.SetTypeAndClear(addressWotsPk)
+ s.T.address.SetKeyPairAddress(addr.GetKeyPairAddress())
+
+ s.T.Reset()
+ curSig := cursor(sig)
+
+ // Signs every nibble of the message, computes the checksum, and
+ // feeds each signature to the T function.
+ for i := range wotsLen1 {
+ addr.SetChainAddress(i)
+ msgi := uint32((msg[i/2] >> ((1 - (i & 1)) << 2)) & 0xF)
+ sigi := s.chain(curSig.Next(s.n), msgi, wotsW-1-msgi, addr)
+
+ s.T.WriteMessage(sigi)
+ csum -= msgi
+ }
+
+ // Every nibble of the checksum is also signed feeding the signature
+ // to the T function.
+ for i := range wotsLen2 {
+ addr.SetChainAddress(wotsLen1 + i)
+ csumi := (csum >> (8 - 4*i)) & 0xF
+ sigi := s.chain(curSig.Next(s.n), csumi, wotsW-1-csumi, addr)
+
+ s.T.WriteMessage(sigi)
+ }
+
+ // Generates the public key as the output of the T function.
+ return s.T.Final()
+}
--- /dev/null
+package circl
+
+// See FIPS 205 -- Section 6
+// eXtended Merkle Signature Scheme (XMSS) extends the WOTS+ signature
+// scheme into one that can sign multiple messages.
+
+type (
+ xmssPublicKey []byte // n bytes
+ xmssSignature struct {
+ wotsSig wotsSignature // wotsSigSize() bytes
+ authPath []byte // hPrime*n bytes
+ } // wotsSigSize() + hPrime*n bytes
+)
+
+func (p *params) xmssPkSize() uint32 { return p.n }
+func (p *params) xmssAuthPathSize() uint32 { return p.hPrime * p.n }
+func (p *params) xmssSigSize() uint32 {
+ return p.wotsSigSize() + p.xmssAuthPathSize()
+}
+
+func (xs *xmssSignature) fromBytes(p *params, c *cursor) {
+ xs.wotsSig.fromBytes(p, c)
+ xs.authPath = c.Next(p.xmssAuthPathSize())
+}
+
+// See FIPS 205 -- Section 6.1 -- Algorithm 9 -- Iterative version.
+//
+// This is a stack-based implementation that computes the tree leaves
+// in order (from the left to the right).
+// Its recursive version can be found at xmss_test.go file.
+func (s *statePriv) xmssNodeIter(
+ stack stackNode, root []byte, i, z uint32, addr address,
+) {
+ if !(z <= s.hPrime && i < (1<<(s.hPrime-z))) {
+ panic(ErrTree)
+ }
+
+ s.H.address.Set(addr)
+ s.H.address.SetTypeAndClear(addressTree)
+
+ twoZ := uint32(1) << z
+ iTwoZ := i << z
+ for k := range twoZ {
+ li := iTwoZ + k
+ lz := uint32(0)
+
+ addr.SetTypeAndClear(addressWotsHash)
+ addr.SetKeyPairAddress(li)
+ node := s.wotsPkGen(addr)
+
+ for !stack.isEmpty() && stack.top().z == lz {
+ left := stack.pop()
+ li = (li - 1) >> 1
+ lz = lz + 1
+
+ s.H.address.SetTreeHeight(lz)
+ s.H.address.SetTreeIndex(li)
+ s.H.SetMsgs(left.node, node)
+ node = s.H.Final()
+ }
+
+ stack.push(item{node, lz})
+ }
+
+ copy(root, stack.pop().node)
+}
+
+// See FIPS 205 -- Section 6.2 -- Algorithm 10.
+func (s *statePriv) xmssSign(
+ stack stackNode, sig xmssSignature, msg []byte, idx uint32, addr address,
+) {
+ authPath := cursor(sig.authPath)
+ for j := range s.hPrime {
+ k := (idx >> j) ^ 1
+ s.xmssNodeIter(stack, authPath.Next(s.n), k, j, addr)
+ }
+
+ addr.SetTypeAndClear(addressWotsHash)
+ addr.SetKeyPairAddress(idx)
+ s.wotsSign(sig.wotsSig, msg, addr)
+}
+
+// See FIPS 205 -- Section 6.3 -- Algorithm 11.
+func (s *state) xmssPkFromSig(
+ out xmssPublicKey, msg []byte, sig xmssSignature, idx uint32, addr address,
+) {
+ addr.SetTypeAndClear(addressWotsHash)
+ addr.SetKeyPairAddress(idx)
+ pk := xmssPublicKey(s.wotsPkFromSig(sig.wotsSig, msg, addr))
+
+ treeIdx := idx
+ s.H.address.Set(addr)
+ s.H.address.SetTypeAndClear(addressTree)
+
+ authPath := cursor(sig.authPath)
+ for k := range s.hPrime {
+ if (idx>>k)&0x1 == 0 {
+ treeIdx = treeIdx >> 1
+ s.H.SetMsgs(pk, authPath.Next(s.n))
+ } else {
+ treeIdx = (treeIdx - 1) >> 1
+ s.H.SetMsgs(authPath.Next(s.n), pk)
+ }
+
+ s.H.address.SetTreeHeight(k + 1)
+ s.H.address.SetTreeIndex(treeIdx)
+ pk = s.H.Final()
+ }
+
+ copy(out, pk)
+}
// You should have received a copy of the GNU Lesser General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.
-package spx
+package slhdsa
import (
- spxParams "github.com/kasperdi/SPHINCSPLUS-golang/parameters"
- spx "github.com/kasperdi/SPHINCSPLUS-golang/sphincs"
+ "crypto/rand"
+
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl"
)
const (
- SPHINCSPlusSHAKE256s = "sphincs+-shake-256s"
- SPHINCSPlusSHAKE256sPh = "sphincs+-shake-256s-ph"
- SPHINCSPlusSHAKE256sMerkle = "sphincs+-shake-256s-merkle"
+ SLHDSASHAKE256s = "slh-dsa-shake-256s"
+ SLHDSASHAKE256sPh = "slh-dsa-shake-256s-ph"
+ SLHDSASHAKE256sMerkle = "slh-dsa-shake-256s-merkle"
)
-var Params = spxParams.MakeSphincsPlusSHAKE256256sSimple(true)
-
func NewKeypair(algo string) (prv, pub []byte, err error) {
- sk, pk := spx.Spx_keygen(Params)
- pub, err = pk.SerializePK()
+ pk, sk, err := circl.GenerateKey(rand.Reader, circl.SHAKE_256s)
+ if err != nil {
+ return
+ }
+ pub, err = pk.MarshalBinary()
if err != nil {
return
}
- prv, err = sk.SerializeSK()
+ prv, err = sk.MarshalBinary()
if err != nil {
return
}
- prv = append(prv, pub...)
return
}
// You should have received a copy of the GNU Lesser General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.
-package spx
+package slhdsa
import (
"crypto"
"hash"
"io"
- spx "github.com/kasperdi/SPHINCSPLUS-golang/sphincs"
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl"
cmhash "go.cypherpunks.su/keks/cm/hash"
"go.cypherpunks.su/keks/cm/hash/merkle"
type Signer struct {
NewHasher func() hash.Hash
- SK *spx.SPHINCS_SK
- PK *spx.SPHINCS_PK
prehasher *hash.Hash
+ PK circl.PublicKey
+ SK circl.PrivateKey
mode mode.Mode
}
func (s *Signer) Algo() string {
switch s.mode {
case mode.Pure:
- return SPHINCSPlusSHAKE256s
+ return SLHDSASHAKE256s
case mode.Prehash:
- return SPHINCSPlusSHAKE256sPh
+ return SLHDSASHAKE256sPh
case mode.Merkle:
- return SPHINCSPlusSHAKE256sMerkle
+ return SLHDSASHAKE256sMerkle
}
return ""
}
msg []byte,
opts crypto.SignerOpts,
) (signature []byte, err error) {
- sig := spx.Spx_sign(Params, msg, s.SK)
- return sig.SerializeSignature()
+ return circl.SignRandomized(&s.SK, rand, circl.NewMessage(msg), []byte(s.Algo()))
}
func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
signer := Signer{}
- signer.SK, err = spx.DeserializeSK(Params, v[:128])
- if err != nil {
- return
- }
- signer.PK, err = spx.DeserializePK(Params, v[128:])
+ signer.SK.ID = circl.SHAKE_256s
+ err = signer.SK.UnmarshalBinary(v)
if err != nil {
return
}
+ signer.PK = signer.SK.PublicKey()
prv = &signer
- pub, err = signer.PK.SerializePK()
return
}
// You should have received a copy of the GNU Lesser General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.
-package spx
+package slhdsa
import (
- spx "github.com/kasperdi/SPHINCSPLUS-golang/sphincs"
+ "go.cypherpunks.su/keks/cm/sign/slhdsa/circl"
)
func Verify(algo string, pub, signed, signature []byte) (valid bool, err error) {
- var pk *spx.SPHINCS_PK
- pk, err = spx.DeserializePK(Params, pub)
+ var pk circl.PublicKey
+ pk.ID = circl.SHAKE_256s
+ err = pk.UnmarshalBinary(pub)
if err != nil {
return
}
- var sig *spx.SPHINCS_SIG
- sig, err = spx.DeserializeSignature(Params, signature)
- if err != nil {
- return
- }
- valid = spx.Spx_verify(Params, signed, sig, pk)
+ valid = circl.Verify(&pk, circl.NewMessage(signed), signature, []byte(algo))
return
}
--- /dev/null
+[cm/prv/] with SLH-DSA-SHAKE-256s.
+255-bit security level, small variant and simple parameters.
+=> https://csrc.nist.gov/pubs/fips/205/final SLH-DSA\r
+Value is 128 byte private key.
+"slh-dsa-shake-256s" algorithm identifier is used.
+++ /dev/null
-[cm/prv/] with SPHINCS+-SHAKE256-256s.
-255-bit security level, small variant and simple parameters.
-=> https://sphincs.org/ SPHINCS+\r
-=> https://keccak.team/ SHAKE256\r
-Value is concatenation of private and public keys (128+64 bytes).
-"sphincs+-shake-256s" algorithm identifier is used.
--- /dev/null
+[cm/pub/] with SLH-DSA-SHAKE-256s.
+255-bit security level, small variant.
+=> https://csrc.nist.gov/pubs/fips/205/final SLH-DSA\r
+"slh-dsa-shake-256s" algorithm identifier is used.
+Public key's fingerprint should be calculated using SHAKE128.
+++ /dev/null
-[cm/pub/] with SPHINCS+-SHAKE256-256s.
-255-bit security level, small variant and simple parameters.
-=> https://sphincs.org/ SPHINCS+\r
-=> https://keccak.team/ SHAKE256\r
-"sphincs+-shake-256s" algorithm identifier is used.
-Public key's fingerprint should be calculated using SHAKE128.
--- /dev/null
+[cm/signed/] with SLH-DSA-SHAKE-256s.
+255-bit security level, small variant.
+=> https://csrc.nist.gov/pubs/fips/205/final SLH-DSA\r
+
+"slh-dsa-shake-256s" algorithm identifier
+must be used for the signature in pure signing mode.
+"slh-dsa-shake-256s-ph" is used in prehash mode.
+Algorithm identifiers are also used as a context in the signature.
--- /dev/null
+[cm/signed/] with SLH-DSA-SHAKE-256s with Merkle-tree hashing.
+
+It is similar to [cm/signed/slh-dsa-shake-256s], but
+[cm/hashed/shake-merkle] SHAKE256 Merkle-tree hashing is used.
+
+"slh-dsa-shake-256s-merkle" algorithm identifier is used for the signature.
+++ /dev/null
-[cm/signed/] with SPHINCS+-SHAKE256-256s.
-255-bit security level, small variant, simple parameters.
-=> https://sphincs.org/ SPHINCS+\r
-=> https://keccak.team/ SHAKE256\r
-
-"sphincs+-shake-256s" algorithm identifier
-must be used for the signature in pure signing mode.
-"sphincs+-shake-256s-ph" is used in prehash mode.
+++ /dev/null
-[cm/signed/] with SPHINCS+-SHAKE256-256s with Merkle-tree hashing.
-
-It is similar to [cm/signed/sphincs+-shake-256s], but
-[cm/hashed/shake-merkle] SHAKE256 Merkle-tree hashing is used.
-
-"sphincs+-shake-256s-merkle" algorithm identifier is used for the signature.