2022-08-26 22:26:53 +02:00
|
|
|
package goldilocks
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"math/bits"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ScalarSize is the size (in bytes) of scalars.
|
|
|
|
const ScalarSize = 56 // 448 / 8
|
|
|
|
|
2023-12-13 14:29:02 +01:00
|
|
|
// _N is the number of 64-bit words to store scalars.
|
2022-08-26 22:26:53 +02:00
|
|
|
const _N = 7 // 448 / 64
|
|
|
|
|
|
|
|
// Scalar represents a positive integer stored in little-endian order.
|
|
|
|
type Scalar [ScalarSize]byte
|
|
|
|
|
|
|
|
type scalar64 [_N]uint64
|
|
|
|
|
|
|
|
func (z *scalar64) fromScalar(x *Scalar) {
|
|
|
|
z[0] = binary.LittleEndian.Uint64(x[0*8 : 1*8])
|
|
|
|
z[1] = binary.LittleEndian.Uint64(x[1*8 : 2*8])
|
|
|
|
z[2] = binary.LittleEndian.Uint64(x[2*8 : 3*8])
|
|
|
|
z[3] = binary.LittleEndian.Uint64(x[3*8 : 4*8])
|
|
|
|
z[4] = binary.LittleEndian.Uint64(x[4*8 : 5*8])
|
|
|
|
z[5] = binary.LittleEndian.Uint64(x[5*8 : 6*8])
|
|
|
|
z[6] = binary.LittleEndian.Uint64(x[6*8 : 7*8])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (z *scalar64) toScalar(x *Scalar) {
|
|
|
|
binary.LittleEndian.PutUint64(x[0*8:1*8], z[0])
|
|
|
|
binary.LittleEndian.PutUint64(x[1*8:2*8], z[1])
|
|
|
|
binary.LittleEndian.PutUint64(x[2*8:3*8], z[2])
|
|
|
|
binary.LittleEndian.PutUint64(x[3*8:4*8], z[3])
|
|
|
|
binary.LittleEndian.PutUint64(x[4*8:5*8], z[4])
|
|
|
|
binary.LittleEndian.PutUint64(x[5*8:6*8], z[5])
|
|
|
|
binary.LittleEndian.PutUint64(x[6*8:7*8], z[6])
|
|
|
|
}
|
|
|
|
|
|
|
|
// add calculates z = x + y. Assumes len(z) > max(len(x),len(y)).
|
|
|
|
func add(z, x, y []uint64) uint64 {
|
|
|
|
l, L, zz := len(x), len(y), y
|
|
|
|
if l > L {
|
|
|
|
l, L, zz = L, l, x
|
|
|
|
}
|
|
|
|
c := uint64(0)
|
|
|
|
for i := 0; i < l; i++ {
|
|
|
|
z[i], c = bits.Add64(x[i], y[i], c)
|
|
|
|
}
|
|
|
|
for i := l; i < L; i++ {
|
|
|
|
z[i], c = bits.Add64(zz[i], 0, c)
|
|
|
|
}
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// sub calculates z = x - y. Assumes len(z) > max(len(x),len(y)).
|
|
|
|
func sub(z, x, y []uint64) uint64 {
|
|
|
|
l, L, zz := len(x), len(y), y
|
|
|
|
if l > L {
|
|
|
|
l, L, zz = L, l, x
|
|
|
|
}
|
|
|
|
c := uint64(0)
|
|
|
|
for i := 0; i < l; i++ {
|
|
|
|
z[i], c = bits.Sub64(x[i], y[i], c)
|
|
|
|
}
|
|
|
|
for i := l; i < L; i++ {
|
|
|
|
z[i], c = bits.Sub64(zz[i], 0, c)
|
|
|
|
}
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// mulWord calculates z = x * y. Assumes len(z) >= len(x)+1.
|
|
|
|
func mulWord(z, x []uint64, y uint64) {
|
|
|
|
for i := range z {
|
|
|
|
z[i] = 0
|
|
|
|
}
|
|
|
|
carry := uint64(0)
|
|
|
|
for i := range x {
|
|
|
|
hi, lo := bits.Mul64(x[i], y)
|
|
|
|
lo, cc := bits.Add64(lo, z[i], 0)
|
|
|
|
hi, _ = bits.Add64(hi, 0, cc)
|
|
|
|
z[i], cc = bits.Add64(lo, carry, 0)
|
|
|
|
carry, _ = bits.Add64(hi, 0, cc)
|
|
|
|
}
|
|
|
|
z[len(x)] = carry
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cmov moves x into z if b=1.
|
|
|
|
func (z *scalar64) Cmov(b uint64, x *scalar64) {
|
|
|
|
m := uint64(0) - b
|
|
|
|
for i := range z {
|
|
|
|
z[i] = (z[i] &^ m) | (x[i] & m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// leftShift shifts to the left the words of z returning the more significant word.
|
|
|
|
func (z *scalar64) leftShift(low uint64) uint64 {
|
|
|
|
high := z[_N-1]
|
|
|
|
for i := _N - 1; i > 0; i-- {
|
|
|
|
z[i] = z[i-1]
|
|
|
|
}
|
|
|
|
z[0] = low
|
|
|
|
return high
|
|
|
|
}
|
|
|
|
|
|
|
|
// reduceOneWord calculates z = z + 2^448*x such that the result fits in a Scalar.
|
|
|
|
func (z *scalar64) reduceOneWord(x uint64) {
|
|
|
|
prod := (&scalar64{})[:]
|
|
|
|
mulWord(prod, residue448[:], x)
|
|
|
|
cc := add(z[:], z[:], prod)
|
|
|
|
mulWord(prod, residue448[:], cc)
|
|
|
|
add(z[:], z[:], prod)
|
|
|
|
}
|
|
|
|
|
|
|
|
// modOrder reduces z mod order.
|
|
|
|
func (z *scalar64) modOrder() {
|
|
|
|
var o64, x scalar64
|
|
|
|
o64.fromScalar(&order)
|
|
|
|
// Performs: while (z >= order) { z = z-order }
|
|
|
|
// At most 8 (eight) iterations reduce 3 bits by subtracting.
|
|
|
|
for i := 0; i < 8; i++ {
|
|
|
|
c := sub(x[:], z[:], o64[:]) // (c || x) = z-order
|
|
|
|
z.Cmov(1-c, &x) // if c != 0 { z = x }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromBytes stores z = x mod order, where x is a number stored in little-endian order.
|
|
|
|
func (z *Scalar) FromBytes(x []byte) {
|
|
|
|
n := len(x)
|
|
|
|
nCeil := (n + 7) >> 3
|
|
|
|
for i := range z {
|
|
|
|
z[i] = 0
|
|
|
|
}
|
|
|
|
if nCeil < _N {
|
|
|
|
copy(z[:], x)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
copy(z[:], x[8*(nCeil-_N):])
|
|
|
|
var z64 scalar64
|
|
|
|
z64.fromScalar(z)
|
|
|
|
for i := nCeil - _N - 1; i >= 0; i-- {
|
|
|
|
low := binary.LittleEndian.Uint64(x[8*i:])
|
|
|
|
high := z64.leftShift(low)
|
|
|
|
z64.reduceOneWord(high)
|
|
|
|
}
|
|
|
|
z64.modOrder()
|
|
|
|
z64.toScalar(z)
|
|
|
|
}
|
|
|
|
|
|
|
|
// divBy4 calculates z = x/4 mod order.
|
|
|
|
func (z *Scalar) divBy4(x *Scalar) { z.Mul(x, &invFour) }
|
|
|
|
|
|
|
|
// Red reduces z mod order.
|
|
|
|
func (z *Scalar) Red() { var t scalar64; t.fromScalar(z); t.modOrder(); t.toScalar(z) }
|
|
|
|
|
|
|
|
// Neg calculates z = -z mod order.
|
|
|
|
func (z *Scalar) Neg() { z.Sub(&order, z) }
|
|
|
|
|
|
|
|
// Add calculates z = x+y mod order.
|
|
|
|
func (z *Scalar) Add(x, y *Scalar) {
|
|
|
|
var z64, x64, y64, t scalar64
|
|
|
|
x64.fromScalar(x)
|
|
|
|
y64.fromScalar(y)
|
|
|
|
c := add(z64[:], x64[:], y64[:])
|
|
|
|
add(t[:], z64[:], residue448[:])
|
|
|
|
z64.Cmov(c, &t)
|
|
|
|
z64.modOrder()
|
|
|
|
z64.toScalar(z)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sub calculates z = x-y mod order.
|
|
|
|
func (z *Scalar) Sub(x, y *Scalar) {
|
|
|
|
var z64, x64, y64, t scalar64
|
|
|
|
x64.fromScalar(x)
|
|
|
|
y64.fromScalar(y)
|
|
|
|
c := sub(z64[:], x64[:], y64[:])
|
|
|
|
sub(t[:], z64[:], residue448[:])
|
|
|
|
z64.Cmov(c, &t)
|
|
|
|
z64.modOrder()
|
|
|
|
z64.toScalar(z)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mul calculates z = x*y mod order.
|
|
|
|
func (z *Scalar) Mul(x, y *Scalar) {
|
|
|
|
var z64, x64, y64 scalar64
|
|
|
|
prod := (&[_N + 1]uint64{})[:]
|
|
|
|
x64.fromScalar(x)
|
|
|
|
y64.fromScalar(y)
|
|
|
|
mulWord(prod, x64[:], y64[_N-1])
|
|
|
|
copy(z64[:], prod[:_N])
|
|
|
|
z64.reduceOneWord(prod[_N])
|
|
|
|
for i := _N - 2; i >= 0; i-- {
|
|
|
|
h := z64.leftShift(0)
|
|
|
|
z64.reduceOneWord(h)
|
|
|
|
mulWord(prod, x64[:], y64[i])
|
|
|
|
c := add(z64[:], z64[:], prod[:_N])
|
|
|
|
z64.reduceOneWord(prod[_N] + c)
|
|
|
|
}
|
|
|
|
z64.modOrder()
|
|
|
|
z64.toScalar(z)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsZero returns true if z=0.
|
|
|
|
func (z *Scalar) IsZero() bool { z.Red(); return *z == Scalar{} }
|