// poly.go - Kyber polynomial. // // To the extent possible under law, Yawning Angel has waived all copyright // and related or neighboring rights to the software, using the Creative // Commons "CC0" public domain dedication. See LICENSE or // for full details. package kyber import "golang.org/x/crypto/sha3" // Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial coeffs[0] + // X*coeffs[1] + X^2*xoeffs[2] + ... + X^{n-1}*coeffs[n-1]. type poly struct { coeffs [kyberN]uint16 } // Compression and subsequent serialization of a polynomial. func (p *poly) compress(r []byte) { var t [8]uint32 for i, k := 0, 0; i < kyberN; i, k = i+8, k+3 { for j := 0; j < 8; j++ { t[j] = uint32((((freeze(p.coeffs[i+j]) << 3) + kyberQ/2) / kyberQ) & 7) } r[k] = byte(t[0] | (t[1] << 3) | (t[2] << 6)) r[k+1] = byte((t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7)) r[k+2] = byte((t[5] >> 1) | (t[6] << 2) | (t[7] << 5)) } } // De-serialization and subsequent decompression of a polynomial; approximate // inverse of poly.compress(). func (p *poly) decompress(a []byte) { for i, off := 0, 0; i < kyberN; i, off = i+8, off+3 { p.coeffs[i+0] = ((uint16(a[off]&7) * kyberQ) + 4) >> 3 p.coeffs[i+1] = (((uint16(a[off]>>3) & 7) * kyberQ) + 4) >> 3 p.coeffs[i+2] = (((uint16(a[off]>>6) | (uint16(a[off+1]<<2) & 4)) * kyberQ) + 4) >> 3 p.coeffs[i+3] = (((uint16(a[off+1]>>1) & 7) * kyberQ) + 4) >> 3 p.coeffs[i+4] = (((uint16(a[off+1]>>4) & 7) * kyberQ) + 4) >> 3 p.coeffs[i+5] = (((uint16(a[off+1]>>7) | (uint16(a[off+2]<<1) & 6)) * kyberQ) + 4) >> 3 p.coeffs[i+6] = (((uint16(a[off+2]>>2) & 7) * kyberQ) + 4) >> 3 p.coeffs[i+7] = (((uint16(a[off+2] >> 5)) * kyberQ) + 4) >> 3 } } // Serialization of a polynomial. func (p *poly) toBytes(r []byte) { var t [8]uint16 for i := 0; i < kyberN/8; i++ { for j := 0; j < 8; j++ { t[j] = freeze(p.coeffs[8*i+j]) } r[13*i+0] = byte(t[0] & 0xff) r[13*i+1] = byte((t[0] >> 8) | ((t[1] & 0x07) << 5)) r[13*i+2] = byte((t[1] >> 3) & 0xff) r[13*i+3] = byte((t[1] >> 11) | ((t[2] & 0x3f) << 2)) r[13*i+4] = byte((t[2] >> 6) | ((t[3] & 0x01) << 7)) r[13*i+5] = byte((t[3] >> 1) & 0xff) r[13*i+6] = byte((t[3] >> 9) | ((t[4] & 0x0f) << 4)) r[13*i+7] = byte((t[4] >> 4) & 0xff) r[13*i+8] = byte((t[4] >> 12) | ((t[5] & 0x7f) << 1)) r[13*i+9] = byte((t[5] >> 7) | ((t[6] & 0x03) << 6)) r[13*i+10] = byte((t[6] >> 2) & 0xff) r[13*i+11] = byte((t[6] >> 10) | ((t[7] & 0x1f) << 3)) r[13*i+12] = byte(t[7] >> 5) } } // De-serialization of a polynomial; inverse of poly.toBytes(). func (p *poly) fromBytes(a []byte) { for i := 0; i < kyberN/8; i++ { p.coeffs[8*i+0] = uint16(a[13*i+0]) | ((uint16(a[13*i+1]) & 0x1f) << 8) p.coeffs[8*i+1] = (uint16(a[13*i+1]) >> 5) | (uint16(a[13*i+2]) << 3) | ((uint16(a[13*i+3]) & 0x03) << 11) p.coeffs[8*i+2] = (uint16(a[13*i+3]) >> 2) | ((uint16(a[13*i+4]) & 0x7f) << 6) p.coeffs[8*i+3] = (uint16(a[13*i+4]) >> 7) | (uint16(a[13*i+5]) << 1) | ((uint16(a[13*i+6]) & 0x0f) << 9) p.coeffs[8*i+4] = (uint16(a[13*i+6]) >> 4) | (uint16(a[13*i+7]) << 4) | ((uint16(a[13*i+8]) & 0x01) << 12) p.coeffs[8*i+5] = (uint16(a[13*i+8]) >> 1) | ((uint16(a[13*i+9]) & 0x3f) << 7) p.coeffs[8*i+6] = (uint16(a[13*i+9]) >> 6) | (uint16(a[13*i+10]) << 2) | ((uint16(a[13*i+11]) & 0x07) << 10) p.coeffs[8*i+7] = (uint16(a[13*i+11]) >> 3) | (uint16(a[13*i+12]) << 5) } } // Convert 32-byte message to polynomial. func (p *poly) fromMsg(msg []byte) { for i, v := range msg[:SymSize] { for j := 0; j < 8; j++ { mask := -((uint16(v) >> uint(j)) & 1) p.coeffs[8*i+j] = mask & ((kyberQ + 1) / 2) } } } // Convert polynomial to 32-byte message. func (p *poly) toMsg(msg []byte) { for i := 0; i < SymSize; i++ { msg[i] = 0 for j := 0; j < 8; j++ { t := (((freeze(p.coeffs[8*i+j]) << 1) + kyberQ/2) / kyberQ) & 1 msg[i] |= byte(t << uint(j)) } } } // Sample a polynomial deterministically from a seed and a nonce, with output // polynomial close to centered binomial distribution with parameter eta. func (p *poly) getNoise(seed []byte, nonce byte, eta int) { extSeed := make([]byte, 0, SymSize+1) extSeed = append(extSeed, seed...) extSeed = append(extSeed, nonce) buf := make([]byte, eta*kyberN/4) sha3.ShakeSum256(buf, extSeed) p.cbd(buf, eta) } // Computes negacyclic number-theoretic transform (NTT) of a polynomial in // place; inputs assumed to be in normal order, output in bitreversed order. func (p *poly) ntt() { hardwareAccelImpl.nttFn(&p.coeffs) } // Computes inverse of negacyclic number-theoretic transform (NTT) of a // polynomial in place; inputs assumed to be in bitreversed order, output in // normal order. func (p *poly) invntt() { hardwareAccelImpl.invnttFn(&p.coeffs) } // Add two polynomials. func (p *poly) add(a, b *poly) { for i := range p.coeffs { p.coeffs[i] = barrettReduce(a.coeffs[i] + b.coeffs[i]) } } // Subtract two polynomials. func (p *poly) sub(a, b *poly) { for i := range p.coeffs { p.coeffs[i] = barrettReduce(a.coeffs[i] + 3*kyberQ - b.coeffs[i]) } }