diff --git a/kem/ntruprime/doc.go b/kem/ntruprime/doc.go new file mode 100644 index 000000000..b6f770b32 --- /dev/null +++ b/kem/ntruprime/doc.go @@ -0,0 +1,10 @@ +//go:generate go run gen.go + +// Package ntruprime implements the NTRU Prime IND-CCA2 secure +// key encapsulation mechanism (KEM) as submitted to round 3 of the NIST PQC +// competition and described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +// +// The code is translated from the C reference implementation. +package ntruprime diff --git a/kem/ntruprime/gen.go b/kem/ntruprime/gen.go new file mode 100644 index 000000000..24f863af4 --- /dev/null +++ b/kem/ntruprime/gen.go @@ -0,0 +1,139 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "bytes" + "go/format" + "io/ioutil" + "strings" + "text/template" +) + +type Instance struct { + Name string + Hash string +} + +func (m Instance) Pkg() string { + return strings.ToLower(m.Name) +} + +var ( + SInstances = []Instance{ + {Name: "SNTRUP761"}, + {Name: "SNTRUP653"}, + {Name: "SNTRUP857"}, + {Name: "SNTRUP953"}, + {Name: "SNTRUP1013"}, + {Name: "SNTRUP1277"}, + } + LPRInstances = []Instance{ + {Name: "NTRULPR761"}, + {Name: "NTRULPR653"}, + {Name: "NTRULPR857"}, + {Name: "NTRULPR953"}, + {Name: "NTRULPR1013"}, + {Name: "NTRULPR1277"}, + } + TemplateWarning = "// Code generated from" +) + +func main() { + generateStreamlinedPackageFiles() + generateLPRPackageFiles() +} + +func generateStreamlinedPackageFiles() { + template, err := template.ParseFiles("templates/sntrup.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range SInstances { + buf := new(bytes.Buffer) + err := template.Execute(buf, mode) + if err != nil { + panic(err) + } + + // Formating output code + code, err := format.Source(buf.Bytes()) + if err != nil { + panic("error formating code") + } + + res := string(code) + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in pkg.templ.go") + } + err = ioutil.WriteFile(mode.Pkg()+"/ntruprime.go", []byte(res[offset:]), 0o644) + if err != nil { + panic(err) + } + } +} + +func generateLPRPackageFiles() { + template, err := template.ParseFiles("templates/ntrulpr.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range LPRInstances { + buf := new(bytes.Buffer) + err := template.Execute(buf, mode) + if err != nil { + panic(err) + } + + // Formating output code + code, err := format.Source(buf.Bytes()) + if err != nil { + panic("error formating code") + } + + res := string(code) + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in pkg.templ.go") + } + err = ioutil.WriteFile(mode.Pkg()+"/ntruprime.go", []byte(res[offset:]), 0o644) + if err != nil { + panic(err) + } + } +} + +func generateKAT() { + template, err := template.ParseFiles("templates/kat.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range SInstances { + buf := new(bytes.Buffer) + err := template.Execute(buf, mode) + if err != nil { + panic(err) + } + + // Formating output code + code, err := format.Source(buf.Bytes()) + if err != nil { + panic("error formating code") + } + + res := string(code) + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in pkg.templ.go") + } + err = ioutil.WriteFile(mode.Pkg()+"/kat_test.go", []byte(res[offset:]), 0o600) + if err != nil { + panic(err) + } + } +} diff --git a/kem/ntruprime/internal/Decode.go b/kem/ntruprime/internal/Decode.go new file mode 100644 index 000000000..770112526 --- /dev/null +++ b/kem/ntruprime/internal/Decode.go @@ -0,0 +1,67 @@ +package internal + +// TO DO: Optimize the Decode function +/* Decode(R,s,M,len) */ +/* assumes 0 < M[i] < 16384 */ +/* produces 0 <= R[i] < M[i] */ +func Decode(out []uint16, S []uint8, M []uint16, len int) { + index := 0 + if len == 1 { + if M[0] == 1 { + out[index] = 0 + } else if M[0] <= 256 { + out[index] = Uint32ModUint14(uint32(S[0]), M[0]) + } else { + out[index] = Uint32ModUint14(uint32(uint16(S[0])+((uint16(S[1]))<<8)), M[0]) + } + } + if len > 1 { + R2 := make([]uint16, (len+1)/2) + M2 := make([]uint16, (len+1)/2) + bottomr := make([]uint16, len/2) + bottomt := make([]uint32, len/2) + i := 0 + for i = 0; i < len-1; i += 2 { + m := uint32(M[i]) * uint32(M[i+1]) + + if m > 256*16383 { + bottomt[i/2] = 256 * 256 + bottomr[i/2] = uint16(S[0]) + 256*uint16(S[1]) + S = S[2:] + M2[i/2] = uint16((((m + 255) >> 8) + 255) >> 8) + } else if m >= 16384 { + bottomt[i/2] = 256 + bottomr[i/2] = uint16(S[0]) + S = S[1:] + M2[i/2] = uint16((m + 255) >> 8) + } else { + bottomt[i/2] = 1 + bottomr[i/2] = 0 + M2[i/2] = uint16(m) + } + } + if i < len { + M2[i/2] = M[i] + } + + Decode(R2, S, M2, (len+1)/2) + + for i = 0; i < len-1; i += 2 { + r := uint32(bottomr[i/2]) + var r1 uint32 + var r0 uint16 + + r += bottomt[i/2] * uint32(R2[i/2]) + Uint32DivmodUint14(&r1, &r0, r, M[i]) + r1 = uint32(Uint32ModUint14(r1, M[i+1])) /* only needed for invalid inputs */ + + out[index] = r0 + index++ + out[index] = uint16(r1) + index++ + } + if i < len { + out[index] = R2[i/2] + } + } +} diff --git a/kem/ntruprime/internal/Divmod.go b/kem/ntruprime/internal/Divmod.go new file mode 100644 index 000000000..8990c599c --- /dev/null +++ b/kem/ntruprime/internal/Divmod.go @@ -0,0 +1,102 @@ +package internal + +/* +CPU division instruction typically takes time depending on x. +This software is designed to take time independent of x. +Time still varies depending on m; user must ensure that m is constant. +Time also varies on CPUs where multiplication is variable-time. +There could be more CPU issues. +There could also be compiler issues. +*/ +// q, r = x/m +// Returns quotient and remainder +func Uint32DivmodUint14(q *uint32, r *uint16, x uint32, m uint16) { + var v uint32 = 0x80000000 + + v /= uint32(m) + + *q = 0 + + qpart := uint32(uint64(x) * uint64(v) >> 31) + + x -= qpart * uint32(m) + *q += qpart + + qpart = uint32(uint64(x) * uint64(v) >> 31) + x -= qpart * uint32(m) + *q += qpart + + x -= uint32(m) + *q += 1 + mask := -(x >> 31) + x += mask & uint32(m) + *q += mask + + *r = uint16(x) +} + +// Returns the quotient of x/m +func Uint32DivUint14(x uint32, m uint16) uint32 { + var q uint32 + var r uint16 + Uint32DivmodUint14(&q, &r, x, m) + return q +} + +// Returns the remainder of x/m +func Uint32ModUint14(x uint32, m uint16) uint16 { + var q uint32 + var r uint16 + Uint32DivmodUint14(&q, &r, x, m) + return r +} + +// Calculates quotient and remainder +func Int32DivmodUint14(q *int32, r *uint16, x int32, m uint16) { + var uq, uq2 uint32 + var ur, ur2 uint16 + var mask uint32 + + Uint32DivmodUint14(&uq, &ur, 0x80000000+uint32(x), m) + Uint32DivmodUint14(&uq2, &ur2, 0x80000000, m) + + ur -= ur2 + uq -= uq2 + mask = -(uint32)(ur >> 15) + ur += uint16(mask & uint32(m)) + uq += mask + *r = ur + *q = int32(uq) +} + +// Returns quotient of x/m +func Int32DivUint14(x int32, m uint16) int32 { + var q int32 + var r uint16 + Int32DivmodUint14(&q, &r, x, m) + return q +} + +// Returns remainder of x/m +func Int32ModUint14(x int32, m uint16) uint16 { + var q int32 + var r uint16 + Int32DivmodUint14(&q, &r, x, m) + return r +} + +// Returns -1 if x!=0; else return 0 +func Int16NonzeroMask(x int16) int { + u := uint16(x) /* 0, else 1...65535 */ + v := uint32(u) /* 0, else 1...65535 */ + v = -v /* 0, else 2^32-65535...2^32-1 */ + v >>= 31 /* 0, else 1 */ + return -int(v) /* 0, else -1 */ +} + +// Returns -1 if x<0; otherwise return 0 +func Int16NegativeMask(x int16) int { + u := uint16(x) + u >>= 15 + return -(int)(u) +} diff --git a/kem/ntruprime/internal/Encode.go b/kem/ntruprime/internal/Encode.go new file mode 100644 index 000000000..effae5e83 --- /dev/null +++ b/kem/ntruprime/internal/Encode.go @@ -0,0 +1,42 @@ +package internal + +/* 0 <= R[i] < M[i] < 16384 */ +func Encode(out []uint8, R []uint16, M []uint16, len int) { + if len > 1 { + R2 := make([]uint16, (len+1)/2) + M2 := make([]uint16, (len+1)/2) + var i int + for ; len > 1; len = (len + 1) / 2 { + for i = 0; i < len-1; i += 2 { + m0 := uint32(M[i]) + r := uint32(R[i]) + uint32(R[i+1])*m0 + m := uint32(M[i+1]) * m0 + for m >= 16384 { + out[0] = uint8(r) + out = out[1:] + + r >>= 8 + m = (m + 255) >> 8 + } + R2[i/2] = uint16(r) + M2[i/2] = uint16(m) + } + if i < len { + R2[i/2] = R[i] + M2[i/2] = M[i] + } + copy(R, R2) + copy(M, M2) + } + } + if len == 1 { + r := R[0] + m := M[0] + for m > 1 { + out[0] = uint8(r) + out = out[1:] + r >>= 8 + m = (m + 255) >> 8 + } + } +} diff --git a/kem/ntruprime/kat_test.go b/kem/ntruprime/kat_test.go new file mode 100644 index 000000000..1cb07c66f --- /dev/null +++ b/kem/ntruprime/kat_test.go @@ -0,0 +1,165 @@ +package ntruprime + +import ( + "bytes" + "crypto/sha512" + "fmt" + "testing" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem/schemes" + sntrupSchemes "github.com/cloudflare/circl/pke/ntruprime/kem/schemes/sntrup" +) + +func TestPQCgenStreamlinedKATKem(t *testing.T) { + kats := []struct { + name string + want string + p int + }{ + // Computed from reference implementation + {"sntrup653", "82249a46c1bc538e980a2335764c81f70701e6374eed3e1d0457e18c57ec2cee64280dcc75504c2648eb3e37ab3eee37955c1114d851f755a28cc997aba781c8", 653}, + {"sntrup761", "1a687f42261c47fe4421b35c5d9faf035433fcb2101458680c66c8d54caafec5fb767ea7725d6681ab100912ef06c38d88862a5d2d86786af2989b7dad33813a", 761}, + {"sntrup857", "79473d6c709dbbc99528886bf2c1d033c409dab1755299154f33232bc57ba1fbe91322fcb741df5252d575a77aa5ca000d52a44c17f1ab64a299884d0f101519", 857}, + {"sntrup953", "6fe0cf3b8cb62a3011c1870ec9eb3cd8825c06993a213e01ecd0f21f5dee670838fe1c89dd120086a09e8227496a00e22188c8f947618a35764c5a24726ce16c", 953}, + {"sntrup1013", "195a38eb843fdda53241f65b641ab925f61fb1cf5b0fffcb5891115da121a85174a796d69c75b86c4e92193453155aef9d27ce53aa268076617be55ee6f5da4f", 1013}, + {"sntrup1277", "ada8a0cbe6b077dc563874fd372f60779bbee1524f576c2931cf9c804163b9632163610d6e380f889170cdf4d9928de0782368a43413f2b6976897ba0e19a828", 1277}, + } + + for _, kat := range kats { + kat := kat + t.Run(kat.name, func(t *testing.T) { + testPQCgenStreamlinedKATKem(t, kat.name, kat.want, kat.p) + }) + } +} + +func TestPQCgenLPRKATKem(t *testing.T) { + kats := []struct { + name string + want string + p int + }{ + // Computed from reference implementation + {"ntrulpr653", "30b750e9bcf5a14d0dc10a1a4f0ff4269f7ff7a5b8b835fe7d50d45de3653bbb33c3943fc50759175ba7fef92fd601ac705d7658d3f15a8a7610973ef098e849", 653}, + {"ntrulpr761", "35f9b8191aef509766019015b7af11dd2afaadf7fca827a9b0a80f7318b7e8325345c64d5b5562ee321465378102850297fbbd70fe78c5bd711e382015189e5a", 761}, + {"ntrulpr857", "919c675a5b1f642d97b866a284c633f52ad309a1f24a5713fa2f7839a84d07091b2c5a80841ce73a2090cc0ce707d9f262772f730d15905ab238a7be1c1e1e3e", 857}, + {"ntrulpr953", "2ae003933ca87d873956969977b3d7b5133e42df0868a0cbb77067cf9144ce18b0e4342ba850e2f4d46257aaea23f1e290448e3a34e6774f9594230343de7038", 953}, + {"ntrulpr1013", "5c054bab923095d3dc4250e5e71923c98b7e3bc778aa4a2a4235b8751106eac2cf0e41dc413d1b6fc7bdc8301a46ca206b19b6301c554cf643d473a55a5940a1", 1013}, + {"ntrulpr1277", "1ec1702ff090324385fdd98a7f1c1adfe80503593e3531c2c3ed7547df47da38fcdd8dedf142d2b426b3f98015b5c8fe3688b41808c513bdada66a15b7f727ab", 1277}, + } + + for _, kat := range kats { + kat := kat + t.Run(kat.name, func(t *testing.T) { + testPQCgenLPRKATKem(t, kat.name, kat.want, kat.p) + }) + } +} + +func testPQCgenLPRKATKem(t *testing.T, name, expected string, p int) { + scheme := schemes.ByName(name) + if scheme == nil { + t.Fatal() + } + + var seed [48]byte + kseed := make([]byte, scheme.SeedSize()) + eseed := make([]byte, scheme.EncapsulationSeedSize()) + seedBytes := 32 + + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + + g1 := nist.NewDRBG(&seed) + + f := sha512.New() + + fmt.Fprintf(f, "# kem/%s\n\n", name) + + for i := 0; i < 100; i++ { + g1.Fill(seed[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + + g2 := nist.NewDRBG(&seed) + + g2.Fill(kseed[:seedBytes]) + for i := 0; i < p; i++ { + g2.Fill(kseed[seedBytes+i*4 : seedBytes+i*4+4]) + } + g2.Fill(kseed[seedBytes+p*4:]) + + pk, sk := scheme.DeriveKeyPair(kseed) + ppk, _ := pk.MarshalBinary() + psk, _ := sk.MarshalBinary() + + g2.Fill(eseed) + ct, ss1, err := scheme.EncapsulateDeterministically(pk, eseed) + if err != nil { + t.Fatal(err) + } + ss2, _ := scheme.Decapsulate(sk, ct) + + if !bytes.Equal(ss1[:], ss2[:]) { + t.Fatal() + } + fmt.Fprintf(f, "pk = %X\n", ppk) + fmt.Fprintf(f, "sk = %X\n", psk) + fmt.Fprintf(f, "ct = %X\n", ct) + fmt.Fprintf(f, "ss = %X\n\n", ss1) + } + if fmt.Sprintf("%x", f.Sum(nil)) != expected { + t.Fatal() + } +} + +func testPQCgenStreamlinedKATKem(t *testing.T, name, expected string, p int) { + scheme := sntrupSchemes.ByName(name) + if scheme == nil { + t.Fatal() + } + + var seed [48]byte + eseed := make([]byte, scheme.EncapsulationSeedSize()) + + for i := 0; i < 48; i++ { + seed[i] = byte(i) + } + + g1 := nist.NewDRBG(&seed) + f := sha512.New() + + fmt.Fprintf(f, "# kem/%s\n\n", name) + for i := 0; i < 100; i++ { + g1.Fill(seed[:]) + + fmt.Fprintf(f, "count = %d\n", i) + fmt.Fprintf(f, "seed = %X\n", seed) + + g2 := nist.NewDRBG(&seed) + + pk, sk := scheme.DeriveKeyPairFromGen(&g2) + ppk, _ := pk.MarshalBinary() + psk, _ := sk.MarshalBinary() + + for i := 0; i < p; i++ { + g2.Fill(eseed[4*i : 4*i+4]) + } + ct, ss1, _ := scheme.EncapsulateDeterministically(pk, eseed) + + ss2, _ := scheme.Decapsulate(sk, ct) + if !bytes.Equal(ss1, ss2) { + t.Fatal() + } + fmt.Fprintf(f, "pk = %X\n", ppk) + fmt.Fprintf(f, "sk = %X\n", psk) + fmt.Fprintf(f, "ct = %X\n", ct) + fmt.Fprintf(f, "ss = %X\n\n", ss1) + } + if fmt.Sprintf("%x", f.Sum(nil)) != expected { + t.Fatal() + } +} diff --git a/kem/ntruprime/ntrulpr1013/ntruprime.go b/kem/ntruprime/ntrulpr1013/ntruprime.go new file mode 100644 index 000000000..fc7566330 --- /dev/null +++ b/kem/ntruprime/ntrulpr1013/ntruprime.go @@ -0,0 +1,832 @@ +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package ntrulpr1013 implements the IND-CCA2 secure key encapsulation mechanism +// ntrulpr1013 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package ntrulpr1013 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/ntrulpr1013" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a, seed) + rqMultSmall(aG, G, a) + round(A, aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8, r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B, bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext = ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small, r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G, S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G, S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte, T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B := make([]Fq, p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B, T, r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c, T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "ntrulpr1013" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/ntrulpr1277/ntruprime.go b/kem/ntruprime/ntrulpr1277/ntruprime.go new file mode 100644 index 000000000..8634ea739 --- /dev/null +++ b/kem/ntruprime/ntrulpr1277/ntruprime.go @@ -0,0 +1,832 @@ +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package ntrulpr1277 implements the IND-CCA2 secure key encapsulation mechanism +// ntrulpr1277 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package ntrulpr1277 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/ntrulpr1277" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a, seed) + rqMultSmall(aG, G, a) + round(A, aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8, r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B, bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext = ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small, r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G, S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G, S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte, T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B := make([]Fq, p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B, T, r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c, T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "ntrulpr1277" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/ntrulpr653/ntruprime.go b/kem/ntruprime/ntrulpr653/ntruprime.go new file mode 100644 index 000000000..758f14313 --- /dev/null +++ b/kem/ntruprime/ntrulpr653/ntruprime.go @@ -0,0 +1,832 @@ +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package ntrulpr653 implements the IND-CCA2 secure key encapsulation mechanism +// ntrulpr653 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package ntrulpr653 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/ntrulpr653" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a, seed) + rqMultSmall(aG, G, a) + round(A, aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8, r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B, bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext = ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small, r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G, S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G, S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte, T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B := make([]Fq, p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B, T, r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c, T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "ntrulpr653" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/ntrulpr761/ntruprime.go b/kem/ntruprime/ntrulpr761/ntruprime.go new file mode 100644 index 000000000..214afc58b --- /dev/null +++ b/kem/ntruprime/ntrulpr761/ntruprime.go @@ -0,0 +1,832 @@ +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package ntrulpr761 implements the IND-CCA2 secure key encapsulation mechanism +// ntrulpr761 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package ntrulpr761 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/ntrulpr761" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a, seed) + rqMultSmall(aG, G, a) + round(A, aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8, r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B, bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext = ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small, r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G, S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G, S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte, T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B := make([]Fq, p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B, T, r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c, T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "ntrulpr761" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/ntrulpr857/ntruprime.go b/kem/ntruprime/ntrulpr857/ntruprime.go new file mode 100644 index 000000000..d3f1543b5 --- /dev/null +++ b/kem/ntruprime/ntrulpr857/ntruprime.go @@ -0,0 +1,832 @@ +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package ntrulpr857 implements the IND-CCA2 secure key encapsulation mechanism +// ntrulpr857 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package ntrulpr857 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/ntrulpr857" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a, seed) + rqMultSmall(aG, G, a) + round(A, aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8, r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B, bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext = ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small, r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G, S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G, S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte, T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B := make([]Fq, p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B, T, r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c, T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "ntrulpr857" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/ntrulpr953/ntruprime.go b/kem/ntruprime/ntrulpr953/ntruprime.go new file mode 100644 index 000000000..6f56710bd --- /dev/null +++ b/kem/ntruprime/ntrulpr953/ntruprime.go @@ -0,0 +1,832 @@ +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package ntrulpr953 implements the IND-CCA2 secure key encapsulation mechanism +// ntrulpr953 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package ntrulpr953 + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/ntrulpr953" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a, seed) + rqMultSmall(aG, G, a) + round(A, aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8, r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B, bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext = ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small, r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G, S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G, S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte, T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B := make([]Fq, p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B, T, r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c, T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "ntrulpr953" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/sntrup1013/ntruprime.go b/kem/ntruprime/sntrup1013/ntruprime.go new file mode 100644 index 000000000..c09bb6a0a --- /dev/null +++ b/kem/ntruprime/sntrup1013/ntruprime.go @@ -0,0 +1,971 @@ +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package sntrup1013 implements the IND-CCA2 secure key encapsulation mechanism +// sntrup1013 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package sntrup1013 + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/sntrup1013" +) + +type ( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4 * p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte) { + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:], cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:], z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk := pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk := priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "sntrup1013" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/sntrup1277/ntruprime.go b/kem/ntruprime/sntrup1277/ntruprime.go new file mode 100644 index 000000000..a1318569d --- /dev/null +++ b/kem/ntruprime/sntrup1277/ntruprime.go @@ -0,0 +1,971 @@ +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package sntrup1277 implements the IND-CCA2 secure key encapsulation mechanism +// sntrup1277 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package sntrup1277 + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/sntrup1277" +) + +type ( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4 * p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte) { + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:], cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:], z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk := pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk := priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "sntrup1277" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/sntrup653/ntruprime.go b/kem/ntruprime/sntrup653/ntruprime.go new file mode 100644 index 000000000..e5d4aae79 --- /dev/null +++ b/kem/ntruprime/sntrup653/ntruprime.go @@ -0,0 +1,971 @@ +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package sntrup653 implements the IND-CCA2 secure key encapsulation mechanism +// sntrup653 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package sntrup653 + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/sntrup653" +) + +type ( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4 * p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte) { + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:], cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:], z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk := pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk := priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "sntrup653" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/sntrup761/ntruprime.go b/kem/ntruprime/sntrup761/ntruprime.go new file mode 100644 index 000000000..1ea6b232d --- /dev/null +++ b/kem/ntruprime/sntrup761/ntruprime.go @@ -0,0 +1,971 @@ +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package sntrup761 implements the IND-CCA2 secure key encapsulation mechanism +// sntrup761 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package sntrup761 + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/sntrup761" +) + +type ( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4 * p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte) { + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:], cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:], z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk := pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk := priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "sntrup761" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/sntrup857/ntruprime.go b/kem/ntruprime/sntrup857/ntruprime.go new file mode 100644 index 000000000..35b2feb19 --- /dev/null +++ b/kem/ntruprime/sntrup857/ntruprime.go @@ -0,0 +1,971 @@ +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package sntrup857 implements the IND-CCA2 secure key encapsulation mechanism +// sntrup857 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package sntrup857 + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/sntrup857" +) + +type ( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4 * p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte) { + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:], cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:], z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk := pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk := priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "sntrup857" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/sntrup953/ntruprime.go b/kem/ntruprime/sntrup953/ntruprime.go new file mode 100644 index 000000000..73bfc5ec7 --- /dev/null +++ b/kem/ntruprime/sntrup953/ntruprime.go @@ -0,0 +1,971 @@ +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package sntrup953 implements the IND-CCA2 secure key encapsulation mechanism +// sntrup953 as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package sntrup953 + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/sntrup953" +) + +type ( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4 * p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte) { + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:], cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:], z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk := pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk := priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "sntrup953" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/templates/ntrulpr.templ.go b/kem/ntruprime/templates/ntrulpr.templ.go new file mode 100644 index 000000000..0556688dc --- /dev/null +++ b/kem/ntruprime/templates/ntrulpr.templ.go @@ -0,0 +1,837 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from ntrulpr.templ.go. DO NOT EDIT. + +// Package {{.Pkg}} implements the IND-CCA2 secure key encapsulation mechanism +// {{.Pkg}} as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package {{.Pkg}} + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + ntrup "github.com/cloudflare/circl/pke/ntruprime/{{.Pkg}}" +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + + w = ntrup.W + tau0 = ntrup.Tau0 + tau1 = ntrup.Tau1 + tau2 = ntrup.Tau2 + tau3 = ntrup.Tau3 + + I = ntrup.I + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = I / 8 + seedBytes = 32 + ciphertextsBytes = roundedBytes + topBytes + secretKeysBytes = smallBytes + publicKeysBytes = seedBytes + roundedBytes + + confirmBytes = 32 + + tau = 16 + topBytes = I / 2 +) + +const ( + // Size of seed for NewKeyFromSeed + KeySeedSize = seedBytes + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = inputsBytes + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +type ( + small int8 + Fq int16 +) + +// arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +/* ----- arithmetic mod q */ +// GF (q) +// type Fq int16 + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +func top(C Fq) int8 { + return int8((tau1*(int32)(C+tau0) + 16384) >> 15) +} + +func right(T int8) Fq { + return fqFreeze(tau3*int32(T) - tau2) +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x, y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out[]small,in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + // h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h := hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +// generator can be passed for deterministic number generation +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out[]small,seed []byte) { + + L := make([]uint32, p) + + if seed != nil { + for i := 0; i < p; i++ { + L[i] = urandom32(seed[i*4 : i*4+4]) + } + } else { + for i := 0; i < p; i++ { + L[i] = urandom32(nil) + } + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out,L_int32) +} + +// NTRU LPRime Core + +// (G,A),a = keyGen(G); leaves G unchanged +func keyGen(A []Fq, a []small, G []Fq, seed []byte) { + aG := make([]Fq, p) + shortRandom(a,seed) + rqMultSmall(aG, G, a) + round(A,aG) +} + +// B,T = encrypt(r,(G,A),b) +func encrypt(B []Fq, T []int8,r []int8, G []Fq, A []Fq, b []small) { + bG := make([]Fq, p) + bA := make([]Fq, p) + + rqMultSmall(bG, G, b) + round(B,bG) + rqMultSmall(bA, A, b) + + for i := 0; i < I; i++ { + T[i] = top(fqFreeze(int32(bA[i]) + int32(r[i])*q12)) + } +} + +// r = decrypt((B,T),a) +func decrypt(r []int8, B []Fq, T []int8, a []small) { + aB := make([]Fq, p) + + rqMultSmall(aB, B, a) + + for i := 0; i < I; i++ { + r[i] = int8(-internal.Int16NegativeMask(int16(fqFreeze(int32(right(T[i])) - int32(aB[i]) + 4*w + 1)))) + } + +} + +// Encoding I-bit inputs +type Inputs [I]int8 + +func inputsEncode(s []byte, r Inputs) { + + for i := 0; i < I; i++ { + s[i>>3] |= byte(r[i] << (i & 7)) + } + +} + +// Expand + +func expand(L []uint32, k []byte) { + temp := make([]byte, len(L)) // plaintext to be encrypted. Should be of the same size as L (4*P) + ciphertext := make([]byte, aes.BlockSize+len(temp)) + + block, err := aes.NewCipher(k[:32]) + if err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, ciphertext[:aes.BlockSize]) + stream.XORKeyStream(ciphertext[aes.BlockSize:], temp) + ciphertext=ciphertext[aes.BlockSize:] + + // convert byte to uint32 + for i := 0; i < len(temp); i++ { + L[i] = uint32(ciphertext[i]) + } + + for i := 0; i < p; i++ { + var L0 uint32 = L[4*i] + var L1 uint32 = L[4*i+1] + var L2 uint32 = L[4*i+2] + var L3 uint32 = L[4*i+3] + L[i] = L0 + (L1 << 8) + (L2 << 16) + (L3 << 24) + } +} + +// generator, hashShort +// G = generator(k) +func generator(G []Fq, k []byte) { + L := make([]uint32, 4*p) + expand(L, k) + for i := 0; i < p; i++ { + G[i] = Fq(internal.Uint32ModUint14(L[i], q) - q12) + } +} + +// out = hashShort(r) +func hashShort(out []small,r Inputs) { + s := make([]byte, inputsBytes) + inputsEncode(s, r) + h := make([]byte, hashBytes) + L := make([]uint32, 4*p) + L_int32 := make([]int32, p) + + hashPrefix(h, 5, s, len(s)) + + expand(L, h) + + // convert []uint32 to []int32 + for i := 0; i < p; i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out,L_int32) +} + +// NTRU LPRime expand + +// (S,A),a = xKeyGen() +func xKeyGen(S []byte, A []Fq, a []small, seed []byte) { + + copy(S, seed[:seedBytes]) + seed = seed[seedBytes:] + G := make([]Fq, p) + + generator(G,S) + + keyGen(A, a, G, seed) +} + +// B,T = xEncrypt(r,(S,A)) +func xEncrypt(B []Fq, T []int8, r []int8, S []byte, A []Fq) { + G := make([]Fq, p) + + generator(G,S) + b := make([]small, p) + + // convert []int8 to Inputs + var r_inputs Inputs + for i := 0; i < len(r); i++ { + r_inputs[i] = r[i] + } + + hashShort(b, r_inputs) + + encrypt(B, T, r, G, A, b) +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Encoding top polynomials + +func topEncode(s []byte,T []int8) { + for i := 0; i < topBytes; i++ { + s[i] = byte(T[2*i] + (T[2*i+1] << 4)) + + } +} + +func topDecode(T []int8, s []byte) { + + for i := 0; i < topBytes; i++ { + T[2*i] = int8(s[i] & 15) + T[2*i+1] = int8(s[i] >> 4) + } +} + +// Streamlined NTRU Prime Core plus encoding + +func inputsRandom(r *Inputs, seed []byte) { + for i := 0; i < I; i++ { + r[i] = int8(1 & (seed[i>>3] >> (i & 7))) + } +} + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, seed []byte) { + A := make([]Fq, p) + a := make([]small, p) + + xKeyGen(pk, A, a, seed) + + pk = pk[seedBytes:] + roundedEncode(pk, A) + + smallEncode(sk, a) +} + +// c = zEncrypt(r,pk) +func zEncrypt(c []byte, r Inputs, pk []byte) { + A := make([]Fq, p) + B:=make([]Fq,p) + T := make([]int8, I) + + roundedDecode(A, pk[seedBytes:]) + xEncrypt(B,T,r[:], pk[:seedBytes], A) + roundedEncode(c, B) + c = c[roundedBytes:] + + topEncode(c,T) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, c []byte, sk []byte) { + a := make([]small, p) + B := make([]Fq, p) + T := make([]int8, I) + + smallDecode(a, sk) + roundedDecode(B, c) + topDecode(T, c[roundedBytes:]) + decrypt(r[:], B, T, a) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, inputsBytes+hashBytes) + + copy(x, r) + copy(x[inputsBytes:], cache) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, inputsBytes+ciphertextsBytes+confirmBytes) + copy(x[:inputsBytes], y) + copy(x[inputsBytes:], z) + + hashPrefix(k, b, x, len(x)) +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, seed []byte) { + + if seed == nil { + seed = make([]byte, KeySeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != KeySeedSize { + panic("seed must be of length KeySeedSize") + } + + zKeyGen(pk, sk, seed[:seedBytes+p*4]) + seed = seed[seedBytes+p*4:] + + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + copy(sk[:inputsBytes], seed) + + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + inputsEncode(r_enc, r) + + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pk PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + + if seed == nil { + seed=make([]byte,EncapsulationSeedSize) + cryptoRand.Read(seed) + } + + if len(seed) != EncapsulationSeedSize { + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + var r Inputs + + hashPrefix(cache, 4, pk.pk[:], publicKeysBytes) + inputsRandom(&r, seed) + hide(c, r_enc, r, pk.pk[:], cache) + hashSession(k, 1, r_enc, c) +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertextsDiffMask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo(ss []byte, ct []byte) { + if len(ct) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(ss) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + sk := priv.sk[:] + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, ct, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertextsDiffMask(ct, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(ss, 1+mask, r_enc, ct) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch kem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +func (*scheme) Name() string { return "{{.Pkg}}" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], seed) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + return ct, ss, nil + +} + +func (*scheme) EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + if len(seed) != EncapsulationSeedSize { + return nil, nil, kem.ErrSeedSize + } + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + return ct, ss, nil +} + + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/ntruprime/templates/sntrup.templ.go b/kem/ntruprime/templates/sntrup.templ.go new file mode 100644 index 000000000..71cc85a10 --- /dev/null +++ b/kem/ntruprime/templates/sntrup.templ.go @@ -0,0 +1,981 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from sntrup.templ.go. DO NOT EDIT. + +// Package {{.Pkg}} implements the IND-CCA2 secure key encapsulation mechanism +// {{.Pkg}} as submitted to round 3 of the NIST PQC competition and +// described in +// +// https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf +package {{.Pkg}} + +import ( + "bytes" + cryptoRand "crypto/rand" + "crypto/sha512" + + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" + "github.com/cloudflare/circl/kem/ntruprime/internal" + sntrupKem "github.com/cloudflare/circl/pke/ntruprime/kem" + ntrup "github.com/cloudflare/circl/pke/ntruprime/{{.Pkg}}" +) + +type( + small int8 + Fq int16 + Inputs [p]small +) + +const ( + p = ntrup.P + q = ntrup.Q + q12 = ((q - 1) / 2) + roundedBytes = ntrup.RoundedBytes + rqBytes = ntrup.RqBytes + w = ntrup.W + + + hashBytes = 32 + + smallBytes = ((p + 3) / 4) + + inputsBytes = smallBytes + ciphertextsBytes = roundedBytes + secretKeysBytes = (2 * smallBytes) + publicKeysBytes = rqBytes + + confirmBytes = 32 +) + +const ( + // Size of seed for NewKeyFromSeed + // Note that during keyGen, a random small is generated until a valid one (whose reciprocal succeeds) is found + // The size of keySeed depends on the number of times the reciprocal fails + // This is why DeriveKeyPairFromGen is used to deterministically derive key pair instead of using seed + KeySeedSize = 4*p + p*4 + inputsBytes + + // Size of seed for EncapsulateTo. + EncapsulationSeedSize = 4*p + + // Size of the established shared key. + SharedKeySize = ntrup.SharedKeySize + + // Size of the encapsulated shared key. + CiphertextSize = ntrup.CiphertextSize + + // Size of a packed public key. + PublicKeySize = ntrup.PublicKeySize + + // Size of a packed private key. + PrivateKeySize = ntrup.PrivateKeySize +) + +// Arithmetic operations over GF(3) + +// A polynomial of R has all of its coefficients in (-1,0,1) +// F3 is always represented as -1,0,1 +// so ZZ_fromF3 is a no-op + +// x must not be close to top int16 +func f3Freeze(x int16) small { + return small(internal.Int32ModUint14(int32(x)+1, 3)) - 1 +} + +// Arithmetic operations over GF(q) + +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +func fqFreeze(x int32) Fq { + return Fq(internal.Int32ModUint14(x+q12, q) - q12) +} + +// Calculates reciprocal of Fq +func fqRecip(a1 Fq) Fq { + var i int = 1 + ai := a1 + + for i < (q - 2) { + ai = fqFreeze(int32(a1) * int32(ai)) + i += 1 + } + return ai +} + + +// Returns 0 if the weight w is equal to r +// otherwise returns -1 +func weightwMask(r []small) int { + var weight int = 0 + + for i := 0; i < p; i++ { + weight += int(r[i]) & 1 + } + + // returns -1 if non zero + // otherwise returns 0 if weight==w + return internal.Int16NonzeroMask(int16(weight - w)) + +} + +/* R3_fromR(R_fromRq(r)) */ +func r3FromRq(out []small, r []Fq) { + for i := 0; i < p; i++ { + out[i] = small(f3Freeze(int16(r[i]))) + } +} + +// h = f*g in the ring R3 +func r3Mult(h []small, f []small, g []small) { + fg := make([]small, p+p-1) + var result small + var i, j int + + for i = 0; i < p; i++ { + result = 0 + for j = 0; j <= i; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p; i < p+p-1; i++ { + result = 0 + for j = i - p + 1; j < p; j++ { + result = f3Freeze(int16(result + f[j]*g[i-j])) + } + fg[i] = result + } + + for i = p + p - 2; i >= p; i-- { + fg[i-p] = f3Freeze(int16(fg[i-p] + fg[i])) + fg[i-p+1] = f3Freeze(int16(fg[i-p+1] + fg[i])) + } + + for i = 0; i < p; i++ { + h[i] = fg[i] + } +} + +// Calculates the reciprocal of R3 polynomials +// Returns 0 if recip succeeded; else -1 +func r3Recip(out []small, in []small) int { + // out := make([]small, p) + f := make([]small, p+1) + g := make([]small, p+1) + v := make([]small, p+1) + r := make([]small, p+1) + + var sign int + + r[0] = 1 + f[0] = 1 + + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = in[i] + } + + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + sign = int(-g[0] * f[0]) + var swap int = int(internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0]))) + delta ^= swap & int(delta^-delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t := swap & int(f[i]^g[i]) + f[i] ^= small(t) + g[i] ^= small(t) + t = swap & int(v[i]^r[i]) + v[i] ^= small(t) + r[i] ^= small(t) + } + for i := 0; i < p+1; i++ { + g[i] = f3Freeze(int16(int(g[i]) + sign*int(f[i]))) + } + + for i := 0; i < p+1; i++ { + r[i] = f3Freeze(int16(int(r[i]) + sign*int(v[i]))) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + + g[p] = 0 + + } + sign = int(f[0]) + + for i := 0; i < p; i++ { + + out[i] = small(sign * int(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Polynomials mod q + +// h = f*g in the ring Rq */ +func rqMultSmall(h []Fq, f []Fq, g []small) { + fg := make([]Fq, p+p-1) + var result Fq + + for i := 0; i < p; i++ { + result = 0 + for j := 0; j <= i; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p; i < p+p-1; i++ { + result = 0 + for j := i - p + 1; j < p; j++ { + result = fqFreeze(int32(result) + int32(f[j])*(int32)(g[i-j])) + } + fg[i] = result + } + + for i := p + p - 2; i >= p; i-- { + fg[i-p] = fqFreeze(int32(fg[i-p] + fg[i])) + fg[i-p+1] = fqFreeze(int32(fg[i-p+1] + fg[i])) + + } + + for i := 0; i < p; i++ { + h[i] = fg[i] + } +} + +// h = 3f in Rq +func rqMult3(h []Fq, f []Fq) { + for i := 0; i < p; i++ { + h[i] = fqFreeze(int32(3 * f[i])) + } +} + +// Returns 0 if recip succeeded; else -1 +// out = 1/(3*in) in Rq +func rqRecip3(out []Fq, in []small) int { + f := make([]Fq, p+1) + g := make([]Fq, p+1) + v := make([]Fq, p+1) + r := make([]Fq, p+1) + + var swap, t int + var f0, g0 int32 + + r[0] = fqRecip(3) + f[0] = 1 + f[p-1] = -1 + f[p] = -1 + + for i := 0; i < p; i++ { + g[p-1-i] = Fq(in[i]) + } + g[p] = 0 + + delta := 1 + + for loop := 0; loop < 2*p-1; loop++ { + for i := p; i > 0; i-- { + v[i] = v[i-1] + } + v[0] = 0 + + swap = internal.Int16NegativeMask(int16(-delta)) & internal.Int16NonzeroMask(int16(g[0])) + delta ^= swap & (delta ^ -delta) + delta += 1 + + for i := 0; i < p+1; i++ { + t = swap & int(f[i]^g[i]) + f[i] ^= Fq(t) + g[i] ^= Fq(t) + t = swap & int(v[i]^r[i]) + v[i] ^= Fq(t) + r[i] ^= Fq(t) + } + + f0 = int32(f[0]) + g0 = int32(g[0]) + + for i := 0; i < p+1; i++ { + g[i] = fqFreeze(f0*int32(g[i]) - g0*int32(f[i])) + } + for i := 0; i < p+1; i++ { + r[i] = fqFreeze(f0*int32(r[i]) - g0*int32(v[i])) + } + + for i := 0; i < p; i++ { + g[i] = g[i+1] + } + g[p] = 0 + } + + scale := Fq(fqRecip(f[0])) + for i := 0; i < p; i++ { + out[i] = fqFreeze(int32(scale) * (int32)(v[p-1-i])) + } + + return internal.Int16NonzeroMask(int16(delta)) + +} + +// Rounding all coefficients of a polynomial to the nearest multiple of 3 +// Rounded polynomials mod q +func round(out []Fq, a []Fq) { + for i := 0; i < p; i++ { + out[i] = a[i] - Fq(f3Freeze(int16(a[i]))) + } +} + + +// Returns (min(x, y), max(x, y)), executes in constant time +func minmax(x , y *uint32) { + var xi uint32 = *x + var yi uint32 = *y + var xy uint32 = xi ^ yi + var c uint32 = yi - xi + c ^= xy & (c ^ yi ^ 0x80000000) + c >>= 31 + c = -c + c &= xy + *x = xi ^ c + *y = yi ^ c +} + +// Sorts the array of unsigned integers +func cryptoSortUint32(x []uint32, n int) { + if n < 2 { + return + } + top := 1 + + for top < n-top { + top += top + } + + for p := top; p > 0; p >>= 1 { + for i := 0; i < n-p; i++ { + if i&p == 0 { + minmax(&x[i], &x[i+p]) + } + } + for q := top; q > p; q >>= 1 { + for i := 0; i < n-q; i++ { + if i&p == 0 { + minmax(&x[i+p], &x[i+q]) + } + } + } + } +} + +// Sorting to generate short polynomial +func shortFromList(out []small, in []int32) { + L := make([]uint32, p) + + var neg2, neg3 int = -2, -3 + + for i := 0; i < w; i++ { + L[i] = uint32(in[i]) & uint32((neg2)) + } + + for i := w; i < p; i++ { + L[i] = (uint32(in[i]) & uint32((neg3))) | 1 + } + + cryptoSortUint32(L, p) + + for i := 0; i < p; i++ { + out[i] = small((L[i] & 3) - 1) + } +} + +// Underlying hash function + +// The input byte array, in, is prepended by the byte b +// and its SHA-512 hash is calculated +// Only the first 32 bytes of the hash are returned +// e.g., b = 0 means out = Hash0(in) +func hashPrefix(out []byte, b int, in []byte, inlen int) { + x := make([]byte, inlen+1) + h := make([]byte, 64) + + x[0] = byte(b) + copy(x[1:], in) + + hash := sha512.New() + hash.Write([]byte(x)) + h = hash.Sum(nil) + + copy(out, h[:32]) + +} + +// Higher level randomness +// Returns a random unsigned integer +func urandom32(seed []byte) uint32 { + var out [4]uint32 + + out[0] = uint32(seed[0]) + out[1] = uint32(seed[1]) << 8 + out[2] = uint32(seed[2]) << 16 + out[3] = uint32(seed[3]) << 24 + return out[0] + out[1] + out[2] + out[3] +} + +// Generates a random short polynomial +func shortRandom(out []small, seed []byte) { + + L := make([]uint32, p) + + for i := 0; i < p; i++ { + L[i] = urandom32(seed[4*i : 4*i+4]) + } + + // Converts uint32 array to int32 array + L_int32 := make([]int32, p) + for i := 0; i < len(L); i++ { + L_int32[i] = int32(L[i]) + } + shortFromList(out, L_int32) +} + +// Generates a random list of small +func smallRandom(out []small, seed []byte){ + for i := 0; i < p; i++ { + out[i] = small(((urandom32(seed[4*i:4*i+4])&0x3fffffff)*3)>>30) - 1 + } +} + +// Streamlined NTRU Prime Core + +// h,(f,ginv) = keyGen() +func keyGen(h []Fq, f []small, ginv []small, gen *nist.DRBG) { + g := make([]small, p) + seed := make([]byte, 4*p+4*p) + + if gen == nil { + for { + cryptoRand.Read(seed[:4*p]) + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + cryptoRand.Read(seed[4*p:]) + } else { + for { + for i := 0; i < p; i++ { + gen.Fill(seed[4*i : 4*i+4]) + } + smallRandom(g, seed[:4*p]) + if r3Recip(ginv, g) == 0 { + break + } + } + for i := 0; i < p; i++ { + gen.Fill(seed[4*p+4*i : 4*p+4*i+4]) + } + } + shortRandom(f, seed[4*p:]) + + finv := make([]Fq, p) + + rqRecip3(finv, f) /* always works */ + rqMultSmall(h, finv, g) +} + +// c = encrypt(r,h) +func encrypt(c []Fq, r []small, h []Fq) { + hr := make([]Fq, p) + + rqMultSmall(hr, h, r) + round(c, hr) +} + +// r = decrypt(c,(f,ginv)) +func decrypt(r []small, c []Fq, f []small, ginv []small) { + cf := make([]Fq, p) + cf3 := make([]Fq, p) + e := make([]small, p) + ev := make([]small, p) + + rqMultSmall(cf, c, f) + rqMult3(cf3, cf) + r3FromRq(e, cf3) + r3Mult(ev, e, ginv) + + mask := weightwMask(ev) /* 0 if weight w, else -1 */ + for i := 0; i < w; i++ { + r[i] = ((ev[i] ^ 1) & small(^mask)) ^ 1 + } + + for i := w; i < p; i++ { + r[i] = ev[i] & small(^mask) + } +} + +// Encoding small polynomials (including short polynomials) + +// Transform polynomial in R to bytes +// these are the only functions that rely on p mod 4 = 1 */ +func smallEncode(s []byte, f []small) { + var x small + var index int = 0 + for i := 0; i < p/4; i++ { + x = f[index] + 1 + index++ + + x += (f[index] + 1) << 2 + index++ + x += (f[index] + 1) << 4 + index++ + x += (f[index] + 1) << 6 + index++ + + s[0] = byte(x) + s = s[1:] + } + x = f[index] + 1 + + s[0] = byte(x) +} + +// Transform bytes into polynomial in R +func smallDecode(f []small, s []byte) { + var index int = 0 + var x byte + + for i := 0; i < p/4; i++ { + x = s[0] + s = s[1:] + + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + x >>= 2 + index++ + f[index] = ((small)(x & 3)) - 1 + index++ + } + x = s[0] + f[index] = ((small)(x & 3)) - 1 +} + +// Encoding general polynomials + +// Transform polynomials in R/q to bytes +func rqEncode(s []byte, r []Fq) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16(r[i] + q12) + M[i] = q + } + internal.Encode(s, R, M, p) +} + +// Transform polynomials in R/q from bytes +func rqDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = q + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = ((Fq)(R[i])) - q12 + } + +} + +// Encoding rounded polynomials + +// Transform rounded polynomials to bytes +func roundedEncode(s []byte, r []Fq) { + + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + R[i] = uint16((int32((r[i])+q12) * 10923) >> 15) + M[i] = (q + 2) / 3 + } + internal.Encode(s, R, M, p) +} + +// Transform bytes to rounded polynomials +func roundedDecode(r []Fq, s []byte) { + R := make([]uint16, p) + M := make([]uint16, p) + + for i := 0; i < p; i++ { + M[i] = (q + 2) / 3 + } + internal.Decode(R, s, M, p) + for i := 0; i < p; i++ { + r[i] = Fq(R[i]*3 - q12) + } + +} + +// Streamlined NTRU Prime Core plus encoding + + +// Generates public key and private key +// pk,sk = zKeyGen() +func zKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + + h := make([]Fq, p) + f := make([]small, p) + v := make([]small, p) + keyGen(h, f, v, gen) + + rqEncode(pk, h) + smallEncode(sk, f) + sk = sk[smallBytes:] + smallEncode(sk, v) + +} + +// C = zEncrypt(r,pk) +func zEncrypt(C []byte, r Inputs, pk []byte) { + h := make([]Fq, p) + c := make([]Fq, p) + rqDecode(h, pk) + encrypt(c, r[:], h) + roundedEncode(C, c) +} + +// r = zDecrypt(C,sk) +func zDecrypt(r *Inputs, C []byte, sk []byte) { + f := make([]small, p) + v := make([]small, p) + c := make([]Fq, p) + + smallDecode(f, sk) + sk = sk[smallBytes:] + smallDecode(v, sk) + roundedDecode(c, C) + + decrypt(r[:], c, f, v) +} + +// Confirmation hash + +// h = hashConfirm(r,pk,cache); cache is Hash4(pk) +func hashConfirm(h []byte, r []byte, pk []byte, cache []byte) { + x := make([]byte, hashBytes*2) + + hashPrefix(x, 3, r, inputsBytes) + + copy(x[hashBytes:],cache[:hashBytes]) + + hashPrefix(h, 2, x, len(x)) + +} + +// Session-key hash + +// k = hashSession(b,y,z) +func hashSession(k []byte, b int, y []byte, z []byte) { + x := make([]byte, hashBytes+ciphertextsBytes+confirmBytes) + + hashPrefix(x, 3, y, inputsBytes) + + copy(x[hashBytes:],z[:ciphertextsBytes+confirmBytes]) + + hashPrefix(k, b, x, len(x)) + +} + +// Streamlined NTRU Prime + +// pk,sk = kemKeyGen() +func kemKeyGen(pk []byte, sk []byte, gen *nist.DRBG) { + zKeyGen(pk, sk, gen) + sk = sk[secretKeysBytes:] + + copy(sk, pk) + sk = sk[publicKeysBytes:] + + if gen != nil { + gen.Fill(sk[:inputsBytes]) + + } else { + cryptoRand.Read(sk[:inputsBytes]) + } + sk = sk[inputsBytes:] + hashPrefix(sk, 4, pk, publicKeysBytes) + +} + +// c,r_enc = hide(r,pk,cache); cache is Hash4(pk) +func hide(c []byte, r_enc []byte, r Inputs, pk []byte, cache []byte) { + smallEncode(r_enc, r[:]) + zEncrypt(c, r, pk) + c = c[ciphertextsBytes:] + hashConfirm(c, r_enc, pk, cache) + +} + +// Takes as input a public key +// Returns ciphertext and shared key +// c,k = encap(pk) +func (pub PublicKey) EncapsulateTo(c []byte, k []byte, seed []byte) { + if seed == nil { + seed = make([]byte, 4*p) + cryptoRand.Read(seed) + } + if len(seed) != 4*p{ + panic("seed must be of length EncapsulationSeedSize") + } + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + pk:=pub.pk[:] + + var r Inputs + r_enc := make([]byte, inputsBytes) + cache := make([]byte, hashBytes) + + hashPrefix(cache, 4, pk, publicKeysBytes) + shortRandom(r[:], seed) + hide(c, r_enc, r, pk, cache) + hashSession(k, 1, r_enc, c) + +} + +// Returns 0 if matching ciphertext+confirm, else -1 +func ciphertexts_diff_mask(c []byte, c2 []byte) int { + var differentbits uint16 = 0 + var len int = ciphertextsBytes + confirmBytes + + for i := 0; i < len; i++ { + differentbits |= uint16((c[i]) ^ (c2[i])) + } + return int((1 & ((differentbits - 1) >> 8)) - 1) + +} + +// Returns shared key from ciphertext and private key +// k = decap(c,sk) +func (priv *PrivateKey) DecapsulateTo (k []byte, c []byte) { + if len(c) != CiphertextSize { + panic("ct must be of length CiphertextSize") + } + + if len(k) != SharedKeySize { + panic("ss must be of length SharedKeySize") + } + + sk:=priv.sk[:] + + pk := sk[secretKeysBytes:] + rho := pk[publicKeysBytes:] + cache := rho[inputsBytes:] + var r Inputs + + r_enc := make([]byte, inputsBytes) + cnew := make([]byte, ciphertextsBytes+confirmBytes) + + zDecrypt(&r, c, sk) + hide(cnew, r_enc, r, pk, cache) + var mask int = ciphertexts_diff_mask(c, cnew) + + for i := 0; i < inputsBytes; i++ { + r_enc[i] ^= byte(mask & int(r_enc[i]^rho[i])) + } + hashSession(k, 1+mask, r_enc, c) +} + +// The structure of the private key is given by the following segments: +// The secret key, the public key, entropy and the hash of the public key +type PrivateKey struct { + sk [PrivateKeySize]byte +} + +type PublicKey struct { + pk [PublicKeySize]byte +} + +type scheme struct{} + +var sch sntrupKem.Scheme = &scheme{} + +// Scheme returns a KEM interface. +func Scheme() kem.Scheme { return sch } + +// SntrupScheme returns a sntrup.KEM interface +func SntrupScheme() sntrupKem.Scheme { return sch } + +func (*scheme) Name() string { return "{{.Pkg}}" } +func (*scheme) PublicKeySize() int { return PublicKeySize } +func (*scheme) PrivateKeySize() int { return PrivateKeySize } +func (*scheme) SeedSize() int { return KeySeedSize } +func (*scheme) SharedKeySize() int { return SharedKeySize } +func (*scheme) CiphertextSize() int { return CiphertextSize } +func (*scheme) EncapsulationSeedSize() int { return EncapsulationSeedSize } + + +func (sk *PrivateKey) Scheme() kem.Scheme { return sch } +func (pk *PublicKey) Scheme() kem.Scheme { return sch } + +func (sk *PrivateKey) MarshalBinary() ([]byte, error) { + var ret [PrivateKeySize]byte + copy(ret[:], sk.sk[:]) + return ret[:], nil +} + + +func (sk *PrivateKey) Equal(other kem.PrivateKey) bool { + oth, ok := other.(*PrivateKey) + if !ok { + return false + } + return bytes.Equal(sk.sk[:], oth.sk[:]) +} + +func (pk *PublicKey) Equal(other kem.PublicKey) bool { + oth, ok := other.(*PublicKey) + if !ok { + return false + } + return bytes.Equal(pk.pk[:], oth.pk[:]) +} + +func (sk *PrivateKey) Public() kem.PublicKey { + var pk [PublicKeySize]byte + skey, _ := sk.MarshalBinary() + ppk := skey[secretKeysBytes : secretKeysBytes+publicKeysBytes] + copy(pk[:], ppk[:]) + return &PublicKey{pk: pk} +} + +func (pk *PublicKey) MarshalBinary() ([]byte, error) { + var ret [PublicKeySize]byte + copy(ret[:], pk.pk[:]) + return ret[:], nil +} + +func (*scheme) GenerateKeyPair() (kem.PublicKey, kem.PrivateKey, error) { + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + kemKeyGen(pk[:], sk[:], nil) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk}, nil + +} + +// Not used +func (*scheme) DeriveKeyPair(seed []byte) (kem.PublicKey, kem.PrivateKey) { + return nil, nil +} + +func (*scheme) DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) { + + if gen == nil { + panic("A nist DRBG must be provided") + } + + var pk [PublicKeySize]byte + var sk [PrivateKeySize]byte + + kemKeyGen(pk[:], sk[:], gen) + + return &PublicKey{pk: pk}, &PrivateKey{sk: sk} +} + +func (*scheme) Encapsulate(pk kem.PublicKey) (ct, ss []byte, err error) { + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, nil) + + return ct, ss, nil + +} + +func (*scheme)EncapsulateDeterministically(pk kem.PublicKey, seed []byte) (ct, ss []byte, err error) { + + ct = make([]byte, CiphertextSize) + ss = make([]byte, SharedKeySize) + + pub, ok := pk.(*PublicKey) + if !ok { + return nil, nil, kem.ErrTypeMismatch + } + + pub.EncapsulateTo(ct, ss, seed) + + return ct, ss, nil +} + +func (*scheme) Decapsulate(sk kem.PrivateKey, ct []byte) ([]byte, error) { + ssk, ok := sk.(*PrivateKey) + if !ok { + return nil, kem.ErrTypeMismatch + } + + if len(ct) != CiphertextSize { + return nil, kem.ErrCiphertextSize + } + ss := [SharedKeySize]byte{} + + ssk.DecapsulateTo(ss[:], ct) + + return ss[:], nil +} + +func (*scheme) UnmarshalBinaryPublicKey(buf []byte) (kem.PublicKey, error) { + if len(buf) != PublicKeySize { + return nil, kem.ErrPubKeySize + } + pk := [PublicKeySize]byte{} + copy(pk[:], buf) + return &PublicKey{pk: pk}, nil +} + +func (*scheme) UnmarshalBinaryPrivateKey(buf []byte) (kem.PrivateKey, error) { + if len(buf) != PrivateKeySize { + return nil, kem.ErrPrivKeySize + } + sk := [PrivateKeySize]byte{} + copy(sk[:], buf) + return &PrivateKey{sk: sk}, nil +} diff --git a/kem/schemes/schemes.go b/kem/schemes/schemes.go index a33e7b96e..15a3eb14f 100644 --- a/kem/schemes/schemes.go +++ b/kem/schemes/schemes.go @@ -14,6 +14,10 @@ // // FrodoKEM-640-SHAKE // Kyber512, Kyber768, Kyber1024 +// Kyber512, Kyber768, Kyber1024 +// NTRULPR653, NTRULPR761, NTRULPR857, NTRULPR953, NTRULPR1013, NTRULPR1277 +// SNTRUP653, SNTRUP761, SNTRUP857, SNTRUP953, SNTRUP1013, SNTRUP1277 + package schemes import ( @@ -26,6 +30,18 @@ import ( "github.com/cloudflare/circl/kem/kyber/kyber1024" "github.com/cloudflare/circl/kem/kyber/kyber512" "github.com/cloudflare/circl/kem/kyber/kyber768" + "github.com/cloudflare/circl/kem/ntruprime/ntrulpr1013" + "github.com/cloudflare/circl/kem/ntruprime/ntrulpr1277" + "github.com/cloudflare/circl/kem/ntruprime/ntrulpr653" + "github.com/cloudflare/circl/kem/ntruprime/ntrulpr761" + "github.com/cloudflare/circl/kem/ntruprime/ntrulpr857" + "github.com/cloudflare/circl/kem/ntruprime/ntrulpr953" + "github.com/cloudflare/circl/kem/ntruprime/sntrup1013" + "github.com/cloudflare/circl/kem/ntruprime/sntrup1277" + "github.com/cloudflare/circl/kem/ntruprime/sntrup653" + "github.com/cloudflare/circl/kem/ntruprime/sntrup761" + "github.com/cloudflare/circl/kem/ntruprime/sntrup857" + "github.com/cloudflare/circl/kem/ntruprime/sntrup953" ) var allSchemes = [...]kem.Scheme{ @@ -43,6 +59,18 @@ var allSchemes = [...]kem.Scheme{ hybrid.Kyber768X448(), hybrid.Kyber1024X448(), hybrid.P256Kyber768Draft00(), + ntrulpr653.Scheme(), + ntrulpr761.Scheme(), + ntrulpr857.Scheme(), + ntrulpr953.Scheme(), + ntrulpr1013.Scheme(), + ntrulpr1277.Scheme(), + sntrup653.Scheme(), + sntrup761.Scheme(), + sntrup857.Scheme(), + sntrup953.Scheme(), + sntrup1013.Scheme(), + sntrup1277.Scheme(), } var allSchemeNames map[string]kem.Scheme diff --git a/kem/schemes/schemes_test.go b/kem/schemes/schemes_test.go index e41840b34..528e85f0f 100644 --- a/kem/schemes/schemes_test.go +++ b/kem/schemes/schemes_test.go @@ -160,4 +160,16 @@ func Example_schemes() { // Kyber768-X448 // Kyber1024-X448 // P256Kyber768Draft00 + // ntrulpr653 + // ntrulpr761 + // ntrulpr857 + // ntrulpr953 + // ntrulpr1013 + // ntrulpr1277 + // sntrup653 + // sntrup761 + // sntrup857 + // sntrup953 + // sntrup1013 + // sntrup1277 } diff --git a/pke/ntruprime/gen.go b/pke/ntruprime/gen.go new file mode 100644 index 000000000..a9d456dfc --- /dev/null +++ b/pke/ntruprime/gen.go @@ -0,0 +1,115 @@ +package main + +import ( + "bytes" + "go/format" + "io/ioutil" + "strings" + "text/template" +) + +type StreamlinedInstance struct { + Name string + P, Q, RoundedBytes, RqBytes, W, SharedKeySize, CiphertextSize, PublicKeySize, PrivateKeySize int +} + +func (m StreamlinedInstance) Pkg() string { + return strings.ToLower(m.Name) +} + +type LPRInstance struct { + Name string + P, Q, RoundedBytes, RqBytes, W, SharedKeySize, CiphertextSize, PublicKeySize, PrivateKeySize int + Tau0, Tau1, Tau2, Tau3 int +} + +func (m LPRInstance) Pkg() string { + return strings.ToLower(m.Name) +} + +var ( + StreamlinedInstances = []StreamlinedInstance{ + {Name: "SNTRUP761", P: 761, Q: 4591, RoundedBytes: 1007, RqBytes: 1158, W: 286, SharedKeySize: 32, CiphertextSize: 1039, PublicKeySize: 1158, PrivateKeySize: 1763}, + {Name: "SNTRUP653", P: 653, Q: 4621, RoundedBytes: 865, RqBytes: 994, W: 288, SharedKeySize: 32, CiphertextSize: 897, PublicKeySize: 994, PrivateKeySize: 1518}, + {Name: "SNTRUP857", P: 857, Q: 5167, RoundedBytes: 1152, RqBytes: 1322, W: 322, SharedKeySize: 32, CiphertextSize: 1184, PublicKeySize: 1322, PrivateKeySize: 1999}, + {Name: "SNTRUP953", P: 953, Q: 6343, RoundedBytes: 1317, RqBytes: 1505, W: 396, SharedKeySize: 32, CiphertextSize: 1349, PublicKeySize: 1505, PrivateKeySize: 2254}, + {Name: "SNTRUP1013", P: 1013, Q: 7177, RoundedBytes: 1423, RqBytes: 1623, W: 448, SharedKeySize: 32, CiphertextSize: 1455, PublicKeySize: 1623, PrivateKeySize: 2417}, + {Name: "SNTRUP1277", P: 1277, Q: 7879, RoundedBytes: 1815, RqBytes: 2067, W: 492, SharedKeySize: 32, CiphertextSize: 1847, PublicKeySize: 2067, PrivateKeySize: 3059}, + } + LPRInstances = []LPRInstance{ + {Name: "NTRULPR653", P: 653, Q: 4621, RoundedBytes: 865, W: 252, Tau0: 2175, Tau1: 113, Tau2: 2031, Tau3: 290, SharedKeySize: 32, CiphertextSize: 1025, PublicKeySize: 897, PrivateKeySize: 1125}, + {Name: "NTRULPR761", P: 761, Q: 4591, RoundedBytes: 1007, W: 250, Tau0: 2156, Tau1: 114, Tau2: 2007, Tau3: 287, SharedKeySize: 32, CiphertextSize: 1167, PublicKeySize: 1039, PrivateKeySize: 1294}, + {Name: "NTRULPR857", P: 857, Q: 5167, RoundedBytes: 1152, W: 281, Tau0: 2433, Tau1: 101, Tau2: 2265, Tau3: 324, SharedKeySize: 32, CiphertextSize: 1312, PublicKeySize: 1184, PrivateKeySize: 1463}, + {Name: "NTRULPR953", P: 953, Q: 6343, RoundedBytes: 1317, W: 345, Tau0: 2997, Tau1: 82, Tau2: 2798, Tau3: 400, SharedKeySize: 32, CiphertextSize: 1477, PublicKeySize: 1349, PrivateKeySize: 1652}, + {Name: "NTRULPR1013", P: 1013, Q: 7177, RoundedBytes: 1423, W: 392, Tau0: 3367, Tau1: 73, Tau2: 3143, Tau3: 449, SharedKeySize: 32, CiphertextSize: 1583, PublicKeySize: 1455, PrivateKeySize: 1773}, + {Name: "NTRULPR1277", P: 1277, Q: 7879, RoundedBytes: 1815, W: 429, Tau0: 3724, Tau1: 66, Tau2: 3469, Tau3: 496, SharedKeySize: 32, CiphertextSize: 1975, PublicKeySize: 1847, PrivateKeySize: 2231}, + } + TemplateWarning = "// Code generated from" +) + +func main() { + generatePackageFiles() + generateLPRFiles() +} + +func generatePackageFiles() { + template, err := template.ParseFiles("templates/sntrup.params.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range StreamlinedInstances { + buf := new(bytes.Buffer) + err := template.Execute(buf, mode) + if err != nil { + panic(err) + } + + // Formating output code + code, err := format.Source(buf.Bytes()) + if err != nil { + panic("error formating code") + } + + res := string(code) + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in pkg.templ.go") + } + err = ioutil.WriteFile(mode.Pkg()+"/params.go", []byte(res[offset:]), 0o600) + if err != nil { + panic(err) + } + } +} + +func generateLPRFiles() { + template, err := template.ParseFiles("templates/ntrulpr.params.templ.go") + if err != nil { + panic(err) + } + + for _, mode := range LPRInstances { + buf := new(bytes.Buffer) + err := template.Execute(buf, mode) + if err != nil { + panic(err) + } + + // Formating output code + code, err := format.Source(buf.Bytes()) + if err != nil { + panic("error formating code") + } + + res := string(code) + offset := strings.Index(res, TemplateWarning) + if offset == -1 { + panic("Missing template warning in pkg.templ.go") + } + err = ioutil.WriteFile(mode.Pkg()+"/params.go", []byte(res[offset:]), 0o600) + if err != nil { + panic(err) + } + } +} diff --git a/pke/ntruprime/kem/kem.go b/pke/ntruprime/kem/kem.go new file mode 100644 index 000000000..59bfd92c4 --- /dev/null +++ b/pke/ntruprime/kem/kem.go @@ -0,0 +1,20 @@ +// Package kem provides a unified interface for Streamlined NTRU Prime KEM schemes. +// +// # A register of Streamlined NTRU Prime schemes is available in the package +// +// github.com/cloudflare/circl/pke/ntruprime/kem/schemes/sntrup +package kem + +import ( + "github.com/cloudflare/circl/internal/nist" + "github.com/cloudflare/circl/kem" +) + +// A Scheme represents a specific instance of a NTRU PRIME KEM. +type Scheme interface { + kem.Scheme + + // DeriveKeyPairFromGen deterministicallly derives a pair of keys from a nist DRBG. + // Only used for deterministic testing + DeriveKeyPairFromGen(gen *nist.DRBG) (kem.PublicKey, kem.PrivateKey) +} diff --git a/pke/ntruprime/kem/schemes/sntrup/schemes.go b/pke/ntruprime/kem/schemes/sntrup/schemes.go new file mode 100644 index 000000000..1fc9b6f1e --- /dev/null +++ b/pke/ntruprime/kem/schemes/sntrup/schemes.go @@ -0,0 +1,49 @@ +// Package schemes contains a register of Streamlined NTRU Prime KEM schemes. +// +// # Schemes Implemented +// +// Post-quantum kems: +// +// SNTRUP653, SNTRUP761, SNTRUP857, SNTRUP953, SNTRUP1013, SNTRUP1277 +package sntrupSchemes + +import ( + "strings" + + "github.com/cloudflare/circl/kem/ntruprime/sntrup1013" + "github.com/cloudflare/circl/kem/ntruprime/sntrup1277" + "github.com/cloudflare/circl/kem/ntruprime/sntrup653" + "github.com/cloudflare/circl/kem/ntruprime/sntrup761" + "github.com/cloudflare/circl/kem/ntruprime/sntrup857" + "github.com/cloudflare/circl/kem/ntruprime/sntrup953" + "github.com/cloudflare/circl/pke/ntruprime/kem" +) + +var allSchemes = [...]kem.Scheme{ + sntrup653.SntrupScheme(), + sntrup761.SntrupScheme(), + sntrup857.SntrupScheme(), + sntrup953.SntrupScheme(), + sntrup1013.SntrupScheme(), + sntrup1277.SntrupScheme(), +} + +var allSchemeNames map[string]kem.Scheme + +func init() { + allSchemeNames = make(map[string]kem.Scheme) + for _, scheme := range allSchemes { + allSchemeNames[strings.ToLower(scheme.Name())] = scheme + } +} + +// ByName returns the scheme with the given name and nil if it is not +// supported. +// +// Names are case insensitive. +func ByName(name string) kem.Scheme { + return allSchemeNames[strings.ToLower(name)] +} + +// All returns all KEM schemes supported. +func All() []kem.Scheme { a := allSchemes; return a[:] } diff --git a/pke/ntruprime/ntrulpr1013/params.go b/pke/ntruprime/ntrulpr1013/params.go new file mode 100644 index 000000000..0f767d5a9 --- /dev/null +++ b/pke/ntruprime/ntrulpr1013/params.go @@ -0,0 +1,31 @@ +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = 1013 + Q = 7177 + RoundedBytes = 1423 + + W = 392 + Tau0 = 3367 + Tau1 = 73 + Tau2 = 3143 + Tau3 = 449 + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1583 + + // Size of a packed public key. + PublicKeySize = 1455 + + // Size of a packed private key. + PrivateKeySize = 1773 +) diff --git a/pke/ntruprime/ntrulpr1277/params.go b/pke/ntruprime/ntrulpr1277/params.go new file mode 100644 index 000000000..3837b235b --- /dev/null +++ b/pke/ntruprime/ntrulpr1277/params.go @@ -0,0 +1,31 @@ +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = 1277 + Q = 7879 + RoundedBytes = 1815 + + W = 429 + Tau0 = 3724 + Tau1 = 66 + Tau2 = 3469 + Tau3 = 496 + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1975 + + // Size of a packed public key. + PublicKeySize = 1847 + + // Size of a packed private key. + PrivateKeySize = 2231 +) diff --git a/pke/ntruprime/ntrulpr653/params.go b/pke/ntruprime/ntrulpr653/params.go new file mode 100644 index 000000000..01b2164de --- /dev/null +++ b/pke/ntruprime/ntrulpr653/params.go @@ -0,0 +1,31 @@ +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = 653 + Q = 4621 + RoundedBytes = 865 + + W = 252 + Tau0 = 2175 + Tau1 = 113 + Tau2 = 2031 + Tau3 = 290 + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1025 + + // Size of a packed public key. + PublicKeySize = 897 + + // Size of a packed private key. + PrivateKeySize = 1125 +) diff --git a/pke/ntruprime/ntrulpr761/params.go b/pke/ntruprime/ntrulpr761/params.go new file mode 100644 index 000000000..085eeb002 --- /dev/null +++ b/pke/ntruprime/ntrulpr761/params.go @@ -0,0 +1,31 @@ +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = 761 + Q = 4591 + RoundedBytes = 1007 + + W = 250 + Tau0 = 2156 + Tau1 = 114 + Tau2 = 2007 + Tau3 = 287 + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1167 + + // Size of a packed public key. + PublicKeySize = 1039 + + // Size of a packed private key. + PrivateKeySize = 1294 +) diff --git a/pke/ntruprime/ntrulpr857/params.go b/pke/ntruprime/ntrulpr857/params.go new file mode 100644 index 000000000..d8bdaea78 --- /dev/null +++ b/pke/ntruprime/ntrulpr857/params.go @@ -0,0 +1,31 @@ +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = 857 + Q = 5167 + RoundedBytes = 1152 + + W = 281 + Tau0 = 2433 + Tau1 = 101 + Tau2 = 2265 + Tau3 = 324 + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1312 + + // Size of a packed public key. + PublicKeySize = 1184 + + // Size of a packed private key. + PrivateKeySize = 1463 +) diff --git a/pke/ntruprime/ntrulpr953/params.go b/pke/ntruprime/ntrulpr953/params.go new file mode 100644 index 000000000..236dac9bd --- /dev/null +++ b/pke/ntruprime/ntrulpr953/params.go @@ -0,0 +1,31 @@ +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = 953 + Q = 6343 + RoundedBytes = 1317 + + W = 345 + Tau0 = 2997 + Tau1 = 82 + Tau2 = 2798 + Tau3 = 400 + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1477 + + // Size of a packed public key. + PublicKeySize = 1349 + + // Size of a packed private key. + PrivateKeySize = 1652 +) diff --git a/pke/ntruprime/sntrup1013/params.go b/pke/ntruprime/sntrup1013/params.go new file mode 100644 index 000000000..c0705606d --- /dev/null +++ b/pke/ntruprime/sntrup1013/params.go @@ -0,0 +1,25 @@ +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime + +const ( + P = 1013 + Q = 7177 + RoundedBytes = 1423 + RqBytes = 1623 + W = 448 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1455 + + // Size of a packed public key. + PublicKeySize = 1623 + + // Size of a packed private key. + PrivateKeySize = 2417 +) diff --git a/pke/ntruprime/sntrup1277/params.go b/pke/ntruprime/sntrup1277/params.go new file mode 100644 index 000000000..ffce7712d --- /dev/null +++ b/pke/ntruprime/sntrup1277/params.go @@ -0,0 +1,25 @@ +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime + +const ( + P = 1277 + Q = 7879 + RoundedBytes = 1815 + RqBytes = 2067 + W = 492 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1847 + + // Size of a packed public key. + PublicKeySize = 2067 + + // Size of a packed private key. + PrivateKeySize = 3059 +) diff --git a/pke/ntruprime/sntrup653/params.go b/pke/ntruprime/sntrup653/params.go new file mode 100644 index 000000000..c2623eb74 --- /dev/null +++ b/pke/ntruprime/sntrup653/params.go @@ -0,0 +1,25 @@ +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime + +const ( + P = 653 + Q = 4621 + RoundedBytes = 865 + RqBytes = 994 + W = 288 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 897 + + // Size of a packed public key. + PublicKeySize = 994 + + // Size of a packed private key. + PrivateKeySize = 1518 +) diff --git a/pke/ntruprime/sntrup761/params.go b/pke/ntruprime/sntrup761/params.go new file mode 100644 index 000000000..dde2fd93c --- /dev/null +++ b/pke/ntruprime/sntrup761/params.go @@ -0,0 +1,25 @@ +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime + +const ( + P = 761 + Q = 4591 + RoundedBytes = 1007 + RqBytes = 1158 + W = 286 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1039 + + // Size of a packed public key. + PublicKeySize = 1158 + + // Size of a packed private key. + PrivateKeySize = 1763 +) diff --git a/pke/ntruprime/sntrup857/params.go b/pke/ntruprime/sntrup857/params.go new file mode 100644 index 000000000..c88d27812 --- /dev/null +++ b/pke/ntruprime/sntrup857/params.go @@ -0,0 +1,25 @@ +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime + +const ( + P = 857 + Q = 5167 + RoundedBytes = 1152 + RqBytes = 1322 + W = 322 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1184 + + // Size of a packed public key. + PublicKeySize = 1322 + + // Size of a packed private key. + PrivateKeySize = 1999 +) diff --git a/pke/ntruprime/sntrup953/params.go b/pke/ntruprime/sntrup953/params.go new file mode 100644 index 000000000..437b3efe6 --- /dev/null +++ b/pke/ntruprime/sntrup953/params.go @@ -0,0 +1,25 @@ +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime + +const ( + P = 953 + Q = 6343 + RoundedBytes = 1317 + RqBytes = 1505 + W = 396 +) + +const ( + + // Size of the established shared key. + SharedKeySize = 32 + + // Size of the encapsulated shared key. + CiphertextSize = 1349 + + // Size of a packed public key. + PublicKeySize = 1505 + + // Size of a packed private key. + PrivateKeySize = 2254 +) diff --git a/pke/ntruprime/templates/ntrulpr.params.templ.go b/pke/ntruprime/templates/ntrulpr.params.templ.go new file mode 100644 index 000000000..58dc9c1b6 --- /dev/null +++ b/pke/ntruprime/templates/ntrulpr.params.templ.go @@ -0,0 +1,35 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from ntrulpr.params.templ.go +package ntruprime + +const ( + P = {{.P}} + Q = {{.Q}} + RoundedBytes = {{.RoundedBytes}} + + W = {{.W}} + Tau0 = {{.Tau0}} + Tau1 = {{.Tau1}} + Tau2 = {{.Tau2}} + Tau3 = {{.Tau3}} + + I = 256 +) + +const ( + + // Size of the established shared key. + SharedKeySize = {{.SharedKeySize}} + + // Size of the encapsulated shared key. + CiphertextSize = {{.CiphertextSize}} + + // Size of a packed public key. + PublicKeySize = {{.PublicKeySize}} + + // Size of a packed private key. + PrivateKeySize = {{.PrivateKeySize}} +) diff --git a/pke/ntruprime/templates/sntrup.params.templ.go b/pke/ntruprime/templates/sntrup.params.templ.go new file mode 100644 index 000000000..7e890b5a4 --- /dev/null +++ b/pke/ntruprime/templates/sntrup.params.templ.go @@ -0,0 +1,28 @@ +// +build ignore +// The previous line (and this one up to the warning below) is removed by the +// template generator. + +// Code generated from sntrup.params.templ.go. DO NOT EDIT. +package ntruprime +const ( + P = {{.P}} + Q = {{.Q}} + RoundedBytes = {{.RoundedBytes}} + RqBytes = {{.RqBytes}} + W = {{.W}} +) + +const ( + + // Size of the established shared key. + SharedKeySize = {{.SharedKeySize}} + + // Size of the encapsulated shared key. + CiphertextSize = {{.CiphertextSize}} + + // Size of a packed public key. + PublicKeySize = {{.PublicKeySize}} + + // Size of a packed private key. + PrivateKeySize = {{.PrivateKeySize}} +)