From 9434bac52e7b09ee5a9dbecd99c7f12df6e74948407dada46b705c1ce5ccb5f5 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Wed, 4 Jun 2025 14:03:26 +0300 Subject: [PATCH] SLH-DSA instead of SPHINCS+ --- go/cm/cmd/cmkeytool/certification.t | 2 +- go/cm/cmd/cmkeytool/main.go | 10 +- go/cm/cmd/cmsigtool/basic.t | 2 +- go/cm/enc/mceliece6960119-x25519/README | 2 +- go/cm/go.mod | 1 - go/cm/go.sum | 2 - go/cm/hash/algo.go | 4 +- go/cm/hash/shake.go | 6 +- go/cm/sign/prv.go | 6 +- go/cm/sign/pub.go | 16 +- go/cm/sign/slhdsa/README | 4 + go/cm/sign/slhdsa/circl/address.go | 92 +++++ go/cm/sign/slhdsa/circl/fors.go | 173 ++++++++ go/cm/sign/slhdsa/circl/hypertree.go | 68 +++ go/cm/sign/slhdsa/circl/internal.go | 135 ++++++ go/cm/sign/slhdsa/circl/internal/conv/conv.go | 173 ++++++++ go/cm/sign/slhdsa/circl/internal/sha3/doc.go | 62 +++ .../sign/slhdsa/circl/internal/sha3/hashes.go | 69 ++++ .../slhdsa/circl/internal/sha3/keccakf.go | 391 ++++++++++++++++++ go/cm/sign/slhdsa/circl/internal/sha3/rc.go | 29 ++ go/cm/sign/slhdsa/circl/internal/sha3/sha3.go | 200 +++++++++ .../sign/slhdsa/circl/internal/sha3/shake.go | 119 ++++++ go/cm/sign/slhdsa/circl/internal/sha3/xor.go | 15 + .../slhdsa/circl/internal/sha3/xor_generic.go | 33 ++ .../circl/internal/sha3/xor_unaligned.go | 61 +++ go/cm/sign/slhdsa/circl/keys.go | 137 ++++++ go/cm/sign/slhdsa/circl/message.go | 28 ++ go/cm/sign/slhdsa/circl/params.go | 182 ++++++++ go/cm/sign/slhdsa/circl/scheme.go | 114 +++++ go/cm/sign/slhdsa/circl/sign/sign.go | 113 +++++ go/cm/sign/slhdsa/circl/slhdsa.go | 131 ++++++ go/cm/sign/slhdsa/circl/state.go | 357 ++++++++++++++++ go/cm/sign/slhdsa/circl/wotsp.go | 142 +++++++ go/cm/sign/slhdsa/circl/xmss.go | 111 +++++ go/cm/sign/{spx => slhdsa}/kp.go | 25 +- go/cm/sign/{spx => slhdsa}/signer.go | 26 +- go/cm/sign/{spx => slhdsa}/verify.go | 16 +- spec/cm/prv/slh-dsa-shake-256s | 5 + spec/cm/prv/sphincs+-shake-256s | 6 - spec/cm/pub/slh-dsa-shake-256s | 5 + spec/cm/pub/sphincs+-shake-256s | 6 - spec/cm/signed/slh-dsa-shake-256s | 8 + spec/cm/signed/slh-dsa-shake-256s-merkle | 6 + spec/cm/signed/sphincs+-shake-256s | 8 - spec/cm/signed/sphincs+-shake-256s-merkle | 6 - 45 files changed, 3017 insertions(+), 90 deletions(-) create mode 100644 go/cm/sign/slhdsa/README create mode 100644 go/cm/sign/slhdsa/circl/address.go create mode 100644 go/cm/sign/slhdsa/circl/fors.go create mode 100644 go/cm/sign/slhdsa/circl/hypertree.go create mode 100644 go/cm/sign/slhdsa/circl/internal.go create mode 100644 go/cm/sign/slhdsa/circl/internal/conv/conv.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/doc.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/hashes.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/keccakf.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/rc.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/sha3.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/shake.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/xor.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/xor_generic.go create mode 100644 go/cm/sign/slhdsa/circl/internal/sha3/xor_unaligned.go create mode 100644 go/cm/sign/slhdsa/circl/keys.go create mode 100644 go/cm/sign/slhdsa/circl/message.go create mode 100644 go/cm/sign/slhdsa/circl/params.go create mode 100644 go/cm/sign/slhdsa/circl/scheme.go create mode 100644 go/cm/sign/slhdsa/circl/sign/sign.go create mode 100644 go/cm/sign/slhdsa/circl/slhdsa.go create mode 100644 go/cm/sign/slhdsa/circl/state.go create mode 100644 go/cm/sign/slhdsa/circl/wotsp.go create mode 100644 go/cm/sign/slhdsa/circl/xmss.go rename go/cm/sign/{spx => slhdsa}/kp.go (65%) rename go/cm/sign/{spx => slhdsa}/signer.go (80%) rename go/cm/sign/{spx => slhdsa}/verify.go (77%) create mode 100644 spec/cm/prv/slh-dsa-shake-256s delete mode 100644 spec/cm/prv/sphincs+-shake-256s create mode 100644 spec/cm/pub/slh-dsa-shake-256s delete mode 100644 spec/cm/pub/sphincs+-shake-256s create mode 100644 spec/cm/signed/slh-dsa-shake-256s create mode 100644 spec/cm/signed/slh-dsa-shake-256s-merkle delete mode 100644 spec/cm/signed/sphincs+-shake-256s delete mode 100644 spec/cm/signed/sphincs+-shake-256s-merkle diff --git a/go/cm/cmd/cmkeytool/certification.t b/go/cm/cmd/cmkeytool/certification.t index 7c03dc6..ed41b13 100755 --- a/go/cm/cmd/cmkeytool/certification.t +++ b/go/cm/cmd/cmkeytool/certification.t @@ -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 \ diff --git a/go/cm/cmd/cmkeytool/main.go b/go/cm/cmd/cmkeytool/main.go index db9520f..142c09b 100644 --- a/go/cm/cmd/cmkeytool/main.go +++ b/go/cm/cmd/cmkeytool/main.go @@ -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") diff --git a/go/cm/cmd/cmsigtool/basic.t b/go/cm/cmd/cmsigtool/basic.t index 87bd284..d23dd8d 100755 --- a/go/cm/cmd/cmsigtool/basic.t +++ b/go/cm/cmd/cmsigtool/basic.t @@ -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" diff --git a/go/cm/enc/mceliece6960119-x25519/README b/go/cm/enc/mceliece6960119-x25519/README index bfb21d3..e285c3b 100644 --- a/go/cm/enc/mceliece6960119-x25519/README +++ b/go/cm/enc/mceliece6960119-x25519/README @@ -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. diff --git a/go/cm/go.mod b/go/cm/go.mod index c14f3ae..6dce99a 100644 --- a/go/cm/go.mod +++ b/go/cm/go.mod @@ -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 diff --git a/go/cm/go.sum b/go/cm/go.sum index 85ba6bb..4f4adc1 100644 --- a/go/cm/go.sum +++ b/go/cm/go.sum @@ -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= diff --git a/go/cm/hash/algo.go b/go/cm/hash/algo.go index 703e7d1..9e7bb52 100644 --- a/go/cm/hash/algo.go +++ b/go/cm/hash/algo.go @@ -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) } diff --git a/go/cm/hash/shake.go b/go/cm/hash/shake.go index 5051d76..7c2e1b8 100644 --- a/go/cm/hash/shake.go +++ b/go/cm/hash/shake.go @@ -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 { diff --git a/go/cm/sign/prv.go b/go/cm/sign/prv.go index 7c7cdbd..3a6eafc 100644 --- a/go/cm/sign/prv.go +++ b/go/cm/sign/prv.go @@ -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) } diff --git a/go/cm/sign/pub.go b/go/cm/sign/pub.go index c71076e..476fbde 100644 --- a/go/cm/sign/pub.go +++ b/go/cm/sign/pub.go @@ -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 index 0000000..28a2e25 --- /dev/null +++ b/go/cm/sign/slhdsa/README @@ -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 index 0000000..4addb96 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/address.go @@ -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 index 0000000..81bf0ce --- /dev/null +++ b/go/cm/sign/slhdsa/circl/fors.go @@ -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 index 0000000..6823c48 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/hypertree.go @@ -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 index 0000000..adf8c88 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal.go @@ -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 index 0000000..1ebade8 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/conv/conv.go @@ -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 index 0000000..dc125a2 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/doc.go @@ -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 index 0000000..35627ca --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/hashes.go @@ -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 index 0000000..eacb748 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/keccakf.go @@ -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 index 0000000..a26a3b9 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/rc.go @@ -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 index 0000000..47be90c --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/sha3.go @@ -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 index 0000000..a899680 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/shake.go @@ -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 index 0000000..ef61707 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/xor.go @@ -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 index 0000000..0d2fc2b --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/xor_generic.go @@ -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 index 0000000..a953af3 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/internal/sha3/xor_unaligned.go @@ -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 index 0000000..fab3ab2 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/keys.go @@ -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 index 0000000..5cb3e03 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/message.go @@ -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 index 0000000..b2080b1 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/params.go @@ -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 index 0000000..66f4969 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/scheme.go @@ -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 index 0000000..d371dde --- /dev/null +++ b/go/cm/sign/slhdsa/circl/sign/sign.go @@ -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 index 0000000..7f1b47b --- /dev/null +++ b/go/cm/sign/slhdsa/circl/slhdsa.go @@ -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 index 0000000..537b75b --- /dev/null +++ b/go/cm/sign/slhdsa/circl/state.go @@ -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 index 0000000..92f6b42 --- /dev/null +++ b/go/cm/sign/slhdsa/circl/wotsp.go @@ -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 index 0000000..1328cbd --- /dev/null +++ b/go/cm/sign/slhdsa/circl/xmss.go @@ -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) +} diff --git a/go/cm/sign/spx/kp.go b/go/cm/sign/slhdsa/kp.go similarity index 65% rename from go/cm/sign/spx/kp.go rename to go/cm/sign/slhdsa/kp.go index 5359c61..b0d9244 100644 --- a/go/cm/sign/spx/kp.go +++ b/go/cm/sign/slhdsa/kp.go @@ -13,31 +13,32 @@ // You should have received a copy of the GNU Lesser General Public // License along with this program. If not, see . -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 } diff --git a/go/cm/sign/spx/signer.go b/go/cm/sign/slhdsa/signer.go similarity index 80% rename from go/cm/sign/spx/signer.go rename to go/cm/sign/slhdsa/signer.go index 57ddcb4..a9bc07c 100644 --- a/go/cm/sign/spx/signer.go +++ b/go/cm/sign/slhdsa/signer.go @@ -13,7 +13,7 @@ // You should have received a copy of the GNU Lesser General Public // License along with this program. If not, see . -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 } diff --git a/go/cm/sign/spx/verify.go b/go/cm/sign/slhdsa/verify.go similarity index 77% rename from go/cm/sign/spx/verify.go rename to go/cm/sign/slhdsa/verify.go index 1391449..680975b 100644 --- a/go/cm/sign/spx/verify.go +++ b/go/cm/sign/slhdsa/verify.go @@ -13,24 +13,20 @@ // You should have received a copy of the GNU Lesser General Public // License along with this program. If not, see . -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 index 0000000..41ebc3a --- /dev/null +++ b/spec/cm/prv/slh-dsa-shake-256s @@ -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 +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 index 75af180..0000000 --- a/spec/cm/prv/sphincs+-shake-256s +++ /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+ -=> https://keccak.team/ SHAKE256 -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 index 0000000..fe319bd --- /dev/null +++ b/spec/cm/pub/slh-dsa-shake-256s @@ -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 +"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 index 7e8085f..0000000 --- a/spec/cm/pub/sphincs+-shake-256s +++ /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+ -=> https://keccak.team/ SHAKE256 -"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 index 0000000..2707a2f --- /dev/null +++ b/spec/cm/signed/slh-dsa-shake-256s @@ -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 + +"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 index 0000000..76e4131 --- /dev/null +++ b/spec/cm/signed/slh-dsa-shake-256s-merkle @@ -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 index 84b0cf1..0000000 --- a/spec/cm/signed/sphincs+-shake-256s +++ /dev/null @@ -1,8 +0,0 @@ -[cm/signed/] with SPHINCS+-SHAKE256-256s. -255-bit security level, small variant, simple parameters. -=> https://sphincs.org/ SPHINCS+ -=> https://keccak.team/ SHAKE256 - -"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 index 0b48b41..0000000 --- a/spec/cm/signed/sphincs+-shake-256s-merkle +++ /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. -- 2.50.0