]> Cypherpunks repositories - keks.git/commitdiff
Parallelised ChaPoly
authorSergey Matveev <stargrave@stargrave.org>
Fri, 14 Feb 2025 13:16:31 +0000 (16:16 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Fri, 14 Feb 2025 13:16:31 +0000 (16:16 +0300)
go/atom-encode.go
go/blob.go
go/cm/cmd/enctool/chapoly.go [deleted file]
go/cm/cmd/enctool/main.go
go/cm/enc/chapoly/dem/dem.go [new file with mode: 0644]
go/cm/enc/chapoly/kem/kem.go [new file with mode: 0644]

index 25a013964beab56aa5d6cc969180be62e1cc39d9f7c6e73820f0e5f43b666bdb..59206b26e7a025f66879ff639a993579c8635b62d1dad3314284d36efee114c7 100644 (file)
@@ -128,16 +128,19 @@ func BigIntEncode(w io.Writer, v *big.Int) (written int64, err error) {
 }
 
 // Write an encoded BLOB atom.
+func BlobAtomEncode(w io.Writer, chunkLen int64) (written int64, err error) {
+       l := make([]byte, 9)
+       l[0] = byte(AtomBLOB)
+       be.Put(l[1:], uint64(chunkLen-1))
+       return io.Copy(w, bytes.NewReader(l))
+}
+
+// Write an encoded BLOB.
 func BlobEncode(w io.Writer, chunkLen int64, r io.Reader) (written int64, err error) {
        if chunkLen == 0 {
                return 0, errors.New("zero chunkLen specified")
        }
-       {
-               l := make([]byte, 9)
-               l[0] = byte(AtomBLOB)
-               be.Put(l[1:], uint64(chunkLen-1))
-               written, err = io.Copy(w, bytes.NewReader(l))
-       }
+       written, err = BlobAtomEncode(w, chunkLen)
        if err != nil {
                return
        }
index 017624b181f072dd48cc806cb62e7c3fd2ea0e30d65c78a931bbeba48c830ba9..8f1c71c8bad8f6471001398bee786b4a9d1395629fc486e3b7a03f475af592a9 100644 (file)
@@ -101,6 +101,7 @@ func (d *BlobDecoder) Next() (chunk []byte, err error) {
        d.d.deTail()
        d.d.strs = d.d.strs[:len(d.d.strs)-1]
        chunk = unsafe.Slice(unsafe.StringData(s), len(s))
+       d.d.readBuf = d.d.readBuf[:max(d.ChunkLen, 8)]
        if int64(len(s)) == d.ChunkLen {
                return chunk, nil
        }
diff --git a/go/cm/cmd/enctool/chapoly.go b/go/cm/cmd/enctool/chapoly.go
deleted file mode 100644 (file)
index 5ebe5d3..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-// enctool -- dealing with KEKS-encoded cm-encrypted utility
-// 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 main
-
-import (
-       "bufio"
-       "crypto/subtle"
-       "errors"
-       "io"
-       "os"
-
-       "go.cypherpunks.su/keks"
-       "golang.org/x/crypto/chacha20poly1305"
-)
-
-const ChaPolyChunkLen = 128 * 1024
-
-var ChaPolyPad = make([]byte, 32)
-
-func incr(data []byte) {
-       for i := len(data) - 1; i >= 0; i-- {
-               data[i]++
-               if data[i] != 0 {
-                       return
-               }
-       }
-}
-
-func demChaPolySeal(cek []byte) error {
-       if len(cek) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize {
-               return errors.New("wrong CEK len")
-       }
-       key, iv := cek[:chacha20poly1305.KeySize], cek[chacha20poly1305.KeySize:]
-       ciph, err := chacha20poly1305.New(key)
-       if err != nil {
-               return err
-       }
-       in := make([]byte, len(ChaPolyPad)+ChaPolyChunkLen)
-       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
-       }()
-       ctr := make([]byte, ciph.NonceSize())
-       nonce := make([]byte, ciph.NonceSize())
-       var n int
-       var eof bool
-       for !eof {
-               n, err = io.ReadFull(br, in[len(ChaPolyPad):])
-               if err != nil {
-                       if err != io.ErrUnexpectedEOF {
-                               return err
-                       }
-                       eof = true
-               }
-               incr(ctr[:len(ctr)-1])
-               if n != ChaPolyChunkLen {
-                       ctr[len(ctr)-1] = 0x01
-               }
-               subtle.XORBytes(nonce, ctr, iv)
-               _, 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()
-}
-
-func demChaPolyOpen(cek []byte) error {
-       if len(cek) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize {
-               return errors.New("wrong CEK len")
-       }
-       key, iv := cek[:chacha20poly1305.KeySize], cek[chacha20poly1305.KeySize:]
-       ciph, err := chacha20poly1305.New(key)
-       if err != nil {
-               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
-       bw := bufio.NewWriterSize(os.Stdout, ChaPolyChunkLen)
-       ctr := make([]byte, ciph.NonceSize())
-       nonce := make([]byte, ciph.NonceSize())
-       for !eof {
-               n, err = io.ReadFull(pr, in)
-               if err != nil {
-                       if err != io.ErrUnexpectedEOF {
-                               return err
-                       }
-                       eof = true
-               }
-               incr(ctr[:len(ctr)-1])
-               if n != len(in) {
-                       ctr[len(ctr)-1] = 0x01
-               }
-               subtle.XORBytes(nonce, ctr, iv)
-               chunk, err = ciph.Open(out[:0], nonce, in[:n], nil)
-               if err != nil {
-                       return err
-               }
-               if subtle.ConstantTimeCompare(chunk[:len(ChaPolyPad)], ChaPolyPad) != 1 {
-                       return errors.New("bad pad")
-               }
-               if _, err = bw.Write(chunk[len(ChaPolyPad):]); err != nil {
-                       return err
-               }
-       }
-       if err = <-blobErr; err != nil {
-               return err
-       }
-       return bw.Flush()
-}
-
-func kemChaPolySeal(kek, cek []byte) ([]byte, error) {
-       ciph, err := chacha20poly1305.New(kek)
-       if err != nil {
-               return nil, err
-       }
-       nonce := make([]byte, ciph.NonceSize())
-       return ciph.Seal(nil, nonce, cek, nil), nil
-}
-
-func kemChaPolyOpen(kek, ciphertext []byte, cekLenExpected int) ([]byte, error) {
-       ciph, err := chacha20poly1305.New(kek)
-       if err != nil {
-               return nil, err
-       }
-       nonce := make([]byte, ciph.NonceSize())
-       var cek []byte
-       cek, err = ciph.Open(nil, nonce, ciphertext, nil)
-       if err != nil {
-               return nil, err
-       }
-       if len(cek) != cekLenExpected {
-               return nil, errors.New("invalid CEK len")
-       }
-       return cek, nil
-}
index 42d79a3f1ccec908bdf096f8a7a7a5d9f1acc3130d6d731fedf0a325c294e1f3..c067f9c669492afdd5dbb15113b1a85933670178763c5c2e1e967b5557008cf9 100644 (file)
@@ -27,6 +27,7 @@ import (
        "io"
        "log"
        "os"
+       "runtime"
 
        "github.com/companyzero/sntrup4591761"
        "github.com/google/uuid"
@@ -38,6 +39,8 @@ import (
        "go.cypherpunks.su/keks"
        "go.cypherpunks.su/keks/cm"
        cmenc "go.cypherpunks.su/keks/cm/enc"
+       chaPolyDEM "go.cypherpunks.su/keks/cm/enc/chapoly/dem"
+       chaPolyKEM "go.cypherpunks.su/keks/cm/enc/chapoly/kem"
        mceliece6960119x25519 "go.cypherpunks.su/keks/cm/enc/mceliece6960119-x25519"
        mceliece6960119 "go.cypherpunks.su/keks/cm/enc/mceliece6960119-x25519/mceliece6960119"
        sntrup4591761x25519 "go.cypherpunks.su/keks/cm/enc/sntrup4591761-x25519"
@@ -96,6 +99,7 @@ func main() {
        balloonT := flag.Int("balloon-t", 3, "Balloon's time cost")
        balloonP := flag.Int("balloon-p", 2, "Balloon's number of threads")
        doDecrypt := flag.Bool("d", false, "Decrypt")
+       parallel := flag.Int("parallel", runtime.NumCPU(), "Parallel cryptors")
        var pubs []*sign.Pub
        flag.Func("pub", "Path to public key to encrypt to", func(v string) error {
                signed, err := sign.PubParse(mustReadFile(v))
@@ -194,7 +198,7 @@ func main() {
                                                log.Fatal(err)
                                        }
                                        var cekp []byte
-                                       cekp, err = kemChaPolyOpen(
+                                       cekp, err = chaPolyKEM.Open(
                                                kek, kem.CEK,
                                                chacha20poly1305.KeySize+chacha20poly1305.NonceSize,
                                        )
@@ -276,7 +280,7 @@ func main() {
                                                        log.Fatal(err)
                                                }
                                                var cekp []byte
-                                               cekp, err = kemChaPolyOpen(
+                                               cekp, err = chaPolyKEM.Open(
                                                        kek, kem.CEK,
                                                        chacha20poly1305.KeySize+chacha20poly1305.NonceSize,
                                                )
@@ -369,7 +373,7 @@ func main() {
                                                        log.Fatal(err)
                                                }
                                                var cekp []byte
-                                               cekp, err = kemChaPolyOpen(
+                                               cekp, err = chaPolyKEM.Open(
                                                        kek[:chacha20poly1305.KeySize],
                                                        kem.CEK,
                                                        chacha20poly1305.KeySize+chacha20poly1305.NonceSize,
@@ -393,7 +397,7 @@ func main() {
                if cek == nil {
                        log.Fatal("no KEMs processed")
                }
-               err = demChaPolyOpen(cek)
+               _, err = chaPolyDEM.Open(os.Stdout, os.Stdin, cek, *parallel)
                if err != nil {
                        log.Fatal(err)
                }
@@ -444,7 +448,7 @@ func main() {
                                if err != nil {
                                        log.Fatal(err)
                                }
-                               kem.CEK, err = kemChaPolySeal(kek, cek)
+                               kem.CEK, err = chaPolyKEM.Seal(kek, cek)
                                if err != nil {
                                        log.Fatal(err)
                                }
@@ -509,7 +513,7 @@ func main() {
                                        if err != nil {
                                                log.Fatal(err)
                                        }
-                                       kem.CEK, err = kemChaPolySeal(kek, cek)
+                                       kem.CEK, err = chaPolyKEM.Seal(kek, cek)
                                        if err != nil {
                                                log.Fatal(err)
                                        }
@@ -575,7 +579,9 @@ func main() {
                                        if err != nil {
                                                log.Fatal(err)
                                        }
-                                       kem.CEK, err = kemChaPolySeal(kek[:chacha20poly1305.KeySize], cek)
+                                       kem.CEK, err = chaPolyKEM.Seal(
+                                               kek[:chacha20poly1305.KeySize], cek,
+                                       )
                                        if err != nil {
                                                log.Fatal(err)
                                        }
@@ -607,7 +613,7 @@ func main() {
                                log.Fatal(err)
                        }
                }
-               if err = demChaPolySeal(cek); err != nil {
+               if _, err = chaPolyDEM.Seal(os.Stdout, os.Stdin, cek, *parallel); err != nil {
                        log.Fatal(err)
                }
        }
diff --git a/go/cm/enc/chapoly/dem/dem.go b/go/cm/enc/chapoly/dem/dem.go
new file mode 100644 (file)
index 0000000..c8a9033
--- /dev/null
@@ -0,0 +1,212 @@
+// enctool -- dealing with KEKS-encoded cm-encrypted utility
+// 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 dem
+
+import (
+       "bytes"
+       "crypto/cipher"
+       "crypto/subtle"
+       "errors"
+       "io"
+
+       "go.cypherpunks.su/keks"
+       "golang.org/x/crypto/chacha20poly1305"
+)
+
+const (
+       ChaPolyChunkLen = 128 * 1024
+       ChaPolyPadLen   = 32
+)
+
+func incr(data []byte) {
+       for i := len(data) - 1; i >= 0; i-- {
+               data[i]++
+               if data[i] != 0 {
+                       return
+               }
+       }
+}
+
+type job struct {
+       bufReady  chan struct{}
+       processed chan struct{}
+       buf       []byte
+       ctr       []byte
+}
+
+func do(
+       w io.Writer,
+       seal bool,
+       r io.Reader,
+       cek []byte,
+       procs int,
+) (total int64, err error) {
+       if len(cek) != chacha20poly1305.KeySize+chacha20poly1305.NonceSize {
+               return 0, errors.New("wrong CEK len")
+       }
+       ready := make(chan *job, procs)
+       dones := make(chan *job, procs)
+       key, iv := cek[:chacha20poly1305.KeySize], cek[chacha20poly1305.KeySize:]
+       var ctr []byte
+       var overhead int
+       {
+               var ciph cipher.AEAD
+               ciph, err = chacha20poly1305.New(key)
+               if err != nil {
+                       return 0, err
+               }
+               ctr = make([]byte, ciph.NonceSize())
+               overhead = ciph.Overhead()
+       }
+       chunkLen := ChaPolyPadLen + ChaPolyChunkLen + overhead
+       var blobDecoder *keks.BlobDecoder
+       if seal {
+               total, err = keks.BlobAtomEncode(w, int64(chunkLen))
+       } else {
+               blobDecoder, err = keks.NewBlobDecoder(r, int64(chunkLen))
+       }
+       if err != nil {
+               return
+       }
+       chaPolyPad := make([]byte, ChaPolyPadLen)
+       var errUnauth error
+       for range procs {
+               go func() {
+                       var ciph cipher.AEAD
+                       ciph, err = chacha20poly1305.New(key)
+                       if err != nil {
+                               panic(err)
+                       }
+                       j := job{
+                               buf:       make([]byte, chunkLen),
+                               ctr:       make([]byte, ciph.NonceSize()),
+                               bufReady:  make(chan struct{}),
+                               processed: make(chan struct{}),
+                       }
+                       nonce := make([]byte, ciph.NonceSize())
+                       ready <- &j
+                       var errOpen error
+                       for {
+                               <-j.bufReady
+                               subtle.XORBytes(nonce, j.ctr, iv)
+                               if seal {
+                                       ciph.Seal(j.buf[:0], nonce, j.buf[:len(j.buf)-overhead], nil)
+                               } else {
+                                       j.buf, errOpen = ciph.Open(j.buf[:0], nonce, j.buf, nil)
+                                       if errOpen == nil {
+                                               if subtle.ConstantTimeCompare(
+                                                       j.buf[:ChaPolyPadLen],
+                                                       chaPolyPad,
+                                               ) != 1 {
+                                                       errUnauth = errors.New("bad pad")
+                                               }
+                                       } else {
+                                               errUnauth = errOpen
+                                       }
+                               }
+                               j.processed <- struct{}{}
+                       }
+               }()
+       }
+       finished := make(chan struct{})
+       var errW error
+       go func() {
+               var n int64
+               for j := range dones {
+                       <-j.processed
+                       if seal {
+                               n, errW = keks.BinEncode(w, j.buf)
+                       } else {
+                               if len(j.buf) == 0 {
+                                       n = 0
+                               } else {
+                                       n, errW = io.Copy(w, bytes.NewReader(j.buf[ChaPolyPadLen:]))
+                               }
+                       }
+                       total += n
+                       if errW != nil {
+                               break
+                       }
+                       j.buf = j.buf[:chunkLen]
+                       ready <- j
+               }
+               close(finished)
+       }()
+       var chunk []byte
+       var errR error
+       var eof bool
+       var n int
+       var j *job
+       for !eof {
+               if errUnauth != nil {
+                       err = errUnauth
+                       close(dones)
+                       <-finished
+                       return
+               }
+               j = <-ready
+               if seal {
+                       n, errR = io.ReadFull(r, j.buf[ChaPolyPadLen:ChaPolyPadLen+ChaPolyChunkLen])
+               } else {
+                       chunk, errR = blobDecoder.Next()
+                       copy(j.buf, chunk)
+                       n = len(chunk)
+                       j.buf = j.buf[:n]
+               }
+               total += int64(n)
+               if errR != nil {
+                       if !(errR == io.ErrUnexpectedEOF || errR == io.EOF) {
+                               err = errR
+                               close(dones)
+                               <-finished
+                               return
+                       }
+                       errR = nil
+                       eof = true
+                       if seal {
+                               j.buf = j.buf[:ChaPolyPadLen+n+overhead]
+                       }
+               }
+               if seal {
+                       clear(j.buf[:ChaPolyPadLen])
+               } else if n == 0 {
+                       break
+               }
+               if (seal && n < ChaPolyChunkLen) || (!seal && n < chunkLen) {
+                       ctr[len(ctr)-1] = 0x01
+               }
+               incr(ctr[:len(ctr)-1])
+               copy(j.ctr, ctr)
+               j.bufReady <- struct{}{}
+               dones <- j
+       }
+       close(dones)
+       <-finished
+       if errUnauth == nil {
+               err = errW
+       } else {
+               err = errUnauth
+       }
+       return
+}
+
+func Seal(w io.Writer, r io.Reader, cek []byte, procs int) (total int64, err error) {
+       return do(w, true, r, cek, procs)
+}
+
+func Open(w io.Writer, r io.Reader, cek []byte, procs int) (total int64, err error) {
+       return do(w, false, r, cek, procs)
+}
diff --git a/go/cm/enc/chapoly/kem/kem.go b/go/cm/enc/chapoly/kem/kem.go
new file mode 100644 (file)
index 0000000..c400ba6
--- /dev/null
@@ -0,0 +1,48 @@
+// GoKEKS/CM -- KEKS-encoded cryptographic messages
+// 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 kem
+
+import (
+       "errors"
+
+       "golang.org/x/crypto/chacha20poly1305"
+)
+
+func Seal(kek, cek []byte) ([]byte, error) {
+       ciph, err := chacha20poly1305.New(kek)
+       if err != nil {
+               return nil, err
+       }
+       nonce := make([]byte, ciph.NonceSize())
+       return ciph.Seal(nil, nonce, cek, nil), nil
+}
+
+func Open(kek, ciphertext []byte, cekLenExpected int) ([]byte, error) {
+       ciph, err := chacha20poly1305.New(kek)
+       if err != nil {
+               return nil, err
+       }
+       nonce := make([]byte, ciph.NonceSize())
+       var cek []byte
+       cek, err = ciph.Open(nil, nonce, ciphertext, nil)
+       if err != nil {
+               return nil, err
+       }
+       if len(cek) != cekLenExpected {
+               return nil, errors.New("invalid CEK len")
+       }
+       return cek, nil
+}