]> Cypherpunks repositories - keks.git/commitdiff
SLH-DSA instead of SPHINCS+
authorSergey Matveev <stargrave@stargrave.org>
Wed, 4 Jun 2025 11:03:26 +0000 (14:03 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 4 Jun 2025 12:55:20 +0000 (15:55 +0300)
45 files changed:
go/cm/cmd/cmkeytool/certification.t
go/cm/cmd/cmkeytool/main.go
go/cm/cmd/cmsigtool/basic.t
go/cm/enc/mceliece6960119-x25519/README
go/cm/go.mod
go/cm/go.sum
go/cm/hash/algo.go
go/cm/hash/shake.go
go/cm/sign/prv.go
go/cm/sign/pub.go
go/cm/sign/slhdsa/README [new file with mode: 0644]
go/cm/sign/slhdsa/circl/address.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/fors.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/hypertree.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/conv/conv.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/doc.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/hashes.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/keccakf.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/rc.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/sha3.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/shake.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/xor.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/xor_generic.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/internal/sha3/xor_unaligned.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/keys.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/message.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/params.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/scheme.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/sign/sign.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/slhdsa.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/state.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/wotsp.go [new file with mode: 0644]
go/cm/sign/slhdsa/circl/xmss.go [new file with mode: 0644]
go/cm/sign/slhdsa/kp.go [moved from go/cm/sign/spx/kp.go with 65% similarity]
go/cm/sign/slhdsa/signer.go [moved from go/cm/sign/spx/signer.go with 80% similarity]
go/cm/sign/slhdsa/verify.go [moved from go/cm/sign/spx/verify.go with 77% similarity]
spec/cm/prv/slh-dsa-shake-256s [new file with mode: 0644]
spec/cm/prv/sphincs+-shake-256s [deleted file]
spec/cm/pub/slh-dsa-shake-256s [new file with mode: 0644]
spec/cm/pub/sphincs+-shake-256s [deleted file]
spec/cm/signed/slh-dsa-shake-256s [new file with mode: 0644]
spec/cm/signed/slh-dsa-shake-256s-merkle [new file with mode: 0644]
spec/cm/signed/sphincs+-shake-256s [deleted file]
spec/cm/signed/sphincs+-shake-256s-merkle [deleted file]

index 7c03dc66cea3941973a9fa8aee04979730d5d09b0b74a3cb577a1705fbe75cbd..ed41b13453ea344d2b258f81758d74d9ee45833fdfba481e2c70f24a8c273f81 100755 (executable)
@@ -7,7 +7,7 @@ TMPDIR=${TMPDIR:-/tmp}
 
 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 \
index db9520fc49c56d0aa56c46990e9698de176fd07f7d229fbb2d31ef9fbdf00a47..142c09bf6cf140eaf1a4df70251e742a4b10cd305e4275414c3dc6c1748bde59 100644 (file)
@@ -36,7 +36,7 @@ import (
        "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 (
@@ -98,7 +98,7 @@ func main() {
                        gost.GOST3410512C,
                        sntrup4591761x25519.SNTRUP4591761X25519,
                        mceliece6960119x25519.ClassicMcEliece6960119X25519,
-                       spx.SPHINCSPlusSHAKE256s,
+                       slhdsa.SLHDSASHAKE256s,
                }
                sort.Strings(algos)
                for _, s := range algos {
@@ -180,8 +180,8 @@ func main() {
                        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")
                }
@@ -211,7 +211,7 @@ func main() {
                                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")
index 87bd2841e5704807233f3fc3086bf6b67691b6633349e9ef87725f4dee832456..d23dd8dc3b8577e4dae53e41756f34950d31e484a21727f7ff42cefd1b34b325 100755 (executable)
@@ -8,7 +8,7 @@ TMPDIR=${TMPDIR:-/tmp}
 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"
index bfb21d3899d6b98c5249c5a2a573013bdd87a1950bb471adb421ddb7639a34a6..e285c3bf401db75a1231022a38c3fcdfea616ad4bcf8537d858ff1a65b02f2f1 100644 (file)
@@ -1,4 +1,4 @@
-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.
index c14f3ae8f968e3540eaf33f5fcab0bf41461a9e5eba1448990a667cf18ae8c2c..6dce99a86f03c28dbc8c9411efd9b401b952649933e7ea70b432c91f96ec5e01 100644 (file)
@@ -5,7 +5,6 @@ go 1.24.0
 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
index 85ba6bb688ced023f467cf21111b0ed26315e75739764957a85ffed965b0dd55..4f4adc1a64eb9a292e691c9be9db42c397a26c485fc305d0929b46b7a3434ea8 100644 (file)
@@ -2,8 +2,6 @@ github.com/companyzero/sntrup4591761 v0.0.0-20220309191932-9e0f3af2f07a h1:clYxJ
 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=
index 703e7d1b71e75e93506f2a77d2cd8e4717096f08f83663e16b4fc7539e2822a4..9e7bb529490cca0f28a7e2d57599ebae66514cc227e9a1427e3abb9c6a3bbc28 100644 (file)
@@ -73,12 +73,12 @@ func ByName(name string) hash.Hash {
                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)
        }
index 5051d760045dee7a856724bc0e6a4661164d81ae9d738a8dfcb1f62695f4a673..7c2e1b89d1960c9d44eaafcca2d567be2fbd57da3af1c9033e295ce246803418 100644 (file)
@@ -27,9 +27,9 @@ const (
        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 {
index 7c7cdbd142005ddbfcf6f49b8ec68bef20debfc06a966ffcf4f3c6ce6eb8fb39..3a6eafcc760915eac0b6ef124ce0b9131d71edb77608b6518a7103a2a4f4c9ef 100644 (file)
@@ -23,7 +23,7 @@ import (
        "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"
 )
 
@@ -60,8 +60,8 @@ func PrvParse(data []byte) (prv Iface, pub []byte, err error) {
                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)
        }
index c71076e51942cf81a54ca766d9fdd374403d7950e51c7891566ed7936d82a64f..476fbde915b776b0e3d4fade6a5c6c4162ceb518821dc93029364019ccd1ee88 100644 (file)
@@ -28,7 +28,7 @@ import (
        "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"
 )
 
@@ -140,11 +140,11 @@ func (pub *PubLoad) CheckSignature(algo string, signed, signature []byte) (err e
                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
                }
@@ -191,14 +191,14 @@ func (pub *PubLoad) CheckSignaturePrehash(
                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
                }
diff --git a/go/cm/sign/slhdsa/README b/go/cm/sign/slhdsa/README
new file mode 100644 (file)
index 0000000..28a2e25
--- /dev/null
@@ -0,0 +1,4 @@
+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.
diff --git a/go/cm/sign/slhdsa/circl/address.go b/go/cm/sign/slhdsa/circl/address.go
new file mode 100644 (file)
index 0000000..4addb96
--- /dev/null
@@ -0,0 +1,92 @@
+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:])
+}
diff --git a/go/cm/sign/slhdsa/circl/fors.go b/go/cm/sign/slhdsa/circl/fors.go
new file mode 100644 (file)
index 0000000..81bf0ce
--- /dev/null
@@ -0,0 +1,173 @@
+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
+}
diff --git a/go/cm/sign/slhdsa/circl/hypertree.go b/go/cm/sign/slhdsa/circl/hypertree.go
new file mode 100644 (file)
index 0000000..6823c48
--- /dev/null
@@ -0,0 +1,68 @@
+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)
+}
diff --git a/go/cm/sign/slhdsa/circl/internal.go b/go/cm/sign/slhdsa/circl/internal.go
new file mode 100644 (file)
index 0000000..adf8c88
--- /dev/null
@@ -0,0 +1,135 @@
+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
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/conv/conv.go b/go/cm/sign/slhdsa/circl/internal/conv/conv.go
new file mode 100644 (file)
index 0000000..1ebade8
--- /dev/null
@@ -0,0 +1,173 @@
+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
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/doc.go b/go/cm/sign/slhdsa/circl/internal/sha3/doc.go
new file mode 100644 (file)
index 0000000..dc125a2
--- /dev/null
@@ -0,0 +1,62 @@
+// 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
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/hashes.go b/go/cm/sign/slhdsa/circl/internal/sha3/hashes.go
new file mode 100644 (file)
index 0000000..35627ca
--- /dev/null
@@ -0,0 +1,69 @@
+// 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
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/keccakf.go b/go/cm/sign/slhdsa/circl/internal/sha3/keccakf.go
new file mode 100644 (file)
index 0000000..eacb748
--- /dev/null
@@ -0,0 +1,391 @@
+// 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)
+       }
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/rc.go b/go/cm/sign/slhdsa/circl/internal/sha3/rc.go
new file mode 100644 (file)
index 0000000..a26a3b9
--- /dev/null
@@ -0,0 +1,29 @@
+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,
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/sha3.go b/go/cm/sign/slhdsa/circl/internal/sha3/sha3.go
new file mode 100644 (file)
index 0000000..47be90c
--- /dev/null
@@ -0,0 +1,200 @@
+// 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
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/shake.go b/go/cm/sign/slhdsa/circl/internal/sha3/shake.go
new file mode 100644 (file)
index 0000000..a899680
--- /dev/null
@@ -0,0 +1,119 @@
+// 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
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/xor.go b/go/cm/sign/slhdsa/circl/internal/sha3/xor.go
new file mode 100644 (file)
index 0000000..ef61707
--- /dev/null
@@ -0,0 +1,15 @@
+// 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)
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/xor_generic.go b/go/cm/sign/slhdsa/circl/internal/sha3/xor_generic.go
new file mode 100644 (file)
index 0000000..0d2fc2b
--- /dev/null
@@ -0,0 +1,33 @@
+// 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:]
+       }
+}
diff --git a/go/cm/sign/slhdsa/circl/internal/sha3/xor_unaligned.go b/go/cm/sign/slhdsa/circl/internal/sha3/xor_unaligned.go
new file mode 100644 (file)
index 0000000..a953af3
--- /dev/null
@@ -0,0 +1,61 @@
+// 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[:])
+}
diff --git a/go/cm/sign/slhdsa/circl/keys.go b/go/cm/sign/slhdsa/circl/keys.go
new file mode 100644 (file)
index 0000000..fab3ab2
--- /dev/null
@@ -0,0 +1,137 @@
+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)
+}
diff --git a/go/cm/sign/slhdsa/circl/message.go b/go/cm/sign/slhdsa/circl/message.go
new file mode 100644 (file)
index 0000000..5cb3e03
--- /dev/null
@@ -0,0 +1,28 @@
+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
+}
diff --git a/go/cm/sign/slhdsa/circl/params.go b/go/cm/sign/slhdsa/circl/params.go
new file mode 100644 (file)
index 0000000..b2080b1
--- /dev/null
@@ -0,0 +1,182 @@
+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)
+               }
+       }
+}
diff --git a/go/cm/sign/slhdsa/circl/scheme.go b/go/cm/sign/slhdsa/circl/scheme.go
new file mode 100644 (file)
index 0000000..66f4969
--- /dev/null
@@ -0,0 +1,114 @@
+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
+}
diff --git a/go/cm/sign/slhdsa/circl/sign/sign.go b/go/cm/sign/slhdsa/circl/sign/sign.go
new file mode 100644 (file)
index 0000000..d371dde
--- /dev/null
@@ -0,0 +1,113 @@
+// 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")
+)
diff --git a/go/cm/sign/slhdsa/circl/slhdsa.go b/go/cm/sign/slhdsa/circl/slhdsa.go
new file mode 100644 (file)
index 0000000..7f1b47b
--- /dev/null
@@ -0,0 +1,131 @@
+// 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")
+)
diff --git a/go/cm/sign/slhdsa/circl/state.go b/go/cm/sign/slhdsa/circl/state.go
new file mode 100644 (file)
index 0000000..537b75b
--- /dev/null
@@ -0,0 +1,357 @@
+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 }
diff --git a/go/cm/sign/slhdsa/circl/wotsp.go b/go/cm/sign/slhdsa/circl/wotsp.go
new file mode 100644 (file)
index 0000000..92f6b42
--- /dev/null
@@ -0,0 +1,142 @@
+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()
+}
diff --git a/go/cm/sign/slhdsa/circl/xmss.go b/go/cm/sign/slhdsa/circl/xmss.go
new file mode 100644 (file)
index 0000000..1328cbd
--- /dev/null
@@ -0,0 +1,111 @@
+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)
+}
similarity index 65%
rename from go/cm/sign/spx/kp.go
rename to go/cm/sign/slhdsa/kp.go
index 5359c61da8f0a78f28c736a2a712e9058470b75b401cc45f2d047fa432cd8972..b0d9244398ca16bbeaad251f30468e83794162f415a8eff97adce15f1b765664 100644 (file)
 // 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
 }
similarity index 80%
rename from go/cm/sign/spx/signer.go
rename to go/cm/sign/slhdsa/signer.go
index 57ddcb4988f39a6c2ce1490e3499d5a4115c3f1eb676ad2294acc74f0aee9428..a9bc07cc21069420f9b39773650648c719b0745533d986abc6e09224a21602f0 100644 (file)
@@ -13,7 +13,7 @@
 // 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"
@@ -21,7 +21,7 @@ import (
        "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"
@@ -30,9 +30,9 @@ import (
 
 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
 }
 
@@ -67,11 +67,11 @@ func (s *Signer) Prehasher() *hash.Hash {
 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 ""
 }
@@ -85,21 +85,17 @@ func (s *Signer) Sign(
        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
 }
similarity index 77%
rename from go/cm/sign/spx/verify.go
rename to go/cm/sign/slhdsa/verify.go
index 13914492d85beb6edf12e4cfe65721e8a4f75cb52e9103e8f706835cfae7e404..680975b226c2f4dd4fbecc840c3351f8715aa4a322929cd7233ca0cc093dfc19 100644 (file)
 // 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
 }
 
diff --git a/spec/cm/prv/slh-dsa-shake-256s b/spec/cm/prv/slh-dsa-shake-256s
new file mode 100644 (file)
index 0000000..41ebc3a
--- /dev/null
@@ -0,0 +1,5 @@
+[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.
diff --git a/spec/cm/prv/sphincs+-shake-256s b/spec/cm/prv/sphincs+-shake-256s
deleted file mode 100644 (file)
index 75af180..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-[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.
diff --git a/spec/cm/pub/slh-dsa-shake-256s b/spec/cm/pub/slh-dsa-shake-256s
new file mode 100644 (file)
index 0000000..fe319bd
--- /dev/null
@@ -0,0 +1,5 @@
+[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.
diff --git a/spec/cm/pub/sphincs+-shake-256s b/spec/cm/pub/sphincs+-shake-256s
deleted file mode 100644 (file)
index 7e8085f..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-[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.
diff --git a/spec/cm/signed/slh-dsa-shake-256s b/spec/cm/signed/slh-dsa-shake-256s
new file mode 100644 (file)
index 0000000..2707a2f
--- /dev/null
@@ -0,0 +1,8 @@
+[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.
diff --git a/spec/cm/signed/slh-dsa-shake-256s-merkle b/spec/cm/signed/slh-dsa-shake-256s-merkle
new file mode 100644 (file)
index 0000000..76e4131
--- /dev/null
@@ -0,0 +1,6 @@
+[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.
diff --git a/spec/cm/signed/sphincs+-shake-256s b/spec/cm/signed/sphincs+-shake-256s
deleted file mode 100644 (file)
index 84b0cf1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-[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.
diff --git a/spec/cm/signed/sphincs+-shake-256s-merkle b/spec/cm/signed/sphincs+-shake-256s-merkle
deleted file mode 100644 (file)
index 0b48b41..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-[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.