RTG-1339 Support post-quantum hybrid key exchange

Func spec: https://wiki.cfops.it/x/ZcBKHw
This commit is contained in:
Bas Westerbaan
2022-08-24 14:33:10 +02:00
committed by Devin Carr
parent 3e0ff3a771
commit 11cbff4ff7
171 changed files with 15270 additions and 196 deletions

View File

@@ -0,0 +1,174 @@
package internal
import (
"github.com/cloudflare/circl/internal/sha3"
"github.com/cloudflare/circl/pke/kyber/internal/common"
)
// A Kyber.CPAPKE private key.
type PrivateKey struct {
sh Vec // NTT(s), normalized
}
// A Kyber.CPAPKE public key.
type PublicKey struct {
rho [32]byte // ρ, the seed for the matrix A
th Vec // NTT(t), normalized
// cached values
aT Mat // the matrix Aᵀ
}
// Packs the private key to buf.
func (sk *PrivateKey) Pack(buf []byte) {
sk.sh.Pack(buf)
}
// Unpacks the private key from buf.
func (sk *PrivateKey) Unpack(buf []byte) {
sk.sh.Unpack(buf)
sk.sh.Normalize()
}
// Packs the public key to buf.
func (pk *PublicKey) Pack(buf []byte) {
pk.th.Pack(buf)
copy(buf[K*common.PolySize:], pk.rho[:])
}
// Unpacks the public key from buf.
func (pk *PublicKey) Unpack(buf []byte) {
pk.th.Unpack(buf)
pk.th.Normalize()
copy(pk.rho[:], buf[K*common.PolySize:])
pk.aT.Derive(&pk.rho, true)
}
// Derives a new Kyber.CPAPKE keypair from the given seed.
func NewKeyFromSeed(seed []byte) (*PublicKey, *PrivateKey) {
var pk PublicKey
var sk PrivateKey
var expandedSeed [64]byte
h := sha3.New512()
_, _ = h.Write(seed)
// This writes hash into expandedSeed. Yes, this is idiomatic Go.
_, _ = h.Read(expandedSeed[:])
copy(pk.rho[:], expandedSeed[:32])
sigma := expandedSeed[32:] // σ, the noise seed
pk.aT.Derive(&pk.rho, false) // Expand ρ to matrix A; we'll transpose later
var eh Vec
sk.sh.DeriveNoise(sigma, 0, Eta1) // Sample secret vector s
sk.sh.NTT()
sk.sh.Normalize()
eh.DeriveNoise(sigma, K, Eta1) // Sample blind e
eh.NTT()
// Next, we compute t = A s + e.
for i := 0; i < K; i++ {
// Note that coefficients of s are bounded by q and those of A
// are bounded by 4.5q and so their product is bounded by 2¹⁵q
// as required for multiplication.
PolyDotHat(&pk.th[i], &pk.aT[i], &sk.sh)
// A and s were not in Montgomery form, so the Montgomery
// multiplications in the inner product added a factor R⁻¹ which
// we'll cancel out now. This will also ensure the coefficients of
// t are bounded in absolute value by q.
pk.th[i].ToMont()
}
pk.th.Add(&pk.th, &eh) // bounded by 8q.
pk.th.Normalize()
pk.aT.Transpose()
return &pk, &sk
}
// Decrypts ciphertext ct meant for private key sk to plaintext pt.
func (sk *PrivateKey) DecryptTo(pt, ct []byte) {
var u Vec
var v, m common.Poly
u.Decompress(ct, DU)
v.Decompress(ct[K*compressedPolySize(DU):], DV)
// Compute m = v - <s, u>
u.NTT()
PolyDotHat(&m, &sk.sh, &u)
m.BarrettReduce()
m.InvNTT()
m.Sub(&v, &m)
m.Normalize()
// Compress polynomial m to original message
m.CompressMessageTo(pt)
}
// Encrypts message pt for the public key to ciphertext ct using randomness
// from seed.
//
// seed has to be of length SeedSize, pt of PlaintextSize and ct of
// CiphertextSize.
func (pk *PublicKey) EncryptTo(ct, pt, seed []byte) {
var rh, e1, u Vec
var e2, v, m common.Poly
// Sample r, e₁ and e₂ from B_η
rh.DeriveNoise(seed, 0, Eta1)
rh.NTT()
rh.BarrettReduce()
e1.DeriveNoise(seed, K, common.Eta2)
e2.DeriveNoise(seed, 2*K, common.Eta2)
// Next we compute u = Aᵀ r + e₁. First Aᵀ.
for i := 0; i < K; i++ {
// Note that coefficients of r are bounded by q and those of Aᵀ
// are bounded by 4.5q and so their product is bounded by 2¹⁵q
// as required for multiplication.
PolyDotHat(&u[i], &pk.aT[i], &rh)
}
u.BarrettReduce()
// Aᵀ and r were not in Montgomery form, so the Montgomery
// multiplications in the inner product added a factor R⁻¹ which
// the InvNTT cancels out.
u.InvNTT()
u.Add(&u, &e1) // u = Aᵀ r + e₁
// Next compute v = <t, r> + e₂ + Decompress_q(m, 1).
PolyDotHat(&v, &pk.th, &rh)
v.BarrettReduce()
v.InvNTT()
m.DecompressMessage(pt)
v.Add(&v, &m)
v.Add(&v, &e2) // v = <t, r> + e₂ + Decompress_q(m, 1)
// Pack ciphertext
u.Normalize()
v.Normalize()
u.CompressTo(ct, DU)
v.CompressTo(ct[K*compressedPolySize(DU):], DV)
}
// Returns whether sk equals other.
func (sk *PrivateKey) Equal(other *PrivateKey) bool {
ret := int16(0)
for i := 0; i < K; i++ {
for j := 0; j < common.N; j++ {
ret |= sk.sh[i][j] ^ other.sh[i][j]
}
}
return ret == 0
}

View File

@@ -0,0 +1,83 @@
package internal
import (
"github.com/cloudflare/circl/pke/kyber/internal/common"
)
// A k by k matrix of polynomials.
type Mat [K]Vec
// Expands the given seed to the corresponding matrix A or its transpose Aᵀ.
func (m *Mat) Derive(seed *[32]byte, transpose bool) {
if !common.DeriveX4Available {
if transpose {
for i := 0; i < K; i++ {
for j := 0; j < K; j++ {
m[i][j].DeriveUniform(seed, uint8(i), uint8(j))
}
}
} else {
for i := 0; i < K; i++ {
for j := 0; j < K; j++ {
m[i][j].DeriveUniform(seed, uint8(j), uint8(i))
}
}
}
return
}
var ps [4]*common.Poly
var xs [4]uint8
var ys [4]uint8
x := uint8(0)
y := uint8(0)
for x != K {
idx := 0
for ; idx < 4; idx++ {
ps[idx] = &m[x][y]
if transpose {
xs[idx] = x
ys[idx] = y
} else {
xs[idx] = y
ys[idx] = x
}
y++
if y == K {
x++
y = 0
if x == K {
if idx == 0 {
// If there is just one left, then a plain DeriveUniform
// is quicker than the X4 variant.
ps[0].DeriveUniform(seed, xs[0], ys[0])
return
}
for idx++; idx < 4; idx++ {
ps[idx] = nil
}
break
}
}
}
common.PolyDeriveUniformX4(ps, seed, xs, ys)
}
}
// Tranposes A in place.
func (m *Mat) Transpose() {
for i := 0; i < K-1; i++ {
for j := i + 1; j < K; j++ {
t := m[i][j]
m[i][j] = m[j][i]
m[j][i] = t
}
}
}

View File

@@ -0,0 +1,21 @@
// Code generated from params.templ.go. DO NOT EDIT.
package internal
import (
"github.com/cloudflare/circl/pke/kyber/internal/common"
)
const (
K = 2
Eta1 = 3
DU = 10
DV = 4
PublicKeySize = 32 + K*common.PolySize
PrivateKeySize = K * common.PolySize
PlaintextSize = common.PlaintextSize
SeedSize = 32
CiphertextSize = 768
)

View File

@@ -0,0 +1,123 @@
package internal
import (
"github.com/cloudflare/circl/pke/kyber/internal/common"
)
// A vector of K polynomials
type Vec [K]common.Poly
// Samples v[i] from a centered binomial distribution with given η,
// seed and nonce+i.
//
// Essentially CBD_η(PRF(seed, nonce+i)) from the specification.
func (v *Vec) DeriveNoise(seed []byte, nonce uint8, eta int) {
for i := 0; i < K; i++ {
v[i].DeriveNoise(seed, nonce+uint8(i), eta)
}
}
// Sets p to the inner product of a and b using "pointwise" multiplication.
//
// See MulHat() and NTT() for a description of the multiplication.
// Assumes a and b are in Montgomery form. p will be in Montgomery form,
// and its coefficients will be bounded in absolute value by 2kq.
// If a and b are not in Montgomery form, then the action is the same
// as "pointwise" multiplication followed by multiplying by R⁻¹, the inverse
// of the Montgomery factor.
func PolyDotHat(p *common.Poly, a, b *Vec) {
var t common.Poly
*p = common.Poly{} // set p to zero
for i := 0; i < K; i++ {
t.MulHat(&a[i], &b[i])
p.Add(&t, p)
}
}
// Almost normalizes coefficients in-place.
//
// Ensures each coefficient is in {0, …, q}.
func (v *Vec) BarrettReduce() {
for i := 0; i < K; i++ {
v[i].BarrettReduce()
}
}
// Normalizes coefficients in-place.
//
// Ensures each coefficient is in {0, …, q-1}.
func (v *Vec) Normalize() {
for i := 0; i < K; i++ {
v[i].Normalize()
}
}
// Applies in-place inverse NTT(). See Poly.InvNTT() for assumptions.
func (v *Vec) InvNTT() {
for i := 0; i < K; i++ {
v[i].InvNTT()
}
}
// Applies in-place forward NTT(). See Poly.NTT() for assumptions.
func (v *Vec) NTT() {
for i := 0; i < K; i++ {
v[i].NTT()
}
}
// Sets v to a + b.
func (v *Vec) Add(a, b *Vec) {
for i := 0; i < K; i++ {
v[i].Add(&a[i], &b[i])
}
}
// Packs v into buf, which must be of length K*PolySize.
func (v *Vec) Pack(buf []byte) {
for i := 0; i < K; i++ {
v[i].Pack(buf[common.PolySize*i:])
}
}
// Unpacks v from buf which must be of length K*PolySize.
func (v *Vec) Unpack(buf []byte) {
for i := 0; i < K; i++ {
v[i].Unpack(buf[common.PolySize*i:])
}
}
// Writes Compress_q(v, d) to m.
//
// Assumes v is normalized and d is in {3, 4, 5, 10, 11}.
func (v *Vec) CompressTo(m []byte, d int) {
size := compressedPolySize(d)
for i := 0; i < K; i++ {
v[i].CompressTo(m[size*i:], d)
}
}
// Set v to Decompress_q(m, 1).
//
// Assumes d is in {3, 4, 5, 10, 11}. v will be normalized.
func (v *Vec) Decompress(m []byte, d int) {
size := compressedPolySize(d)
for i := 0; i < K; i++ {
v[i].Decompress(m[size*i:], d)
}
}
// ⌈(256 d)/8⌉
func compressedPolySize(d int) int {
switch d {
case 4:
return 128
case 5:
return 160
case 10:
return 320
case 11:
return 352
}
panic("unsupported d")
}

View File

@@ -0,0 +1,145 @@
// Code generated from modePkg.templ.go. DO NOT EDIT.
// kyber512 implements the IND-CPA-secure Public Key Encryption
// scheme Kyber512.CPAPKE as submitted to round 3 of the NIST PQC competition
// and described in
//
// https://pq-crystals.org/kyber/data/kyber-specification-round3.pdf
package kyber512
import (
cryptoRand "crypto/rand"
"io"
"github.com/cloudflare/circl/pke/kyber/kyber512/internal"
)
const (
// Size of seed for NewKeyFromSeed
KeySeedSize = internal.SeedSize
// Size of seed for EncryptTo
EncryptionSeedSize = internal.SeedSize
// Size of a packed PublicKey
PublicKeySize = internal.PublicKeySize
// Size of a packed PrivateKey
PrivateKeySize = internal.PrivateKeySize
// Size of a ciphertext
CiphertextSize = internal.CiphertextSize
// Size of a plaintext
PlaintextSize = internal.PlaintextSize
)
// PublicKey is the type of Kyber512.CPAPKE public key
type PublicKey internal.PublicKey
// PrivateKey is the type of Kyber512.CPAPKE private key
type PrivateKey internal.PrivateKey
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (*PublicKey, *PrivateKey, error) {
var seed [KeySeedSize]byte
if rand == nil {
rand = cryptoRand.Reader
}
_, err := io.ReadFull(rand, seed[:])
if err != nil {
return nil, nil, err
}
pk, sk := internal.NewKeyFromSeed(seed[:])
return (*PublicKey)(pk), (*PrivateKey)(sk), nil
}
// NewKeyFromSeed derives a public/private key pair using the given seed.
//
// Panics if seed is not of length KeySeedSize.
func NewKeyFromSeed(seed []byte) (*PublicKey, *PrivateKey) {
if len(seed) != KeySeedSize {
panic("seed must be of length KeySeedSize")
}
pk, sk := internal.NewKeyFromSeed(seed)
return (*PublicKey)(pk), (*PrivateKey)(sk)
}
// EncryptTo encrypts message pt for the public key and writes the ciphertext
// to ct using randomness from seed.
//
// This function panics if the lengths of pt, seed, and ct are not
// PlaintextSize, EncryptionSeedSize, and CiphertextSize respectively.
func (pk *PublicKey) EncryptTo(ct []byte, pt []byte, seed []byte) {
if len(pt) != PlaintextSize {
panic("pt must be of length PlaintextSize")
}
if len(ct) != CiphertextSize {
panic("ct must be of length CiphertextSize")
}
if len(seed) != EncryptionSeedSize {
panic("seed must be of length EncryptionSeedSize")
}
(*internal.PublicKey)(pk).EncryptTo(ct, pt, seed)
}
// DecryptTo decrypts message ct for the private key and writes the
// plaintext to pt.
//
// This function panics if the lengths of ct and pt are not
// CiphertextSize and PlaintextSize respectively.
func (sk *PrivateKey) DecryptTo(pt []byte, ct []byte) {
if len(pt) != PlaintextSize {
panic("pt must be of length PlaintextSize")
}
if len(ct) != CiphertextSize {
panic("ct must be of length CiphertextSize")
}
(*internal.PrivateKey)(sk).DecryptTo(pt, ct)
}
// Packs pk into the given buffer.
//
// Panics if buf is not of length PublicKeySize.
func (pk *PublicKey) Pack(buf []byte) {
if len(buf) != PublicKeySize {
panic("buf must be of size PublicKeySize")
}
(*internal.PublicKey)(pk).Pack(buf)
}
// Packs sk into the given buffer.
//
// Panics if buf is not of length PrivateKeySize.
func (sk *PrivateKey) Pack(buf []byte) {
if len(buf) != PrivateKeySize {
panic("buf must be of size PrivateKeySize")
}
(*internal.PrivateKey)(sk).Pack(buf)
}
// Unpacks pk from the given buffer.
//
// Panics if buf is not of length PublicKeySize.
func (pk *PublicKey) Unpack(buf []byte) {
if len(buf) != PublicKeySize {
panic("buf must be of size PublicKeySize")
}
(*internal.PublicKey)(pk).Unpack(buf)
}
// Unpacks sk from the given buffer.
//
// Panics if buf is not of length PrivateKeySize.
func (sk *PrivateKey) Unpack(buf []byte) {
if len(buf) != PrivateKeySize {
panic("buf must be of size PrivateKeySize")
}
(*internal.PrivateKey)(sk).Unpack(buf)
}
// Returns whether the two private keys are equal.
func (sk *PrivateKey) Equal(other *PrivateKey) bool {
return (*internal.PrivateKey)(sk).Equal((*internal.PrivateKey)(other))
}