]> Cypherpunks repositories - keks.git/commitdiff
Add various suggested hash algorithms and Ed25519-BLAKE2b scheme
authorSergey Matveev <stargrave@stargrave.org>
Sat, 30 Nov 2024 14:54:44 +0000 (17:54 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Tue, 3 Dec 2024 14:47:35 +0000 (17:47 +0300)
19 files changed:
gyac/yacpki/algo.go
gyac/yacpki/cer.go
gyac/yacpki/cmd/yacertool/basic.t
gyac/yacpki/cmd/yacertool/main.go
gyac/yacpki/ed25519-blake2b/.gitignore [new file with mode: 0644]
gyac/yacpki/ed25519-blake2b/clean [new file with mode: 0755]
gyac/yacpki/ed25519-blake2b/ed25519-to-blake2b.patch [new file with mode: 0644]
gyac/yacpki/ed25519-blake2b/mk-from-go [new file with mode: 0755]
gyac/yacpki/gost.go [new file with mode: 0644]
gyac/yacpki/prv.go
gyac/yacpki/signed-data.go
spec/format/cer.texi
spec/format/hashed-data.texi
spec/format/index.texi
spec/format/private-key.texi
spec/format/registry.texi [new file with mode: 0644]
spec/format/signed-data.texi
spec/index.texi
spec/registry.texi [deleted file]

index 99997a0dc5236ba0ec90ef3b73fa7bef665b08993b7d2f5ec3131ba9ebfd36b1..cd7070304098e78e3d150358443a3b7868dac17100aa4ff68c169ef6267019a6 100644 (file)
@@ -3,26 +3,21 @@ package yacpki
 import (
        "bytes"
        "hash"
-       "log"
 
        "github.com/google/uuid"
-       "go.cypherpunks.su/gogost/v6/gost3410"
        "go.cypherpunks.su/gogost/v6/gost34112012256"
        "go.cypherpunks.su/gogost/v6/gost34112012512"
        "go.cypherpunks.su/yac/gyac"
        "go.cypherpunks.su/yac/gyac/yacpki/utils"
+       "golang.org/x/crypto/blake2b"
 )
 
 const (
-       AlgoStreebog256  = "streebog256"
-       AlgoStreebog512  = "streebog512"
-       AlgoGOST3410256A = "gost3410-256A"
-       AlgoGOST3410256B = "gost3410-256B"
-       AlgoGOST3410256C = "gost3410-256C"
-       AlgoGOST3410256D = "gost3410-256D"
-       AlgoGOST3410512A = "gost3410-512A"
-       AlgoGOST3410512B = "gost3410-512B"
-       AlgoGOST3410512C = "gost3410-512C"
+       AlgoEd25519BLAKE2b = "ed25519-blake2b"
+       AlgoGOST3410256A   = "gost3410-256A"
+       AlgoGOST3410512C   = "gost3410-512C"
+       AlgoStreebog256    = "streebog256"
+       AlgoStreebog512    = "streebog512"
 )
 
 var HashToNew = map[string]func() hash.Hash{
@@ -38,7 +33,13 @@ type AV struct {
 func (av *AV) Id() (id uuid.UUID) {
        var hasher hash.Hash
        switch av.A {
-       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D, AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
+       case AlgoEd25519BLAKE2b:
+               var err error
+               hasher, err = blake2b.New256(nil)
+               if err != nil {
+                       panic(err)
+               }
+       case AlgoGOST3410256A, AlgoGOST3410512C:
                hasher = gost34112012256.New()
        default:
                panic("unsupported algorithm")
@@ -50,37 +51,3 @@ func (av *AV) Id() (id uuid.UUID) {
        }
        return id
 }
-
-func GOST3410CurveByName(name string) (curve *gost3410.Curve) {
-       switch name {
-       case AlgoGOST3410256A:
-               curve = gost3410.CurveIdtc26gost341012256paramSetA()
-       case AlgoGOST3410256B:
-               curve = gost3410.CurveIdtc26gost341012256paramSetB()
-       case AlgoGOST3410256C:
-               curve = gost3410.CurveIdtc26gost341012256paramSetC()
-       case AlgoGOST3410256D:
-               curve = gost3410.CurveIdtc26gost341012256paramSetD()
-       case AlgoGOST3410512A:
-               curve = gost3410.CurveIdtc26gost341012512paramSetA()
-       case AlgoGOST3410512B:
-               curve = gost3410.CurveIdtc26gost341012512paramSetB()
-       case AlgoGOST3410512C:
-               curve = gost3410.CurveIdtc26gost341012512paramSetC()
-       default:
-               log.Fatal("unknown curve")
-       }
-       return
-}
-
-func HasherByKeyAlgo(a string) hash.Hash {
-       switch a {
-       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D:
-               return gost34112012256.New()
-       case AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
-               return gost34112012512.New()
-       default:
-               log.Fatal("unsupported algorithm")
-       }
-       return nil
-}
index a1c7e2edb80c3030f717f9cb0103c40f2697b1b2816ec9af38238c0a2c44fd19..54f5cec6ca4e4427762c971605ffa09583b16e51a08ebfeae8ec94c80d12ce76 100644 (file)
@@ -4,11 +4,15 @@ import (
        "crypto"
        "errors"
        "fmt"
+       "hash"
        "time"
 
        "github.com/google/uuid"
        "go.cypherpunks.su/gogost/v6/gost3410"
+       "go.cypherpunks.su/gogost/v6/gost34112012256"
+       "go.cypherpunks.su/gogost/v6/gost34112012512"
        "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519"
        "go.cypherpunks.su/yac/gyac/yacpki/utils"
 )
 
@@ -130,13 +134,28 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
        }
        pub := cer.Pub[0]
        switch pub.A {
-       case AlgoGOST3410256A, AlgoGOST3410256B, AlgoGOST3410256C, AlgoGOST3410256D, AlgoGOST3410512A, AlgoGOST3410512B, AlgoGOST3410512C:
+       case AlgoEd25519BLAKE2b:
+               if len(pub.V) != ed25519.PublicKeySize {
+                       err = errors.New("invalid ed25519 public key size")
+                       return
+               }
+               if !ed25519.Verify(ed25519.PublicKey(pub.V), signed, signature) {
+                       err = errors.New("invalid ed25519 signature")
+                       return
+               }
+       case AlgoGOST3410256A, AlgoGOST3410512C:
                var pk *gost3410.PublicKey
                pk, err = gost3410.NewPublicKeyBE(GOST3410CurveByName(pub.A), pub.V)
                if err != nil {
                        return
                }
-               hasher := HasherByKeyAlgo(pub.A)
+               var hasher hash.Hash
+               switch pub.A {
+               case AlgoGOST3410256A:
+                       hasher = gost34112012256.New()
+               case AlgoGOST3410512C:
+                       hasher = gost34112012512.New()
+               }
                utils.MustWrite(hasher, signed)
                hsh := hasher.Sum(nil)
                var valid bool
index 0c936754a2f89ea900e48b2644433574d05482de3d811f17733643403f9d5d08..fccc5d33d452c11e2e9cccc8ef433c515cd0474a2639de0a531f2779f9f416f7 100755 (executable)
@@ -1,45 +1,52 @@
 #!/bin/sh
 
 testname=`basename "$0"`
-test_description="Check that basic functionality works"
+test_description="Check that basic GOST-related functionality works"
 . $SHARNESS_TEST_SRCDIR/sharness.sh
 
 TMPDIR=${TMPDIR:-/tmp}
 
+
+echo "gost3410-512C gost3410-256A
+ed25519-blake2b ed25519-blake2b" | while read caAlgo eeAlgo ; do
+
 subj="-subj CN=CA -subj C=RU"
-test_expect_success "CA generation" "yacertool \
-    -algo gost3410-512C \
+test_expect_success "$caAlgo: CA generation" "yacertool \
+    -algo $caAlgo \
     -ku ca -ku sig $subj \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer"
-test_expect_success "CA regeneration" "yacertool \
-    -algo gost3410-512C \
+test_expect_success "$caAlgo: CA regeneration" "yacertool \
+    -algo $caAlgo \
     -ku ca -ku sig $subj \
     -prv $TMPDIR/ca.prv -cer $TMPDIR/ca.cer \
     -reuse-key"
-test_expect_success "CA self-signature" "yacertool \
+test_expect_success "$caAlgo: CA self-signature" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
     -cer $TMPDIR/ca.cer \
     -verify"
 
 subj="-subj CN=SubCA -subj C=RU"
-test_expect_success "SubCA generation" "yacertool \
+test_expect_success "$eeAlgo: SubCA generation" "yacertool \
+    -algo $eeAlgo \
     -ku ca -ku sig $subj \
     -prv $TMPDIR/subca.prv -cer $TMPDIR/subca.cer \
     -ca-cer $TMPDIR/ca.cer -ca-prv $TMPDIR/ca.prv"
-test_expect_success "SubCA signature" "yacertool \
+test_expect_success "$eeAlgo: SubCA signature" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
     -cer $TMPDIR/subca.cer \
     -verify"
 
 subj="-subj CN=EE -subj C=RU"
-test_expect_success "EE generation" "yacertool \
-    -algo gost3410-256A $subj \
+test_expect_success "$eeAlgo: EE generation" "yacertool \
+    -algo $eeAlgo $subj \
     -ca-prv $TMPDIR/subca.prv -ca-cer $TMPDIR/subca.cer \
     -prv $TMPDIR/ee.prv -cer $TMPDIR/ee.cer"
-test_expect_success "EE chain" "yacertool \
+test_expect_success "$eeAlgo: EE chain" "yacertool \
     -ca-cer $TMPDIR/ca.cer \
     -ca-cer $TMPDIR/subca.cer \
     -cer $TMPDIR/ee.cer \
     -verify"
 
+done
+
 test_done
index a32fb3cacb88649f92d40511c650e24c8a2ccd669e91a7527e703054e1ff2648..0546c7b0e99253e3af6c9fbc277c20f24dbddd8f21fbacfafa423626e59426b5 100644 (file)
@@ -14,6 +14,7 @@ import (
        "go.cypherpunks.su/gogost/v6/gost3410"
        "go.cypherpunks.su/yac/gyac"
        "go.cypherpunks.su/yac/gyac/yacpki"
+       "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519"
        "go.cypherpunks.su/yac/gyac/yacpki/utils"
 )
 
@@ -85,7 +86,7 @@ func main() {
        }
        till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
 
-       var caPrv *gost3410.PrivateKey
+       var caPrv crypto.Signer
        var caCers []*yacpki.SignedData
        for _, issuingCer := range issuingCers {
                var sd *yacpki.SignedData
@@ -99,12 +100,10 @@ func main() {
                if *issuingPrv == "" {
                        log.Fatal("no -ca-key is set")
                }
-               var signer crypto.Signer
-               signer, err = yacpki.PrvParse(utils.MustReadFile(*issuingPrv))
+               caPrv, err = yacpki.PrvParse(utils.MustReadFile(*issuingPrv))
                if err != nil {
                        log.Fatal(err)
                }
-               caPrv = signer.(*gost3410.PrivateKey)
        }
 
        if *verify {
@@ -124,45 +123,80 @@ func main() {
                log.Fatal("no -prv is set")
        }
 
-       curve := yacpki.GOST3410CurveByName(*algo)
-       if curve == nil {
-               log.Fatal("unknown -algo specified")
-       }
-       var prv *gost3410.PrivateKey
-       if *reuseKey {
-               var signer crypto.Signer
-               signer, err = yacpki.PrvParse(utils.MustReadFile(*prvPath))
-               if err != nil {
-                       log.Fatal(err)
-               }
-               prv = signer.(*gost3410.PrivateKey)
-               if prv.C.Name != curve.Name {
-                       log.Fatal("-algo is not same with private key")
+       var prv crypto.Signer
+       var pubRaw []byte
+       switch *algo {
+       case yacpki.AlgoEd25519BLAKE2b:
+               if *reuseKey {
+                       prv, err = yacpki.PrvParse(utils.MustReadFile(*prvPath))
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       prvEd25519 := prv.(ed25519.PrivateKey)
+                       pubRaw = prvEd25519[ed25519.SeedSize:]
+               } else {
+                       var prvEd25519 ed25519.PrivateKey
+                       var pubEd25519 ed25519.PublicKey
+                       pubEd25519, prvEd25519, err = ed25519.GenerateKey(rand.Reader)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       prv = prvEd25519
+                       pubRaw = pubEd25519[:]
+                       err = os.WriteFile(*prvPath, gyac.EncodeItem(nil,
+                               gyac.ItemFromGo(yacpki.AV{A: *algo, V: prvEd25519.Seed()})), 0o600)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
                }
-       } else {
-               prvRaw := make([]byte, curve.PointSize())
-               if _, err = io.ReadFull(rand.Reader, prvRaw); err != nil {
-                       log.Fatal(err)
+       default: // GOST
+               curve := yacpki.GOST3410CurveByName(*algo)
+               if curve == nil {
+                       log.Fatal("unknown -algo specified")
                }
-               prv, err = gost3410.NewPrivateKeyBE(curve, prvRaw)
-               if err != nil {
-                       log.Fatal(err)
+               var signer *yacpki.GOSTSigner
+               if *reuseKey {
+                       prv, err = yacpki.PrvParse(utils.MustReadFile(*prvPath))
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       signer = prv.(*yacpki.GOSTSigner)
+                       if signer.Prv.C.Name != curve.Name {
+                               log.Fatal("-algo is not same with private key")
+                       }
+               } else {
+                       prvRaw := make([]byte, curve.PointSize())
+                       if _, err = io.ReadFull(rand.Reader, prvRaw); err != nil {
+                               log.Fatal(err)
+                       }
+                       var prvKey *gost3410.PrivateKey
+                       prvKey, err = gost3410.NewPrivateKeyBE(curve, prvRaw)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       raw := gyac.EncodeItem(nil,
+                               gyac.ItemFromGo(yacpki.AV{A: *algo, V: prvKey.RawBE()}))
+                       prv, err = yacpki.PrvParse(raw)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       err = os.WriteFile(*prvPath, raw, 0o600)
+                       if err != nil {
+                               log.Fatal(err)
+                       }
+                       signer = prv.(*yacpki.GOSTSigner)
                }
-               err = os.WriteFile(*prvPath, gyac.EncodeItem(nil,
-                       gyac.ItemFromGo(yacpki.AV{A: *algo, V: prv.RawBE()})), 0o600)
+               var pub *gost3410.PublicKey
+               pub, err = signer.Prv.PublicKey()
                if err != nil {
                        log.Fatal(err)
                }
+               pubRaw = pub.RawBE()
        }
 
-       var pub *gost3410.PublicKey
-       pub, err = prv.PublicKey()
-       if err != nil {
-               log.Fatal(err)
-       }
-       pubMap := yacpki.Pub{A: *algo, V: pub.RawBE()}
+       pubMap := yacpki.Pub{A: *algo, V: pubRaw}
        {
-               av := yacpki.AV{A: *algo, V: pub.RawBE()}
+               av := yacpki.AV{A: *algo, V: pubRaw}
                pubMap.Id = av.Id()
        }
        cerLoad := yacpki.CerLoad{Subj: subj, Pub: []yacpki.Pub{pubMap}}
diff --git a/gyac/yacpki/ed25519-blake2b/.gitignore b/gyac/yacpki/ed25519-blake2b/.gitignore
new file mode 100644 (file)
index 0000000..ca24282
--- /dev/null
@@ -0,0 +1,4 @@
+byteorder/
+ed25519/
+edwards25519/
+go.mod
diff --git a/gyac/yacpki/ed25519-blake2b/clean b/gyac/yacpki/ed25519-blake2b/clean
new file mode 100755 (executable)
index 0000000..24504f8
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh -e
+
+exec rm -rf byteorder ed25519 edwards25519 go.mod
diff --git a/gyac/yacpki/ed25519-blake2b/ed25519-to-blake2b.patch b/gyac/yacpki/ed25519-blake2b/ed25519-to-blake2b.patch
new file mode 100644 (file)
index 0000000..859d327
--- /dev/null
@@ -0,0 +1,153 @@
+--- ed25519/ed25519.go 2024-12-03 10:59:27.811011000 +0300
++++ ed25519/ed25519.go 2024-12-03 11:07:51.892841000 +0300
+@@ -20,11 +20,12 @@
+       "crypto"
+       "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/edwards25519"
+       cryptorand "crypto/rand"
+-      "crypto/sha512"
+       "crypto/subtle"
+       "errors"
+       "io"
+       "strconv"
++
++      "golang.org/x/crypto/blake2b"
+ )
+ const (
+@@ -81,13 +82,13 @@
+ // Sign signs the given message with priv. rand is ignored and can be nil.
+ //
+-// If opts.HashFunc() is [crypto.SHA512], the pre-hashed variant Ed25519ph is used
+-// and message is expected to be a SHA-512 hash, otherwise opts.HashFunc() must
++// If opts.HashFunc() is [crypto.BLAKE2b_512], the pre-hashed variant Ed25519ph is used
++// and message is expected to be a BLAKE2b-512 hash, otherwise opts.HashFunc() must
+ // be [crypto.Hash](0) and the message must not be hashed, as Ed25519 performs two
+ // passes over messages to be signed.
+ //
+ // A value of type [Options] can be used as opts, or crypto.Hash(0) or
+-// crypto.SHA512 directly to select plain Ed25519 or Ed25519ph, respectively.
++// crypto.BLAKE2b_512 directly to select plain Ed25519 or Ed25519ph, respectively.
+ func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
+       hash := opts.HashFunc()
+       context := ""
+@@ -95,8 +96,8 @@
+               context = opts.Context
+       }
+       switch {
+-      case hash == crypto.SHA512: // Ed25519ph
+-              if l := len(message); l != sha512.Size {
++      case hash == crypto.BLAKE2b_512: // Ed25519ph
++              if l := len(message); l != blake2b.Size {
+                       return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
+               }
+               if l := len(context); l > 255 {
+@@ -122,7 +123,7 @@
+ // Options can be used with [PrivateKey.Sign] or [VerifyWithOptions]
+ // to select Ed25519 variants.
+ type Options struct {
+-      // Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph.
++      // Hash can be zero for regular Ed25519, or crypto.BLAKE2b_512 for Ed25519ph.
+       Hash crypto.Hash
+       // Context, if not empty, selects Ed25519ctx or provides the context string
+@@ -171,7 +172,7 @@
+               panic("ed25519: bad seed length: " + strconv.Itoa(l))
+       }
+-      h := sha512.Sum512(seed)
++      h := blake2b.Sum512(seed)
+       s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
+       if err != nil {
+               panic("ed25519: internal error: setting scalar failed")
+@@ -213,14 +214,17 @@
+       }
+       seed, publicKey := privateKey[:SeedSize], privateKey[SeedSize:]
+-      h := sha512.Sum512(seed)
++      h := blake2b.Sum512(seed)
+       s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
+       if err != nil {
+               panic("ed25519: internal error: setting scalar failed")
+       }
+       prefix := h[32:]
+-      mh := sha512.New()
++      mh, err := blake2b.New512(nil)
++      if err != nil {
++              panic(err)
++      }
+       if domPrefix != domPrefixPure {
+               mh.Write([]byte(domPrefix))
+               mh.Write([]byte{byte(len(context))})
+@@ -228,7 +232,7 @@
+       }
+       mh.Write(prefix)
+       mh.Write(message)
+-      messageDigest := make([]byte, 0, sha512.Size)
++      messageDigest := make([]byte, 0, blake2b.Size)
+       messageDigest = mh.Sum(messageDigest)
+       r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
+       if err != nil {
+@@ -237,7 +241,10 @@
+       R := (&edwards25519.Point{}).ScalarBaseMult(r)
+-      kh := sha512.New()
++      kh, err := blake2b.New512(nil)
++      if err != nil {
++              panic(err)
++      }
+       if domPrefix != domPrefixPure {
+               kh.Write([]byte(domPrefix))
+               kh.Write([]byte{byte(len(context))})
+@@ -246,7 +253,7 @@
+       kh.Write(R.Bytes())
+       kh.Write(publicKey)
+       kh.Write(message)
+-      hramDigest := make([]byte, 0, sha512.Size)
++      hramDigest := make([]byte, 0, blake2b.Size)
+       hramDigest = kh.Sum(hramDigest)
+       k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
+       if err != nil {
+@@ -272,7 +279,7 @@
+ // publicKey. A valid signature is indicated by returning a nil error. It will
+ // panic if len(publicKey) is not [PublicKeySize].
+ //
+-// If opts.Hash is [crypto.SHA512], the pre-hashed variant Ed25519ph is used and
++// If opts.Hash is [crypto.BLAKE2b_512], the pre-hashed variant Ed25519ph is used and
+ // message is expected to be a SHA-512 hash, otherwise opts.Hash must be
+ // [crypto.Hash](0) and the message must not be hashed, as Ed25519 performs two
+ // passes over messages to be signed.
+@@ -281,8 +288,8 @@
+ // channels, or if an attacker has control of part of the inputs.
+ func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error {
+       switch {
+-      case opts.Hash == crypto.SHA512: // Ed25519ph
+-              if l := len(message); l != sha512.Size {
++      case opts.Hash == crypto.BLAKE2b_512: // Ed25519ph
++              if l := len(message); l != blake2b.Size {
+                       return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l))
+               }
+               if l := len(opts.Context); l > 255 {
+@@ -324,7 +331,10 @@
+               return false
+       }
+-      kh := sha512.New()
++      kh, err := blake2b.New512(nil)
++      if err != nil {
++              panic(err)
++      }
+       if domPrefix != domPrefixPure {
+               kh.Write([]byte(domPrefix))
+               kh.Write([]byte{byte(len(context))})
+@@ -333,7 +343,7 @@
+       kh.Write(sig[:32])
+       kh.Write(publicKey)
+       kh.Write(message)
+-      hramDigest := make([]byte, 0, sha512.Size)
++      hramDigest := make([]byte, 0, blake2b.Size)
+       hramDigest = kh.Sum(hramDigest)
+       k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
+       if err != nil {
diff --git a/gyac/yacpki/ed25519-blake2b/mk-from-go b/gyac/yacpki/ed25519-blake2b/mk-from-go
new file mode 100755 (executable)
index 0000000..1ab8cce
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh -e
+# Unfortunately native crypto/ed25519 is not flexible enough and does
+# not give ability to use different hash in Ed25519.
+# That script copies the library (tested on 1.23.3) and patches it to
+# use BLAKE2b hash.
+
+modname=go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b
+go mod init $modname
+dst=$PWD
+cd $(go env GOROOT)/src
+cp -r crypto/ed25519 crypto/internal/edwards25519 internal/byteorder $dst
+cd $dst
+
+cd ed25519
+rm -r testdata
+rm *_test.go
+cd ..
+
+cd edwards25519
+rm doc.go *_test.go
+cd ..
+
+find . -name "*.go" -exec perl -i -npe 's#^(\s+)".*/?internal/(.*)"#$1"'$modname'/$2"#' {} +
+
+cd edwards25519/field/_asm
+go mod edit -module $modname/edwards25519/field/_asm
+perl -i -npe "s#crypto/internal#$modname#" fe_amd64_asm.go
+
+cd $dst
+patch <ed25519-to-blake2b.patch
diff --git a/gyac/yacpki/gost.go b/gyac/yacpki/gost.go
new file mode 100644 (file)
index 0000000..bf0b66b
--- /dev/null
@@ -0,0 +1,47 @@
+package yacpki
+
+import (
+       "crypto"
+       "hash"
+       "io"
+       "log"
+
+       "go.cypherpunks.su/gogost/v6/gost3410"
+)
+
+type GOSTSigner struct {
+       Prv    *gost3410.PrivateKey
+       hasher func() hash.Hash
+}
+
+func (s *GOSTSigner) Public() crypto.PublicKey {
+       return s.Prv.Public()
+}
+
+func (s *GOSTSigner) Sign(
+       rand io.Reader,
+       msg []byte,
+       opts crypto.SignerOpts,
+) (signature []byte, err error) {
+       h := s.hasher()
+       h.Write(msg)
+       dgst := h.Sum(nil)
+       signature, err = s.Prv.Sign(rand, dgst, opts)
+       if err != nil {
+               return
+       }
+       signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...)
+       return
+}
+
+func GOST3410CurveByName(name string) (curve *gost3410.Curve) {
+       switch name {
+       case AlgoGOST3410256A:
+               curve = gost3410.CurveIdtc26gost341012256paramSetA()
+       case AlgoGOST3410512C:
+               curve = gost3410.CurveIdtc26gost341012512paramSetC()
+       default:
+               log.Fatal("unknown curve")
+       }
+       return
+}
index 17d01790b6a7a1c3221866411c302fbed17881f358ea335ea233af4f8cbe73ce..d560f1d4d028fb40dd82f079079864b566343048f32bb8df0981d847df357ebb 100644 (file)
@@ -6,7 +6,10 @@ import (
        "fmt"
 
        "go.cypherpunks.su/gogost/v6/gost3410"
+       "go.cypherpunks.su/gogost/v6/gost34112012256"
+       "go.cypherpunks.su/gogost/v6/gost34112012512"
        "go.cypherpunks.su/yac/gyac"
+       "go.cypherpunks.su/yac/gyac/yacpki/ed25519-blake2b/ed25519"
 )
 
 func PrvParse(data []byte) (prv crypto.Signer, err error) {
@@ -21,34 +24,24 @@ func PrvParse(data []byte) (prv crypto.Signer, err error) {
                return
        }
        switch av.A {
+       case AlgoEd25519BLAKE2b:
+               prv = ed25519.NewKeyFromSeed(av.V)
+               if len(av.V) != ed25519.SeedSize {
+                       err = errors.New("wrong ed25519 private key size")
+                       return
+               }
        case AlgoGOST3410256A:
-               prv, err = gost3410.NewPrivateKeyBE(
+               signer := &GOSTSigner{hasher: gost34112012256.New}
+               signer.Prv, err = gost3410.NewPrivateKeyBE(
                        gost3410.CurveIdtc26gost341012256paramSetA(), av.V,
                )
-       case AlgoGOST3410256B:
-               prv, err = gost3410.NewPrivateKeyBE(
-                       gost3410.CurveIdtc26gost341012256paramSetB(), av.V,
-               )
-       case AlgoGOST3410256C:
-               prv, err = gost3410.NewPrivateKeyBE(
-                       gost3410.CurveIdtc26gost341012256paramSetC(), av.V,
-               )
-       case AlgoGOST3410256D:
-               prv, err = gost3410.NewPrivateKeyBE(
-                       gost3410.CurveIdtc26gost341012256paramSetD(), av.V,
-               )
-       case AlgoGOST3410512A:
-               prv, err = gost3410.NewPrivateKeyBE(
-                       gost3410.CurveIdtc26gost341012512paramSetA(), av.V,
-               )
-       case AlgoGOST3410512B:
-               prv, err = gost3410.NewPrivateKeyBE(
-                       gost3410.CurveIdtc26gost341012512paramSetB(), av.V,
-               )
+               prv = signer
        case AlgoGOST3410512C:
-               prv, err = gost3410.NewPrivateKeyBE(
+               signer := &GOSTSigner{hasher: gost34112012512.New}
+               signer.Prv, err = gost3410.NewPrivateKeyBE(
                        gost3410.CurveIdtc26gost341012512paramSetC(), av.V,
                )
+               prv = signer
        default:
                err = fmt.Errorf("unknown private key algo: %s", av.A)
        }
index 305c8b7e4e8e004425feefd1ddccd3b431c17720a1f8018e896c3f62e81864ee..e44075645547687c8a4cb216cdba3d21afaa35fe32aa1edcff9232c67260aa32 100644 (file)
@@ -8,7 +8,6 @@ import (
 
        "github.com/google/uuid"
        "go.cypherpunks.su/yac/gyac"
-       "go.cypherpunks.su/yac/gyac/yacpki/utils"
 )
 
 type SignedDataLoad struct {
@@ -123,18 +122,23 @@ func SignedDataParseItem(item *gyac.Item) (sd *SignedData, err error) {
        return
 }
 
-func (sd *SignedData) SignWith(parent *CerLoad, prv crypto.Signer, sigTBS SigTBS) error {
+func (sd *SignedData) SignWith(
+       parent *CerLoad,
+       prv crypto.Signer,
+       sigTBS SigTBS,
+) (err error) {
        if !parent.Can(KUSig) || len(parent.Pub) != 1 {
                return errors.New("parent can not sign")
        }
        sigTBS.SID = parent.Pub[0].Id
        sdTBS := SignedDataTBS{T: sd.Load.T, V: sd.Load.V, TBS: sigTBS}
        sig := Sig{TBS: sigTBS}
-       hasher := HasherByKeyAlgo(parent.Pub[0].A)
-       utils.MustWrite(hasher, gyac.EncodeItem(nil, gyac.ItemFromGo(sdTBS)))
        sig.Sign.A = parent.Pub[0].A
-       s, err := prv.Sign(rand.Reader, hasher.Sum(nil), nil)
-       sig.Sign.V = append(s[len(s)/2:], s[:len(s)/2]...)
+       sig.Sign.V, err = prv.Sign(
+               rand.Reader,
+               gyac.EncodeItem(nil, gyac.ItemFromGo(sdTBS)),
+               crypto.Hash(0),
+       )
        if err != nil {
                return err
        }
index 2632eff8b575b59ade40af1fbf2d04bc9d8ad35f311fa096addf46b53e134ccc..e6f7d312deea151853163284946684b38f96f0bc7092d3bd2061c31d09e057a7 100644 (file)
@@ -2,8 +2,9 @@
 @cindex cer
 @section cer format
 
-Certificate is the @ref{signed-data} structure. Its @code{/load/t}
-equals to @code{cer}. @code{/load/v} contains @code{cer-load}:
+Certificate is the @code{@ref{signed-data}} structure.
+Its @code{/load/t} equals to @code{cer}.
+@code{/load/v} contains @code{cer-load}:
 
 @verbatiminclude format/cer-load.cddl
 
@@ -91,6 +92,14 @@ Example minimal certificate may look like:
 @subsection cer with GOST R 34.10-2012
 
 Same rules of serialisation must be used as with
-@ref{signed-data-gost3410, signed-data-gost3410}. Public key's
-identifier and and @code{cid} should be calculated using Streebog-256
-hash.
+@code{@ref{signed-data-gost3410}}. Public key's
+identifier and and @code{cid} should be calculated
+using big-endian Streebog-256 hash.
+
+@node cer-ed25519-blake2b
+@subsection cer with Ed25519-BLAKE2b
+
+Same calculation and serialisation rules must be used as with
+@code{@ref{signed-data-ed25519-blake2b}}.
+Public key's identifier and and @code{cid} should be calculated
+using BLAKE2b hash with 128 or 256 bit output length specified.
index ad1618975c57352f320d166737755291131d8fa98c091c741e0a9f283d5d3b4e..9db082da6f917e82ef6f9bf713bd1aa2e7c2b37950ab7f985e35bf53cfd90534 100644 (file)
@@ -18,14 +18,54 @@ converted from BIN to BLOB.
 @code{/hash} contains the hash values for all corresponding @code{/a}
 algorithms.
 
+@node hashed-data-blake2b
+@subsection hashed-data with BLAKE2b
+
+@url{https://www.blake2.net/, BLAKE2b} with
+512-bit output has @code{blake2b} algorithm identifier.
+
+256-bit output has @code{blake2b256} algorithm identifier.
+
+@node hashed-data-blake3
+@subsection hashed-data with BLAKE3
+
+    @url{https://github.com/BLAKE3-team/BLAKE3/, BLAKE3} with fixed
+    256-bit output has @code{blake3} algorithm identifier.
+
+@node hashed-data-sha2
+@subsection hashed-data with SHA2
+
+    SHA2-256 has @code{sha2-256} algorithm identifier.
+
+    SHA2-512 has @code{sha2-512} algorithm identifier.
+
+@node hashed-data-shake
+@subsection hashed-data with SHAKE
+
+    @url{https://keccak.team/, SHAKE} XOF function with fixed
+    256 (SHAKE128) or 512 (SHAKE256) bit output.
+
+    Following algorithm identifiers are acceptable:
+    @code{shake128}, @code{shake256}.
+
+@node hashed-data-skein512
+@subsection hashed-data with Skein-512
+
+    512-bit @url{https://www.schneier.com/academic/skein/, Skein-512} hash.
+
+    @code{skein512} is acceptable algorithm identifier.
+
 @node hashed-data-gost3411
 @subsection hashed-data with GOST R 34.11-2012
 
-Streebog must be big-endian serialised.
+    Streebog must be big-endian serialised.
+
+    Following algorithm identifiers are acceptable:
+    @code{streebog256}, @code{streebog512}.
+
+@node hashed-data-xxh3-128
+@subsection hashed-data with XXH3-128
 
-Following algorithm identifiers are acceptable:
+    128-bit @url{https://xxhash.com/, XXH3} hash must be big-endian encoded.
 
-@verbatim
-streebog256
-streebog512
-@end verbatim
+    @code{xxh3-128} is acceptable algorithm identifier.
index 9ac3a7453b831acf1b52aefb39bda2ee44eba31c8b838430ce4c1763e4cf46a7..f68bd8db415819cf72149d5b4725d2dab66b5ea076151cfba770192a4ec5699e 100644 (file)
@@ -9,3 +9,4 @@ They are written in
 @include format/signed-data.texi
 @include format/cer.texi
 @include format/hashed-data.texi
+@include format/registry.texi
index 64199c5ca5443430157541fd7990506a98980e3a227d6e8faaf4bdebfb858cad..828c18ffacec1173ea029c23ceedec715fc6f9d19133f667dba491c5505e47b9 100644 (file)
@@ -12,13 +12,14 @@ Private key is stored in trivial map:
 Big-endian private key representation must be used.
 
 Following algorithm identifiers are acceptable:
+@code{gost3410-256A}, @code{gost3410-512C}.
 
-@verbatim
-gost3410-256A
-gost3410-256B
-gost3410-256C
-gost3410-256D
-gost3410-512A
-gost3410-512B
-gost3410-512C
-@end verbatim
+@node private-key-ed25519-blake2b
+@subsection private-key with Ed25519-BLAKE2b
+
+32-byte Ed25519 private key is used, as described in
+@url{https://datatracker.ietf.org/doc/html/rfc8032, EdDSA} RFC.
+In many libraries it is called "seed".
+
+@code{ed25519-blake2b} algorithm identifier is used, however actually no
+hash is involved in private key storage.
diff --git a/spec/format/registry.texi b/spec/format/registry.texi
new file mode 100644 (file)
index 0000000..b476b76
--- /dev/null
@@ -0,0 +1,66 @@
+@node Registry
+@cindex Registry
+@section AI registry
+
+There is example registry of known algorithm identifiers.
+
+@node AI Hashes
+@subsection Hashes
+
+@table @code
+@item blake2b, blake2b256
+    @code{@ref{cer-ed25519-blake2b}},
+    @code{@ref{hashed-data-blake2b}},
+    @code{@ref{signed-data-ed25519-blake2b}}
+@item blake3
+    @code{@ref{hashed-data-blake3}}
+@item sha2-256, sha2-512
+    @code{@ref{hashed-data-sha2}}
+@item shake128, shake256
+    @code{@ref{hashed-data-shake}}
+@item skein512
+    @code{@ref{hashed-data-skein512}}
+@item streebog256, streebog512
+    @code{@ref{cer-gost3410}},
+    @code{@ref{hashed-data-gost3411}},
+    @code{@ref{signed-data-gost3410}}
+@item xxh3-128
+    @code{@ref{hashed-data-xxh3-128}}
+@end table
+
+@node AI DH
+@subsection DH
+
+@table @code
+@item ecdsa-nist256p, ecdsa-nist521p
+@item x25519
+@item x448
+@item gost3410-256A, gost3410-512C
+    @code{@ref{cer-gost3410}},
+    @code{@ref{private-key-gost3410}}
+@end table
+
+@node AI Sign
+@subsection Signatures
+
+@table @code
+@item ecdsa-nist256p, ecdsa-nist521p
+@item ed25519-blake2b
+    @code{@ref{private-key-ed25519-blake2b}}
+    @code{@ref{signed-data-ed25519-blake2b}},
+    @code{@ref{cer-ed25519-blake2b}}
+@item ed448
+@item gost3410-256A, gost3410-512C
+    @code{@ref{cer-gost3410}},
+    @code{@ref{private-key-gost3410}},
+    @code{@ref{signed-data-gost3410}}
+@end table
+
+@node AI Content types
+@subsection Content types
+
+@itemize
+@item @ref{cer, @code{cer}}
+@item @ref{signed-data, @code{data}}
+@item @ref{private-key, @code{prv}}
+@end itemize
index f9f52d97e18d7801c89c7f6199e9d0f292c6fd943b23d3f9bb2ee292e8380309..de102e60753e13fb2b7849a004b6743529aedce8abb1fcab443b149547a784d4 100644 (file)
@@ -38,9 +38,20 @@ function. Its digest must be big-endian serialised. Public key must be
 in @code{BE(X)||BE(Y)} format. Signature is in @code{BE(R)||BE(S)}
 format.
 
-Following algorithm identifiers are acceptable for the hash:
+Following algorithm identifiers should be used for the hash:
+@code{streebog256}, @code{streebog512}.
 
-@verbatim
-streebog256
-streebog512
-@end verbatim
+Following algorithm identifiers are acceptable for the public key and
+signature: @code{gost3410-256A}, @code{gost3410-512C}.
+
+@node signed-data-ed25519-blake2b
+@subsection signed-data with Ed25519-BLAKE2b
+
+@url{https://datatracker.ietf.org/doc/html/rfc8032, EdDSA} with
+Edwards25519 is used similarly as in RFC 8032.
+But BLAKE2b is used instead of SHA2-512 hash.
+
+Strict @url{https://zips.z.cash/zip-0215, ZIP-0215} validation rules
+should be used while verifying the signature.
+
+@code{ed25519-blake2b} algorithm identifier is used.
index 0bbaf282a5d53295b3441233c28d2ec51cb72b5f966f8175b8606a14961f0f9e..d471df641089c04399c82bbbe069fe988607eb31a7dad5354f00065fd80a795e 100644 (file)
@@ -5,6 +5,8 @@
 Copyright @copyright{} 2024-2025 @email{stargrave@@stargrave.org, Sergey Matveev}
 @end copying
 
+@firstparagraphindent insert
+
 @node Top
 @top YAC
 
@@ -125,7 +127,6 @@ and won't be able to interpret/validate them.
 @include encoding/index.texi
 @include schema.texi
 @include format/index.texi
-@include registry.texi
 
 @node Concepts Index
 @unnumbered Concepts Index
diff --git a/spec/registry.texi b/spec/registry.texi
deleted file mode 100644 (file)
index 77586e5..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-@node Registry
-@cindex Registry
-@unnumbered AI registry
-
-There is example registry of known algorithm identifiers.
-
-@node AI Hashes
-@section Hashes
-
-@verbatim
-blake2b256
-blake2b512
-blake3-256
-sha2-224
-sha2-256
-sha2-384
-sha2-512
-sha3-224
-sha3-256
-sha3-384
-sha3-512
-shake128
-shake256
-skein512
-streebog256
-streebog512
-xxh3-128
-@end verbatim
-
-@node AI DH
-@section DH
-
-@verbatim
-ecdsa-nist192p
-ecdsa-nist224p
-ecdsa-nist256p
-ecdsa-nist384p
-ecdsa-nist521p
-gost3410-256A
-gost3410-256B
-gost3410-256C
-gost3410-256D
-gost3410-512A
-gost3410-512B
-gost3410-512C
-x25519
-x448
-@end verbatim
-
-@node AI Sign
-@section Signatures
-
-@verbatim
-ecdsa-nist192p
-ecdsa-nist224p
-ecdsa-nist256p
-ecdsa-nist384p
-ecdsa-nist521p
-ed25519
-ed448
-gost3410-256A
-gost3410-256B
-gost3410-256C
-gost3410-256D
-gost3410-512A
-gost3410-512B
-gost3410-512C
-@end verbatim
-
-@node AI Content types
-@section Content types
-
-@verbatim
-cer
-data
-prv
-@end verbatim