Browse Source

Update to match 20160328.

 * Alternate encoding format(s).
 * Code cleanups on the C side that don't translate that much to the Go.

Test vectors regenerated wit the reference code (and match).
Yawning Angel 3 years ago
parent
commit
30b27b903e
7 changed files with 3125 additions and 3111 deletions
  1. 8 14
      error_correction.go
  2. 40 32
      newhope.go
  3. 3000 3000
      newhope_testvectors_test.go
  4. 4 4
      ntt.go
  5. 6 4
      params.go
  6. 63 57
      poly.go
  7. 4 0
      reduce.go

+ 8 - 14
error_correction.go

@@ -62,7 +62,7 @@ func llDecode(xi0, xi1, xi2, xi3 int32) int16 {
 	return int16(t & 1)
 }
 
-func (c *poly) helpRec(v *poly, seed *[seedBytes]byte, nonce byte) {
+func (c *poly) helpRec(v *poly, seed *[SeedBytes]byte, nonce byte) {
 	var v0, v1, vTmp [4]int32
 	var k int32
 	var rand [32]byte
@@ -81,13 +81,7 @@ func (c *poly) helpRec(v *poly, seed *[seedBytes]byte, nonce byte) {
 	for i := uint(0); i < 256; i++ {
 		rBit := int32((rand[i>>3] >> (i & 7)) & 1)
 
-		vTmp[0], vTmp[1], vTmp[2], vTmp[3] = int32(v.v[i]), int32(v.v[256+i]), int32(v.v[512+i]), int32(v.v[768+i])
-
-		// newhope-20151110 - Old version of the reconciliation.
-		// k = f(&v0[0], &v1[0], 8*vTmp[0]+4*paramQ*rBit)
-		// k += f(&v0[1], &v1[1], 8*vTmp[1]+4*paramQ*rBit)
-		// k += f(&v0[2], &v1[2], 8*vTmp[2]+4*paramQ*int32(rBit))
-		// k += f(&v0[3], &v1[3], 8*vTmp[3]+4*paramQ*int32(rBit))
+		vTmp[0], vTmp[1], vTmp[2], vTmp[3] = int32(v.coeffs[i]), int32(v.coeffs[256+i]), int32(v.coeffs[512+i]), int32(v.coeffs[768+i])
 
 		// newhope-20151209 - New version of the reconciliation.
 		k = f(&v0[0], &v1[0], 8*vTmp[0]+4*rBit)
@@ -102,10 +96,10 @@ func (c *poly) helpRec(v *poly, seed *[seedBytes]byte, nonce byte) {
 		vTmp[2] = ((^k) & v0[2]) ^ (k & v1[2])
 		vTmp[3] = ((^k) & v0[3]) ^ (k & v1[3])
 
-		c.v[0+i] = uint16((vTmp[0] - vTmp[3]) & 3)
-		c.v[256+i] = uint16((vTmp[1] - vTmp[3]) & 3)
-		c.v[512+i] = uint16((vTmp[2] - vTmp[3]) & 3)
-		c.v[768+i] = uint16((-k + 2*vTmp[3]) & 3)
+		c.coeffs[0+i] = uint16((vTmp[0] - vTmp[3]) & 3)
+		c.coeffs[256+i] = uint16((vTmp[1] - vTmp[3]) & 3)
+		c.coeffs[512+i] = uint16((vTmp[2] - vTmp[3]) & 3)
+		c.coeffs[768+i] = uint16((-k + 2*vTmp[3]) & 3)
 	}
 
 	for i := range vTmp {
@@ -120,8 +114,8 @@ func rec(key *[32]byte, v, c *poly) {
 	}
 
 	for i := uint(0); i < 256; i++ {
-		vTmp[0], vTmp[1], vTmp[2], vTmp[3] = int32(v.v[i]), int32(v.v[256+i]), int32(v.v[512+i]), int32(v.v[768+i])
-		cTmp[0], cTmp[1], cTmp[2], cTmp[3] = int32(c.v[i]), int32(c.v[256+i]), int32(c.v[512+i]), int32(c.v[768+i])
+		vTmp[0], vTmp[1], vTmp[2], vTmp[3] = int32(v.coeffs[i]), int32(v.coeffs[256+i]), int32(v.coeffs[512+i]), int32(v.coeffs[768+i])
+		cTmp[0], cTmp[1], cTmp[2], cTmp[3] = int32(c.coeffs[i]), int32(c.coeffs[256+i]), int32(c.coeffs[512+i]), int32(c.coeffs[768+i])
 		tmp[0] = 16*paramQ + 8*vTmp[0] - paramQ*(2*cTmp[0]+cTmp[3])
 		tmp[1] = 16*paramQ + 8*vTmp[1] - paramQ*(2*cTmp[1]+cTmp[3])
 		tmp[2] = 16*paramQ + 8*vTmp[2] - paramQ*(2*cTmp[2]+cTmp[3])

+ 40 - 32
newhope.go

@@ -25,41 +25,46 @@ const (
 
 	// UpstreamVersion is the version of the upstream package this
 	// implementation is compatible with.
-	UpstreamVersion = "20151209"
+	UpstreamVersion = "20160328"
+
+	// RecBytes is the length of the reconciliation data in bytes.
+	RecBytes = 256
+
+	// SendASize is the length of Alice's public key in bytes.
+	SendASize = PolyBytes + SeedBytes
+
+	// SendBSize is the length of Bob's public key in bytes.
+	SendBSize = PolyBytes + RecBytes
 )
 
-func encodeA(r []byte, pk *poly, seed *[seedBytes]byte) {
+func encodeA(r []byte, pk *poly, seed *[SeedBytes]byte) {
 	pk.toBytes(r)
-	for i, v := range seed {
-		for j := 0; j < 4; j++ {
-			r[2*(4*i+j)+1] |= v << 6
-			v >>= 2
-		}
+	for i := 0; i < SeedBytes; i++ {
+		r[PolyBytes+i] = seed[i]
 	}
 }
 
-func decodeA(pk *poly, seed *[seedBytes]byte, r []byte) {
+func decodeA(pk *poly, seed *[SeedBytes]byte, r []byte) {
 	pk.fromBytes(r)
-
 	for i := range seed {
-		seed[i] = 0
-		for j := uint(0); j < 4; j++ {
-			seed[i] |= byte(r[2*(4*uint(i)+j)+1]>>6) << (2 * j)
-		}
+		seed[i] = r[PolyBytes+i]
 	}
 }
 
 func encodeB(r []byte, b *poly, c *poly) {
 	b.toBytes(r)
-	for i, v := range c.v {
-		r[2*i+1] |= byte(v << 6)
+	for i := 0; i < paramN/4; i++ {
+		r[PolyBytes+i] = byte(c.coeffs[4*i]) | byte(c.coeffs[4*i+1]<<2) | byte(c.coeffs[4*i+2]<<4) | byte(c.coeffs[4*i+3]<<6)
 	}
 }
 
 func decodeB(b *poly, c *poly, r []byte) {
 	b.fromBytes(r)
-	for i := range c.v {
-		c.v[i] = uint16(r[2*i+1] >> 6)
+	for i := 0; i < paramN/4; i++ {
+		c.coeffs[4*i+0] = uint16(r[PolyBytes+i]) & 0x03
+		c.coeffs[4*i+1] = uint16(r[PolyBytes+i]>>2) & 0x03
+		c.coeffs[4*i+2] = uint16(r[PolyBytes+i]>>4) & 0x03
+		c.coeffs[4*i+3] = uint16(r[PolyBytes+i] >> 6)
 	}
 }
 
@@ -69,13 +74,13 @@ func memwipe(b []byte) {
 	}
 }
 
-// PublicKey is a New Hope public key.
-type PublicKey struct {
-	Send [PolyBytes]byte
+// PublicKeyAlice is Alice's New Hope public key.
+type PublicKeyAlice struct {
+	Send [SendASize]byte
 }
 
-// PrivateKey is a New Hope private key.
-type PrivateKey struct {
+// PrivateKeyAlice is Alice's New Hope private key.
+type PrivateKeyAlice struct {
 	sk poly
 }
 
@@ -83,9 +88,9 @@ type PrivateKey struct {
 // generated using the given reader, which must return random data.  The
 // receiver side of the key exchange (aka "Bob") MUST use KeyExchangeBob()
 // instead of this routine.
-func GenerateKeyPair(rand io.Reader) (*PrivateKey, *PublicKey, error) {
+func GenerateKeyPair(rand io.Reader) (*PrivateKeyAlice, *PublicKeyAlice, error) {
 	var a, e, pk, r poly
-	var seed, noiseSeed [seedBytes]byte
+	var seed, noiseSeed [SeedBytes]byte
 
 	// seed <- Sample({0, 1}^256)
 	if _, err := io.ReadFull(rand, seed[:]); err != nil {
@@ -99,14 +104,14 @@ func GenerateKeyPair(rand io.Reader) (*PrivateKey, *PublicKey, error) {
 		return nil, nil, err
 	}
 	defer memwipe(noiseSeed[:])
-	privKey := new(PrivateKey)
+	privKey := new(PrivateKeyAlice)
 	privKey.sk.getNoise(&noiseSeed, 0)
 	privKey.sk.ntt()
 	e.getNoise(&noiseSeed, 1)
 	e.ntt()
 
 	// b <- as + e
-	pubKey := new(PublicKey)
+	pubKey := new(PublicKeyAlice)
 	r.pointwise(&privKey.sk, &a)
 	pk.add(&e, &r)
 	encodeA(pubKey.Send[:], &pk, &seed)
@@ -114,12 +119,17 @@ func GenerateKeyPair(rand io.Reader) (*PrivateKey, *PublicKey, error) {
 	return privKey, pubKey, nil
 }
 
+// PublicKeyBob is Bob's New Hope public key.
+type PublicKeyBob struct {
+	Send [SendBSize]byte
+}
+
 // KeyExchangeBob is the Responder side of the Ring-LWE key exchange.  The
 // shared secret and "public key" (key + reconciliation data) are generated
 // using the given reader, which must return random data.
-func KeyExchangeBob(rand io.Reader, alicePk *PublicKey) (*PublicKey, []byte, error) {
+func KeyExchangeBob(rand io.Reader, alicePk *PublicKeyAlice) (*PublicKeyBob, []byte, error) {
 	var pka, a, sp, ep, u, v, epp, r poly
-	var seed, noiseSeed [seedBytes]byte
+	var seed, noiseSeed [SeedBytes]byte
 
 	if _, err := io.ReadFull(rand, noiseSeed[:]); err != nil {
 		return nil, nil, err
@@ -143,14 +153,13 @@ func KeyExchangeBob(rand io.Reader, alicePk *PublicKey) (*PublicKey, []byte, err
 
 	// v <- bs' + e''
 	v.pointwise(&pka, &sp)
-	v.bitrev()
 	v.invNtt()
 	v.add(&v, &epp)
 
 	// r <- Sample(HelpRec(v))
 	r.helpRec(&v, &noiseSeed, 3)
 
-	pubKey := new(PublicKey)
+	pubKey := new(PublicKeyBob)
 	encodeB(pubKey.Send[:], &u, &r)
 
 	// nu <- Rec(v, r)
@@ -171,14 +180,13 @@ func KeyExchangeBob(rand io.Reader, alicePk *PublicKey) (*PublicKey, []byte, err
 // KeyExchangeAlice is the Initiaitor side of the Ring-LWE key exchange.  The
 // provided private key is obliterated prior to returning, to promote
 // implementing Perfect Forward Secrecy.
-func KeyExchangeAlice(bobPk *PublicKey, aliceSk *PrivateKey) ([]byte, error) {
+func KeyExchangeAlice(bobPk *PublicKeyBob, aliceSk *PrivateKeyAlice) ([]byte, error) {
 	var u, r, vp poly
 
 	decodeB(&u, &r, bobPk.Send[:])
 
 	// v' <- us
 	vp.pointwise(&aliceSk.sk, &u)
-	vp.bitrev()
 	vp.invNtt()
 
 	// nu <- Rec(v', r)

File diff suppressed because it is too large
+ 3000 - 3000
newhope_testvectors_test.go


+ 4 - 4
ntt.go

@@ -83,18 +83,18 @@ var bitrevTable = [paramN]uint16{
 }
 
 func (p *poly) bitrev() {
-	for i, v := range p.v {
+	for i, v := range p.coeffs {
 		r := bitrevTable[i]
 		if uint16(i) < r {
-			p.v[i] = p.v[r]
-			p.v[r] = v
+			p.coeffs[i] = p.coeffs[r]
+			p.coeffs[r] = v
 		}
 	}
 }
 
 func (p *poly) mulCoefficients(factors *[paramN]uint16) {
 	for i, v := range factors {
-		p.v[i] = montgomeryReduce(uint32(p.v[i]) * uint32(v))
+		p.coeffs[i] = montgomeryReduce(uint32(p.coeffs[i]) * uint32(v))
 	}
 }
 

+ 6 - 4
params.go

@@ -8,8 +8,10 @@
 package newhope
 
 const (
-	paramN    = 1024
-	paramK    = 12 // used in sampler
-	paramQ    = 12289
-	seedBytes = 32
+	paramN = 1024
+	paramK = 16 // used in sampler
+	paramQ = 12289
+
+	// SeedBytes is the size of the seed in bytes.
+	SeedBytes = 32
 )

+ 63 - 57
poly.go

@@ -16,41 +16,71 @@ import (
 
 const (
 	// PolyBytes is the length of an encoded polynomial in bytes.
-	PolyBytes = 2048
+	PolyBytes = 1792
 
 	shake128Rate = 168 // Stupid that this isn't exposed.
 )
 
 type poly struct {
-	v [paramN]uint16
+	coeffs [paramN]uint16
 }
 
 func (p *poly) reset() {
-	for i := range p.v {
-		p.v[i] = 0
+	for i := range p.coeffs {
+		p.coeffs[i] = 0
 	}
 }
 
 func (p *poly) fromBytes(a []byte) {
-	for i := range p.v {
-		p.v[i] = binary.LittleEndian.Uint16(a[2*i:]) & 0x3fff
+	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, v := range p.v {
+	for i := 0; i < paramN/4; i++ {
 		// Make sure that coefficients have only 14 bits.
-		t := barrettReduce(v)
-		m := t - paramQ
+		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
-		// Make sure that coefficients are in [0,q]
-		t = m ^ ((t ^ m) & uint16(c))
-		binary.LittleEndian.PutUint16(r[2*i:], t)
+		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) uniform(seed *[seedBytes]byte) {
+func (p *poly) uniform(seed *[SeedBytes]byte) {
 	nBlocks := 16
 	var buf [shake128Rate * 16]byte
 
@@ -64,7 +94,7 @@ func (p *poly) uniform(seed *[seedBytes]byte) {
 		val := binary.LittleEndian.Uint16(buf[pos:]) & 0x3fff
 
 		if val < paramQ {
-			p.v[ctr] = val
+			p.coeffs[ctr] = val
 			ctr++
 		}
 		pos += 2
@@ -76,11 +106,9 @@ func (p *poly) uniform(seed *[seedBytes]byte) {
 	}
 }
 
-func (p *poly) getNoise(seed *[seedBytes]byte, nonce byte) {
-	var buf [3 * paramN]byte
+func (p *poly) getNoise(seed *[SeedBytes]byte, nonce byte) {
+	var buf [4 * paramN]byte
 	var n [8]byte
-	var v uint32
-	var b [4]byte
 
 	n[0] = nonce
 	stream, err := chacha20.NewCipher(seed[:], n[:])
@@ -90,69 +118,47 @@ func (p *poly) getNoise(seed *[seedBytes]byte, nonce byte) {
 	stream.KeyStream(buf[:])
 	stream.Reset()
 
-	// First half of the output.
-	for i := 0; i < paramN/2; i += 2 {
-		v = 0
-		jV := binary.LittleEndian.Uint32(buf[2*i:])
-		for j := uint(0); j < 8; j++ {
-			v += (jV >> j) & 0x01010101
-		}
-		jV = binary.LittleEndian.Uint32(buf[2*i+2*paramN:])
-		for j := uint(0); j < 4; j++ {
-			v += (jV >> j) & 0x01010101
-		}
-		binary.LittleEndian.PutUint32(b[0:], v)
-		p.v[i] = paramQ + uint16(b[0]) - uint16(b[1])
-		p.v[i+1] = paramQ + uint16(b[2]) - uint16(b[3])
-	}
-
-	// Second half of the output.
-	for i := 0; i < paramN/2; i += 2 {
-		v = 0
-		jV := binary.LittleEndian.Uint32(buf[2*i+paramN:])
+	for i := 0; i < paramN; i++ {
+		t := binary.LittleEndian.Uint32(buf[4*i:])
+		d := uint32(0)
 		for j := uint(0); j < 8; j++ {
-			v += (jV >> j) & 0x01010101
-		}
-		jV = binary.LittleEndian.Uint32(buf[2*i+2*paramN:])
-		for j := uint(0); j < 4; j++ {
-			v += (jV >> (j + 4)) & 0x01010101
+			d += (t >> j) & 0x01010101
 		}
-		binary.LittleEndian.PutUint32(b[0:], v)
-		p.v[i+paramN/2] = paramQ + uint16(b[0]) - uint16(b[1])
-		p.v[i+paramN/2+1] = paramQ + uint16(b[2]) - uint16(b[3])
+		a := ((d >> 8) & 0xff) + (d & 0xff)
+		b := (d >> 24) + ((d >> 16) & 0xff)
+		p.coeffs[i] = uint16(a) + paramQ - uint16(b)
 	}
 
 	// Scrub the random bits...
-	v = 0
-	memwipe(b[:])
 	memwipe(buf[:])
 }
 
 func (p *poly) pointwise(a, b *poly) {
-	for i := range p.v {
-		t := montgomeryReduce(3186 * uint32(b.v[i]))          // t is now in Montgomery domain
-		p.v[i] = montgomeryReduce(uint32(a.v[i]) * uint32(t)) // p.v[i] is back in normal domain
+	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.v {
-		p.v[i] = barrettReduce(a.v[i] + b.v[i])
+	for i := range p.coeffs {
+		p.coeffs[i] = barrettReduce(a.coeffs[i] + b.coeffs[i])
 	}
 }
 
 func (p *poly) ntt() {
 	p.mulCoefficients(&psisBitrevMontgomery)
-	ntt(&p.v, &omegasMontgomery)
+	ntt(&p.coeffs, &omegasMontgomery)
 }
 
 func (p *poly) invNtt() {
-	ntt(&p.v, &omegasInvMontgomery)
+	p.bitrev()
+	ntt(&p.coeffs, &omegasInvMontgomery)
 	p.mulCoefficients(&psisInvMontgomery)
 }
 
 func init() {
-	if paramK != 12 {
-		panic("poly.getNoise() only supports k=12")
+	if paramK != 16 {
+		panic("poly.getNoise() only supports k=16")
 	}
 }

+ 4 - 0
reduce.go

@@ -7,6 +7,10 @@
 
 package newhope
 
+// Incomplete-reduction routines; for details on allowed input ranges
+// and produced output ranges, see the description in the paper:
+// https://cryptojedi.org/papers/#newhope
+
 const (
 	qinv = 12287 // -inverse_mod(p,2^18)
 	rlog = 18