From: Sergey Matveev Date: Wed, 16 Apr 2025 18:49:54 +0000 (+0300) Subject: sphincs+-shake-256s X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=5cfa432ddd7b3ad5c5f242a1b825cda01d463b388aa0d11771d98a4393a23b48;p=keks.git sphincs+-shake-256s --- diff --git a/go/cm/cmd/cmkeytool/certification.t b/go/cm/cmd/cmkeytool/certification.t index e71bed1..7c03dc6 100755 --- a/go/cm/cmd/cmkeytool/certification.t +++ b/go/cm/cmd/cmkeytool/certification.t @@ -6,7 +6,8 @@ test_description="Check certification" TMPDIR=${TMPDIR:-/tmp} echo "gost3410-512C gost3410-256A -ed25519-blake2b ed25519-blake2b" | while read caAlgo eeAlgo ; do +ed25519-blake2b ed25519-blake2b +sphincs+-shake-256s sphincs+-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 785dee0..077b7cf 100644 --- a/go/cm/cmd/cmkeytool/main.go +++ b/go/cm/cmd/cmkeytool/main.go @@ -36,6 +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" ) const ( @@ -97,6 +98,7 @@ func main() { gost.GOST3410512C, sntrup4591761x25519.SNTRUP4591761X25519, mceliece6960119x25519.ClassicMcEliece6960119X25519, + spx.SPHINCSPlusSHAKE256s, } sort.Strings(algos) for _, s := range algos { @@ -178,6 +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) default: err = errors.New("unknown -algo specified") } @@ -207,7 +211,7 @@ func main() { hasher = cmhash.ByName(cmhash.BLAKE2b256) case gost.GOST3410256A, gost.GOST3410512C: hasher = cmhash.ByName(cmhash.Streebog256) - case mceliece6960119x25519.ClassicMcEliece6960119X25519: + case mceliece6960119x25519.ClassicMcEliece6960119X25519, spx.SPHINCSPlusSHAKE256s: 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 8e3cb3b..87bd284 100755 --- a/go/cm/cmd/cmsigtool/basic.t +++ b/go/cm/cmd/cmsigtool/basic.t @@ -7,7 +7,8 @@ TMPDIR=${TMPDIR:-/tmp} echo "gost3410-512C gost3410-256A -ed25519-blake2b" | while read keyalgo ; do +ed25519-blake2b +sphincs+-shake-256s" | while read keyalgo ; do sub="-sub what=ever" typ="some-different-type" diff --git a/go/cm/go.mod b/go/cm/go.mod index 6dce99a..c14f3ae 100644 --- a/go/cm/go.mod +++ b/go/cm/go.mod @@ -5,6 +5,7 @@ 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 4f4adc1..85ba6bb 100644 --- a/go/cm/go.sum +++ b/go/cm/go.sum @@ -2,6 +2,8 @@ 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 073f1dc..5819400 100644 --- a/go/cm/hash/algo.go +++ b/go/cm/hash/algo.go @@ -38,8 +38,6 @@ const ( Streebog512 = "streebog512" BLAKE2bMerkle = "blake2b-merkle" - SHAKE128Merkle = "shake128-merkle" - SHAKE256Merkle = "shake256-merkle" Streebog256Merkle = "streebog256-merkle" Streebog512Merkle = "streebog512-merkle" ) @@ -75,12 +73,12 @@ func ByName(name string) hash.Hash { return h case SHAKE128: return NewSHAKE128() - case SHAKE256: + case SHAKE256, SPHINCSPlusSHAKE256sNonRandom, SPHINCSPlusSHAKE256sNonRandomPh: return NewSHAKE256() case SHAKE128Merkle: return NewSHAKE128MerkleHasher( merkle.DefaultChunkLen, DefaultNumCPU) - case SHAKE256Merkle: + case SHAKE256Merkle, SPHINCSPlusSHAKE256sNonRandomMerkle: return NewSHAKE256MerkleHasher( merkle.DefaultChunkLen, DefaultNumCPU) } diff --git a/go/cm/hash/shake.go b/go/cm/hash/shake.go index 78157b8..4de9849 100644 --- a/go/cm/hash/shake.go +++ b/go/cm/hash/shake.go @@ -23,6 +23,15 @@ import ( "go.cypherpunks.su/keks/cm/hash/merkle" ) +const ( + SHAKE128Merkle = "shake128-merkle" + SHAKE256Merkle = "shake256-merkle" + + SPHINCSPlusSHAKE256sNonRandom = "sphincs+-shake-256s-nonrandom" + SPHINCSPlusSHAKE256sNonRandomPh = "sphincs+-shake-256s-nonrandom-ph" + SPHINCSPlusSHAKE256sNonRandomMerkle = "sphincs+-shake-256s-nonrandom-merkle" +) + type SHAKE struct { xof *sha3.SHAKE l int diff --git a/go/cm/sign/prv.go b/go/cm/sign/prv.go index eb0a299..7c7cdbd 100644 --- a/go/cm/sign/prv.go +++ b/go/cm/sign/prv.go @@ -23,6 +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/schema" ) @@ -59,6 +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) 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 6df41da..88a1972 100644 --- a/go/cm/sign/pub.go +++ b/go/cm/sign/pub.go @@ -28,6 +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/schema" ) @@ -139,6 +140,14 @@ func (pub *PubLoad) CheckSignature(algo string, signed, signature []byte) (err e if !valid { err = ErrSigInvalid } + case spx.SPHINCSPlusSHAKE256s: + if algo != spx.SPHINCSPlusSHAKE256sNonRandom { + return ErrBadSigAlgo + } + valid, err = spx.Verify(key.A, key.V, signed, signature) + if !valid { + err = ErrSigInvalid + } default: err = errors.New("unsupported signature algorithm") } @@ -182,6 +191,17 @@ func (pub *PubLoad) CheckSignaturePrehash( if !valid { err = ErrSigInvalid } + case spx.SPHINCSPlusSHAKE256s: + switch algo { + case spx.SPHINCSPlusSHAKE256sNonRandomPh: + case spx.SPHINCSPlusSHAKE256sNonRandomMerkle: + default: + return ErrBadSigAlgo + } + valid, err = spx.VerifyPrehash(key.A, key.V, prehash, signature) + if !valid { + err = ErrSigInvalid + } default: err = errors.New("unsupported signature algorithm") } diff --git a/go/cm/sign/spx/kp.go b/go/cm/sign/spx/kp.go new file mode 100644 index 0000000..9b7b2c1 --- /dev/null +++ b/go/cm/sign/spx/kp.go @@ -0,0 +1,45 @@ +// GoKEKS/CM -- KEKS-encoded cryptographic messages +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package spx + +import ( + spxParams "github.com/kasperdi/SPHINCSPLUS-golang/parameters" + spx "github.com/kasperdi/SPHINCSPLUS-golang/sphincs" +) + +const ( + SPHINCSPlusSHAKE256s = "sphincs+-shake-256s" + SPHINCSPlusSHAKE256sNonRandom = "sphincs+-shake-256s-nonrandom" + SPHINCSPlusSHAKE256sNonRandomPh = "sphincs+-shake-256s-nonrandom-ph" + SPHINCSPlusSHAKE256sNonRandomMerkle = "sphincs+-shake-256s-nonrandom-merkle" +) + +var Params = spxParams.MakeSphincsPlusSHAKE256256sRobust(false) + +func NewKeypair(algo string) (signer *Signer, prv, pub []byte, err error) { + sk, pk := spx.Spx_keygen(Params) + pub, err = pk.SerializePK() + if err != nil { + return + } + prv, err = sk.SerializeSK() + if err != nil { + return + } + prv = append(prv, pub...) + signer, _, err = NewSigner(prv) + return +} diff --git a/go/cm/sign/spx/signer.go b/go/cm/sign/spx/signer.go new file mode 100644 index 0000000..5531a79 --- /dev/null +++ b/go/cm/sign/spx/signer.go @@ -0,0 +1,105 @@ +// GoKEKS/CM -- KEKS-encoded cryptographic messages +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package spx + +import ( + "crypto" + "errors" + "hash" + "io" + + spx "github.com/kasperdi/SPHINCSPLUS-golang/sphincs" + + cmhash "go.cypherpunks.su/keks/cm/hash" + "go.cypherpunks.su/keks/cm/hash/merkle" + "go.cypherpunks.su/keks/cm/sign/mode" +) + +type Signer struct { + NewHasher func() hash.Hash + SK *spx.SPHINCS_SK + PK *spx.SPHINCS_PK + prehasher *hash.Hash + mode mode.Mode +} + +func (s *Signer) SetMode(m mode.Mode) error { + switch m { + case mode.Pure: + s.mode = m + return nil + case mode.Prehash: + s.mode = m + p := cmhash.NewSHAKE256() + s.prehasher = &p + return nil + case mode.Merkle: + s.mode = m + p := cmhash.NewSHAKE256MerkleHasher(merkle.DefaultChunkLen, cmhash.DefaultNumCPU) + s.prehasher = &p + return nil + default: + return errors.New("unsupported mode") + } +} + +func (s *Signer) Mode() mode.Mode { + return s.mode +} + +func (s *Signer) Prehasher() *hash.Hash { + return s.prehasher +} + +func (s *Signer) Algo() string { + switch s.mode { + case mode.Pure: + return SPHINCSPlusSHAKE256sNonRandom + case mode.Prehash: + return SPHINCSPlusSHAKE256sNonRandomPh + case mode.Merkle: + return SPHINCSPlusSHAKE256sNonRandomMerkle + } + return "" +} + +func (s *Signer) Public() crypto.PublicKey { + return s.PK +} + +func (s *Signer) Sign( + rand io.Reader, + msg []byte, + opts crypto.SignerOpts, +) (signature []byte, err error) { + sig := spx.Spx_sign(Params, msg, s.SK) + return sig.SerializeSignature() +} + +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:]) + if err != nil { + return + } + prv = &signer + pub, err = signer.PK.SerializePK() + return +} diff --git a/go/cm/sign/spx/verify.go b/go/cm/sign/spx/verify.go new file mode 100644 index 0000000..1391449 --- /dev/null +++ b/go/cm/sign/spx/verify.go @@ -0,0 +1,39 @@ +// GoKEKS/CM -- KEKS-encoded cryptographic messages +// Copyright (C) 2024-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this program. If not, see . + +package spx + +import ( + spx "github.com/kasperdi/SPHINCSPLUS-golang/sphincs" +) + +func Verify(algo string, pub, signed, signature []byte) (valid bool, err error) { + var pk *spx.SPHINCS_PK + pk, err = spx.DeserializePK(Params, 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) + return +} + +func VerifyPrehash(algo string, pub, hsh, signature []byte) (valid bool, err error) { + return Verify(algo, pub, hsh, signature) +} diff --git a/spec/cm/prv.texi b/spec/cm/prv.texi index 876977f..d00294a 100644 --- a/spec/cm/prv.texi +++ b/spec/cm/prv.texi @@ -50,3 +50,15 @@ Stored in a file, it should begin with "cm/prv" @ref{MAGIC, magic}. and X25519's 32-byte one. @code{mceliece6960119-x25519} algorithm identifier is used. + +@node cm-prv-sphincs+-shake-256s +@cindex cm-prv-sphincs+-shake-256s +@nodedescription cm/prv with SPHINCS+-SHAKE256-256s-robust +@subsection cm/prv with SPHINCS+-SHAKE256-256s-robust + + @url{https://sphincs.org/, SPHINCS+} with + @url{https://keccak.team/, SHAKE256} hash, + 255-bit security level, small signatures and robust parameters. + Value is concatenation of private and public keys (128+64 bytes). + + Algorithm identifier for the public key: @code{sphincs+-shake-256s}. diff --git a/spec/cm/pub.texi b/spec/cm/pub.texi index a9bdfae..f922522 100644 --- a/spec/cm/pub.texi +++ b/spec/cm/pub.texi @@ -190,3 +190,16 @@ MAP { of the concatenated public keys in @code{/load/v/pub/0}, that could save resources during @ref{kem-mceliece6960119-x25519-hkdf-shake256} KDF calculations. + +@node cm-pub-sphincs+-shake-256s +@cindex cm-pub-sphincs+-shake-256s +@nodedescription cm/pub with SPHINCS+-SHAKE256-256s-robust +@subsection cm/pub with SPHINCS+-SHAKE256-256s-robust + + @url{https://sphincs.org/, SPHINCS+} with + @url{https://keccak.team/, SHAKE256} hash, + 255-bit security level, small signatures and robust parameters. + + @code{sphincs+-shake-256s} algorithm identifier is used. + + Public key's fingerprint should be calculated using SHAKE128. diff --git a/spec/cm/signed.texi b/spec/cm/signed.texi index c5da863..4252f8f 100644 --- a/spec/cm/signed.texi +++ b/spec/cm/signed.texi @@ -115,3 +115,27 @@ recipient's public key fingerprint(s). @ref{cm-hashed-blake2b-merkle} Merkle-tree hashing is used. HashEdDSA mode is used with @code{ed25519ph-blake2b-merkle} algorithm identifier for signature. + +@node cm-signed-sphincs+-shake-256s-nonrandom +@cindex cm-signed-sphincs+-shake-256s-nonrandom +@nodedescription cm/signed with SPHINCS+-SHAKE256-256s-robust non-random +@subsection cm/signed with SPHINCS+-SHAKE256-256s-robust non-random + + @url{https://sphincs.org/, SPHINCS+} with + @url{https://keccak.team/, SHAKE256} hash, + 255-bit security level, small signatures, + robust parameters and deterministic signatures. + + @code{sphincs+-shake-256s-nonrandom} algorithm identifier + must be used for the signature in pure signing mode. + @code{sphincs+-shake-256s-nonrandom-ph} is used in prehash mode. + +@node cm-signed-sphincs+-shake-256s-nonrandom-merkle +@cindex cm-signed-sphincs+-shake-256s-nonrandom-merkle +@nodedescription cm-signed-sphincs+-shake-256s-nonrandom with Merkle-tree hashing +@subsection cm-signed-sphincs+-shake-256s-nonrandom with Merkle-tree hashing + + @ref{cm-hashed-shake-merkle, shake256-merkle} Merkle-tree hashing is used. + + @code{sphincs+-shake-256s-nonrandom-merkle} algorithm + identifier must be used for the signature.