├── .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 | [](https://github.com/symbolicsoft/kyber-k2so/actions)
6 | [](https://pkg.go.dev/github.com/symbolicsoft/kyber-k2so?tab=overview)
7 | [](https://goreportcard.com/report/github.com/symbolicsoft/kyber-k2so)
8 | 
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 |
--------------------------------------------------------------------------------