]> Cypherpunks repositories - keks.git/commitdiff
Properly prehashed signatures
authorSergey Matveev <stargrave@stargrave.org>
Fri, 24 Jan 2025 08:55:22 +0000 (11:55 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Sun, 26 Jan 2025 14:04:11 +0000 (17:04 +0300)
24 files changed:
go/pki/algo.go
go/pki/cer.go
go/pki/cmd/certool/main.go
go/pki/cmd/enctool/chapoly.go
go/pki/cmd/enctool/main.go
go/pki/cmd/enctool/usage.go
go/pki/cmd/sigtool/basic.t [new file with mode: 0755]
go/pki/cmd/sigtool/main.go
go/pki/ed25519-blake2b/algo.go
go/pki/ed25519-blake2b/kp.go
go/pki/ed25519-blake2b/signer.go [moved from go/pki/ed25519-blake2b/prv.go with 60% similarity]
go/pki/ed25519-blake2b/verify.go
go/pki/gost/gost.go
go/pki/gost/kp.go
go/pki/gost/signer.go
go/pki/gost/verify.go
go/pki/hash/algo.go
go/pki/prv.go
go/pki/sign/iface.go [new file with mode: 0644]
go/pki/sign/mode.go [new file with mode: 0644]
go/pki/signed.go
spec/format/cer.texi
spec/format/signed-tbs.cddl [deleted file]
spec/format/signed.texi

index 695aae9e366a6695015b627f98055dcdd29cc21c51b91c61580141560406da33..ec129e0e4e4a95c0cb2a2371ec7ed56d8794ee91b9da326ea34a0827e0c96a26 100644 (file)
@@ -9,6 +9,7 @@ import (
 
 const (
        Ed25519BLAKE2b                 = ed25519blake2b.Ed25519BLAKE2b
+       Ed25519PhBLAKE2b               = ed25519blake2b.Ed25519PhBLAKE2b
        GOST3410256A                   = gost.GOST3410256A
        GOST3410512C                   = gost.GOST3410512C
        SNTRUP4591761X25519            = sntrup4591761x25519.SNTRUP4591761X25519
index e5d38a00e378d67da4253a520fd5857ef682fddf4ce944f53732bb0d93b37a6b..e6a349184d32f89431cd12a5ee4aa20aef4146af601ded542e510b9981151a5a 100644 (file)
@@ -16,7 +16,7 @@
 package pki
 
 import (
-       "crypto"
+       "bytes"
        "errors"
        "fmt"
        "hash"
@@ -27,6 +27,7 @@ import (
        "go.cypherpunks.su/keks"
        ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
        "go.cypherpunks.su/keks/pki/gost"
+       "go.cypherpunks.su/keks/pki/sign"
 )
 
 const (
@@ -36,6 +37,11 @@ const (
        CerMagic = keks.Magic("pki/cer")
 )
 
+var (
+       ErrSigInvalid = errors.New("signature is invalid")
+       ErrBadSigAlgo = errors.New("bad signature algo")
+)
+
 // Public key.
 type Pub struct {
        A  string    `keks:"a"`
@@ -145,7 +151,7 @@ func (cer *CerLoad) Can(ku string) (yes bool) {
 // generated UUIDv7. since and till times must not have nanoseconds part.
 func (signed *Signed) CerIssueWith(
        parent *CerLoad,
-       prv crypto.Signer,
+       prv sign.Iface,
        since, till time.Time,
 ) error {
        exp := []time.Time{since, till}
@@ -156,11 +162,9 @@ func (signed *Signed) CerIssueWith(
        return signed.SignWith(parent, prv, SigTBS{CID: &cid, Exp: &exp})
 }
 
-var ErrSigInvalid = errors.New("signature is invalid")
-
 // Verify signature of signed data. ErrSigInvalid will be returned in
 // case of invalid signature.
-func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
+func (cer *CerLoad) CheckSignature(algo string, signed, signature []byte) (err error) {
        if !cer.Can(KUSig) || len(cer.Pub) != 1 {
                err = errors.New("cer can not sign")
                return
@@ -169,11 +173,17 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
        var valid bool
        switch pub.A {
        case Ed25519BLAKE2b:
+               if algo != Ed25519BLAKE2b {
+                       return ErrBadSigAlgo
+               }
                valid, err = ed25519blake2b.Verify(pub.V, signed, signature)
                if !valid {
                        err = ErrSigInvalid
                }
        case GOST3410256A, GOST3410512C:
+               if algo != pub.A {
+                       return ErrBadSigAlgo
+               }
                valid, err = gost.Verify(pub.A, pub.V, signed, signature)
                if !valid {
                        err = ErrSigInvalid
@@ -186,7 +196,10 @@ func (cer *CerLoad) CheckSignature(signed, signature []byte) (err error) {
 
 // Verify signature of signed data, by providing prehashed data.
 // ErrSigInvalid will be returned in case of invalid signature.
-func (cer *CerLoad) CheckSignatureDigest(digest, signature []byte) (err error) {
+func (cer *CerLoad) CheckSignaturePrehash(
+       algo string,
+       prehash, signature []byte,
+) (err error) {
        if !cer.Can(KUSig) || len(cer.Pub) != 1 {
                err = errors.New("cer can not sign")
                return
@@ -195,12 +208,18 @@ func (cer *CerLoad) CheckSignatureDigest(digest, signature []byte) (err error) {
        var valid bool
        switch pub.A {
        case Ed25519BLAKE2b:
-               valid, err = ed25519blake2b.VerifyDigest(pub.V, digest, signature)
+               if algo != Ed25519PhBLAKE2b {
+                       return ErrBadSigAlgo
+               }
+               valid, err = ed25519blake2b.VerifyPrehash(pub.V, prehash, signature)
                if !valid {
                        err = ErrSigInvalid
                }
        case GOST3410256A, GOST3410512C:
-               valid, err = gost.VerifyDigest(pub.A, pub.V, digest, signature)
+               if algo != pub.A {
+                       return ErrBadSigAlgo
+               }
+               valid, err = gost.VerifyPrehash(pub.A, pub.V, prehash, signature)
                if !valid {
                        err = ErrSigInvalid
                }
@@ -211,11 +230,11 @@ func (cer *CerLoad) CheckSignatureDigest(digest, signature []byte) (err error) {
 }
 
 // Verify Signed CerLoad certificate's signature with provided parent.
-// If hasher is specified, then prehashed signature mode is used.
+// If prehasher is specified, then prehashed signature mode is used.
 // Currently only single signature can be verified.
 func (signed *Signed) CerCheckSignatureFrom(
        parent *CerLoad,
-       hasher *hash.Hash,
+       prehasher *hash.Hash,
 ) (err error) {
        if len(signed.Sigs) != 1 {
                err = errors.New("can verify only single signature")
@@ -230,22 +249,23 @@ func (signed *Signed) CerCheckSignatureFrom(
                err = errors.New("sid != parent pub id")
                return
        }
-       var tbs SignedTBS
-       if hasher == nil {
-               tbs = SignedTBS{T: signed.Load.T, V: signed.Load.V, TBS: sig.TBS}
-               var buf []byte
-               buf, err = keks.EncodeBuf(tbs, nil)
-               if err != nil {
+       if prehasher == nil {
+               var tbs bytes.Buffer
+               if _, err = keks.Encode(&tbs, signed.Load, nil); err != nil {
+                       return
+               }
+               if _, err = keks.Encode(&tbs, sig.TBS, nil); err != nil {
                        return
                }
-               return parent.CheckSignature(buf, sig.Sign.V)
+               return parent.CheckSignature(sig.Sign.A, tbs.Bytes(), sig.Sign.V)
        } else {
-               tbs = SignedTBS{T: signed.Load.T, TBS: sig.TBS}
-               _, err = keks.Encode(*hasher, tbs, nil)
-               if err != nil {
+               if _, err = keks.Encode(*prehasher, signed.Load, nil); err != nil {
+                       return
+               }
+               if _, err = keks.Encode(*prehasher, sig.TBS, nil); err != nil {
                        return
                }
-               return parent.CheckSignatureDigest((*hasher).Sum(nil), sig.Sign.V)
+               return parent.CheckSignaturePrehash(sig.Sign.A, (*prehasher).Sum(nil), sig.Sign.V)
        }
 }
 
index 2b84822a2013ce1d212a6fd8058960552175f9d5c26fbf78aa281b29b4fbe31d..c437c73dc415d40260c2f141d9669239c059a0396fb83db0063c6901c8c5130d 100644 (file)
@@ -17,7 +17,6 @@ package main
 
 import (
        "bytes"
-       "crypto"
        "errors"
        "flag"
        "fmt"
@@ -31,6 +30,7 @@ import (
        "go.cypherpunks.su/keks/pki"
        ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
        "go.cypherpunks.su/keks/pki/gost"
+       "go.cypherpunks.su/keks/pki/sign"
        sntrup4591761x25519 "go.cypherpunks.su/keks/pki/sntrup4591761-x25519"
        "go.cypherpunks.su/keks/pki/utils"
 )
@@ -113,7 +113,7 @@ func main() {
        }
        till := since.Add(time.Duration(*lifetime) * 24 * time.Hour)
 
-       var caPrv crypto.Signer
+       var caPrv sign.Iface
        var caCers []*pki.Signed
        for _, issuingCer := range issuingCers {
                var signed *pki.Signed
index 7c745abca8d7a6c1a02d6bd0b4ab7e82677b6b2d702c9e0b2f7ca7d951320120..a8aa95582df473e91759ebbdefeb0628f935d365c4471c612108daeb11fb666e 100644 (file)
@@ -22,6 +22,7 @@ import (
        "io"
        "os"
 
+       "go.cypherpunks.su/keks"
        "golang.org/x/crypto/chacha20poly1305"
 )
 
@@ -47,6 +48,12 @@ func demChaPolySeal(cek []byte) error {
        out := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen+ciph.Overhead())
        br := bufio.NewReaderSize(os.Stdin, ChaPolyChunkLen)
        bw := bufio.NewWriterSize(os.Stdout, len(out))
+       pr, pw := io.Pipe()
+       blobErr := make(chan error)
+       go func() {
+               _, e := keks.BlobEncode(bw, int64(len(out)), pr)
+               blobErr <- e
+       }()
        nonce := make([]byte, ciph.NonceSize())
        var n int
        var eof bool
@@ -62,11 +69,17 @@ func demChaPolySeal(cek []byte) error {
                if n != ChaPolyChunkLen {
                        nonce[len(nonce)-1] = 0x01
                }
-               _, err = bw.Write(ciph.Seal(out[:0], nonce, in[:len(ChaPolyPad)+n], nil))
+               _, err = pw.Write(ciph.Seal(out[:0], nonce, in[:len(ChaPolyPad)+n], nil))
                if err != nil {
                        return err
                }
        }
+       if err = pw.Close(); err != nil {
+               return err
+       }
+       if err = <-blobErr; err != nil {
+               return err
+       }
        return bw.Flush()
 }
 
@@ -76,15 +89,43 @@ func demChaPolyOpen(cek []byte) error {
                return err
        }
        in := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen+ciph.Overhead())
+       pr, pw := io.Pipe()
+       blobErr := make(chan error)
+       go func() {
+               d, e := keks.NewBlobDecoder(
+                       bufio.NewReaderSize(os.Stdin, len(in)),
+                       int64(len(in)),
+               )
+               defer func() {
+                       pw.Close()
+                       blobErr <- e
+               }()
+               if e != nil {
+                       return
+               }
+               var chunk []byte
+               for {
+                       chunk, e = d.Next()
+                       if e != nil {
+                               if e == io.EOF {
+                                       e = nil
+                                       break
+                               }
+                               return
+                       }
+                       if _, e = pw.Write(chunk); e != nil {
+                               return
+                       }
+               }
+       }()
        out := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen)
        var n int
        var eof bool
        var chunk []byte
-       br := bufio.NewReaderSize(os.Stdin, len(in))
        bw := bufio.NewWriterSize(os.Stdout, ChaPolyChunkLen)
        nonce := make([]byte, ciph.NonceSize())
        for !eof {
-               n, err = io.ReadFull(br, in)
+               n, err = io.ReadFull(pr, in)
                if err != nil {
                        if err != io.ErrUnexpectedEOF {
                                return err
@@ -106,6 +147,9 @@ func demChaPolyOpen(cek []byte) error {
                        return err
                }
        }
+       if err = <-blobErr; err != nil {
+               return err
+       }
        return bw.Flush()
 }
 
index 2e85721bfb5ddb7c34845579a35c8f27df1434a0e696d2be8246511078bb0f1a..25a2b31c1c8836a862dff19cb3cf4894dd4219b6072b6382fb08a125c8ab4285 100644 (file)
@@ -21,6 +21,7 @@ import (
        "crypto/rand"
        "errors"
        "flag"
+       "fmt"
        "hash"
        "io"
        "log"
@@ -32,6 +33,7 @@ import (
        "golang.org/x/crypto/blake2b"
        "golang.org/x/crypto/chacha20poly1305"
        "golang.org/x/crypto/hkdf"
+       "golang.org/x/term"
 
        "go.cypherpunks.su/keks"
        "go.cypherpunks.su/keks/pki"
@@ -84,12 +86,33 @@ func blake2b256() hash.Hash {
        return h
 }
 
+func readPasswd(prompt string) (passwd []byte) {
+       tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer tty.Close()
+       tty.WriteString(prompt)
+       passwd, err = term.ReadPassword(int(tty.Fd()))
+       if err != nil {
+               log.Fatalln(err)
+       }
+       // taken from age/cmd/age/tui.go:clearLine
+       const (
+               CUI = "\033["   // Control Sequence Introducer
+               CPL = CUI + "F" // Cursor Previous Line
+               EL  = CUI + "K" // Erase in Line
+       )
+       fmt.Fprintf(tty, "\r\n"+CPL+EL)
+       return
+}
+
 func main() {
        log.SetFlags(log.Lshortfile)
        flag.Usage = usage
        setBind := flag.String("bind", "", "Set that /bind instead of autogeneration")
        includeTo := flag.Bool("include-to", false, `Include "to" field in KEMs`)
-       passwd := flag.String("passwd", "", "Passphrase")
+       passphrase := flag.Bool("p", false, "Use passphrase")
        balloonS := flag.Int("balloon-s", 1<<16, "Balloon's space cost")
        balloonT := flag.Int("balloon-t", 3, "Balloon's time cost")
        balloonP := flag.Int("balloon-p", 2, "Balloon's number of threads")
@@ -167,7 +190,7 @@ func main() {
                for kemIdx, kem := range encrypted.KEM {
                        switch kem.A {
                        case pki.BalloonBLAKE2bHKDF:
-                               if *passwd == "" {
+                               if !*passphrase {
                                        log.Println(kemIdx, kem.A, "skipping because no -passwd")
                                        continue
                                }
@@ -177,16 +200,17 @@ func main() {
                                if kem.Cost == nil {
                                        log.Fatalln("missing cost")
                                }
+                               passwd := readPasswd("Passphrase:")
                                {
                                        kek := hkdf.Extract(blake2b256, balloon.H(blake2b256,
-                                               []byte(*passwd),
+                                               passwd,
                                                append(encrypted.Bind[:], *kem.Salt...),
                                                int(kem.Cost.S), int(kem.Cost.T), int(kem.Cost.P),
                                        ), []byte(BalloonHKDFSalt))
                                        var cekp []byte
                                        cekp, err = kemChaPolyOpen(kek, kem.CEK, chacha20poly1305.KeySize)
                                        if err != nil {
-                                               log.Println(kemIdx, kem.A, err, "skipping")
+                                               log.Println(kemIdx, kem.A, err, "skipping")
                                                continue
                                        }
                                        cek = cekp
@@ -250,7 +274,7 @@ func main() {
                                                var cekp []byte
                                                cekp, err = kemChaPolyOpen(kek, kem.CEK, chacha20poly1305.KeySize)
                                                if err != nil {
-                                                       log.Println(kemIdx, kem.A, err, "skipping")
+                                                       log.Println(kemIdx, kem.A, err, "skipping")
                                                        continue
                                                }
                                                cek = cekp
@@ -287,7 +311,14 @@ func main() {
                if err != nil {
                        log.Fatal(err)
                }
-               if *passwd != "" {
+               if *passphrase {
+                       passwd := readPasswd("Passphrase:")
+                       {
+                               confirm := readPasswd("Confirm:")
+                               if !bytes.Equal(passwd, confirm) {
+                                       log.Fatal("passphrases do not match")
+                               }
+                       }
                        salt := make([]byte, BalloonSaltLen)
                        if _, err = io.ReadFull(rand.Reader, salt); err != nil {
                                log.Fatal(err)
@@ -303,7 +334,7 @@ func main() {
                        }
                        {
                                kek := hkdf.Extract(blake2b256, balloon.H(blake2b256,
-                                       []byte(*passwd),
+                                       passwd,
                                        append(binding[:], salt...),
                                        *balloonS, *balloonT, *balloonP,
                                ), []byte(BalloonHKDFSalt))
index b9744cd726f7ef6215e73812bd351cd49fec2a0ad7ca3a5c20e1857658452eea..360254e8d76001ea46795fdd5869c9a67cd2323512f9f9751f7b3a6c3ee46b7b 100644 (file)
@@ -26,10 +26,10 @@ func usage() {
   Encrypt to recipient:
     enctool -cer CER [-include-to] [-bind UUID] <DATA >DATA.encrypted
   Encrypt on passphrase:
-    enctool -passwd PASSPHRASE [-bind UUID] <DATA >DATA.encrypted
+    enctool -p [-bind UUID] <DATA >DATA.encrypted
       [-balloon-s X] [-balloon-t X] [-balloon-p X]
   Decrypt by providing possible KEMs:
-    enctool -d [-passwd ...] [-prv PRV ...] <DATA.encrypted >DATA
+    enctool -d [-p] [-prv PRV ...] <DATA.encrypted >DATA
 
 `)
        flag.PrintDefaults()
diff --git a/go/pki/cmd/sigtool/basic.t b/go/pki/cmd/sigtool/basic.t
new file mode 100755 (executable)
index 0000000..399c049
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description="TODO"
+. $SHARNESS_TEST_SRCDIR/sharness.sh
+
+TMPDIR=${TMPDIR:-/tmp}
+
+echo "gost3410-512C
+gost3410-256A
+ed25519-blake2b" | while read algo ; do
+
+subj="-subj what=ever"
+typ="some-different-type"
+test_expect_success "$algo: cer generation" "certool \
+    -algo $algo -ku sig $subj \
+    -prv $TMPDIR/sign.prv -cer $TMPDIR/sign.cer"
+dd if=/dev/urandom of=$TMPDIR/sign.data bs=300K count=1 2>/dev/null
+bind="-encrypted-binding $(uuidgen)"
+badBind="-encrypted-binding $(uuidgen)"
+
+test_expect_success "$algo: signing" "sigtool \
+    -prv $TMPDIR/sign.prv -cer $TMPDIR/sign.cer -type $typ \
+    $bind <$TMPDIR/sign.data >$TMPDIR/sign.sig"
+test_expect_success "$algo: verifying" "sigtool \
+    -verify -cer $TMPDIR/sign.cer -type $typ \
+        <$TMPDIR/sign.sig >$TMPDIR/sign.data.got"
+test_expect_success "$algo: comparing" \
+    "test_cmp $TMPDIR/sign.data $TMPDIR/sign.data.got"
+test_expect_success "$algo: differing type" "! sigtool \
+    -verify -cer $TMPDIR/sign.cer <$TMPDIR/sign.sig >/dev/null"
+test_expect_success "$algo: good bind" "! sigtool \
+    -verify -cer $TMPDIR/sign.cer $bind <$TMPDIR/sign.sig >/dev/null"
+test_expect_success "$algo: bad bind" "! sigtool \
+    -verify -cer $TMPDIR/sign.cer $badBind <$TMPDIR/sign.sig >/dev/null"
+
+test_expect_success "$algo: detached signing" "sigtool -detached \
+    -prv $TMPDIR/sign.prv -cer $TMPDIR/sign.cer -type $typ \
+        <$TMPDIR/sign.data >$TMPDIR/sign.sig"
+test_expect_success "$algo: detached verifying" \
+    "cat $TMPDIR/sign.sig $TMPDIR/sign.data |
+        sigtool -detached -verify -cer $TMPDIR/sign.cer -type $typ"
+test_expect_success "$algo: differing type" "! sigtool -detached \
+    -verify -cer $TMPDIR/sign.cer <$TMPDIR/sign.sig >/dev/null"
+test_expect_success "$algo: good bind" "! sigtool -detached \
+    -verify -cer $TMPDIR/sign.cer $bind <$TMPDIR/sign.sig >/dev/null"
+test_expect_success "$algo: bad bind" "! sigtool -detached \
+    -verify -cer $TMPDIR/sign.cer $badBind <$TMPDIR/sign.sig >/dev/null"
+
+done
+
+test_done
index 614536eca44bfd185ef9f08661c306357e429d71100ffb4dcdd0c1988d4361fe..c8077e296ea30ad1b4a468bce2dc81ff6f8c059da52908b2dd1e3c56d1fcafa8 100644 (file)
@@ -30,6 +30,7 @@ import (
        "go.cypherpunks.su/keks"
        "go.cypherpunks.su/keks/pki"
        pkihash "go.cypherpunks.su/keks/pki/hash"
+       "go.cypherpunks.su/keks/pki/sign"
        "go.cypherpunks.su/keks/pki/utils"
        "go.cypherpunks.su/keks/types"
 )
@@ -66,19 +67,6 @@ func main() {
                log.Fatal(err)
        }
 
-       var signer pki.Signer
-       var hasher hash.Hash
-       if !*verify {
-               if *prvPath == "" {
-                       log.Fatal("no -prv is set")
-               }
-               signer, _, err = pki.PrvParse(utils.MustReadFile(*prvPath))
-               if err != nil {
-                       log.Fatal(err)
-               }
-               hasher = signer.NewHasher()
-       }
-
        stdin := bufio.NewReaderSize(os.Stdin, BlobChunkLen)
        if *verify {
                decoder := keks.NewDecoderFromReader(stdin, nil)
@@ -97,7 +85,8 @@ func main() {
                var prehash pki.SignedPrehash
                var signed pki.Signed
                err = decoder.UnmarshalStruct(&prehash)
-               if err == nil && prehash.T == pki.SignedPrehashT {
+               var hasher hash.Hash
+               if err == nil && prehash.T == sign.PrehashT {
                        if len(prehash.Sigs) == 0 {
                                log.Fatal("prehash: no sigs")
                        }
@@ -107,6 +96,9 @@ func main() {
                        for algo := range prehash.Sigs {
                                hasher = pkihash.ByName(algo)
                        }
+                       if hasher == nil {
+                               log.Fatal("prehash: unsupported algorithm")
+                       }
                        var blob *keks.BlobDecoder
                        blob, err = keks.NewBlobDecoder(stdin, 1<<32)
                        if err != nil {
@@ -145,9 +137,6 @@ func main() {
                }
                sig := signed.Sigs[0]
                signer := cer.CerLoad()
-               if sig.Sign.A != signer.Pub[0].A {
-                       log.Fatal("differing signature algorithms")
-               }
                if signed.Load.T != *typ {
                        log.Fatalln("differing load type:", signed.Load.T)
                }
@@ -169,17 +158,29 @@ func main() {
                        log.Fatal(err)
                }
        } else {
+               if *prvPath == "" {
+                       log.Fatal("no -prv is set")
+               }
+               var signer sign.Iface
+               signer, _, err = pki.PrvParse(utils.MustReadFile(*prvPath))
+               if err != nil {
+                       log.Fatal(err)
+               }
+               if err = signer.SetMode(sign.ModePrehash); err != nil {
+                       log.Fatal(err)
+               }
+
                if _, err = keks.Encode(os.Stdout, pki.SignedMagic, nil); err != nil {
                        log.Fatal(err)
                }
                if *detached {
-                       if _, err = io.Copy(hasher, stdin); err != nil {
+                       if _, err = io.Copy(*signer.Prehasher(), stdin); err != nil {
                                log.Fatal(err)
                        }
                } else {
                        if _, err = keks.Encode(os.Stdout, pki.SignedPrehash{
-                               T:    pki.SignedPrehashT,
-                               Sigs: map[string]*struct{}{cer.CerLoad().Pub[0].A: nil},
+                               T:    sign.PrehashT,
+                               Sigs: map[string]*struct{}{signer.Algo(): nil},
                        }, nil); err != nil {
                                log.Fatal(err)
                        }
@@ -187,7 +188,7 @@ func main() {
                        br := keks.BlobReader{R: pr, ChunkLen: BlobChunkLen}
                        copyErr := make(chan error)
                        go func() {
-                               _, e := io.Copy(io.MultiWriter(pw, hasher), stdin)
+                               _, e := io.Copy(io.MultiWriter(pw, *signer.Prehasher()), stdin)
                                pw.Close()
                                copyErr <- e
                        }()
@@ -201,14 +202,12 @@ func main() {
                }
                var signed pki.Signed
                signed.Load.T = *typ
-               when := time.Now().UTC().Truncate(1000 * time.Microsecond)
+               when := time.Now().UTC().Truncate(time.Millisecond)
                sigTbs := pki.SigTBS{When: &when}
                if encryptedBinding != uuid.Nil {
                        sigTbs.EncryptedBinding = &encryptedBinding
                }
-               if err = signed.SignPrehashedWith(
-                       hasher, cer.CerLoad(), signer, sigTbs,
-               ); err != nil {
+               if err = signed.SignWith(cer.CerLoad(), signer, sigTbs); err != nil {
                        log.Fatal(err)
                }
                if _, err = keks.Encode(os.Stdout, signed, nil); err != nil {
index 2bacaf6662f92ca0cf48736f4ba376032cb3a3a9de93ab3d0513be0134502ca9..f3a68bc0b80ca928401b34e6f962c0879a34973faf32756e97dccc38a777b765 100644 (file)
@@ -1,3 +1,6 @@
 package ed25519blake2b
 
-const Ed25519BLAKE2b = "ed25519-blake2b"
+const (
+       Ed25519BLAKE2b   = "ed25519-blake2b"
+       Ed25519PhBLAKE2b = "ed25519ph-blake2b"
+)
index 13aa582baad1887558bdf7f877088d1f44de71092ec8bb8033e5f30ec4430a34..12a51fa6af7102cfe153ba18b235dce20a9ccfab26930bb1a8a1bea66703c9fe 100644 (file)
@@ -28,7 +28,7 @@ func NewKeypair() (signer *Signer, prv, pub []byte, err error) {
        if err != nil {
                return
        }
-       signer = &Signer{prvEd}
+       signer = &Signer{Prv: prvEd}
        prv = prvEd.Seed()
        pub = pubEd[:]
        return
similarity index 60%
rename from go/pki/ed25519-blake2b/prv.go
rename to go/pki/ed25519-blake2b/signer.go
index d9c62d861859f6dabaaada6d9053c0d2d9f46f3225496d708539dc1375f2b995..d2da760d500e24a5d762b0a8c8d98f46aff4aa1c06c4aab6ad08668172a33068 100644 (file)
@@ -23,10 +23,46 @@ import (
 
        "go.cypherpunks.su/keks/pki/ed25519-blake2b/ed25519"
        "golang.org/x/crypto/blake2b"
+
+       "go.cypherpunks.su/keks/pki/sign"
 )
 
 type Signer struct {
-       Prv ed25519.PrivateKey
+       Prv       ed25519.PrivateKey
+       mode      sign.Mode
+       prehasher *hash.Hash
+}
+
+func (s *Signer) SetMode(m sign.Mode) error {
+       switch m {
+       case sign.ModePure:
+               s.mode = m
+               return nil
+       case sign.ModePrehash:
+               s.mode = m
+               h, err := blake2b.New512(nil)
+               if err != nil {
+                       return err
+               }
+               s.prehasher = &h
+               return nil
+       default:
+               return errors.New("unsupported mode")
+       }
+}
+
+func (s *Signer) Mode() sign.Mode {
+       return s.mode
+}
+
+func (s *Signer) Algo() string {
+       switch s.mode {
+       case sign.ModePure:
+               return Ed25519BLAKE2b
+       case sign.ModePrehash:
+               return Ed25519PhBLAKE2b
+       }
+       return ""
 }
 
 func (s *Signer) Public() crypto.PublicKey {
@@ -38,19 +74,18 @@ func (s *Signer) Sign(
        msg []byte,
        opts crypto.SignerOpts,
 ) (signature []byte, err error) {
-       return s.Prv.Sign(rand, msg, opts)
-}
-
-func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err error) {
-       return s.Prv.Sign(rand, dgst, &ed25519.Options{Hash: crypto.BLAKE2b_512})
+       switch s.mode {
+       case sign.ModePure:
+               return s.Prv.Sign(rand, msg, opts)
+       case sign.ModePrehash:
+               return s.Prv.Sign(rand, msg, &ed25519.Options{Hash: crypto.BLAKE2b_512})
+       default:
+               panic("unsupported mode")
+       }
 }
 
-func (s *Signer) NewHasher() hash.Hash {
-       h, err := blake2b.New512(nil)
-       if err != nil {
-               panic(err)
-       }
-       return h
+func (s *Signer) Prehasher() *hash.Hash {
+       return s.prehasher
 }
 
 func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
@@ -60,6 +95,6 @@ func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
        }
        p := ed25519.NewKeyFromSeed(v)
        pub = p[ed25519.SeedSize:]
-       prv = &Signer{p}
+       prv = &Signer{Prv: p}
        return
 }
index 662c7f4b6e67df2b505933cc4597ce659f19d6adfcc72807ea294f13a8cba645..e7d1ef51f27eaa1825b426e2e787b0f00c1129b35833d1134b3915540125717b 100644 (file)
@@ -31,14 +31,14 @@ func Verify(pub, signed, signature []byte) (valid bool, err error) {
        return
 }
 
-func VerifyDigest(pub, digest, signature []byte) (valid bool, err error) {
+func VerifyPrehash(pub, hsh, signature []byte) (valid bool, err error) {
        if len(pub) != ed25519.PublicKeySize {
                err = errors.New("invalid ed25519 public key size")
                return
        }
        err = ed25519.VerifyWithOptions(
                ed25519.PublicKey(pub),
-               digest,
+               hsh,
                signature,
                &ed25519.Options{Hash: crypto.BLAKE2b_512},
        )
index bffbd6289f9438102c339c85bc81853fb5156e76ee91bae40d15aad44134ed60..dbd743d5baac03dae4a3d53e889e8373aee09cf3f935a40ee00a54cca35d580d 100644 (file)
@@ -1,3 +1,18 @@
+// GoKEKS/PKI -- PKI-related capabilities based on KEKS encoded formats
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
 package gost
 
 import (
index b89c25563a0612a54fde4c0cb3b00076beb4f1e051f0f22a70a087fd31d569cd..59db966a4eea82fae37154cccca97ca3de2d835b20a841a01f6bd95cfa5739f3 100644 (file)
@@ -1,3 +1,18 @@
+// GoKEKS/PKI -- PKI-related capabilities based on KEKS encoded formats
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
 package gost
 
 import (
@@ -24,6 +39,6 @@ func NewKeypair(algo string) (signer *Signer, prv, pub []byte, err error) {
        if err != nil {
                return
        }
-       signer, pub, err = NewSigner(algo, pk.RawBE())
+       signer, pub, err = NewSigner(pk.RawBE())
        return
 }
index b8696314098c25c616392893c277a458aa76851d5a26d5e40e755e51084c45ff..547b44ed683cb5db24688280c782bf867e21dd47db699ac4e6537d5e35921174 100644 (file)
@@ -1,3 +1,18 @@
+// GoKEKS/PKI -- PKI-related capabilities based on KEKS encoded formats
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
 package gost
 
 import (
@@ -9,11 +24,48 @@ import (
        "go.cypherpunks.su/gogost/v6/gost3410"
        "go.cypherpunks.su/gogost/v6/gost34112012256"
        "go.cypherpunks.su/gogost/v6/gost34112012512"
+
+       "go.cypherpunks.su/keks/pki/sign"
 )
 
 type Signer struct {
-       Prv    *gost3410.PrivateKey
-       Hasher func() hash.Hash
+       mode      sign.Mode
+       Prv       *gost3410.PrivateKey
+       NewHasher func() hash.Hash
+       prehasher *hash.Hash
+}
+
+func (s *Signer) SetMode(m sign.Mode) error {
+       switch m {
+       case sign.ModePure:
+               s.mode = m
+               return nil
+       case sign.ModePrehash:
+               s.mode = m
+               p := s.NewHasher()
+               s.prehasher = &p
+               return nil
+       default:
+               return errors.New("unsupported mode")
+       }
+}
+
+func (s *Signer) Mode() sign.Mode {
+       return s.mode
+}
+
+func (s *Signer) Prehasher() *hash.Hash {
+       return s.prehasher
+}
+
+func (s *Signer) Algo() string {
+       switch s.Prv.C.PointSize() {
+       case 32:
+               return GOST3410256A
+       case 64:
+               return GOST3410512C
+       }
+       return ""
 }
 
 func (s *Signer) Public() crypto.PublicKey {
@@ -25,19 +77,18 @@ func (s *Signer) Sign(
        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
+       var hsh []byte
+       switch s.mode {
+       case sign.ModePure:
+               h := s.NewHasher()
+               h.Write(msg)
+               hsh = h.Sum(nil)
+       case sign.ModePrehash:
+               hsh = msg
+       default:
+               panic("unsupported mode")
        }
-       signature = append(signature[len(signature)/2:], signature[:len(signature)/2]...)
-       return
-}
-
-func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err error) {
-       signature, err = s.Prv.SignDigest(dgst, rand)
+       signature, err = s.Prv.SignDigest(hsh, rand)
        if err != nil {
                return
        }
@@ -45,20 +96,16 @@ func (s *Signer) SignDigest(rand io.Reader, dgst []byte) (signature []byte, err
        return
 }
 
-func (s *Signer) NewHasher() hash.Hash {
-       return s.Hasher()
-}
-
-func NewSigner(a string, v []byte) (prv *Signer, pub []byte, err error) {
+func NewSigner(v []byte) (prv *Signer, pub []byte, err error) {
        signer := Signer{}
-       switch a {
-       case GOST3410256A:
-               signer.Hasher = gost34112012256.New
+       switch len(v) {
+       case 32:
+               signer.NewHasher = gost34112012256.New
                signer.Prv, err = gost3410.NewPrivateKeyBE(
                        gost3410.CurveIdtc26gost341012256paramSetA(), v,
                )
-       case GOST3410512C:
-               signer.Hasher = gost34112012512.New
+       case 64:
+               signer.NewHasher = gost34112012512.New
                signer.Prv, err = gost3410.NewPrivateKeyBE(
                        gost3410.CurveIdtc26gost341012512paramSetC(), v,
                )
index 6046b02be82f7aba429a0ebd2726ffd764e1c62f4e7f8869aa8a244b8d7fa1e6..f914899526121df4a11b7d1f80362c4b9f358c5e3aa0ac214486b663737d9c6e 100644 (file)
@@ -1,3 +1,18 @@
+// GoKEKS/PKI -- PKI-related capabilities based on KEKS encoded formats
+// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
+//
+// 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 <http://www.gnu.org/licenses/>.
+
 package gost
 
 import (
@@ -29,13 +44,13 @@ func Verify(algo string, pub, signed, signature []byte) (valid bool, err error)
        return
 }
 
-func VerifyDigest(algo string, pub, digest, signature []byte) (valid bool, err error) {
+func VerifyPrehash(algo string, pub, hsh, signature []byte) (valid bool, err error) {
        var pk *gost3410.PublicKey
        pk, err = gost3410.NewPublicKeyBE(CurveByName(algo), pub)
        if err != nil {
                return
        }
-       valid, err = pk.VerifyDigest(digest,
+       valid, err = pk.VerifyDigest(hsh,
                append(signature[len(signature)/2:], signature[:len(signature)/2]...))
        return
 }
index fdde23bc15243715e4d91db15204f52e156e393a1ed490a65ac64d280ee60fba..5350d321e3e3a0aef1b84f0bb6da167c4d901e87b209d53f5216fe4d06cf142b 100644 (file)
@@ -39,7 +39,7 @@ func ByName(name string) hash.Hash {
                return gost34112012256.New()
        case Streebog512, gost.GOST3410512C:
                return gost34112012512.New()
-       case BLAKE2b, ed25519blake2b.Ed25519BLAKE2b:
+       case BLAKE2b, ed25519blake2b.Ed25519BLAKE2b, ed25519blake2b.Ed25519PhBLAKE2b:
                h, err := blake2b.New512(nil)
                if err != nil {
                        panic(err)
index 94acb28c22bb20ef5edeb8ff2a08a9a687bb3e1539b3cc2ce69993cea98b12db..fe9b9baed2a4288e7edb1d685eef4ef5456d5f23398bade4258dde0b9e091197 100644 (file)
@@ -22,10 +22,11 @@ import (
        "go.cypherpunks.su/keks"
        ed25519blake2b "go.cypherpunks.su/keks/pki/ed25519-blake2b"
        "go.cypherpunks.su/keks/pki/gost"
+       "go.cypherpunks.su/keks/pki/sign"
 )
 
 // Parse private key contained in AV KEKS-encoded structure.
-func PrvParse(data []byte) (prv Signer, pub []byte, err error) {
+func PrvParse(data []byte) (prv sign.Iface, pub []byte, err error) {
        {
                var magic keks.Magic
                magic, data = keks.StripMagic(data)
@@ -47,7 +48,7 @@ func PrvParse(data []byte) (prv Signer, pub []byte, err error) {
        case Ed25519BLAKE2b:
                prv, pub, err = ed25519blake2b.NewSigner(av.V)
        case GOST3410256A, GOST3410512C:
-               prv, pub, err = gost.NewSigner(av.A, av.V)
+               prv, pub, err = gost.NewSigner(av.V)
        default:
                err = fmt.Errorf("unknown private key algo: %s", av.A)
        }
diff --git a/go/pki/sign/iface.go b/go/pki/sign/iface.go
new file mode 100644 (file)
index 0000000..b8b5ca1
--- /dev/null
@@ -0,0 +1,14 @@
+package sign
+
+import (
+       "crypto"
+       "hash"
+)
+
+type Iface interface {
+       crypto.Signer
+       SetMode(Mode) error
+       Prehasher() *hash.Hash
+       Algo() string
+       Mode() Mode
+}
diff --git a/go/pki/sign/mode.go b/go/pki/sign/mode.go
new file mode 100644 (file)
index 0000000..773a4a3
--- /dev/null
@@ -0,0 +1,10 @@
+package sign
+
+type Mode int
+
+const (
+       ModePure    Mode = 0
+       ModePrehash      = iota
+
+       PrehashT = "prehash"
+)
index 922579f22192ba78a4638cfa4d06cc76eb013891b2953eca1771a829a4dc40cb..d6d070e3b956e8860841e7a26dfc17bef2c9d0ac14a3691c6b813a1a21406cd4 100644 (file)
 package pki
 
 import (
+       "bytes"
        "crypto"
        "crypto/rand"
        "errors"
-       "hash"
-       "io"
        "time"
 
        "github.com/google/uuid"
 
        "go.cypherpunks.su/keks"
+       "go.cypherpunks.su/keks/pki/sign"
 )
 
 const SignedMagic = keks.Magic("pki/signed")
 
-type Signer interface {
-       crypto.Signer
-       SignDigest(io.Reader, []byte) ([]byte, error)
-       NewHasher() hash.Hash
-}
-
-const SignedPrehashT = "prehash"
-
 type SignedPrehash struct {
        T    string               `keks:"t"`
        Sigs map[string]*struct{} `keks:"sigs"`
@@ -63,12 +55,6 @@ type Sig struct {
        Sign   AV        `keks:"sign"`
 }
 
-type SignedTBS struct {
-       V   any    `keks:"v,omitempty"`
-       T   string `keks:"t"`
-       TBS SigTBS `keks:"tbs"`
-}
-
 type Signed struct {
        Cers *[]*Signed `keks:"certs,omitempty"`
        Load SignedLoad `keks:"load"`
@@ -133,50 +119,33 @@ func SignedParse(data []byte) (*Signed, error) {
 // Sign Signed's contents and sigTBS corresponding data with the
 // provided prv signer, having parent certificate. Signature is appended
 // to the signed.Sigs. parent certificate must have "sig" key-usage.
-func (signed *Signed) SignWith(
-       parent *CerLoad,
-       prv crypto.Signer,
-       sigTBS SigTBS,
-) (err error) {
+func (signed *Signed) SignWith(parent *CerLoad, prv sign.Iface, 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
-       signedTBS := SignedTBS{T: signed.Load.T, V: signed.Load.V, TBS: sigTBS}
-       sig := Sig{TBS: sigTBS}
-       sig.Sign.A = parent.Pub[0].A
-       var buf []byte
-       buf, err = keks.EncodeBuf(signedTBS, nil)
-       if err != nil {
-               return
-       }
-       sig.Sign.V, err = prv.Sign(rand.Reader, buf, crypto.Hash(0))
-       if err != nil {
-               return
-       }
-       signed.Sigs = append(signed.Sigs, &sig)
-       return nil
-}
-
-// Same as SignWith, but use prehashed mode.
-func (signed *Signed) SignPrehashedWith(
-       hasher hash.Hash,
-       parent *CerLoad,
-       prv Signer,
-       sigTBS SigTBS,
-) (err error) {
-       if !parent.Can(KUSig) || len(parent.Pub) != 1 {
-               return errors.New("parent can not sign")
+       var tbs []byte
+       if prv.Mode() == sign.ModePure {
+               var b bytes.Buffer
+               if _, err = keks.Encode(&b, signed.Load, nil); err != nil {
+                       return
+               }
+               if _, err = keks.Encode(&b, sigTBS, nil); err != nil {
+                       return
+               }
+               tbs = b.Bytes()
+       } else {
+               if _, err = keks.Encode(*prv.Prehasher(), signed.Load, nil); err != nil {
+                       return
+               }
+               if _, err = keks.Encode(*prv.Prehasher(), sigTBS, nil); err != nil {
+                       return
+               }
+               tbs = (*prv.Prehasher()).Sum(nil)
        }
-       sigTBS.SID = parent.Pub[0].Id
-       signedTBS := SignedTBS{T: signed.Load.T, TBS: sigTBS}
        sig := Sig{TBS: sigTBS}
-       sig.Sign.A = parent.Pub[0].A
-       _, err = keks.Encode(hasher, signedTBS, nil)
-       if err != nil {
-               return
-       }
-       sig.Sign.V, err = prv.SignDigest(rand.Reader, hasher.Sum(nil))
+       sig.Sign.A = prv.Algo()
+       sig.Sign.V, err = prv.Sign(rand.Reader, tbs, crypto.Hash(0))
        if err != nil {
                return
        }
index b180ae01d70f82be6138e746ad72a440e5229cdac28a326cd0ae7051b5faee9c..d63ef010a0b0a90d5dce3f8d7c82794f8c34fb99237370ab8b5e1e317df6678a 100644 (file)
@@ -97,10 +97,15 @@ Example minimal certificate may look like:
 @node cer-gost3410
 @subsection cer with GOST R 34.10-2012
 
-Same rules of serialisation must be used as with
-@code{@ref{pki-signed-gost3410}}. Public key's
-identifier and @code{cid} should be calculated
-using big-endian Streebog-256 hash.
+GOST R 34.10-2012 must be used with Streebog (GOST R 34.11-2012) hash
+function. Its digest must be big-endian serialised. Public key must be
+in @code{BE(X)||BE(Y)} format.
+
+Algorithm identifiers for the public key: @code{gost3410-256A},
+@code{gost3410-512C}.
+
+Public key's identifier and @code{cid} should be calculated using
+big-endian Streebog-256 hash.
 
 @node cer-ed25519-blake2b
 @subsection cer with Ed25519-BLAKE2b
@@ -109,6 +114,7 @@ Same calculation and serialisation rules must be used as with
 @code{@ref{pki-signed-ed25519-blake2b}}.
 Public key's identifier and @code{cid} should be calculated
 using BLAKE2b hash with 128 or 256 bit output length specified.
+Algorithm identifiers for the public key: @code{ed25519ph-blake2b},
 
 @node cer-sntrup4591761-x25519
 @subsection cer with SNTRUP4591761-X25519
diff --git a/spec/format/signed-tbs.cddl b/spec/format/signed-tbs.cddl
deleted file mode 100644 (file)
index 94e8a04..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-pki-signed-tbs = {
-    t: text, ; = /load/t
-    ? v: any, ; /load/v if it is supplied
-    tbs: map, ; = /sigs/?/tbs
-}
index 52f413fffa5b6fb00b16d54629115bf55d9be18d06729d36dc96dee84d528e1a..e02409d005a8a64ad0a28faf9c54cbd8f07800905e8557ccea7f7669c2f31614 100644 (file)
@@ -10,9 +10,11 @@ unless it is a @ref{cer, certificate}.
 
 @verbatiminclude format/signed.cddl
 
-Signature is created by signing the following encoded MAP:
+Signature is created by signing the:
 
-@verbatiminclude format/signed-tbs.cddl
+@verbatim
+[detached-data] || /load || sig-tbs
+@end verbatim
 
 If no @code{/load/v} is provided, then the data is detached from the
 @code{pki-signed} structure itself and it is fed into hasher before that
@@ -21,15 +23,14 @@ detached data closely to the @code{pki-signed}, you should use the
 following approach:
 
 @verbatim
-pki-signed-prehash || BLOB || pki-signed
+pki-signed-prehash || BLOB(detached-data) || pki-signed
 @end verbatim
 
 @verbatiminclude format/signed-prehash.cddl
 
 With @code{pki-signed-prehash} you initialise your hashers used during
 signing process and fed BLOB's contents (not the encoded BLOB itself!)
-into the them. Finally you add @code{pki-signed-tbs} without @code{v}
-field to them.
+into the them.
 
 @code{/sigs/*/tbs/when} is optional signing time.
 
@@ -49,12 +50,11 @@ then @code{/sigs/*/tbs/encrypted-binding} should be set to
 @subsection pki-signed with GOST R 34.10-2012
 
 GOST R 34.10-2012 must be used with Streebog (GOST R 34.11-2012) hash
-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.
+function. Its digest must be big-endian serialised. Signature is in
+@code{BE(R)||BE(S)} format.
 
-Following algorithm identifiers are acceptable for the public key and
-signature: @code{gost3410-256A}, @code{gost3410-512C}.
+Algorithm identifiers for the signature: @code{gost3410-256A},
+@code{gost3410-512C}.
 
 @node pki-signed-ed25519-blake2b
 @subsection pki-signed with Ed25519-BLAKE2b
@@ -66,7 +66,8 @@ 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.
 
-PureEdDSA @strong{must} be used when no detached data exists.
-HashEdDSA @strong{must} be used otherwise, using BLAKE2b-512 as a hash.
+PureEdDSA @strong{must} be used when no detached data exists and
+@code{ed25519-blake2b} algorithm identifier is used for signature.
 
-@code{ed25519-blake2b} algorithm identifier is used.
+HashEdDSA @strong{must} be used otherwise, using BLAKE2b-512 as a hash,
+using @code{ed25519ph-blake2b} algorithm identifier for signature.