mirror of https://gogs.blitter.com/RLabs/xs
213 lines
4.7 KiB
Go
213 lines
4.7 KiB
Go
// poly.go - NewHope polynomial.
|
|
//
|
|
// To the extent possible under law, Yawning Angel has waived all copyright
|
|
// and related or neighboring rights to newhope, using the Creative
|
|
// Commons "CC0" public domain dedication. See LICENSE or
|
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
|
|
|
package newhope
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
"blitter.com/go/chacha20"
|
|
"golang.org/x/crypto/sha3"
|
|
)
|
|
|
|
const (
|
|
// PolyBytes is the length of an encoded polynomial in bytes.
|
|
PolyBytes = 1792
|
|
|
|
shake128Rate = 168 // Stupid that this isn't exposed.
|
|
)
|
|
|
|
type poly struct {
|
|
coeffs [paramN]uint16
|
|
}
|
|
|
|
func (p *poly) reset() {
|
|
for i := range p.coeffs {
|
|
p.coeffs[i] = 0
|
|
}
|
|
}
|
|
|
|
func (p *poly) fromBytes(a []byte) {
|
|
for i := 0; i < paramN/4; i++ {
|
|
p.coeffs[4*i+0] = uint16(a[7*i+0]) | ((uint16(a[7*i+1]) & 0x3f) << 8)
|
|
p.coeffs[4*i+1] = (uint16(a[7*i+1]) >> 6) | (uint16(a[7*i+2]) << 2) | ((uint16(a[7*i+3]) & 0x0f) << 10)
|
|
|
|
p.coeffs[4*i+2] = (uint16(a[7*i+3]) >> 4) | (uint16(a[7*i+4]) << 4) | ((uint16(a[7*i+5]) & 0x03) << 12)
|
|
p.coeffs[4*i+3] = (uint16(a[7*i+5]) >> 2) | (uint16(a[7*i+6]) << 6)
|
|
}
|
|
}
|
|
|
|
func (p *poly) toBytes(r []byte) {
|
|
for i := 0; i < paramN/4; i++ {
|
|
// Make sure that coefficients have only 14 bits.
|
|
t0 := barrettReduce(p.coeffs[4*i+0])
|
|
t1 := barrettReduce(p.coeffs[4*i+1])
|
|
t2 := barrettReduce(p.coeffs[4*i+2])
|
|
t3 := barrettReduce(p.coeffs[4*i+3])
|
|
|
|
// Make sure that coefficients are in [0,q]
|
|
m := t0 - paramQ
|
|
c := int16(m)
|
|
c >>= 15
|
|
t0 = m ^ ((t0 ^ m) & uint16(c))
|
|
|
|
m = t1 - paramQ
|
|
c = int16(m)
|
|
c >>= 15
|
|
t1 = m ^ ((t1 ^ m) & uint16(c))
|
|
|
|
m = t2 - paramQ
|
|
c = int16(m)
|
|
c >>= 15
|
|
t2 = m ^ ((t2 ^ m) & uint16(c))
|
|
|
|
m = t3 - paramQ
|
|
c = int16(m)
|
|
c >>= 15
|
|
t3 = m ^ ((t3 ^ m) & uint16(c))
|
|
|
|
r[7*i+0] = byte(t0 & 0xff)
|
|
r[7*i+1] = byte(t0>>8) | byte(t1<<6)
|
|
r[7*i+2] = byte(t1 >> 2)
|
|
r[7*i+3] = byte(t1>>10) | byte(t2<<4)
|
|
r[7*i+4] = byte(t2 >> 4)
|
|
r[7*i+5] = byte(t2>>12) | byte(t3<<2)
|
|
r[7*i+6] = byte(t3 >> 6)
|
|
}
|
|
}
|
|
|
|
func (p *poly) discardTo(xbuf []byte) bool {
|
|
var x [shake128Rate * 16 / 2]uint16
|
|
for i := range x {
|
|
x[i] = binary.LittleEndian.Uint16(xbuf[i*2:])
|
|
}
|
|
|
|
for i := 0; i < 16; i++ {
|
|
batcher84(x[i:])
|
|
}
|
|
|
|
// Check whether we're safe:
|
|
r := int(0)
|
|
for i := 1000; i < 1024; i++ {
|
|
r |= 61444 - int(x[i])
|
|
}
|
|
if r>>31 != 0 {
|
|
return true
|
|
}
|
|
|
|
// If we are, copy coefficients to polynomial:
|
|
for i := range p.coeffs {
|
|
p.coeffs[i] = x[i]
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (p *poly) uniform(seed *[SeedBytes]byte, torSampling bool) {
|
|
if !torSampling {
|
|
// Reference version, vartime.
|
|
nBlocks := 14
|
|
var buf [shake128Rate * 14]byte
|
|
|
|
// h and buf are left unscrubbed because the output is public.
|
|
h := sha3.NewShake128()
|
|
_, _ = h.Write(seed[:])
|
|
_, _ = h.Read(buf[:])
|
|
|
|
for ctr, pos := 0, 0; ctr < paramN; {
|
|
val := binary.LittleEndian.Uint16(buf[pos:])
|
|
|
|
if val < 5*paramQ {
|
|
p.coeffs[ctr] = val
|
|
ctr++
|
|
}
|
|
pos += 2
|
|
if pos > shake128Rate*nBlocks-2 {
|
|
nBlocks = 1
|
|
_, _ = h.Read(buf[:shake128Rate])
|
|
pos = 0
|
|
}
|
|
}
|
|
} else {
|
|
// `torref` version, every valid `a` is generate in constant time,
|
|
// though the number of attempts varies.
|
|
const nBlocks = 16
|
|
var buf [shake128Rate * nBlocks]byte
|
|
|
|
// h and buf are left unscrubbed because the output is public.
|
|
h := sha3.NewShake128()
|
|
_, _ = h.Write(seed[:])
|
|
|
|
for {
|
|
_, _ = h.Read(buf[:])
|
|
if !p.discardTo(buf[:]) {
|
|
break
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (p *poly) getNoise(seed *[SeedBytes]byte, nonce byte) {
|
|
// The `ref` code uses a uint32 vector instead of a byte vector,
|
|
// but converting between the two in Go is cumbersome.
|
|
var buf [4 * paramN]byte
|
|
var n [8]byte
|
|
|
|
n[0] = nonce
|
|
stream, err := chacha20.New(seed[:], n[:])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
stream.KeyStream(buf[:])
|
|
stream.Reset()
|
|
|
|
for i := 0; i < paramN; i++ {
|
|
t := binary.LittleEndian.Uint32(buf[4*i:])
|
|
d := uint32(0)
|
|
for j := uint(0); j < 8; j++ {
|
|
d += (t >> j) & 0x01010101
|
|
}
|
|
a := ((d >> 8) & 0xff) + (d & 0xff)
|
|
b := (d >> 24) + ((d >> 16) & 0xff)
|
|
p.coeffs[i] = uint16(a) + paramQ - uint16(b)
|
|
}
|
|
|
|
// Scrub the random bits...
|
|
memwipe(buf[:])
|
|
}
|
|
|
|
func (p *poly) pointwise(a, b *poly) {
|
|
for i := range p.coeffs {
|
|
t := montgomeryReduce(3186 * uint32(b.coeffs[i])) // t is now in Montgomery domain
|
|
p.coeffs[i] = montgomeryReduce(uint32(a.coeffs[i]) * uint32(t)) // p.coeffs[i] is back in normal domain
|
|
}
|
|
}
|
|
|
|
func (p *poly) add(a, b *poly) {
|
|
for i := range p.coeffs {
|
|
p.coeffs[i] = barrettReduce(a.coeffs[i] + b.coeffs[i])
|
|
}
|
|
}
|
|
|
|
func (p *poly) ntt() {
|
|
p.mulCoefficients(&psisBitrevMontgomery)
|
|
ntt(&p.coeffs, &omegasMontgomery)
|
|
}
|
|
|
|
func (p *poly) invNtt() {
|
|
p.bitrev()
|
|
ntt(&p.coeffs, &omegasInvMontgomery)
|
|
p.mulCoefficients(&psisInvMontgomery)
|
|
}
|
|
|
|
func init() {
|
|
if paramK != 16 {
|
|
panic("poly.getNoise() only supports k=16")
|
|
}
|
|
}
|