From 5c2544cf3bce522cb36cd14a5a01892a2711e348 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Fri, 19 Jul 2019 11:59:10 +0300 Subject: [PATCH] Add pure-go GF(2^127) implementation --- gogf127/gf127_test.go | 110 ++++++++++++++++++++++ gogf127/gogf127.go | 211 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 gogf127/gf127_test.go create mode 100644 gogf127/gogf127.go diff --git a/gogf127/gf127_test.go b/gogf127/gf127_test.go new file mode 100644 index 0000000..d098925 --- /dev/null +++ b/gogf127/gf127_test.go @@ -0,0 +1,110 @@ +package gogf127 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +const maxUint64 = ^uint64(0) + +func TestAdd(t *testing.T) { + var ( + a = Random() + b = Random() + e = &GF127{a[0] ^ b[0], a[1] ^ b[1]} + c = new(GF127) + ) + Add(a, b, c) + require.Equal(t, e, c) +} + +var testCasesMul = [][3]*GF127{ + // (x+1)*(x^63+x^62+...+1) == x^64+1 + {&GF127{3, 0}, &GF127{maxUint64, 0}, &GF127{1, 1}}, + + // x^126 * x^2 == x^128 == x^64 + x + {&GF127{0, 1 << 62}, &GF127{4, 0}, &GF127{2, 1}}, + + // (x^64+x^63+1) * (x^64+x) == x^128+x^65+x^127+x^64+x^64+x == x^65+x^64+x^63+1 + {&GF127{1 + 1<<63, 1}, &GF127{2, 1}, &GF127{0x8000000000000001, 3}}, +} + +func TestMul(t *testing.T) { + c := new(GF127) + for _, tc := range testCasesMul { + Mul(tc[0], tc[1], c) + require.Equal(t, tc[2], c) + } +} + +var testCasesMul10 = [][2]*GF127{ + {&GF127{123, 0}, &GF127{246, 0}}, + {&GF127{maxUint64, 2}, &GF127{maxUint64 - 1, 5}}, + {&GF127{0, maxUint64 >> 1}, &GF127{1 + 1<<63, maxUint64>>1 - 1}}, +} + +func TestMul10(t *testing.T) { + c := new(GF127) + for _, tc := range testCasesMul10 { + Mul10(tc[0], c) + require.Equal(t, tc[1], c) + } +} + +var testCasesMul11 = [][2]*GF127{ + {&GF127{123, 0}, &GF127{141, 0}}, + {&GF127{maxUint64, 2}, &GF127{1, 7}}, + {&GF127{0, maxUint64 >> 1}, &GF127{1 + 1<<63, 1}}, +} + +func TestMul11(t *testing.T) { + c := new(GF127) + for _, tc := range testCasesMul11 { + Mul11(tc[0], c) + require.Equal(t, tc[1], c) + } +} + +var testCasesInv = [][2]*GF127{ + {&GF127{1, 0}, &GF127{1, 0}}, + {&GF127{3, 0}, &GF127{msb64, ^msb64}}, + {&GF127{54321, 12345}, &GF127{8230555108620784737, 3929873967650665114}}, +} + +func TestInv(t *testing.T) { + var a, b, c = new(GF127), new(GF127), new(GF127) + for _, tc := range testCasesInv { + Inv(tc[0], c) + require.Equal(t, tc[1], c) + } + + for i := 0; i < 3; i++ { + // 0 has no inverse + if a = Random(); a.Equals(&GF127{0, 0}) { + continue + } + Inv(a, b) + Mul(a, b, c) + require.Equal(t, &GF127{1, 0}, c) + } +} + +func TestGF127_MarshalBinary(t *testing.T) { + a := New(0xFF, 0xEE) + data, err := a.MarshalBinary() + require.NoError(t, err) + require.Equal(t, data, []byte{0, 0, 0, 0, 0, 0, 0, 0xEE, 0, 0, 0, 0, 0, 0, 0, 0xFF}) + + a = Random() + data, err = a.MarshalBinary() + require.NoError(t, err) + + b := new(GF127) + err = b.UnmarshalBinary(data) + require.NoError(t, err) + require.Equal(t, a, b) + + err = b.UnmarshalBinary([]byte{0, 1, 2, 3}) + require.Error(t, err) +} diff --git a/gogf127/gogf127.go b/gogf127/gogf127.go new file mode 100644 index 0000000..cff06a3 --- /dev/null +++ b/gogf127/gogf127.go @@ -0,0 +1,211 @@ +// Copyright 2019 (c) NSPCC +// +// Package gf127 implements the GF(2^127) arithmetic +// modulo reduction polynomial x^127 + x^63 + 1 . +// Implementation is in pure Go. +package gogf127 + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "math/bits" + "math/rand" +) + +// GF127 represents element of GF(2^127) +type GF127 [2]uint64 + +const ( + msb64 = uint64(0x8000000000000000) + byteSize = 16 +) + +var ( + // x126x631 is reduction polynomial x^127+x^63+1 + x127x631 = GF127{msb64 + 1, msb64} +) + +// New constructs new element of GF(2^127) as hi*x^64 + lo. +// It is assumed that hi has zero MSB. +func New(lo, hi uint64) *GF127 { + return &GF127{lo, hi} +} + +// Random returns random element from GF(2^127). +// Is used mostly for testing. +func Random() *GF127 { + return &GF127{rand.Uint64(), rand.Uint64() >> 1} +} + +// String returns hex-encoded representation, starting with MSB. +func (c *GF127) String() string { + return hex.EncodeToString(c.ByteArray()) +} + +// Equals checks if two reduced (zero MSB) elements of GF(2^127) are equal +func (c *GF127) Equals(b *GF127) bool { + return c[0] == b[0] && c[1] == b[1] +} + +// ByteArray represents element of GF(2^127) as byte array of length 16. +func (c *GF127) ByteArray() (buf []byte) { + buf = make([]byte, 16) + binary.BigEndian.PutUint64(buf[:8], c[1]) + binary.BigEndian.PutUint64(buf[8:], c[0]) + return +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (c *GF127) MarshalBinary() (data []byte, err error) { + return c.ByteArray(), nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (c *GF127) UnmarshalBinary(data []byte) error { + if len(data) != byteSize { + return errors.New("data must be 16-bytes long") + } + + c[0] = binary.BigEndian.Uint64(data[8:]) + c[1] = binary.BigEndian.Uint64(data[:8]) + if c[1]&msb64 != 0 { + return errors.New("MSB must be zero") + } + + return nil +} + +// Inv sets b to a^-1 +// Algorithm is based on Extended Euclidean Algorithm +// and is described by Hankerson, Hernandez, Menezes in +// https://link.springer.com/content/pdf/10.1007/3-540-44499-8_1.pdf +func Inv(a, b *GF127) { + var ( + v = x127x631 + u = *a + c, d = &GF127{1, 0}, &GF127{0, 0} + t = new(GF127) + x *GF127 + ) + + // degree of polynomial is a position of most significant bit + for du, dv := msb(&u), msb(&v); du != 0; du, dv = msb(&u), msb(&v) { + if du < dv { + v, u = u, v + dv, du = du, dv + d, c = c, d + } + + x = xN(du - dv) + + Mul(x, &v, t) + Add(&u, t, &u) + + // becasuse mul performs reduction on t, we need + // manually reduce u at first step + if msb(&u) == 127 { + Add(&u, &x127x631, &u) + } + + Mul(x, d, t) + Add(c, t, c) + } + *b = *c +} + +func xN(n int) *GF127 { + if n < 64 { + return &GF127{1 << uint(n), 0} + } + return &GF127{0, 1 << uint(n-64)} +} + +func msb(a *GF127) (x int) { + x = bits.LeadingZeros64(a[1]) + if x == 64 { + x = bits.LeadingZeros64(a[0]) + 64 + } + return 127 - x +} + +// Mul sets c to the product a*b and returns c. +func (c *GF127) Mul(a, b *GF127) *GF127 { + Mul(a, b, c) + return c +} + +// Add sets c to the sum a+b and returns c. +func (c *GF127) Add(a, b *GF127) *GF127 { + Add(a, b, c) + return c +} + +// Mul1 copies a to b. +func Mul1(a, b *GF127) { + b[0] = a[0] + b[1] = a[1] +} + +// And sets c to a & b (bitwise-and). +func And(a, b, c *GF127) { + c[0] = a[0] & b[0] + c[1] = a[1] & b[1] +} + +// Add sets c to a+b. +func Add(a, b, c *GF127) { + c[0] = a[0] ^ b[0] + c[1] = a[1] ^ b[1] +} + +// Mul sets c to a*b. +// TODO optimization: no need to perform shift by i every time, cache results +func Mul(a, b, c *GF127) { + c[0] = 0 + c[1] = 0 + d := new(GF127) + for i := uint(0); i < 64; i++ { + if b[0]&(1<> 63 + b[0] = a[0] << 1 + b[1] = (a[1] << 1) ^ c + if b[1]&msb64 != 0 { + b[0] ^= x127x631[0] + b[1] ^= x127x631[1] + } +} + +// Mul11 sets b to a*(x+1). +func Mul11(a, b *GF127) { + c := (a[0] & msb64) >> 63 + b[0] = a[0] ^ (a[0] << 1) + b[1] = a[1] ^ (a[1] << 1) ^ c + if b[1]&msb64 != 0 { + b[0] ^= x127x631[0] + b[1] ^= x127x631[1] + } +}