Skip to content

Commit

Permalink
Add support for Kyber-512 and Kyber-1024
Browse files Browse the repository at this point in the history
  • Loading branch information
nadimkobeissi committed Jul 30, 2020
1 parent 2571325 commit 38c8472
Show file tree
Hide file tree
Showing 10 changed files with 2,228 additions and 283 deletions.
31 changes: 1 addition & 30 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,4 @@
.DS_Store

# Visual Studio Code
.vscode

# Snapcraft
snapcraft.login

# Coq
*.glob
*.vo
*.vok
*.vos
*.aux

# Scripts
scripts

# C
*.so
*.o
test_kex1024
test_kex512
test_kex768
test_kyber1024
test_kyber512
test_kyber768
test_speed1024
test_speed512
test_speed768
test_vectors1024
test_vectors512
test_vectors768
.vscode
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ linters:
- typecheck
- depguard
- dogsled
- dupl
- funlen
- gochecknoinits
- godox
Expand Down
45 changes: 32 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

**Kyber-K2SO** is a 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 one of the candidate algorithms submitted to the [NIST post-quantum cryptography project](https://csrc.nist.gov/Projects/Post-Quantum-Cryptography).

Kyber-K2SO implements only Kyber-768, and does not provide Kyber-512, Kyber-1024, or the _"90s Kyber"_ variants, because there does not appear to be a convincing reason to ever do so.

## Security Disclaimer
🚨 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.

Expand All @@ -20,7 +18,7 @@ Keeping in mind the Security Disclaimer above, Kyber-K2SO appears to be appropri

* 🟢 **Purely functional, easy to read code.** Code readability and predictability is prioritized over performance.
* 🟢 **Smallest codebase.** Kyber-K2SO is to our knowledge the smallest implementation of Kyber Version 2, and is 4.3 times smaller than the reference implementation.
* 🟢 **Simple API.** `KemKeypair()` to generate a private key and a public key, `KemEncrypt(publicKey)` generate and encrypt a shared secret, and `KemDecrypt(ciphertext, privateKey)` to decrypt the shared secret.
* 🟢 **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.
* 🟢 **Good performance.** Kyber-K2SO is more than fast enough for regular usage in any environment supported by the Go programming language.
* 🟢 **Constant time (probably).** As far as we can tell, decryption appears to perform in constant time. Further analysis is encouraged.

Expand All @@ -37,20 +35,32 @@ import (
)

func main() {
privateKey, publicKey, _ := kyberk2so.KemKeypair()
ciphertext, ssA, _ := kyberk2so.KemEncrypt(publicKey)
ssB, _ := kyberk2so.KemDecrypt(ciphertext, privateKey)
privateKey, publicKey, _ := kyberk2so.KemKeypair768()
ciphertext, ssA, _ := kyberk2so.KemEncrypt768(publicKey)
ssB, _ := kyberk2so.KemDecrypt768(ciphertext, privateKey)
}
```

Yes, it's that simple!
Replace `768` with `512` or `1024` in the above function names in order to call Kyber-512 or Kyber-1024 instead of Kyber-768.

## Running Tests
```bash
> go test

> go test -v

=== RUN TestVectors512
--- PASS: TestVectors512 (0.01s)
=== RUN TestVectors768
--- PASS: TestVectors768 (0.01s)
=== RUN TestVectors1024
--- PASS: TestVectors1024 (0.01s)
=== RUN TestSelf512
--- PASS: TestSelf512 (0.19s)
=== RUN TestSelf768
--- PASS: TestSelf768 (0.30s)
=== RUN TestSelf1024
--- PASS: TestSelf1024 (0.46s)
PASS
ok github.com/symbolicsoft/kyber-k2so 3.114s
ok github.com/symbolicsoft/kyber-k2so 1.140s
```

## Running Benchmarks
Expand All @@ -60,9 +70,18 @@ ok github.com/symbolicsoft/kyber-k2so 3.114s
goos: linux
goarch: amd64
pkg: github.com/symbolicsoft/kyber-k2so
BenchmarkKemKeypair-8 1000000000 0.000144 ns/op
BenchmarkKemEncrypt-8 1000000000 0.000158 ns/op
BenchmarkKemDecrypt-8 1000000000 0.000179 ns/op
BenchmarkKemKeypair512-8 18256 55685 ns/op
BenchmarkKemKeypair768-8 12267 95178 ns/op
BenchmarkKemKeypair1024-8 10000 146807 ns/op
BenchmarkKemEncrypt512-8 16358 86358 ns/op
BenchmarkKemEncrypt768-8 7099 148577 ns/op
BenchmarkKemEncrypt1024-8 7285 188457 ns/op
BenchmarkKemDecrypt512-8 12092 113796 ns/op
BenchmarkKemDecrypt768-8 8926 138097 ns/op
BenchmarkKemDecrypt1024-8 6120 228477 ns/op
PASS
ok github.com/symbolicsoft/kyber-k2so 20.074s

```

# About Kyber-K2SO
Expand Down
702 changes: 702 additions & 0 deletions assets/PQCkemKAT_1632.rsp

Large diffs are not rendered by default.

702 changes: 702 additions & 0 deletions assets/PQCkemKAT_3168.rsp

Large diffs are not rendered by default.

116 changes: 69 additions & 47 deletions indcpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,54 @@ import (
"golang.org/x/crypto/sha3"
)

func indcpaPackPublicKey(publicKey polyvec, seed []byte) []byte {
return append(polyvecToBytes(publicKey), seed...)
func indcpaPackPublicKey(publicKey polyvec, seed []byte, paramsK int) []byte {
return append(polyvecToBytes(publicKey, paramsK), seed...)
}

func indcpaUnpackPublicKey(packedPublicKey []byte) (polyvec, []byte) {
publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytes])
seed := packedPublicKey[paramsPolyvecBytes:]
return publicKeyPolyvec, seed
func indcpaUnpackPublicKey(packedPublicKey []byte, paramsK int) (polyvec, []byte) {
switch paramsK {
case 2:
publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytesK2], paramsK)
seed := packedPublicKey[paramsPolyvecBytesK2:]
return publicKeyPolyvec, seed
case 3:
publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytesK3], paramsK)
seed := packedPublicKey[paramsPolyvecBytesK3:]
return publicKeyPolyvec, seed
default:
publicKeyPolyvec := polyvecFromBytes(packedPublicKey[:paramsPolyvecBytesK4], paramsK)
seed := packedPublicKey[paramsPolyvecBytesK4:]
return publicKeyPolyvec, seed
}
}

func indcpaPackPrivateKey(privateKey polyvec) []byte {
return polyvecToBytes(privateKey)
func indcpaPackPrivateKey(privateKey polyvec, paramsK int) []byte {
return polyvecToBytes(privateKey, paramsK)
}

func indcpaUnpackPrivateKey(packedPrivateKey []byte) polyvec {
return polyvecFromBytes(packedPrivateKey)
func indcpaUnpackPrivateKey(packedPrivateKey []byte, paramsK int) polyvec {
return polyvecFromBytes(packedPrivateKey, paramsK)
}

func indcpaPackCiphertext(b polyvec, v poly) []byte {
return append(polyvecCompress(b), polyCompress(v)...)
func indcpaPackCiphertext(b polyvec, v poly, paramsK int) []byte {
return append(polyvecCompress(b, paramsK), polyCompress(v, paramsK)...)
}

func indcpaUnpackCiphertext(c []byte) (polyvec, poly) {
b := polyvecDecompress(c[:paramsPolyvecCompressedBytes])
v := polyDecompress(c[paramsPolyvecCompressedBytes:])
return b, v
func indcpaUnpackCiphertext(c []byte, paramsK int) (polyvec, poly) {
switch paramsK {
case 2:
b := polyvecDecompress(c[:paramsPolyvecCompressedBytesK2], paramsK)
v := polyDecompress(c[paramsPolyvecCompressedBytesK2:], paramsK)
return b, v
case 3:
b := polyvecDecompress(c[:paramsPolyvecCompressedBytesK3], paramsK)
v := polyDecompress(c[paramsPolyvecCompressedBytesK3:], paramsK)
return b, v
default:
b := polyvecDecompress(c[:paramsPolyvecCompressedBytesK4], paramsK)
v := polyDecompress(c[paramsPolyvecCompressedBytesK4:], paramsK)
return b, v
}
}

func indcpaRejUniform(l int, buf []byte, bufl int) ([]int16, int) {
Expand All @@ -54,13 +76,13 @@ func indcpaRejUniform(l int, buf []byte, bufl int) ([]int16, int) {
return r, ctr
}

func indcpaGenMatrix(seed []byte, transposed bool) ([]polyvec, error) {
func indcpaGenMatrix(seed []byte, transposed bool, paramsK int) ([]polyvec, error) {
r := make([]polyvec, paramsK)
buf := make([]byte, 4*168)
xof := sha3.NewShake128()
ctr := 0
for i := 0; i < paramsK; i++ {
r[i] = polyvecNew()
r[i] = polyvecNew(paramsK)
for j := 0; j < paramsK; j++ {
transposon := []byte{byte(j), byte(i)}
if transposed {
Expand Down Expand Up @@ -100,10 +122,10 @@ func indcpaPrf(l int, key []byte, nonce byte) []byte {
return hash
}

func indcpaKeypair() ([]byte, []byte, error) {
skpv := polyvecNew()
pkpv := polyvecNew()
e := polyvecNew()
func indcpaKeypair(paramsK int) ([]byte, []byte, error) {
skpv := polyvecNew(paramsK)
pkpv := polyvecNew(paramsK)
e := polyvecNew(paramsK)
buf := make([]byte, 2*paramsSymBytes)
h := sha3.New512()
_, err := rand.Read(buf[:paramsSymBytes])
Expand All @@ -117,7 +139,7 @@ func indcpaKeypair() ([]byte, []byte, error) {
buf = buf[:0]
buf = h.Sum(buf)
publicSeed, noiseSeed := buf[:paramsSymBytes], buf[paramsSymBytes:]
a, err := indcpaGenMatrix(publicSeed, false)
a, err := indcpaGenMatrix(publicSeed, false, paramsK)
if err != nil {
return []byte{}, []byte{}, err
}
Expand All @@ -130,25 +152,25 @@ func indcpaKeypair() ([]byte, []byte, error) {
e.vec[i] = polyGetNoise(noiseSeed, nonce)
nonce = nonce + 1
}
skpv = polyvecNtt(skpv)
e = polyvecNtt(e)
skpv = polyvecNtt(skpv, paramsK)
e = polyvecNtt(e, paramsK)
for i := 0; i < paramsK; i++ {
pkpv.vec[i] = polyvecPointWiseAccMontgomery(a[i], skpv)
pkpv.vec[i] = polyvecPointWiseAccMontgomery(a[i], skpv, paramsK)
pkpv.vec[i] = polyToMont(pkpv.vec[i])
}
pkpv = polyvecAdd(pkpv, e)
pkpv = polyvecReduce(pkpv)
return indcpaPackPrivateKey(skpv), indcpaPackPublicKey(pkpv, publicSeed), nil
pkpv = polyvecAdd(pkpv, e, paramsK)
pkpv = polyvecReduce(pkpv, paramsK)
return indcpaPackPrivateKey(skpv, paramsK), indcpaPackPublicKey(pkpv, publicSeed, paramsK), nil
}

func indcpaEncrypt(m []byte, publicKey []byte, coins []byte) ([]byte, error) {
sp := polyvecNew()
ep := polyvecNew()
bp := polyvecNew()
func indcpaEncrypt(m []byte, publicKey []byte, coins []byte, paramsK int) ([]byte, error) {
sp := polyvecNew(paramsK)
ep := polyvecNew(paramsK)
bp := polyvecNew(paramsK)
nonce := byte(0)
publicKeyPolyvec, seed := indcpaUnpackPublicKey(publicKey)
publicKeyPolyvec, seed := indcpaUnpackPublicKey(publicKey, paramsK)
k := polyFromMsg(m)
at, err := indcpaGenMatrix(seed[:paramsSymBytes], true)
at, err := indcpaGenMatrix(seed[:paramsSymBytes], true, paramsK)
if err != nil {
return []byte{}, err
}
Expand All @@ -161,26 +183,26 @@ func indcpaEncrypt(m []byte, publicKey []byte, coins []byte) ([]byte, error) {
nonce = nonce + 1
}
epp := polyGetNoise(coins, nonce)
sp = polyvecNtt(sp)
sp = polyvecNtt(sp, paramsK)
for i := 0; i < paramsK; i++ {
bp.vec[i] = polyvecPointWiseAccMontgomery(at[i], sp)
bp.vec[i] = polyvecPointWiseAccMontgomery(at[i], sp, paramsK)
}
v := polyvecPointWiseAccMontgomery(publicKeyPolyvec, sp)
bp = polyvecInvNttToMont(bp)
v := polyvecPointWiseAccMontgomery(publicKeyPolyvec, sp, paramsK)
bp = polyvecInvNttToMont(bp, paramsK)
v = polyInvNttToMont(v)
bp = polyvecAdd(bp, ep)
bp = polyvecAdd(bp, ep, paramsK)
v = polyAdd(v, epp)
v = polyAdd(v, k)
bp = polyvecReduce(bp)
bp = polyvecReduce(bp, paramsK)
v = polyReduce(v)
return indcpaPackCiphertext(bp, v), nil
return indcpaPackCiphertext(bp, v, paramsK), nil
}

func indcpaDecrypt(c []byte, privateKey []byte) []byte {
bp, v := indcpaUnpackCiphertext(c)
privateKeyPolyvec := indcpaUnpackPrivateKey(privateKey)
bp = polyvecNtt(bp)
mp := polyvecPointWiseAccMontgomery(privateKeyPolyvec, bp)
func indcpaDecrypt(c []byte, privateKey []byte, paramsK int) []byte {
bp, v := indcpaUnpackCiphertext(c, paramsK)
privateKeyPolyvec := indcpaUnpackPrivateKey(privateKey, paramsK)
bp = polyvecNtt(bp, paramsK)
mp := polyvecPointWiseAccMontgomery(privateKeyPolyvec, bp, paramsK)
mp = polyInvNttToMont(mp)
mp = polySub(v, mp)
mp = polyReduce(mp)
Expand Down
Loading

0 comments on commit 38c8472

Please sign in to comment.