├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── assets ├── PQCkemKAT_1632.rsp ├── PQCkemKAT_2400.rsp ├── PQCkemKAT_3168.rsp └── kyber-k2so.png ├── byteops.go ├── go.mod ├── go.sum ├── indcpa.go ├── kem.go ├── kem_test.go ├── ntt.go ├── params.go └── poly.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Kyber-K2SO 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.21 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v . 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.upx 8 | *.test 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # Visual Studio Code 14 | .vscode 15 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | skip-files: 3 | - kem_test.go 4 | 5 | linters: 6 | enable: 7 | - govet 8 | - staticcheck 9 | - gosimple 10 | - gofmt 11 | - ineffassign 12 | - typecheck 13 | - dogsled 14 | - funlen 15 | - gochecknoinits 16 | - godox 17 | - gocritic 18 | - gocyclo 19 | - gosec 20 | - lll 21 | - misspell 22 | - nakedret 23 | - prealloc 24 | - exportloopref 25 | - unconvert 26 | - unparam 27 | - unused 28 | - errcheck 29 | - unused 30 | 31 | linters-settings: 32 | gocritic: 33 | enabled-checks: 34 | - caseOrder 35 | - dupArg 36 | - dupBranchBody 37 | - dupCase 38 | - dupSubExpr 39 | - flagDeref 40 | - captLocal 41 | - defaultCaseOrder 42 | - elseif 43 | - ifElseChain 44 | - regexpMust 45 | - sloppyLen 46 | - switchTrue 47 | - typeSwitchVar 48 | - underef 49 | - unlambda 50 | - unslice 51 | - argOrder 52 | - badCall 53 | - badCond 54 | - evalOrder 55 | - exitAfterDefer 56 | - flagName 57 | - mapKey 58 | - nilValReturn 59 | - octalLiteral 60 | - offBy1 61 | - regexpPattern 62 | - sloppyReassign 63 | - truncateCmp 64 | - weakCond 65 | - boolExprSimplify 66 | - builtinShadow 67 | - dupImport 68 | - methodExprCall 69 | - initClause 70 | - newDeref 71 | - nestingReduce 72 | - stringXbytes 73 | - unlabelStmt 74 | - typeUnparen 75 | - unnecessaryBlock 76 | - valSwap 77 | - wrapperFunc 78 | - yodaStyleExpr 79 | goconst: 80 | min-len: 12 81 | funlen: 82 | statements: 64 83 | lines: 128 84 | gocyclo: 85 | min-complexity: 15 86 | govet: 87 | enable-all: true 88 | lll: 89 | line-length: 120 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nadim Kobeissi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kyber-K2SO 2 | 3 | 4 | 5 | [![Kyber-K2SO](https://github.com/symbolicsoft/kyber-k2so/workflows/Kyber-K2SO/badge.svg)](https://github.com/symbolicsoft/kyber-k2so/actions) 6 | [![GoDoc](https://godoc.org/github.com/symbolicsoft/kyber-k2so?status.svg)](https://pkg.go.dev/github.com/symbolicsoft/kyber-k2so?tab=overview) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/symbolicsoft/kyber-k2so)](https://goreportcard.com/report/github.com/symbolicsoft/kyber-k2so) 8 | ![GitHub](https://img.shields.io/github/license/symbolicsoft/kyber-k2so) 9 | 10 | **[Kyber-K2SO](https://github.com/symbolicsoft/kyber-k2so)** is Symbolic Software's clean implementation of the [Kyber](https://pq-crystals.org/kyber/) IND-CCA2-secure key encapsulation mechanism (KEM), whose security is based on the hardness of solving the learning-with-errors (LWE) problem over module lattices. Kyber is the winning candidate algorithm, of those submitted to the NIST post-quantum cryptography project. 11 | 12 | ## Security Disclaimer 13 | 14 | 🚨 Extensive effort has been undertaken in order to ensure the correctness, interoperability, safety and reliability of this library. Furthermore, it is unlikely that the API will change in the future. While this library is likely ready for production use, it is offered as-is, and without a guarantee. 15 | 16 | ## Features & Usage 17 | 18 | Keeping in mind the Security Disclaimer above, Kyber-K2SO appears to be appropriate for use in any environment supported by Go: client-side application, server-side applications and more. All operations take no more than a few milliseconds on regular computing hardware. 19 | 20 | ### Features 21 | 22 | * **Small, easy to read code.** Kyber-K2SO is to our knowledge the smallest implementation of Kyber Version 3. 23 | * **Simple API.** `KemKeypair768()` to generate a private key and a public key, `KemEncrypt768(publicKey)` generate and encrypt a shared secret, and `KemDecrypt768(ciphertext, privateKey)` to decrypt the shared secret. Aside from Kyber-768, Kyber-512 and Kyber-1024 are also offered. 24 | * **Good performance.** Kyber-K2SO is more than fast enough for regular usage in any environment supported by the Go programming language. 25 | * **Constant time (probably).** As far as we can tell, decryption appears to perform in constant time. Further analysis is encouraged. 26 | 27 | ### Using Kyber-K2SO 28 | 29 | ```bash 30 | go get -u github.com/symbolicsoft/kyber-k2so 31 | ``` 32 | 33 | ```go 34 | package main 35 | 36 | import ( 37 | kyberk2so "github.com/symbolicsoft/kyber-k2so" 38 | ) 39 | 40 | func main() { 41 | privateKey, publicKey, _ := kyberk2so.KemKeypair768() 42 | ciphertext, ssA, _ := kyberk2so.KemEncrypt768(publicKey) 43 | ssB, _ := kyberk2so.KemDecrypt768(ciphertext, privateKey) 44 | } 45 | ``` 46 | 47 | Replace `768` with `512` or `1024` in the above function names in order to call Kyber-512 or Kyber-1024 instead of Kyber-768. 48 | 49 | ### Running Tests 50 | 51 | ```bash 52 | > go test -v 53 | 54 | === RUN TestVectors512 55 | --- PASS: TestVectors512 (0.01s) 56 | === RUN TestVectors768 57 | --- PASS: TestVectors768 (0.01s) 58 | === RUN TestVectors1024 59 | --- PASS: TestVectors1024 (0.01s) 60 | === RUN TestSelf512 61 | --- PASS: TestSelf512 (0.19s) 62 | === RUN TestSelf768 63 | --- PASS: TestSelf768 (0.30s) 64 | === RUN TestSelf1024 65 | --- PASS: TestSelf1024 (0.46s) 66 | PASS 67 | ok github.com/symbolicsoft/kyber-k2so 1.140s 68 | ``` 69 | 70 | ### Running Benchmarks 71 | 72 | ```bash 73 | > go test -bench=. 74 | 75 | goos: linux 76 | goarch: amd64 77 | pkg: github.com/symbolicsoft/kyber-k2so 78 | BenchmarkKemKeypair512-8 28116 41519 ns/op 79 | BenchmarkKemKeypair768-8 15864 74150 ns/op 80 | BenchmarkKemKeypair1024-8 10000 105946 ns/op 81 | BenchmarkKemEncrypt512-8 21409 56336 ns/op 82 | BenchmarkKemEncrypt768-8 13629 87541 ns/op 83 | BenchmarkKemEncrypt1024-8 9987 131054 ns/op 84 | BenchmarkKemDecrypt512-8 17650 65348 ns/op 85 | BenchmarkKemDecrypt768-8 12352 99300 ns/op 86 | BenchmarkKemDecrypt1024-8 8913 140804 ns/op 87 | PASS 88 | ok github.com/symbolicsoft/kyber-k2so 16.180s 89 | ``` 90 | 91 | ## About Kyber-K2SO 92 | 93 | Kyber-K2SO is published by [Symbolic Software](https://symbolic.software) under the MIT License. 94 | 95 | Kyber-K2SO originally implemented Kyber version 2. Kyber-K2SO was subsequently updated in version 0.1.0 to implement Kyber version 3 thanks to work contributed by [Anton Tutoveanu](https://github.com/antontutoveanu) in March 2021. 96 | 97 | We thank [Peter Schwabe](https://cryptojedi.org/peter) for his feedback during the development of this implementation. 98 | -------------------------------------------------------------------------------- /assets/kyber-k2so.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symbolicsoft/kyber-k2so/27680ae10e8c88fd0760e2b6cbe874c7520a5105/assets/kyber-k2so.png -------------------------------------------------------------------------------- /byteops.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | package kyberk2so 5 | 6 | // byteopsLoad32 returns a 32-bit unsigned integer loaded from byte x. 7 | func byteopsLoad32(x []byte) uint32 { 8 | var r uint32 9 | r = uint32(x[0]) 10 | r = r | (uint32(x[1]) << 8) 11 | r = r | (uint32(x[2]) << 16) 12 | r = r | (uint32(x[3]) << 24) 13 | return r 14 | } 15 | 16 | // byteopsLoad24 returns a 32-bit unsigned integer loaded from byte x. 17 | func byteopsLoad24(x []byte) uint32 { 18 | var r uint32 19 | r = uint32(x[0]) 20 | r = r | (uint32(x[1]) << 8) 21 | r = r | (uint32(x[2]) << 16) 22 | return r 23 | } 24 | 25 | // byteopsCbd computers a polynomial with coefficients distributed 26 | // according to a centered binomial distribution with parameter eta, 27 | // given an array of uniformly random bytes. 28 | func byteopsCbd(buf []byte, paramsK int) poly { 29 | var t, d uint32 30 | var a, b int16 31 | var r poly 32 | switch paramsK { 33 | case 2: 34 | for i := 0; i < paramsN/4; i++ { 35 | t = byteopsLoad24(buf[3*i:]) 36 | d = t & 0x00249249 37 | d = d + ((t >> 1) & 0x00249249) 38 | d = d + ((t >> 2) & 0x00249249) 39 | for j := 0; j < 4; j++ { 40 | a = int16((d >> (6*j + 0)) & 0x7) 41 | b = int16((d >> (6*j + paramsETAK512)) & 0x7) 42 | r[4*i+j] = a - b 43 | } 44 | } 45 | default: 46 | for i := 0; i < paramsN/8; i++ { 47 | t = byteopsLoad32(buf[4*i:]) 48 | d = t & 0x55555555 49 | d = d + ((t >> 1) & 0x55555555) 50 | for j := 0; j < 8; j++ { 51 | a = int16((d >> (4*j + 0)) & 0x3) 52 | b = int16((d >> (4*j + paramsETAK768K1024)) & 0x3) 53 | r[8*i+j] = a - b 54 | } 55 | } 56 | } 57 | return r 58 | } 59 | 60 | // byteopsMontgomeryReduce computes a Montgomery reduction; given 61 | // a 32-bit integer `a`, returns `a * R^-1 mod Q` where `R=2^16`. 62 | func byteopsMontgomeryReduce(a int32) int16 { 63 | return int16((a - int32(int16(a*int32(paramsQInv)))*int32(paramsQ)) >> 16) 64 | } 65 | 66 | // byteopsBarrettReduce computes a Barrett reduction; given 67 | // a 16-bit integer `a`, returns a 16-bit integer congruent to 68 | // `a mod Q` in {0,...,Q}. 69 | func byteopsBarrettReduce(a int16) int16 { 70 | var t int16 71 | var v int16 = int16(((uint32(1) << 26) + uint32(paramsQ/2)) / uint32(paramsQ)) 72 | t = int16(int32(v) * int32(a) >> 26) 73 | t = t * int16(paramsQ) 74 | return a - t 75 | } 76 | 77 | // byteopsCSubQ conditionally subtracts Q from a. 78 | func byteopsCSubQ(a int16) int16 { 79 | a = a - int16(paramsQ) 80 | a = a + ((a >> 15) & int16(paramsQ)) 81 | return a 82 | } 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | // SPDX-License-Identifier: MIT 3 | 4 | module github.com/symbolicsoft/kyber-k2so 5 | 6 | go 1.23 7 | 8 | require golang.org/x/crypto v0.29.0 9 | 10 | require golang.org/x/sys v0.27.0 // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= 2 | golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= 3 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 4 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 5 | -------------------------------------------------------------------------------- /indcpa.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | package kyberk2so 5 | 6 | import ( 7 | "crypto/rand" 8 | 9 | "golang.org/x/crypto/sha3" 10 | ) 11 | 12 | // indcpaPackPublicKey serializes the public key as a concatenation of the 13 | // serialized vector of polynomials of the public key, and the public seed 14 | // used to generate the matrix `A`. 15 | func indcpaPackPublicKey(publicKey polyvec, seed []byte, paramsK int) []byte { 16 | return append(polyvecToBytes(publicKey, paramsK), seed...) 17 | } 18 | 19 | // indcpaUnpackPublicKey de-serializes the public key from a byte array 20 | // and represents the approximate inverse of indcpaPackPublicKey. 21 | func indcpaUnpackPublicKey(packedPublicKey []byte, paramsK int) (polyvec, []byte) { 22 | switch paramsK { 23 | case 2: 24 | publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytesK512], paramsK) 25 | seed := packedPublicKey[paramsPolyvecBytesK512:] 26 | return publicKeyPolyvec, seed 27 | case 3: 28 | publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytesK768], paramsK) 29 | seed := packedPublicKey[paramsPolyvecBytesK768:] 30 | return publicKeyPolyvec, seed 31 | default: 32 | publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytesK1024], paramsK) 33 | seed := packedPublicKey[paramsPolyvecBytesK1024:] 34 | return publicKeyPolyvec, seed 35 | } 36 | } 37 | 38 | // indcpaPackPrivateKey serializes the private key. 39 | func indcpaPackPrivateKey(privateKey polyvec, paramsK int) []byte { 40 | return polyvecToBytes(privateKey, paramsK) 41 | } 42 | 43 | // indcpaUnpackPrivateKey de-serializes the private key and represents 44 | // the inverse of indcpaPackPrivateKey. 45 | func indcpaUnpackPrivateKey(packedPrivateKey []byte, paramsK int) polyvec { 46 | return polyvecFromBytes(packedPrivateKey, paramsK) 47 | } 48 | 49 | // indcpaPackCiphertext serializes the ciphertext as a concatenation of 50 | // the compressed and serialized vector of polynomials `b` and the 51 | // compressed and serialized polynomial `v`. 52 | func indcpaPackCiphertext(b polyvec, v poly, paramsK int) []byte { 53 | return append(polyvecCompress(b, paramsK), polyCompress(v, paramsK)...) 54 | } 55 | 56 | // indcpaUnpackCiphertext de-serializes and decompresses the ciphertext 57 | // from a byte array, and represents the approximate inverse of 58 | // indcpaPackCiphertext. 59 | func indcpaUnpackCiphertext(c []byte, paramsK int) (polyvec, poly) { 60 | switch paramsK { 61 | case 2: 62 | b := polyvecDecompress(c[:paramsPolyvecCompressedBytesK512], paramsK) 63 | v := polyDecompress(c[paramsPolyvecCompressedBytesK512:], paramsK) 64 | return b, v 65 | case 3: 66 | b := polyvecDecompress(c[:paramsPolyvecCompressedBytesK768], paramsK) 67 | v := polyDecompress(c[paramsPolyvecCompressedBytesK768:], paramsK) 68 | return b, v 69 | default: 70 | b := polyvecDecompress(c[:paramsPolyvecCompressedBytesK1024], paramsK) 71 | v := polyDecompress(c[paramsPolyvecCompressedBytesK1024:], paramsK) 72 | return b, v 73 | } 74 | } 75 | 76 | // indcpaRejUniform runs rejection sampling on uniform random bytes 77 | // to generate uniform random integers modulo `Q`. 78 | func indcpaRejUniform(buf []byte, bufl int, l int) (poly, int) { 79 | var r poly 80 | var d1 uint16 81 | var d2 uint16 82 | i := 0 83 | j := 0 84 | for i < l && j+3 <= bufl { 85 | d1 = (uint16((buf[j])>>0) | (uint16(buf[j+1]) << 8)) & 0xFFF 86 | d2 = (uint16((buf[j+1])>>4) | (uint16(buf[j+2]) << 4)) & 0xFFF 87 | j = j + 3 88 | if d1 < uint16(paramsQ) { 89 | r[i] = int16(d1) 90 | i = i + 1 91 | } 92 | if i < l && d2 < uint16(paramsQ) { 93 | r[i] = int16(d2) 94 | i = i + 1 95 | } 96 | } 97 | return r, i 98 | } 99 | 100 | // indcpaGenMatrix deterministically generates a matrix `A` (or the transpose of `A`) 101 | // from a seed. Entries of the matrix are polynomials that look uniformly random. 102 | // Performs rejection sampling on the output of an extendable-output function (XOF). 103 | func indcpaGenMatrix(seed []byte, transposed bool, paramsK int) ([]polyvec, error) { 104 | r := make([]polyvec, paramsK) 105 | buf := make([]byte, 672) 106 | xof := sha3.NewShake128() 107 | ctr := 0 108 | for i := 0; i < paramsK; i++ { 109 | r[i] = polyvecNew(paramsK) 110 | for j := 0; j < paramsK; j++ { 111 | xof.Reset() 112 | var err error 113 | if transposed { 114 | _, err = xof.Write(append(seed, byte(i), byte(j))) 115 | } else { 116 | _, err = xof.Write(append(seed, byte(j), byte(i))) 117 | } 118 | if err != nil { 119 | return []polyvec{}, err 120 | } 121 | _, err = xof.Read(buf) 122 | if err != nil { 123 | return []polyvec{}, err 124 | } 125 | r[i][j], ctr = indcpaRejUniform(buf[:504], 504, paramsN) 126 | for ctr < paramsN { 127 | missing, ctrn := indcpaRejUniform(buf[504:672], 168, paramsN-ctr) 128 | for k := ctr; k < paramsN; k++ { 129 | r[i][j][k] = missing[k-ctr] 130 | } 131 | ctr = ctr + ctrn 132 | } 133 | } 134 | } 135 | return r, nil 136 | } 137 | 138 | // indcpaPrf provides a pseudo-random function (PRF) which returns 139 | // a byte array of length `l`, using the provided key and nonce 140 | // to instantiate the PRF's underlying hash function. 141 | func indcpaPrf(l int, key []byte, nonce byte) []byte { 142 | hash := make([]byte, l) 143 | sha3.ShakeSum256(hash, append(key, nonce)) 144 | return hash 145 | } 146 | 147 | // indcpaKeypair generates public and private keys for the CPA-secure 148 | // public-key encryption scheme underlying Kyber. 149 | func indcpaKeypair(paramsK int) ([]byte, []byte, error) { 150 | skpv := polyvecNew(paramsK) 151 | pkpv := polyvecNew(paramsK) 152 | e := polyvecNew(paramsK) 153 | buf := make([]byte, 2*paramsSymBytes) 154 | h := sha3.New512() 155 | _, err := rand.Read(buf[:paramsSymBytes]) 156 | if err != nil { 157 | return []byte{}, []byte{}, err 158 | } 159 | _, err = h.Write(buf[:paramsSymBytes]) 160 | if err != nil { 161 | return []byte{}, []byte{}, err 162 | } 163 | buf = buf[:0] 164 | buf = h.Sum(buf) 165 | publicSeed := make([]byte, paramsSymBytes) 166 | noiseSeed := make([]byte, paramsSymBytes) 167 | copy(publicSeed, buf[:paramsSymBytes]) 168 | copy(noiseSeed, buf[paramsSymBytes:]) 169 | a, err := indcpaGenMatrix(publicSeed, false, paramsK) 170 | if err != nil { 171 | return []byte{}, []byte{}, err 172 | } 173 | var nonce byte 174 | for i := 0; i < paramsK; i++ { 175 | skpv[i] = polyGetNoise(noiseSeed, nonce, paramsK) 176 | nonce = nonce + 1 177 | } 178 | for i := 0; i < paramsK; i++ { 179 | e[i] = polyGetNoise(noiseSeed, nonce, paramsK) 180 | nonce = nonce + 1 181 | } 182 | polyvecNtt(skpv, paramsK) 183 | polyvecReduce(skpv, paramsK) 184 | polyvecNtt(e, paramsK) 185 | for i := 0; i < paramsK; i++ { 186 | pkpv[i] = polyToMont(polyvecPointWiseAccMontgomery(a[i], skpv, paramsK)) 187 | } 188 | polyvecAdd(pkpv, e, paramsK) 189 | polyvecReduce(pkpv, paramsK) 190 | return indcpaPackPrivateKey(skpv, paramsK), indcpaPackPublicKey(pkpv, publicSeed, paramsK), nil 191 | } 192 | 193 | // indcpaEncrypt is the encryption function of the CPA-secure 194 | // public-key encryption scheme underlying Kyber. 195 | func indcpaEncrypt(m []byte, publicKey []byte, coins []byte, paramsK int) ([]byte, error) { 196 | sp := polyvecNew(paramsK) 197 | ep := polyvecNew(paramsK) 198 | bp := polyvecNew(paramsK) 199 | publicKeyPolyvec, seed := indcpaUnpackPublicKey(publicKey, paramsK) 200 | k := polyFromMsg(m) 201 | at, err := indcpaGenMatrix(seed[:paramsSymBytes], true, paramsK) 202 | if err != nil { 203 | return []byte{}, err 204 | } 205 | for i := 0; i < paramsK; i++ { 206 | sp[i] = polyGetNoise(coins, byte(i), paramsK) 207 | ep[i] = polyGetNoise(coins, byte(i+paramsK), 3) 208 | } 209 | epp := polyGetNoise(coins, byte(paramsK*2), 3) 210 | polyvecNtt(sp, paramsK) 211 | polyvecReduce(sp, paramsK) 212 | for i := 0; i < paramsK; i++ { 213 | bp[i] = polyvecPointWiseAccMontgomery(at[i], sp, paramsK) 214 | } 215 | v := polyvecPointWiseAccMontgomery(publicKeyPolyvec, sp, paramsK) 216 | polyvecInvNttToMont(bp, paramsK) 217 | v = polyInvNttToMont(v) 218 | polyvecAdd(bp, ep, paramsK) 219 | v = polyAdd(polyAdd(v, epp), k) 220 | polyvecReduce(bp, paramsK) 221 | return indcpaPackCiphertext(bp, polyReduce(v), paramsK), nil 222 | } 223 | 224 | // indcpaDecrypt is the decryption function of the CPA-secure 225 | // public-key encryption scheme underlying Kyber. 226 | func indcpaDecrypt(c []byte, privateKey []byte, paramsK int) []byte { 227 | bp, v := indcpaUnpackCiphertext(c, paramsK) 228 | privateKeyPolyvec := indcpaUnpackPrivateKey(privateKey, paramsK) 229 | polyvecNtt(bp, paramsK) 230 | mp := polyvecPointWiseAccMontgomery(privateKeyPolyvec, bp, paramsK) 231 | mp = polyInvNttToMont(mp) 232 | mp = polySub(v, mp) 233 | mp = polyReduce(mp) 234 | return polyToMsg(mp) 235 | } 236 | -------------------------------------------------------------------------------- /kem.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | // Package kyberk2so provides a clean implementation of the Kyber IND-CCA2-secure 5 | // key encapsulation mechanism (KEM), whose security is based on the hardness of 6 | // solving the learning-with-errors (LWE) problem over module lattices. 7 | package kyberk2so 8 | 9 | import ( 10 | "crypto/rand" 11 | "crypto/subtle" 12 | 13 | "golang.org/x/crypto/sha3" 14 | ) 15 | 16 | // KemKeypair512 returns a Kyber-512 private key 17 | // and a corresponding Kyber-512 public key. 18 | // An accompanying error is returned if no sufficient 19 | // randomness could be obtained from the system. 20 | func KemKeypair512() ([Kyber512SKBytes]byte, [Kyber512PKBytes]byte, error) { 21 | const paramsK = 2 22 | var privateKeyFixedLength [Kyber512SKBytes]byte 23 | var publicKeyFixedLength [Kyber512PKBytes]byte 24 | indcpaPrivateKey, indcpaPublicKey, err := indcpaKeypair(paramsK) 25 | if err != nil { 26 | return privateKeyFixedLength, publicKeyFixedLength, err 27 | } 28 | pkh := sha3.Sum256(indcpaPublicKey) 29 | rnd := make([]byte, paramsSymBytes) 30 | _, err = rand.Read(rnd) 31 | if err != nil { 32 | return privateKeyFixedLength, publicKeyFixedLength, err 33 | } 34 | privateKey := append(indcpaPrivateKey, indcpaPublicKey...) 35 | privateKey = append(privateKey, pkh[:]...) 36 | privateKey = append(privateKey, rnd...) 37 | copy(privateKeyFixedLength[:], privateKey) 38 | copy(publicKeyFixedLength[:], indcpaPublicKey) 39 | return privateKeyFixedLength, publicKeyFixedLength, nil 40 | } 41 | 42 | // KemKeypair768 returns a Kyber-768 private key 43 | // and a corresponding Kyber-768 public key. 44 | // An accompanying error is returned if no sufficient 45 | // randomness could be obtained from the system. 46 | func KemKeypair768() ([Kyber768SKBytes]byte, [Kyber768PKBytes]byte, error) { 47 | const paramsK int = 3 48 | var privateKeyFixedLength [Kyber768SKBytes]byte 49 | var publicKeyFixedLength [Kyber768PKBytes]byte 50 | indcpaPrivateKey, indcpaPublicKey, err := indcpaKeypair(paramsK) 51 | if err != nil { 52 | return privateKeyFixedLength, publicKeyFixedLength, err 53 | } 54 | pkh := sha3.Sum256(indcpaPublicKey) 55 | rnd := make([]byte, paramsSymBytes) 56 | _, err = rand.Read(rnd) 57 | if err != nil { 58 | return privateKeyFixedLength, publicKeyFixedLength, err 59 | } 60 | privateKey := append(indcpaPrivateKey, indcpaPublicKey...) 61 | privateKey = append(privateKey, pkh[:]...) 62 | privateKey = append(privateKey, rnd...) 63 | copy(privateKeyFixedLength[:], privateKey) 64 | copy(publicKeyFixedLength[:], indcpaPublicKey) 65 | return privateKeyFixedLength, publicKeyFixedLength, nil 66 | } 67 | 68 | // KemKeypair1024 returns a Kyber-1024 private key 69 | // and a corresponding Kyber-1024 public key. 70 | // An accompanying error is returned if no sufficient 71 | // randomness could be obtained from the system. 72 | func KemKeypair1024() ([Kyber1024SKBytes]byte, [Kyber1024PKBytes]byte, error) { 73 | const paramsK int = 4 74 | var privateKeyFixedLength [Kyber1024SKBytes]byte 75 | var publicKeyFixedLength [Kyber1024PKBytes]byte 76 | indcpaPrivateKey, indcpaPublicKey, err := indcpaKeypair(paramsK) 77 | if err != nil { 78 | return privateKeyFixedLength, publicKeyFixedLength, err 79 | } 80 | pkh := sha3.Sum256(indcpaPublicKey) 81 | rnd := make([]byte, paramsSymBytes) 82 | _, err = rand.Read(rnd) 83 | if err != nil { 84 | return privateKeyFixedLength, publicKeyFixedLength, err 85 | } 86 | privateKey := append(indcpaPrivateKey, indcpaPublicKey...) 87 | privateKey = append(privateKey, pkh[:]...) 88 | privateKey = append(privateKey, rnd...) 89 | copy(privateKeyFixedLength[:], privateKey) 90 | copy(publicKeyFixedLength[:], indcpaPublicKey) 91 | return privateKeyFixedLength, publicKeyFixedLength, nil 92 | } 93 | 94 | // KemEncrypt512 takes a public key (from KemKeypair512) as input 95 | // and returns a ciphertext and a 32-byte shared secret. 96 | // An accompanying error is returned if no sufficient 97 | // randomness could be obtained from the system. 98 | func KemEncrypt512(publicKey [Kyber512PKBytes]byte) ( 99 | [Kyber512CTBytes]byte, [KyberSSBytes]byte, error, 100 | ) { 101 | const paramsK int = 2 102 | var ciphertextFixedLength [Kyber512CTBytes]byte 103 | var sharedSecretFixedLength [KyberSSBytes]byte 104 | sharedSecret := make([]byte, paramsSymBytes) 105 | buf := make([]byte, 2*paramsSymBytes) 106 | _, err := rand.Read(buf[:paramsSymBytes]) 107 | if err != nil { 108 | return ciphertextFixedLength, sharedSecretFixedLength, err 109 | } 110 | buf1 := sha3.Sum256(buf[:paramsSymBytes]) 111 | buf2 := sha3.Sum256(publicKey[:]) 112 | kr := sha3.Sum512(append(buf1[:], buf2[:]...)) 113 | ciphertext, err := indcpaEncrypt(buf1[:], publicKey[:], kr[paramsSymBytes:], paramsK) 114 | krc := sha3.Sum256(ciphertext) 115 | sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krc[:]...)) 116 | copy(ciphertextFixedLength[:], ciphertext) 117 | copy(sharedSecretFixedLength[:], sharedSecret) 118 | return ciphertextFixedLength, sharedSecretFixedLength, err 119 | } 120 | 121 | // KemEncrypt768 takes a public key (from KemKeypair768) as input and 122 | // returns a ciphertext and a 32-byte shared secret. 123 | // An accompanying error is returned if no sufficient 124 | // randomness could be obtained from the system. 125 | func KemEncrypt768(publicKey [Kyber768PKBytes]byte) ( 126 | [Kyber768CTBytes]byte, [KyberSSBytes]byte, error, 127 | ) { 128 | const paramsK int = 3 129 | var ciphertextFixedLength [Kyber768CTBytes]byte 130 | var sharedSecretFixedLength [KyberSSBytes]byte 131 | sharedSecret := make([]byte, paramsSymBytes) 132 | buf := make([]byte, 2*paramsSymBytes) 133 | _, err := rand.Read(buf[:paramsSymBytes]) 134 | if err != nil { 135 | return ciphertextFixedLength, sharedSecretFixedLength, err 136 | } 137 | buf1 := sha3.Sum256(buf[:paramsSymBytes]) 138 | buf2 := sha3.Sum256(publicKey[:]) 139 | kr := sha3.Sum512(append(buf1[:], buf2[:]...)) 140 | ciphertext, err := indcpaEncrypt(buf1[:], publicKey[:], kr[paramsSymBytes:], paramsK) 141 | krc := sha3.Sum256(ciphertext) 142 | sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krc[:]...)) 143 | copy(ciphertextFixedLength[:], ciphertext) 144 | copy(sharedSecretFixedLength[:], sharedSecret) 145 | return ciphertextFixedLength, sharedSecretFixedLength, err 146 | } 147 | 148 | // KemEncrypt1024 takes a public key (from KemKeypair1024) as input 149 | // and returns a ciphertext and a 32-byte shared secret. 150 | // An accompanying error is returned if no sufficient 151 | // randomness could be obtained from the system. 152 | func KemEncrypt1024(publicKey [Kyber1024PKBytes]byte) ( 153 | [Kyber1024CTBytes]byte, [KyberSSBytes]byte, error, 154 | ) { 155 | const paramsK int = 4 156 | var ciphertextFixedLength [Kyber1024CTBytes]byte 157 | var sharedSecretFixedLength [KyberSSBytes]byte 158 | sharedSecret := make([]byte, paramsSymBytes) 159 | buf := make([]byte, 2*paramsSymBytes) 160 | _, err := rand.Read(buf[:paramsSymBytes]) 161 | if err != nil { 162 | return ciphertextFixedLength, sharedSecretFixedLength, err 163 | } 164 | buf1 := sha3.Sum256(buf[:paramsSymBytes]) 165 | buf2 := sha3.Sum256(publicKey[:]) 166 | kr := sha3.Sum512(append(buf1[:], buf2[:]...)) 167 | ciphertext, err := indcpaEncrypt(buf1[:], publicKey[:], kr[paramsSymBytes:], paramsK) 168 | krc := sha3.Sum256(ciphertext) 169 | sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krc[:]...)) 170 | copy(ciphertextFixedLength[:], ciphertext) 171 | copy(sharedSecretFixedLength[:], sharedSecret) 172 | return ciphertextFixedLength, sharedSecretFixedLength, err 173 | } 174 | 175 | // KemDecrypt512 takes a ciphertext (from KeyEncrypt512), 176 | // a private key (from KemKeypair512) and returns a 32-byte shared secret. 177 | // An accompanying error is returned if no sufficient 178 | // randomness could be obtained from the system. 179 | func KemDecrypt512( 180 | ciphertext [Kyber512CTBytes]byte, 181 | privateKey [Kyber512SKBytes]byte, 182 | ) ([KyberSSBytes]byte, error) { 183 | const paramsK int = 2 184 | var sharedSecretFixedLength [KyberSSBytes]byte 185 | sharedSecret := make([]byte, KyberSSBytes) 186 | indcpaPrivateKey := privateKey[:paramsIndcpaSecretKeyBytesK512] 187 | pki := paramsIndcpaSecretKeyBytesK512 + paramsIndcpaPublicKeyBytesK512 188 | publicKey := privateKey[paramsIndcpaSecretKeyBytesK512:pki] 189 | buf := indcpaDecrypt(ciphertext[:], indcpaPrivateKey, paramsK) 190 | ski := Kyber512SKBytes - 2*paramsSymBytes 191 | kr := sha3.Sum512(append(buf, privateKey[ski:ski+paramsSymBytes]...)) 192 | cmp, err := indcpaEncrypt(buf, publicKey, kr[paramsSymBytes:], paramsK) 193 | fail := byte(subtle.ConstantTimeCompare(ciphertext[:], cmp) - 1) 194 | krh := sha3.Sum256(ciphertext[:]) 195 | for i := 0; i < paramsSymBytes; i++ { 196 | skx := privateKey[:Kyber512SKBytes-paramsSymBytes+i] 197 | kr[i] = kr[i] ^ (fail & (kr[i] ^ skx[i])) 198 | } 199 | sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krh[:]...)) 200 | copy(sharedSecretFixedLength[:], sharedSecret) 201 | return sharedSecretFixedLength, err 202 | } 203 | 204 | // KemDecrypt768 takes a ciphertext (from KeyEncrypt768), 205 | // a private key (from KemKeypair768) and returns a 32-byte shared secret. 206 | // An accompanying error is returned if no sufficient 207 | // randomness could be obtained from the system. 208 | func KemDecrypt768( 209 | ciphertext [Kyber768CTBytes]byte, 210 | privateKey [Kyber768SKBytes]byte, 211 | ) ([KyberSSBytes]byte, error) { 212 | const paramsK int = 3 213 | var sharedSecretFixedLength [KyberSSBytes]byte 214 | sharedSecret := make([]byte, KyberSSBytes) 215 | indcpaPrivateKey := privateKey[:paramsIndcpaSecretKeyBytesK768] 216 | pki := paramsIndcpaSecretKeyBytesK768 + paramsIndcpaPublicKeyBytesK768 217 | publicKey := privateKey[paramsIndcpaSecretKeyBytesK768:pki] 218 | buf := indcpaDecrypt(ciphertext[:], indcpaPrivateKey, paramsK) 219 | ski := Kyber768SKBytes - 2*paramsSymBytes 220 | kr := sha3.Sum512(append(buf, privateKey[ski:ski+paramsSymBytes]...)) 221 | cmp, err := indcpaEncrypt(buf, publicKey, kr[paramsSymBytes:], paramsK) 222 | fail := byte(subtle.ConstantTimeCompare(ciphertext[:], cmp) - 1) 223 | krh := sha3.Sum256(ciphertext[:]) 224 | for i := 0; i < paramsSymBytes; i++ { 225 | skx := privateKey[:Kyber768SKBytes-paramsSymBytes+i] 226 | kr[i] = kr[i] ^ (fail & (kr[i] ^ skx[i])) 227 | } 228 | sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krh[:]...)) 229 | copy(sharedSecretFixedLength[:], sharedSecret) 230 | return sharedSecretFixedLength, err 231 | } 232 | 233 | // KemDecrypt1024 takes a ciphertext (from KeyEncrypt1024), 234 | // a private key (from KemKeypair1024) and returns a 32-byte shared secret. 235 | // An accompanying error is returned if no sufficient 236 | // randomness could be obtained from the system. 237 | func KemDecrypt1024( 238 | ciphertext [Kyber1024CTBytes]byte, 239 | privateKey [Kyber1024SKBytes]byte, 240 | ) ([KyberSSBytes]byte, error) { 241 | const paramsK int = 4 242 | var sharedSecretFixedLength [KyberSSBytes]byte 243 | sharedSecret := make([]byte, KyberSSBytes) 244 | indcpaPrivateKey := privateKey[:paramsIndcpaSecretKeyBytesK1024] 245 | pki := paramsIndcpaSecretKeyBytesK1024 + paramsIndcpaPublicKeyBytesK1024 246 | publicKey := privateKey[paramsIndcpaSecretKeyBytesK1024:pki] 247 | buf := indcpaDecrypt(ciphertext[:], indcpaPrivateKey, paramsK) 248 | ski := Kyber1024SKBytes - 2*paramsSymBytes 249 | kr := sha3.Sum512(append(buf, privateKey[ski:ski+paramsSymBytes]...)) 250 | cmp, err := indcpaEncrypt(buf, publicKey, kr[paramsSymBytes:], paramsK) 251 | fail := byte(subtle.ConstantTimeCompare(ciphertext[:], cmp) - 1) 252 | krh := sha3.Sum256(ciphertext[:]) 253 | for i := 0; i < paramsSymBytes; i++ { 254 | skx := privateKey[:Kyber1024SKBytes-paramsSymBytes+i] 255 | kr[i] = kr[i] ^ (fail & (kr[i] ^ skx[i])) 256 | } 257 | sha3.ShakeSum256(sharedSecret, append(kr[:paramsSymBytes], krh[:]...)) 258 | copy(sharedSecretFixedLength[:], sharedSecret) 259 | return sharedSecretFixedLength, err 260 | } 261 | -------------------------------------------------------------------------------- /kem_test.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | package kyberk2so 5 | 6 | import ( 7 | "crypto/subtle" 8 | "encoding/hex" 9 | "io/ioutil" 10 | "log" 11 | "path/filepath" 12 | "regexp" 13 | "strings" 14 | "testing" 15 | ) 16 | 17 | type kemTest512 struct { 18 | privateKey [Kyber512SKBytes]byte 19 | publicKey [Kyber512PKBytes]byte 20 | ciphertext [Kyber512CTBytes]byte 21 | sharedSecret [KyberSSBytes]byte 22 | } 23 | 24 | type kemTest768 struct { 25 | privateKey [Kyber768SKBytes]byte 26 | publicKey [Kyber768PKBytes]byte 27 | ciphertext [Kyber768CTBytes]byte 28 | sharedSecret [KyberSSBytes]byte 29 | } 30 | 31 | type kemTest1024 struct { 32 | privateKey [Kyber1024SKBytes]byte 33 | publicKey [Kyber1024PKBytes]byte 34 | ciphertext [Kyber1024CTBytes]byte 35 | sharedSecret [KyberSSBytes]byte 36 | } 37 | 38 | var kemTests512, kemTests768, kemTests1024 = func() ([100]kemTest512, [100]kemTest768, [100]kemTest1024) { 39 | var kt512 [100]kemTest512 40 | var kt768 [100]kemTest768 41 | var kt1024 [100]kemTest1024 42 | rsps := [3]string{ 43 | "PQCkemKAT_1632.rsp", 44 | "PQCkemKAT_2400.rsp", 45 | "PQCkemKAT_3168.rsp", 46 | } 47 | for r, rsp := range rsps { 48 | katPath := filepath.Join("assets", rsp) 49 | katBytes, err := ioutil.ReadFile(katPath) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | kat := string(katBytes) 54 | rPk := regexp.MustCompile(`pk = [A-F0-9]+\n`) 55 | rSk := regexp.MustCompile(`sk = [A-F0-9]+\n`) 56 | rCt := regexp.MustCompile(`ct = [A-F0-9]+\n`) 57 | rSs := regexp.MustCompile(`ss = [A-F0-9]+\n`) 58 | allPk := rPk.FindAllString(kat, -1) 59 | allSk := rSk.FindAllString(kat, -1) 60 | allCt := rCt.FindAllString(kat, -1) 61 | allSs := rSs.FindAllString(kat, -1) 62 | if len(allPk) != 100 { 63 | log.Fatal("not all public key test vectors were read") 64 | } 65 | if len(allSk) != 100 { 66 | log.Fatal("not all private key test vectors were read") 67 | } 68 | if len(allCt) != 100 { 69 | log.Fatal("not all ciphertext test vectors were read") 70 | } 71 | if len(allSs) != 100 { 72 | log.Fatal("not all shared secret test vectors were read") 73 | } 74 | for i := 0; i < len(allPk); i++ { 75 | switch r { 76 | case 0: 77 | var privateKey [Kyber512SKBytes]byte 78 | var publicKey [Kyber512PKBytes]byte 79 | var ciphertext [Kyber512CTBytes]byte 80 | var sharedSecret [KyberSSBytes]byte 81 | sk, err := hex.DecodeString(strings.TrimSuffix(allSk[i][5:], "\n")) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | pk, err := hex.DecodeString(strings.TrimSuffix(allPk[i][5:], "\n")) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | ct, err := hex.DecodeString(strings.TrimSuffix(allCt[i][5:], "\n")) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | ss, err := hex.DecodeString(strings.TrimSuffix(allSs[i][5:], "\n")) 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | copy(privateKey[:], sk) 98 | copy(publicKey[:], pk) 99 | copy(ciphertext[:], ct) 100 | copy(sharedSecret[:], ss) 101 | kt512[i] = kemTest512{ 102 | privateKey: privateKey, 103 | publicKey: publicKey, 104 | ciphertext: ciphertext, 105 | sharedSecret: sharedSecret, 106 | } 107 | case 1: 108 | var privateKey [Kyber768SKBytes]byte 109 | var publicKey [Kyber768PKBytes]byte 110 | var ciphertext [Kyber768CTBytes]byte 111 | var sharedSecret [KyberSSBytes]byte 112 | sk, err := hex.DecodeString(strings.TrimSuffix(allSk[i][5:], "\n")) 113 | if err != nil { 114 | log.Fatal(err) 115 | } 116 | pk, err := hex.DecodeString(strings.TrimSuffix(allPk[i][5:], "\n")) 117 | if err != nil { 118 | log.Fatal(err) 119 | } 120 | ct, err := hex.DecodeString(strings.TrimSuffix(allCt[i][5:], "\n")) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | ss, err := hex.DecodeString(strings.TrimSuffix(allSs[i][5:], "\n")) 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | copy(privateKey[:], sk) 129 | copy(publicKey[:], pk) 130 | copy(ciphertext[:], ct) 131 | copy(sharedSecret[:], ss) 132 | kt768[i] = kemTest768{ 133 | privateKey: privateKey, 134 | publicKey: publicKey, 135 | ciphertext: ciphertext, 136 | sharedSecret: sharedSecret, 137 | } 138 | case 2: 139 | var privateKey [Kyber1024SKBytes]byte 140 | var publicKey [Kyber1024PKBytes]byte 141 | var ciphertext [Kyber1024CTBytes]byte 142 | var sharedSecret [KyberSSBytes]byte 143 | sk, err := hex.DecodeString(strings.TrimSuffix(allSk[i][5:], "\n")) 144 | if err != nil { 145 | log.Fatal(err) 146 | } 147 | pk, err := hex.DecodeString(strings.TrimSuffix(allPk[i][5:], "\n")) 148 | if err != nil { 149 | log.Fatal(err) 150 | } 151 | ct, err := hex.DecodeString(strings.TrimSuffix(allCt[i][5:], "\n")) 152 | if err != nil { 153 | log.Fatal(err) 154 | } 155 | ss, err := hex.DecodeString(strings.TrimSuffix(allSs[i][5:], "\n")) 156 | if err != nil { 157 | log.Fatal(err) 158 | } 159 | copy(privateKey[:], sk) 160 | copy(publicKey[:], pk) 161 | copy(ciphertext[:], ct) 162 | copy(sharedSecret[:], ss) 163 | kt1024[i] = kemTest1024{ 164 | privateKey: privateKey, 165 | publicKey: publicKey, 166 | ciphertext: ciphertext, 167 | sharedSecret: sharedSecret, 168 | } 169 | } 170 | } 171 | } 172 | return kt512, kt768, kt1024 173 | }() 174 | 175 | func TestVectors512(t *testing.T) { 176 | for i, test := range kemTests512 { 177 | ssB, err := KemDecrypt512(test.ciphertext, test.privateKey) 178 | if err != nil { 179 | t.Error(err) 180 | } 181 | if subtle.ConstantTimeCompare(test.sharedSecret[:], ssB[:]) == 0 { 182 | t.Errorf("kyber-512 test vector %d failed", i) 183 | } 184 | } 185 | } 186 | 187 | func TestVectors768(t *testing.T) { 188 | for i, test := range kemTests768 { 189 | ssB, err := KemDecrypt768(test.ciphertext, test.privateKey) 190 | if err != nil { 191 | t.Error(err) 192 | } 193 | if subtle.ConstantTimeCompare(test.sharedSecret[:], ssB[:]) == 0 { 194 | t.Errorf("kyber-768 test vector %d failed", i) 195 | } 196 | } 197 | } 198 | 199 | func TestVectors1024(t *testing.T) { 200 | for i, test := range kemTests1024 { 201 | ssB, err := KemDecrypt1024(test.ciphertext, test.privateKey) 202 | if err != nil { 203 | t.Error(err) 204 | } 205 | if subtle.ConstantTimeCompare(test.sharedSecret[:], ssB[:]) == 0 { 206 | t.Errorf("kyber-1024 test vector %d failed", i) 207 | } 208 | } 209 | } 210 | 211 | func TestSelf512(t *testing.T) { 212 | for i := 0; i < 1000; i++ { 213 | privateKey, publicKey, err := KemKeypair512() 214 | if err != nil { 215 | t.Error(err) 216 | } 217 | ciphertext, ssA, err := KemEncrypt512(publicKey) 218 | if err != nil { 219 | t.Error(err) 220 | } 221 | ssB, err := KemDecrypt512(ciphertext, privateKey) 222 | if err != nil { 223 | t.Error(err) 224 | } 225 | if subtle.ConstantTimeCompare(ssA[:], ssB[:]) == 0 { 226 | t.Errorf("kyber-512 self-test failed at iteration %d", i) 227 | } 228 | } 229 | } 230 | 231 | func TestSelf768(t *testing.T) { 232 | for i := 0; i < 1000; i++ { 233 | privateKey, publicKey, err := KemKeypair768() 234 | if err != nil { 235 | t.Error(err) 236 | } 237 | ciphertext, ssA, err := KemEncrypt768(publicKey) 238 | if err != nil { 239 | t.Error(err) 240 | } 241 | ssB, err := KemDecrypt768(ciphertext, privateKey) 242 | if err != nil { 243 | t.Error(err) 244 | } 245 | if subtle.ConstantTimeCompare(ssA[:], ssB[:]) == 0 { 246 | t.Errorf("kyber-768 self-test failed at iteration %d", i) 247 | } 248 | } 249 | } 250 | 251 | func TestSelf1024(t *testing.T) { 252 | for i := 0; i < 1000; i++ { 253 | privateKey, publicKey, err := KemKeypair1024() 254 | if err != nil { 255 | t.Error(err) 256 | } 257 | ciphertext, ssA, err := KemEncrypt1024(publicKey) 258 | if err != nil { 259 | t.Error(err) 260 | } 261 | ssB, err := KemDecrypt1024(ciphertext, privateKey) 262 | if err != nil { 263 | t.Error(err) 264 | } 265 | if subtle.ConstantTimeCompare(ssA[:], ssB[:]) == 0 { 266 | t.Errorf("kyber-1024 self-test failed at iteration %d", i) 267 | } 268 | } 269 | } 270 | 271 | func BenchmarkKemKeypair512(b *testing.B) { 272 | for n := 0; n < b.N; n++ { 273 | _, _, err := KemKeypair512() 274 | if err != nil { 275 | b.Error(err) 276 | } 277 | } 278 | } 279 | 280 | func BenchmarkKemKeypair768(b *testing.B) { 281 | for n := 0; n < b.N; n++ { 282 | _, _, err := KemKeypair768() 283 | if err != nil { 284 | b.Error(err) 285 | } 286 | } 287 | } 288 | 289 | func BenchmarkKemKeypair1024(b *testing.B) { 290 | for n := 0; n < b.N; n++ { 291 | _, _, err := KemKeypair1024() 292 | if err != nil { 293 | b.Error(err) 294 | } 295 | } 296 | } 297 | 298 | func BenchmarkKemEncrypt512(b *testing.B) { 299 | for n := 0; n < b.N; n++ { 300 | KemEncrypt512(kemTests512[n%99].publicKey) 301 | } 302 | } 303 | 304 | func BenchmarkKemEncrypt768(b *testing.B) { 305 | for n := 0; n < b.N; n++ { 306 | KemEncrypt768(kemTests768[n%99].publicKey) 307 | } 308 | } 309 | 310 | func BenchmarkKemEncrypt1024(b *testing.B) { 311 | for n := 0; n < b.N; n++ { 312 | KemEncrypt1024(kemTests1024[n%99].publicKey) 313 | } 314 | } 315 | 316 | func BenchmarkKemDecrypt512(b *testing.B) { 317 | for n := 0; n < b.N; n++ { 318 | KemDecrypt512( 319 | kemTests512[n%99].ciphertext, 320 | kemTests512[n%99].privateKey, 321 | ) 322 | } 323 | } 324 | 325 | func BenchmarkKemDecrypt768(b *testing.B) { 326 | for n := 0; n < b.N; n++ { 327 | KemDecrypt768( 328 | kemTests768[n%99].ciphertext, 329 | kemTests768[n%99].privateKey, 330 | ) 331 | } 332 | } 333 | 334 | func BenchmarkKemDecrypt1024(b *testing.B) { 335 | for n := 0; n < b.N; n++ { 336 | KemDecrypt1024( 337 | kemTests1024[n%99].ciphertext, 338 | kemTests1024[n%99].privateKey, 339 | ) 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /ntt.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | package kyberk2so 5 | 6 | var nttZetas [128]int16 = [128]int16{ 7 | 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, 8 | 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, 9 | 732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047, 10 | 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830, 11 | 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226, 12 | 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, 13 | 1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, 14 | 418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, 15 | 1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459, 16 | 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628, 17 | } 18 | 19 | var nttZetasInv [128]int16 = [128]int16{ 20 | 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, 21 | 1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, 22 | 1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685, 23 | 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235, 24 | 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652, 25 | 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, 26 | 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, 27 | 2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, 28 | 829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, 29 | 3127, 3042, 1907, 1836, 1517, 359, 758, 1441, 30 | } 31 | 32 | // nttFqMul performs multiplication followed by Montgomery reduction 33 | // and returns a 16-bit integer congruent to `a*b*R^{-1} mod Q`. 34 | func nttFqMul(a int16, b int16) int16 { 35 | return byteopsMontgomeryReduce(int32(a) * int32(b)) 36 | } 37 | 38 | // ntt performs an inplace number-theoretic transform (NTT) in `Rq`. 39 | // The input is in standard order, the output is in bit-reversed order. 40 | func ntt(r poly) poly { 41 | j := 0 42 | k := 1 43 | for l := 128; l >= 2; l >>= 1 { 44 | for start := 0; start < 256; start = j + l { 45 | zeta := nttZetas[k] 46 | k = k + 1 47 | for j = start; j < start+l; j++ { 48 | t := nttFqMul(zeta, r[j+l]) 49 | r[j+l] = r[j] - t 50 | r[j] = r[j] + t 51 | } 52 | } 53 | } 54 | return r 55 | } 56 | 57 | // nttInv performs an inplace inverse number-theoretic transform (NTT) 58 | // in `Rq` and multiplication by Montgomery factor 2^16. 59 | // The input is in bit-reversed order, the output is in standard order. 60 | func nttInv(r poly) poly { 61 | j := 0 62 | k := 0 63 | for l := 2; l <= 128; l <<= 1 { 64 | for start := 0; start < 256; start = j + l { 65 | zeta := nttZetasInv[k] 66 | k = k + 1 67 | for j = start; j < start+l; j++ { 68 | t := r[j] 69 | r[j] = byteopsBarrettReduce(t + r[j+l]) 70 | r[j+l] = t - r[j+l] 71 | r[j+l] = nttFqMul(zeta, r[j+l]) 72 | } 73 | } 74 | } 75 | for j := 0; j < 256; j++ { 76 | r[j] = nttFqMul(r[j], nttZetasInv[127]) 77 | } 78 | return r 79 | } 80 | 81 | // nttBaseMul performs the multiplication of polynomials 82 | // in `Zq[X]/(X^2-zeta)`. Used for multiplication of elements 83 | // in `Rq` in the number-theoretic transformation domain. 84 | func nttBaseMul( 85 | a0 int16, a1 int16, 86 | b0 int16, b1 int16, 87 | zeta int16, 88 | ) (int16, int16) { 89 | return nttFqMul(nttFqMul(a1, b1), zeta) + nttFqMul(a0, b0), 90 | nttFqMul(a0, b1) + nttFqMul(a1, b0) 91 | } 92 | -------------------------------------------------------------------------------- /params.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | package kyberk2so 5 | 6 | const paramsN int = 256 7 | const paramsQ int = 3329 8 | const paramsQDivBy2Ceil uint32 = 1665 9 | const params2Pow28DivByQ uint32 = 80635 10 | const params2Pow27DivByQ uint32 = 40318 11 | const params2Pow31DivByQ uint64 = 645084 12 | const params2Pow32DivByQ uint64 = 1290167 13 | const paramsQInv int = 62209 14 | const paramsSymBytes int = 32 15 | const paramsPolyBytes int = 384 16 | const paramsETAK512 int = 3 17 | const paramsETAK768K1024 int = 2 18 | const paramsPolyvecBytesK512 int = 2 * paramsPolyBytes 19 | const paramsPolyvecBytesK768 int = 3 * paramsPolyBytes 20 | const paramsPolyvecBytesK1024 int = 4 * paramsPolyBytes 21 | const paramsPolyCompressedBytesK512 int = 128 22 | const paramsPolyCompressedBytesK768 int = 128 23 | const paramsPolyCompressedBytesK1024 int = 160 24 | const paramsPolyvecCompressedBytesK512 int = 2 * 320 25 | const paramsPolyvecCompressedBytesK768 int = 3 * 320 26 | const paramsPolyvecCompressedBytesK1024 int = 4 * 352 27 | const paramsIndcpaPublicKeyBytesK512 int = paramsPolyvecBytesK512 + paramsSymBytes 28 | const paramsIndcpaPublicKeyBytesK768 int = paramsPolyvecBytesK768 + paramsSymBytes 29 | const paramsIndcpaPublicKeyBytesK1024 int = paramsPolyvecBytesK1024 + paramsSymBytes 30 | const paramsIndcpaSecretKeyBytesK512 int = 2 * paramsPolyBytes 31 | const paramsIndcpaSecretKeyBytesK768 int = 3 * paramsPolyBytes 32 | const paramsIndcpaSecretKeyBytesK1024 int = 4 * paramsPolyBytes 33 | 34 | // Kyber512SKBytes is a constant representing the byte length of private keys in Kyber-512. 35 | const Kyber512SKBytes int = paramsPolyvecBytesK512 + ((paramsPolyvecBytesK512 + paramsSymBytes) + 2*paramsSymBytes) 36 | 37 | // Kyber768SKBytes is a constant representing the byte length of private keys in Kyber-768. 38 | const Kyber768SKBytes int = paramsPolyvecBytesK768 + ((paramsPolyvecBytesK768 + paramsSymBytes) + 2*paramsSymBytes) 39 | 40 | // Kyber1024SKBytes is a constant representing the byte length of private keys in Kyber-1024. 41 | const Kyber1024SKBytes int = paramsPolyvecBytesK1024 + ((paramsPolyvecBytesK1024 + paramsSymBytes) + 2*paramsSymBytes) 42 | 43 | // Kyber512PKBytes is a constant representing the byte length of public keys in Kyber-512. 44 | const Kyber512PKBytes int = paramsPolyvecBytesK512 + paramsSymBytes 45 | 46 | // Kyber768PKBytes is a constant representing the byte length of public keys in Kyber-768. 47 | const Kyber768PKBytes int = paramsPolyvecBytesK768 + paramsSymBytes 48 | 49 | // Kyber1024PKBytes is a constant representing the byte length of public keys in Kyber-1024. 50 | const Kyber1024PKBytes int = paramsPolyvecBytesK1024 + paramsSymBytes 51 | 52 | // Kyber512CTBytes is a constant representing the byte length of ciphertexts in Kyber-512. 53 | const Kyber512CTBytes int = paramsPolyvecCompressedBytesK512 + paramsPolyCompressedBytesK512 54 | 55 | // Kyber768CTBytes is a constant representing the byte length of ciphertexts in Kyber-768. 56 | const Kyber768CTBytes int = paramsPolyvecCompressedBytesK768 + paramsPolyCompressedBytesK768 57 | 58 | // Kyber1024CTBytes is a constant representing the byte length of ciphertexts in Kyber-1024. 59 | const Kyber1024CTBytes int = paramsPolyvecCompressedBytesK1024 + paramsPolyCompressedBytesK1024 60 | 61 | // KyberSSBytes is a constant representing the byte length of shared secrets in Kyber. 62 | const KyberSSBytes int = 32 63 | -------------------------------------------------------------------------------- /poly.go: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: © 2020-2024 Nadim Kobeissi 2 | * SPDX-License-Identifier: MIT */ 3 | 4 | package kyberk2so 5 | 6 | type poly [paramsPolyBytes]int16 7 | type polyvec []poly 8 | 9 | // polyCompress lossily compresses and subsequently serializes a polynomial. 10 | func polyCompress(a poly, paramsK int) []byte { 11 | t := make([]byte, 8) 12 | a = polyCSubQ(a) 13 | rr := 0 14 | switch paramsK { 15 | case 2, 3: 16 | r := make([]byte, paramsPolyCompressedBytesK768) // 128 17 | for i := 0; i < paramsN/8; i++ { 18 | for j := 0; j < 8; j++ { 19 | t[j] = byte((((uint32(a[8*i+j]) << 4) + paramsQDivBy2Ceil) * params2Pow28DivByQ) >> 28) 20 | } 21 | r[rr+0] = t[0] | (t[1] << 4) 22 | r[rr+1] = t[2] | (t[3] << 4) 23 | r[rr+2] = t[4] | (t[5] << 4) 24 | r[rr+3] = t[6] | (t[7] << 4) 25 | rr = rr + 4 26 | } 27 | return r 28 | default: 29 | r := make([]byte, paramsPolyCompressedBytesK1024) // 160 30 | for i := 0; i < paramsN/8; i++ { 31 | for j := 0; j < 8; j++ { 32 | t[j] = byte((((uint32(a[8*i+j]) << 5) + (paramsQDivBy2Ceil - 1)) * params2Pow27DivByQ) >> 27) 33 | } 34 | r[rr+0] = (t[0] >> 0) | (t[1] << 5) 35 | r[rr+1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7) 36 | r[rr+2] = (t[3] >> 1) | (t[4] << 4) 37 | r[rr+3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6) 38 | r[rr+4] = (t[6] >> 2) | (t[7] << 3) 39 | rr = rr + 5 40 | } 41 | return r 42 | } 43 | } 44 | 45 | // polyDecompress de-serializes and subsequently decompresses a polynomial, 46 | // representing the approximate inverse of polyCompress. 47 | // Note that compression is lossy, and thus decompression will not match the 48 | // original input. 49 | func polyDecompress(a []byte, paramsK int) poly { 50 | var r poly 51 | t := make([]byte, 8) 52 | aa := 0 53 | switch paramsK { 54 | case 2, 3: 55 | for i := 0; i < paramsN/2; i++ { 56 | r[2*i+0] = int16(((uint16(a[aa]&15) * uint16(paramsQ)) + 8) >> 4) 57 | r[2*i+1] = int16(((uint16(a[aa]>>4) * uint16(paramsQ)) + 8) >> 4) 58 | aa = aa + 1 59 | } 60 | case 4: 61 | for i := 0; i < paramsN/8; i++ { 62 | t[0] = (a[aa+0] >> 0) 63 | t[1] = (a[aa+0] >> 5) | (a[aa+1] << 3) 64 | t[2] = (a[aa+1] >> 2) 65 | t[3] = (a[aa+1] >> 7) | (a[aa+2] << 1) 66 | t[4] = (a[aa+2] >> 4) | (a[aa+3] << 4) 67 | t[5] = (a[aa+3] >> 1) 68 | t[6] = (a[aa+3] >> 6) | (a[aa+4] << 2) 69 | t[7] = (a[aa+4] >> 3) 70 | aa = aa + 5 71 | for j := 0; j < 8; j++ { 72 | r[8*i+j] = int16(((uint32(t[j]&31) * uint32(paramsQ)) + 16) >> 5) 73 | } 74 | } 75 | } 76 | return r 77 | } 78 | 79 | // polyToBytes serializes a polynomial into an array of bytes. 80 | func polyToBytes(a poly) []byte { 81 | var t0, t1 uint16 82 | r := make([]byte, paramsPolyBytes) 83 | a = polyCSubQ(a) 84 | for i := 0; i < paramsN/2; i++ { 85 | t0 = uint16(a[2*i]) 86 | t1 = uint16(a[2*i+1]) 87 | r[3*i+0] = byte(t0 >> 0) 88 | r[3*i+1] = byte(t0>>8) | byte(t1<<4) 89 | r[3*i+2] = byte(t1 >> 4) 90 | } 91 | return r 92 | } 93 | 94 | // polyFromBytes de-serialises an array of bytes into a polynomial, 95 | // and represents the inverse of polyToBytes. 96 | func polyFromBytes(a []byte) poly { 97 | var r poly 98 | for i := 0; i < paramsN/2; i++ { 99 | r[2*i] = int16(((uint16(a[3*i+0]) >> 0) | (uint16(a[3*i+1]) << 8)) & 0xFFF) 100 | r[2*i+1] = int16(((uint16(a[3*i+1]) >> 4) | (uint16(a[3*i+2]) << 4)) & 0xFFF) 101 | } 102 | return r 103 | } 104 | 105 | // polyFromMsg converts a 32-byte message to a polynomial. 106 | func polyFromMsg(msg []byte) poly { 107 | var r poly 108 | var mask int16 109 | for i := 0; i < paramsN/8; i++ { 110 | for j := 0; j < 8; j++ { 111 | mask = -int16((msg[i] >> j) & 1) 112 | r[8*i+j] = mask & int16((paramsQ+1)/2) 113 | } 114 | } 115 | return r 116 | } 117 | 118 | // polyToMsg converts a polynomial to a 32-byte message 119 | // and represents the inverse of polyFromMsg. 120 | func polyToMsg(a poly) []byte { 121 | msg := make([]byte, paramsSymBytes) 122 | var t uint32 123 | a = polyCSubQ(a) 124 | for i := 0; i < paramsN/8; i++ { 125 | msg[i] = 0 126 | for j := 0; j < 8; j++ { 127 | t = (uint32(a[8*i+j]) << 1) + paramsQDivBy2Ceil 128 | t = ((t * params2Pow28DivByQ) >> 28) & 1 129 | msg[i] |= byte(t << j) 130 | } 131 | } 132 | return msg 133 | } 134 | 135 | // polyGetNoise samples a polynomial deterministically from a seed 136 | // and nonce, with the output polynomial being close to a centered 137 | // binomial distribution. 138 | func polyGetNoise(seed []byte, nonce byte, paramsK int) poly { 139 | switch paramsK { 140 | case 2: 141 | l := paramsETAK512 * paramsN / 4 142 | p := indcpaPrf(l, seed, nonce) 143 | return byteopsCbd(p, paramsK) 144 | default: 145 | l := paramsETAK768K1024 * paramsN / 4 146 | p := indcpaPrf(l, seed, nonce) 147 | return byteopsCbd(p, paramsK) 148 | } 149 | } 150 | 151 | // polyNtt computes a negacyclic number-theoretic transform (NTT) of 152 | // a polynomial in-place; the input is assumed to be in normal order, 153 | // while the output is in bit-reversed order. 154 | func polyNtt(r poly) poly { 155 | return ntt(r) 156 | } 157 | 158 | // polyInvNttToMont computes the inverse of a negacyclic number-theoretic 159 | // transform (NTT) of a polynomial in-place; the input is assumed to be in 160 | // bit-reversed order, while the output is in normal order. 161 | func polyInvNttToMont(r poly) poly { 162 | return nttInv(r) 163 | } 164 | 165 | // polyBaseMulMontgomery performs the multiplication of two polynomials 166 | // in the number-theoretic transform (NTT) domain. 167 | func polyBaseMulMontgomery(a poly, b poly) poly { 168 | for i := 0; i < paramsN/4; i++ { 169 | a[4*i+0], a[4*i+1] = nttBaseMul( 170 | a[4*i+0], a[4*i+1], 171 | b[4*i+0], b[4*i+1], 172 | nttZetas[64+i], 173 | ) 174 | a[4*i+2], a[4*i+3] = nttBaseMul( 175 | a[4*i+2], a[4*i+3], 176 | b[4*i+2], b[4*i+3], 177 | -nttZetas[64+i], 178 | ) 179 | } 180 | return a 181 | } 182 | 183 | // polyToMont performs the in-place conversion of all coefficients 184 | // of a polynomial from the normal domain to the Montgomery domain. 185 | func polyToMont(r poly) poly { 186 | var f int16 = int16((uint64(1) << 32) % uint64(paramsQ)) 187 | for i := 0; i < paramsN; i++ { 188 | r[i] = byteopsMontgomeryReduce(int32(r[i]) * int32(f)) 189 | } 190 | return r 191 | } 192 | 193 | // polyReduce applies Barrett reduction to all coefficients of a polynomial. 194 | func polyReduce(r poly) poly { 195 | for i := 0; i < paramsN; i++ { 196 | r[i] = byteopsBarrettReduce(r[i]) 197 | } 198 | return r 199 | } 200 | 201 | // polyCSubQ applies the conditional subtraction of `Q` to each coefficient 202 | // of a polynomial. 203 | func polyCSubQ(r poly) poly { 204 | for i := 0; i < paramsN; i++ { 205 | r[i] = byteopsCSubQ(r[i]) 206 | } 207 | return r 208 | } 209 | 210 | // polyAdd adds two polynomials. 211 | func polyAdd(a poly, b poly) poly { 212 | for i := 0; i < paramsN; i++ { 213 | a[i] = a[i] + b[i] 214 | } 215 | return a 216 | } 217 | 218 | // polySub subtracts two polynomials. 219 | func polySub(a poly, b poly) poly { 220 | for i := 0; i < paramsN; i++ { 221 | a[i] = a[i] - b[i] 222 | } 223 | return a 224 | } 225 | 226 | // polyvecNew instantiates a new vector of polynomials. 227 | func polyvecNew(paramsK int) polyvec { 228 | var pv polyvec = make([]poly, paramsK) 229 | return pv 230 | } 231 | 232 | // polyvecCompress lossily compresses and serializes a vector of polynomials. 233 | func polyvecCompress(a polyvec, paramsK int) []byte { 234 | var r []byte 235 | polyvecCSubQ(a, paramsK) 236 | rr := 0 237 | switch paramsK { 238 | case 2: 239 | r = make([]byte, paramsPolyvecCompressedBytesK512) 240 | case 3: 241 | r = make([]byte, paramsPolyvecCompressedBytesK768) 242 | case 4: 243 | r = make([]byte, paramsPolyvecCompressedBytesK1024) 244 | } 245 | switch paramsK { 246 | case 2, 3: 247 | t := make([]uint16, 4) 248 | for i := 0; i < paramsK; i++ { 249 | for j := 0; j < paramsN/4; j++ { 250 | for k := 0; k < 4; k++ { 251 | t[k] = uint16(((((uint64(a[i][4*j+k]) << 10) + uint64(paramsQDivBy2Ceil)) * params2Pow32DivByQ) >> 32) & 0x3ff) 252 | } 253 | r[rr+0] = byte(t[0] >> 0) 254 | r[rr+1] = byte((t[0] >> 8) | (t[1] << 2)) 255 | r[rr+2] = byte((t[1] >> 6) | (t[2] << 4)) 256 | r[rr+3] = byte((t[2] >> 4) | (t[3] << 6)) 257 | r[rr+4] = byte((t[3] >> 2)) 258 | rr = rr + 5 259 | } 260 | } 261 | return r 262 | default: 263 | t := make([]uint16, 8) 264 | for i := 0; i < paramsK; i++ { 265 | for j := 0; j < paramsN/8; j++ { 266 | for k := 0; k < 8; k++ { 267 | t[k] = uint16(((((uint64(a[i][8*j+k]) << 11) + uint64(paramsQDivBy2Ceil-1)) * params2Pow31DivByQ) >> 31) & 0x7ff) 268 | } 269 | r[rr+0] = byte((t[0] >> 0)) 270 | r[rr+1] = byte((t[0] >> 8) | (t[1] << 3)) 271 | r[rr+2] = byte((t[1] >> 5) | (t[2] << 6)) 272 | r[rr+3] = byte((t[2] >> 2)) 273 | r[rr+4] = byte((t[2] >> 10) | (t[3] << 1)) 274 | r[rr+5] = byte((t[3] >> 7) | (t[4] << 4)) 275 | r[rr+6] = byte((t[4] >> 4) | (t[5] << 7)) 276 | r[rr+7] = byte((t[5] >> 1)) 277 | r[rr+8] = byte((t[5] >> 9) | (t[6] << 2)) 278 | r[rr+9] = byte((t[6] >> 6) | (t[7] << 5)) 279 | r[rr+10] = byte((t[7] >> 3)) 280 | rr = rr + 11 281 | } 282 | } 283 | return r 284 | } 285 | } 286 | 287 | // polyvecDecompress de-serializes and decompresses a vector of polynomials and 288 | // represents the approximate inverse of polyvecCompress. Since compression is lossy, 289 | // the results of decompression will may not match the original vector of polynomials. 290 | func polyvecDecompress(a []byte, paramsK int) polyvec { 291 | r := polyvecNew(paramsK) 292 | aa := 0 293 | switch paramsK { 294 | case 2, 3: 295 | t := make([]uint16, 4) 296 | for i := 0; i < paramsK; i++ { 297 | for j := 0; j < paramsN/4; j++ { 298 | t[0] = (uint16(a[aa+0]) >> 0) | (uint16(a[aa+1]) << 8) 299 | t[1] = (uint16(a[aa+1]) >> 2) | (uint16(a[aa+2]) << 6) 300 | t[2] = (uint16(a[aa+2]) >> 4) | (uint16(a[aa+3]) << 4) 301 | t[3] = (uint16(a[aa+3]) >> 6) | (uint16(a[aa+4]) << 2) 302 | aa = aa + 5 303 | for k := 0; k < 4; k++ { 304 | r[i][4*j+k] = int16((uint32(t[k]&0x3FF)*uint32(paramsQ) + 512) >> 10) 305 | } 306 | } 307 | } 308 | case 4: 309 | t := make([]uint16, 8) 310 | for i := 0; i < paramsK; i++ { 311 | for j := 0; j < paramsN/8; j++ { 312 | t[0] = (uint16(a[aa+0]) >> 0) | (uint16(a[aa+1]) << 8) 313 | t[1] = (uint16(a[aa+1]) >> 3) | (uint16(a[aa+2]) << 5) 314 | t[2] = (uint16(a[aa+2]) >> 6) | (uint16(a[aa+3]) << 2) | (uint16(a[aa+4]) << 10) 315 | t[3] = (uint16(a[aa+4]) >> 1) | (uint16(a[aa+5]) << 7) 316 | t[4] = (uint16(a[aa+5]) >> 4) | (uint16(a[aa+6]) << 4) 317 | t[5] = (uint16(a[aa+6]) >> 7) | (uint16(a[aa+7]) << 1) | (uint16(a[aa+8]) << 9) 318 | t[6] = (uint16(a[aa+8]) >> 2) | (uint16(a[aa+9]) << 6) 319 | t[7] = (uint16(a[aa+9]) >> 5) | (uint16(a[aa+10]) << 3) 320 | aa = aa + 11 321 | for k := 0; k < 8; k++ { 322 | r[i][8*j+k] = int16((uint32(t[k]&0x7FF)*uint32(paramsQ) + 1024) >> 11) 323 | } 324 | } 325 | } 326 | } 327 | return r 328 | } 329 | 330 | // polyvecToBytes serializes a vector of polynomials. 331 | func polyvecToBytes(a polyvec, paramsK int) []byte { 332 | r := []byte{} 333 | for i := 0; i < paramsK; i++ { 334 | r = append(r, polyToBytes(a[i])...) 335 | } 336 | return r 337 | } 338 | 339 | // polyvecFromBytes deserializes a vector of polynomials. 340 | func polyvecFromBytes(a []byte, paramsK int) polyvec { 341 | r := polyvecNew(paramsK) 342 | for i := 0; i < paramsK; i++ { 343 | start := (i * paramsPolyBytes) 344 | end := (i + 1) * paramsPolyBytes 345 | r[i] = polyFromBytes(a[start:end]) 346 | } 347 | return r 348 | } 349 | 350 | // polyvecNtt applies forward number-theoretic transforms (NTT) 351 | // to all elements of a vector of polynomials. 352 | func polyvecNtt(r polyvec, paramsK int) { 353 | for i := 0; i < paramsK; i++ { 354 | r[i] = polyNtt(r[i]) 355 | } 356 | } 357 | 358 | // polyvecInvNttToMont applies the inverse number-theoretic transform (NTT) 359 | // to all elements of a vector of polynomials and multiplies by Montgomery 360 | // factor `2^16`. 361 | func polyvecInvNttToMont(r polyvec, paramsK int) { 362 | for i := 0; i < paramsK; i++ { 363 | r[i] = polyInvNttToMont(r[i]) 364 | } 365 | } 366 | 367 | // polyvecPointWiseAccMontgomery pointwise-multiplies elements of polynomial-vectors 368 | // `a` and `b`, accumulates the results into `r`, and then multiplies by `2^-16`. 369 | func polyvecPointWiseAccMontgomery(a polyvec, b polyvec, paramsK int) poly { 370 | r := polyBaseMulMontgomery(a[0], b[0]) 371 | for i := 1; i < paramsK; i++ { 372 | t := polyBaseMulMontgomery(a[i], b[i]) 373 | r = polyAdd(r, t) 374 | } 375 | return polyReduce(r) 376 | } 377 | 378 | // polyvecReduce applies Barrett reduction to each coefficient of each element 379 | // of a vector of polynomials. 380 | func polyvecReduce(r polyvec, paramsK int) { 381 | for i := 0; i < paramsK; i++ { 382 | r[i] = polyReduce(r[i]) 383 | } 384 | } 385 | 386 | // polyvecCSubQ applies the conditional subtraction of `Q` to each coefficient 387 | // of each element of a vector of polynomials. 388 | func polyvecCSubQ(r polyvec, paramsK int) { 389 | for i := 0; i < paramsK; i++ { 390 | r[i] = polyCSubQ(r[i]) 391 | } 392 | } 393 | 394 | // polyvecAdd adds two vectors of polynomials. 395 | func polyvecAdd(a polyvec, b polyvec, paramsK int) { 396 | for i := 0; i < paramsK; i++ { 397 | a[i] = polyAdd(a[i], b[i]) 398 | } 399 | } 400 | --------------------------------------------------------------------------------