diff --git a/dcrec/secp256k1/field64.go b/dcrec/secp256k1/field64.go new file mode 100644 index 0000000000..c751f2b07a --- /dev/null +++ b/dcrec/secp256k1/field64.go @@ -0,0 +1,768 @@ +// Copyright (c) 2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package secp256k1 + +import ( + "encoding/binary" + "encoding/hex" + "math/bits" +) + +// FieldVal64 is a secp256k1 field element stored as four little-endian uint64 +// limbs with Crandall reduction for p = 2^256 - 0x1000003D1. +// +// Unlike FieldVal (10 x uint32 base 2^26), this uses tight 256-bit packing and +// fully reduces after each operation. +type FieldVal64 struct { + n [4]uint64 +} + +const ( + field64PrimeComplement = 0x1000003D1 // 2^32 + 977 + + field64Prime0 = 0xFFFFFFFEFFFFFC2F + field64Prime1 = 0xFFFFFFFFFFFFFFFF + field64Prime2 = 0xFFFFFFFFFFFFFFFF + field64Prime3 = 0xFFFFFFFFFFFFFFFF +) + +// String returns the field value as a human-readable hex string. +func (f FieldVal64) String() string { + return hex.EncodeToString(f.Bytes()[:]) +} + +// Zero sets the field value to zero in constant time. A newly created field +// value is already set to zero. This function can be useful to clear an +// existing field value for reuse. +func (f *FieldVal64) Zero() { + f.n = [4]uint64{} +} + +// Set sets the field value equal to the passed value in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f := new(FieldVal).Set(f2).Add(1) so that f = f2 + 1 where f2 is not +// modified. +func (f *FieldVal64) Set(val *FieldVal64) *FieldVal64 { + f.n = val.n + return f +} + +// SetInt sets the field value to the passed integer in constant time. This is +// a convenience function since it is fairly common to perform some arithmetic +// with small native integers. +// +// The field value is returned to support chaining. This enables syntax such +// as f := new(FieldVal).SetInt(2).Mul(f2) so that f = 2 * f2. +func (f *FieldVal64) SetInt(v uint16) *FieldVal64 { + f.n = [4]uint64{uint64(v), 0, 0, 0} + return f +} + +// SetBytes packs the passed 32-byte big-endian value into the internal field +// value representation in constant time. SetBytes interprets the provided +// array as a 256-bit big-endian unsigned integer, packs it into the internal +// field value representation, and returns either 1 if it is greater than or +// equal to the field prime (aka it overflowed) or 0 otherwise in constant time. +// +// Note that a bool is not used here because it is not possible in Go to convert +// from a bool to numeric value in constant time and many constant-time +// operations require a numeric value. +func (f *FieldVal64) SetBytes(b *[32]byte) uint32 { + f.n[0] = binary.BigEndian.Uint64(b[24:32]) + f.n[1] = binary.BigEndian.Uint64(b[16:24]) + f.n[2] = binary.BigEndian.Uint64(b[8:16]) + f.n[3] = binary.BigEndian.Uint64(b[0:8]) + + // Subtract p once. The input overflowed (>= p) when f - p does not borrow, + // in which case the reduced result s replaces f via constant-time select. + var s0, s1, s2, s3, borrow uint64 + s0, borrow = bits.Sub64(f.n[0], field64Prime0, 0) + s1, borrow = bits.Sub64(f.n[1], field64Prime1, borrow) + s2, borrow = bits.Sub64(f.n[2], field64Prime2, borrow) + s3, borrow = bits.Sub64(f.n[3], field64Prime3, borrow) + + mask := -(1 - borrow) + f.n[0] ^= (s0 ^ f.n[0]) & mask + f.n[1] ^= (s1 ^ f.n[1]) & mask + f.n[2] ^= (s2 ^ f.n[2]) & mask + f.n[3] ^= (s3 ^ f.n[3]) & mask + return uint32(1 - borrow) +} + +// SetByteSlice interprets the provided slice as a 256-bit big-endian unsigned +// integer (meaning it is truncated to the first 32 bytes), packs it into the +// internal field value representation, and returns whether or not the resulting +// truncated 256-bit integer is greater than or equal to the field prime (aka it +// overflowed) in constant time. +// +// Note that since passing a slice with more than 32 bytes is truncated, it is +// possible that the truncated value is less than the field prime and hence it +// will not be reported as having overflowed in that case. It is up to the +// caller to decide whether it needs to provide numbers of the appropriate size +// or it if is acceptable to use this function with the described truncation and +// overflow behavior. +func (f *FieldVal64) SetByteSlice(b []byte) bool { + var b32 [32]byte + b = b[:constantTimeMin(uint32(len(b)), 32)] + copy(b32[:], b32[:32-len(b)]) + copy(b32[32-len(b):], b) + result := f.SetBytes(&b32) + zeroArray32(&b32) + return result != 0 +} + +// PutBytesUnchecked unpacks the field value to a 32-byte big-endian value +// directly into the passed byte slice in constant time. The target slice must +// have at least 32 bytes available or it will panic. +// +// There is a similar function, PutBytes, which unpacks the field value into a +// 32-byte array directly. This version is provided since it can be useful +// to write directly into part of a larger buffer without needing a separate +// allocation. +func (f *FieldVal64) PutBytesUnchecked(b []byte) { + binary.BigEndian.PutUint64(b[0:8], f.n[3]) + binary.BigEndian.PutUint64(b[8:16], f.n[2]) + binary.BigEndian.PutUint64(b[16:24], f.n[1]) + binary.BigEndian.PutUint64(b[24:32], f.n[0]) +} + +// PutBytes unpacks the field value to a 32-byte big-endian value using the +// passed byte array in constant time. +// +// There is a similar function, PutBytesUnchecked, which unpacks the field value +// into a slice that must have at least 32 bytes available. This version is +// provided since it can be useful to write directly into an array that is type +// checked. +// +// Alternatively, there is also Bytes, which unpacks the field value into a new +// array and returns that which can sometimes be more ergonomic in applications +// that aren't concerned about an additional copy. +func (f *FieldVal64) PutBytes(b *[32]byte) { + f.PutBytesUnchecked(b[:]) +} + +// Bytes unpacks the field value to a 32-byte big-endian value in constant time. +// +// See PutBytes and PutBytesUnchecked for variants that allow an array or slice +// to be passed which can be useful to cut down on the number of allocations by +// allowing the caller to reuse a buffer or write directly into part of a larger +// buffer. +func (f *FieldVal64) Bytes() *[32]byte { + var b [32]byte + f.PutBytes(&b) + return &b +} + +// IsZeroBit returns 1 when the field value is equal to zero or 0 otherwise in +// constant time. +// +// Note that a bool is not used here because it is not possible in Go to convert +// from a bool to numeric value in constant time and many constant-time +// operations require a numeric value. See IsZero for the version that returns +// a bool. +func (f *FieldVal64) IsZeroBit() uint32 { + return constantTimeEq64(f.n[0]|f.n[1]|f.n[2]|f.n[3], 0) +} + +// IsZero returns whether or not the field value is equal to zero in constant +// time. +func (f *FieldVal64) IsZero() bool { + return (f.n[0] | f.n[1] | f.n[2] | f.n[3]) == 0 +} + +// IsOneBit returns 1 when the field value is equal to one or 0 otherwise in +// constant time. +// +// Note that a bool is not used here because it is not possible in Go to convert +// from a bool to numeric value in constant time and many constant-time +// operations require a numeric value. See IsOne for the version that returns a +// bool. +func (f *FieldVal64) IsOneBit() uint32 { + // The value can only be one if the single lowest significant bit is set in + // the first word and no other bits are set in any of the other words. + // This is a constant time implementation. + return constantTimeEq64((f.n[0]^1)|f.n[1]|f.n[2]|f.n[3], 0) +} + +// IsOne returns whether or not the field value is equal to one in constant +// time. +func (f *FieldVal64) IsOne() bool { + // The value can only be one if the single lowest significant bit is set in + // the first word and no other bits are set in any of the other words. + // This is a constant time implementation. + return ((f.n[0] ^ 1) | f.n[1] | f.n[2] | f.n[3]) == 0 +} + +// IsOddBit returns 1 when the field value is an odd number or 0 otherwise in +// constant time. +// +// Note that a bool is not used here because it is not possible in Go to convert +// from a bool to numeric value in constant time and many constant-time +// operations require a numeric value. See IsOdd for the version that returns a +// bool. +func (f *FieldVal64) IsOddBit() uint32 { + return uint32(f.n[0] & 1) +} + +// IsOdd returns whether or not the field value is an odd number in constant +// time. +func (f *FieldVal64) IsOdd() bool { + return f.n[0]&1 == 1 +} + +// Equals returns whether or not the two field values are the same in constant +// time. +func (f *FieldVal64) Equals(val *FieldVal64) bool { + // Xor only sets bits when they are different, so the two field values + // can only be the same if no bits are set after xoring each word. + // This is a constant time implementation. + return ((f.n[0] ^ val.n[0]) | (f.n[1] ^ val.n[1]) | (f.n[2] ^ val.n[2]) | (f.n[3] ^ val.n[3])) == 0 +} + +// NegateVal negates the passed value and stores the result in f in constant +// time.v +// +// The field value is returned to support chaining. This enables syntax like: +// f.NegateVal(f2).AddInt(1) so that f = -f2 + 1. +func (f *FieldVal64) NegateVal(val *FieldVal64) *FieldVal64 { + // Pass 1: subtract val from 0. borrow is set iff val != 0. + var t0, t1, t2, t3, borrow uint64 + t0, borrow = bits.Sub64(0, val.n[0], 0) + t1, borrow = bits.Sub64(0, val.n[1], borrow) + t2, borrow = bits.Sub64(0, val.n[2], borrow) + t3, borrow = bits.Sub64(0, val.n[3], borrow) + + // Pass 2: mask the modulus with the borrow (p when val != 0, else 0). + mask := -borrow + maskedPrime0 := field64Prime0 & mask + + // Pass 3: add the masked modulus + var carry uint64 + f.n[0], carry = bits.Add64(t0, maskedPrime0, 0) + f.n[1], carry = bits.Add64(t1, mask, carry) + f.n[2], carry = bits.Add64(t2, mask, carry) + f.n[3], _ = bits.Add64(t3, mask, carry) + return f +} + +// Negate negates the field value in constant time. The existing field value is +// modified. +// +// The field value is returned to support chaining. This enables syntax like: +// f.Negate().AddInt(1) so that f = -f + 1. +func (f *FieldVal64) Negate() *FieldVal64 { + return f.NegateVal(f) +} + +// AddInt adds the passed integer to the existing field value and stores the +// result in f in constant time. This is a convenience function since it is +// fairly common to perform some arithmetic with small native integers. +// +// The field value is returned to support chaining. This enables syntax like: +// f.AddInt(1).Add(f2) so that f = f + 1 + f2. +func (f *FieldVal64) AddInt(ui uint16) *FieldVal64 { + var t FieldVal64 + t.SetInt(ui) + return f.Add(&t) +} + +// Add adds the passed value to the existing field value and stores the result +// in f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f.Add(f2).AddInt(1) so that f = f + f2 + 1. +func (f *FieldVal64) Add(val *FieldVal64) *FieldVal64 { + return f.Add2(f, val) +} + +// Add2 adds the passed two field values together and stores the result in f in +// constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f3.Add2(f, f2).AddInt(1) so that f3 = f + f2 + 1. +func (f *FieldVal64) Add2(a, b *FieldVal64) *FieldVal64 { + var t0, t1, t2, t3, overflow, carry uint64 + + // Pass 1: add. + t0, carry = bits.Add64(a.n[0], b.n[0], 0) + t1, carry = bits.Add64(a.n[1], b.n[1], carry) + t2, carry = bits.Add64(a.n[2], b.n[2], carry) + t3, overflow = bits.Add64(a.n[3], b.n[3], carry) + + // Pass 2: subtract p. + var s0, s1, s2, s3, borrow uint64 + s0, borrow = bits.Sub64(t0, field64Prime0, 0) + s1, borrow = bits.Sub64(t1, field64Prime1, borrow) + s2, borrow = bits.Sub64(t2, field64Prime2, borrow) + s3, borrow = bits.Sub64(t3, field64Prime3, borrow) + + // Pass 3: constant-time select. Keep t only when there was no overflow and + // t < p (borrow set); otherwise use s + mask := -((1 - overflow) & borrow) + f.n[0] = s0 ^ ((t0 ^ s0) & mask) + f.n[1] = s1 ^ ((t1 ^ s1) & mask) + f.n[2] = s2 ^ ((t2 ^ s2) & mask) + f.n[3] = s3 ^ ((t3 ^ s3) & mask) + return f +} + +// MulBy2 multiplies the field value by 2 and stores the result in +// f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f.MulBy2().Add(f2) so that f = 2 * f + f2. +func (f *FieldVal64) MulBy2() *FieldVal64 { + return f.Add(f) +} + +// MulBy3 multiplies the field value by 3 and stores the result in +// f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f.MulBy3().Add(f2) so that f = 3 * f + f2. +func (f *FieldVal64) MulBy3() *FieldVal64 { + var orig FieldVal64 + orig.Set(f) + return f.MulBy2().Add(&orig) +} + +// MulBy4 multiplies the field value by 4 and stores the result in +// f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f.MulBy4().Add(f2) so that f = 4 * f + f2. +func (f *FieldVal64) MulBy4() *FieldVal64 { + return f.MulBy2().MulBy2() +} + +// MulBy8 multiplies the field value by 8 and stores the result in +// f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f.MulBy8().Add(f2) so that f = 8 * f + f2. +func (f *FieldVal64) MulBy8() *FieldVal64 { + return f.MulBy4().MulBy2() +} + +// MulInt multiplies the field value by the passed int and stores the result in +// f in constant time. +// For the specific small multipliers used in the curve equations, prefer the +// dedicated MulBy2, MulBy3, MulBy4, and MulBy8 methods. +// +// The field value is returned to support chaining. This enables syntax like: +// f.MulInt(2).Add(f2) so that f = 2 * f + f2. +func (f *FieldVal64) MulInt(val uint8) *FieldVal64 { + var t FieldVal64 + t.SetInt(uint16(val)) + return f.Mul(&t) +} + +// Mul multiplies the passed value to the existing field value and stores the +// result in f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f.Mul(f2).AddInt(1) so that f = (f * f2) + 1. +func (f *FieldVal64) Mul(val *FieldVal64) *FieldVal64 { + return f.Mul2(f, val) +} + +// Mul2 multiplies the passed two field values together and stores the result in +// f in constant time. +// +// The field value is returned to support chaining. This enables syntax like: +// f3.Mul2(f, f2).AddInt(1) so that f3 = (f * f2) + 1. +func (f *FieldVal64) Mul2(a, b *FieldVal64) *FieldVal64 { + field64Mul(&f.n, &a.n, &b.n) + return f +} + +// Square squares the field value in constant time. The existing field value is +// modified. +// +// The field value is returned to support chaining. This enables syntax like: +// f.Square().Mul(f2) so that f = f^2 * f2. +func (f *FieldVal64) Square() *FieldVal64 { + return f.SquareVal(f) +} + +// SquareVal squares the passed value and stores the result in f in constant +// time. +// +// The field value is returned to support chaining. This enables syntax like: +// f3.SquareVal(f).Mul(f) so that f3 = f^2 * f = f^3. +func (f *FieldVal64) SquareVal(val *FieldVal64) *FieldVal64 { + field64Square(&f.n, &val.n) + return f +} + +// field64Mul512 sets t = x * y as an unreduced 512-bit product via a row-by-row +// schoolbook multiply. +func field64Mul512(t *[8]uint64, x, y *[4]uint64) { + a0, a1, a2, a3 := x[0], x[1], x[2], x[3] + b0, b1, b2, b3 := y[0], y[1], y[2], y[3] + var c uint64 + + // Row 0: p0..p4 = a * b0. + h0, p0 := bits.Mul64(a0, b0) + h1, p1 := bits.Mul64(a1, b0) + h2, p2 := bits.Mul64(a2, b0) + h3, p3 := bits.Mul64(a3, b0) + p1, c = bits.Add64(p1, h0, 0) + p2, c = bits.Add64(p2, h1, c) + p3, c = bits.Add64(p3, h2, c) + p4 := h3 + c + + // Row 1: p1..p5 += a * b1. + h0, q0 := bits.Mul64(a0, b1) + h1, q1 := bits.Mul64(a1, b1) + h2, q2 := bits.Mul64(a2, b1) + h3, q3 := bits.Mul64(a3, b1) + q1, c = bits.Add64(q1, h0, 0) + q2, c = bits.Add64(q2, h1, c) + q3, c = bits.Add64(q3, h2, c) + q4 := h3 + c + p1, c = bits.Add64(p1, q0, 0) + p2, c = bits.Add64(p2, q1, c) + p3, c = bits.Add64(p3, q2, c) + p4, c = bits.Add64(p4, q3, c) + p5 := q4 + c + + // Row 2: p2..p6 += a * b2. + h0, q0 = bits.Mul64(a0, b2) + h1, q1 = bits.Mul64(a1, b2) + h2, q2 = bits.Mul64(a2, b2) + h3, q3 = bits.Mul64(a3, b2) + q1, c = bits.Add64(q1, h0, 0) + q2, c = bits.Add64(q2, h1, c) + q3, c = bits.Add64(q3, h2, c) + q4 = h3 + c + p2, c = bits.Add64(p2, q0, 0) + p3, c = bits.Add64(p3, q1, c) + p4, c = bits.Add64(p4, q2, c) + p5, c = bits.Add64(p5, q3, c) + p6 := q4 + c + + // Row 3: p3..p7 += a * b3. + h0, q0 = bits.Mul64(a0, b3) + h1, q1 = bits.Mul64(a1, b3) + h2, q2 = bits.Mul64(a2, b3) + h3, q3 = bits.Mul64(a3, b3) + q1, c = bits.Add64(q1, h0, 0) + q2, c = bits.Add64(q2, h1, c) + q3, c = bits.Add64(q3, h2, c) + q4 = h3 + c + p3, c = bits.Add64(p3, q0, 0) + p4, c = bits.Add64(p4, q1, c) + p5, c = bits.Add64(p5, q2, c) + p6, c = bits.Add64(p6, q3, c) + p7 := q4 + c + + t[0], t[1], t[2], t[3] = p0, p1, p2, p3 + t[4], t[5], t[6], t[7] = p4, p5, p6, p7 +} + +// field64Square512 sets t = a^2 as an unreduced 512-bit product. +func field64Square512(t *[8]uint64, a *[4]uint64) { + a0, a1, a2, a3 := a[0], a[1], a[2], a[3] + + // Off-diagonal upper-triangle products (not yet doubled). + p2, p1 := bits.Mul64(a0, a1) + h02, l02 := bits.Mul64(a0, a2) + h03, l03 := bits.Mul64(a0, a3) + var c uint64 + p2, c = bits.Add64(p2, l02, 0) + p3, c := bits.Add64(h02, l03, c) + p4, _ := bits.Add64(h03, 0, c) + + h12, l12 := bits.Mul64(a1, a2) + p3, c = bits.Add64(p3, l12, 0) + p4, c = bits.Add64(p4, h12, c) + p5 := c + + h13, l13 := bits.Mul64(a1, a3) + p4, c = bits.Add64(p4, l13, 0) + p5, _ = bits.Add64(p5, h13, c) + + h23, l23 := bits.Mul64(a2, a3) + p5, c = bits.Add64(p5, l23, 0) + p6, _ := bits.Add64(h23, 0, c) + + // Double p1..p6, capturing the top carry into p7. + p1, c = bits.Add64(p1, p1, 0) + p2, c = bits.Add64(p2, p2, c) + p3, c = bits.Add64(p3, p3, c) + p4, c = bits.Add64(p4, p4, c) + p5, c = bits.Add64(p5, p5, c) + p6, c = bits.Add64(p6, p6, c) + p7 := c + + // Add the diagonal squares a[i]^2 at columns 0,2,4,6 in one carry chain. + h0, p0 := bits.Mul64(a0, a0) + h1, l1 := bits.Mul64(a1, a1) + h2, l2 := bits.Mul64(a2, a2) + h3, l3 := bits.Mul64(a3, a3) + p1, c = bits.Add64(p1, h0, 0) + p2, c = bits.Add64(p2, l1, c) + p3, c = bits.Add64(p3, h1, c) + p4, c = bits.Add64(p4, l2, c) + p5, c = bits.Add64(p5, h2, c) + p6, c = bits.Add64(p6, l3, c) + p7, _ = bits.Add64(p7, h3, c) + + t[0], t[1], t[2], t[3] = p0, p1, p2, p3 + t[4], t[5], t[6], t[7] = p4, p5, p6, p7 +} + +// field64Reduce512 reduces a 512-bit little-endian limb array modulo p in +// constant time using Crandall folding (p = 2^256 - 0x1000003D1). +func field64Reduce512(r *[4]uint64, x *[8]uint64) { + var t0, t1, t2, t3, t4, h, lo, hi, carry uint64 + + h, t0 = bits.Mul64(x[4], field64PrimeComplement) + + hi, lo = bits.Mul64(x[5], field64PrimeComplement) + t1, carry = bits.Add64(lo, h, 0) + h, _ = bits.Add64(hi, 0, carry) + + hi, lo = bits.Mul64(x[6], field64PrimeComplement) + t2, carry = bits.Add64(lo, h, 0) + h, _ = bits.Add64(hi, 0, carry) + + hi, lo = bits.Mul64(x[7], field64PrimeComplement) + t3, carry = bits.Add64(lo, h, 0) + t4, _ = bits.Add64(hi, 0, carry) + + t0, carry = bits.Add64(t0, x[0], 0) + t1, carry = bits.Add64(t1, x[1], carry) + t2, carry = bits.Add64(t2, x[2], carry) + t3, carry = bits.Add64(t3, x[3], carry) + t4 += carry + + h, t4 = bits.Mul64(t4, field64PrimeComplement) + + t0, carry = bits.Add64(t0, t4, 0) + t1, carry = bits.Add64(t1, h, carry) + t2, carry = bits.Add64(t2, 0, carry) + t3, carry = bits.Add64(t3, 0, carry) + + // The second fold can carry out of t3. Keep it as a fifth limb (t4) and let + // the conditional subtract resolve it: the value is < 2p, so one 5-limb + // subtract of p fully reduces it. + t4 = carry + + var s0, s1, s2, s3, mask, borrow uint64 + s0, borrow = bits.Sub64(t0, field64Prime0, 0) + s1, borrow = bits.Sub64(t1, field64Prime1, borrow) + s2, borrow = bits.Sub64(t2, field64Prime2, borrow) + s3, borrow = bits.Sub64(t3, field64Prime3, borrow) + _, borrow = bits.Sub64(t4, 0, borrow) + mask = -borrow + r[0] = s0 ^ ((t0 ^ s0) & mask) + r[1] = s1 ^ ((t1 ^ s1) & mask) + r[2] = s2 ^ ((t2 ^ s2) & mask) + r[3] = s3 ^ ((t3 ^ s3) & mask) +} + +// field64Mul sets r = a * b (mod p). +func field64Mul(r *[4]uint64, a, b *[4]uint64) { + var product [8]uint64 + field64Mul512(&product, a, b) + field64Reduce512(r, &product) +} + +// field64Square sets r = a^2 (mod p). +func field64Square(r *[4]uint64, a *[4]uint64) { + var product [8]uint64 + field64Square512(&product, a) + field64Reduce512(r, &product) +} + +// IsGtOrEqPrimeMinusOrder returns whether or not the field value is greater +// than or equal to the field prime minus the secp256k1 group order in constant +// time. +func (f *FieldVal64) IsGtOrEqPrimeMinusOrder() bool { + // p - n (field prime minus the group order) as little-endian 64-bit limbs. + const ( + field64PMinusN0 = 0x402da1722fc9baee + field64PMinusN1 = 0x4551231950b75fc4 + field64PMinusN2 = 0x0000000000000001 + field64PMinusN3 = 0x0000000000000000 + ) + + var borrow uint64 + _, borrow = bits.Sub64(f.n[0], field64PMinusN0, 0) + _, borrow = bits.Sub64(f.n[1], field64PMinusN1, borrow) + _, borrow = bits.Sub64(f.n[2], field64PMinusN2, borrow) + _, borrow = bits.Sub64(f.n[3], field64PMinusN3, borrow) + return borrow == 0 +} + +// SquareRootVal either calculates the square root of the passed value when it +// exists or the square root of the negation of the value when it does not exist +// and stores the result in f in constant time. The return flag is true when +// the calculated square root is for the passed value itself and false when it +// is for its negation. +func (f *FieldVal64) SquareRootVal(val *FieldVal64) bool { + var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 FieldVal64 + a.Set(val) + a2.SquareVal(&a).Mul(&a) // a2 = a^(2^2 - 1) + a3.SquareVal(&a2).Mul(&a) // a3 = a^(2^3 - 1) + a6.SquareVal(&a3).Square().Square() // a6 = a^(2^6 - 2^3) + a6.Mul(&a3) // a6 = a^(2^6 - 1) + a9.SquareVal(&a6).Square().Square() // a9 = a^(2^9 - 2^3) + a9.Mul(&a3) // a9 = a^(2^9 - 1) + a11.SquareVal(&a9).Square() // a11 = a^(2^11 - 2^2) + a11.Mul(&a2) // a11 = a^(2^11 - 1) + a22.SquareVal(&a11).Square().Square().Square().Square() // a22 = a^(2^16 - 2^5) + a22.Square().Square().Square().Square().Square() // a22 = a^(2^21 - 2^10) + a22.Square() // a22 = a^(2^22 - 2^11) + a22.Mul(&a11) // a22 = a^(2^22 - 1) + a44.SquareVal(&a22).Square().Square().Square().Square() // a44 = a^(2^27 - 2^5) + a44.Square().Square().Square().Square().Square() // a44 = a^(2^32 - 2^10) + a44.Square().Square().Square().Square().Square() // a44 = a^(2^37 - 2^15) + a44.Square().Square().Square().Square().Square() // a44 = a^(2^42 - 2^20) + a44.Square().Square() // a44 = a^(2^44 - 2^22) + a44.Mul(&a22) // a44 = a^(2^44 - 1) + a88.SquareVal(&a44).Square().Square().Square().Square() // a88 = a^(2^49 - 2^5) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^54 - 2^10) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^59 - 2^15) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^64 - 2^20) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^69 - 2^25) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^74 - 2^30) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^79 - 2^35) + a88.Square().Square().Square().Square().Square() // a88 = a^(2^84 - 2^40) + a88.Square().Square().Square().Square() // a88 = a^(2^88 - 2^44) + a88.Mul(&a44) // a88 = a^(2^88 - 1) + a176.SquareVal(&a88).Square().Square().Square().Square() // a176 = a^(2^93 - 2^5) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^98 - 2^10) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^103 - 2^15) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^108 - 2^20) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^113 - 2^25) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^118 - 2^30) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^123 - 2^35) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^128 - 2^40) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^133 - 2^45) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^138 - 2^50) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^143 - 2^55) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^148 - 2^60) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^153 - 2^65) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^158 - 2^70) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^163 - 2^75) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^168 - 2^80) + a176.Square().Square().Square().Square().Square() // a176 = a^(2^173 - 2^85) + a176.Square().Square().Square() // a176 = a^(2^176 - 2^88) + a176.Mul(&a88) // a176 = a^(2^176 - 1) + a220.SquareVal(&a176).Square().Square().Square().Square() // a220 = a^(2^181 - 2^5) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^186 - 2^10) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^191 - 2^15) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^196 - 2^20) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^201 - 2^25) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^206 - 2^30) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^211 - 2^35) + a220.Square().Square().Square().Square().Square() // a220 = a^(2^216 - 2^40) + a220.Square().Square().Square().Square() // a220 = a^(2^220 - 2^44) + a220.Mul(&a44) // a220 = a^(2^220 - 1) + a223.SquareVal(&a220).Square().Square() // a223 = a^(2^223 - 2^3) + a223.Mul(&a3) // a223 = a^(2^223 - 1) + + f.SquareVal(&a223).Square().Square().Square().Square() // f = a^(2^228 - 2^5) + f.Square().Square().Square().Square().Square() // f = a^(2^233 - 2^10) + f.Square().Square().Square().Square().Square() // f = a^(2^238 - 2^15) + f.Square().Square().Square().Square().Square() // f = a^(2^243 - 2^20) + f.Square().Square().Square() // f = a^(2^246 - 2^23) + f.Mul(&a22) // f = a^(2^246 - 2^22 - 1) + f.Square().Square().Square().Square().Square() // f = a^(2^251 - 2^27 - 2^5) + f.Square() // f = a^(2^252 - 2^28 - 2^6) + f.Mul(&a2) // f = a^(2^252 - 2^28 - 2^6 - 2^1 - 1) + f.Square().Square() // f = a^(2^254 - 2^30 - 244) = a^((p+1)/4) + + // Verify the result is actually the square root by squaring it and checking + // against the original value. + var sqr FieldVal64 + return sqr.SquareVal(f).Equals(val) +} + +// Inverse finds the modular multiplicative inverse of the field value in +// constant time. The existing field value is modified. +// +// The field value is returned to support chaining. This enables syntax like: +// f.Inverse().Mul(f2) so that f = f^-1 * f2. +func (f *FieldVal64) Inverse() *FieldVal64 { + var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 FieldVal64 + a.Set(f) + a2.SquareVal(&a).Mul(&a) + a3.SquareVal(&a2).Mul(&a) + a6.SquareVal(&a3).Square().Square() + a6.Mul(&a3) + a9.SquareVal(&a6).Square().Square() + a9.Mul(&a3) + a11.SquareVal(&a9).Square() + a11.Mul(&a2) + a22.SquareVal(&a11).Square().Square().Square().Square() + a22.Square().Square().Square().Square().Square() + a22.Square() + a22.Mul(&a11) + a44.SquareVal(&a22).Square().Square().Square().Square() + a44.Square().Square().Square().Square().Square() + a44.Square().Square().Square().Square().Square() + a44.Square().Square().Square().Square().Square() + a44.Square().Square() + a44.Mul(&a22) + a88.SquareVal(&a44).Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square().Square() + a88.Square().Square().Square().Square() + a88.Mul(&a44) + a176.SquareVal(&a88).Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square().Square().Square() + a176.Square().Square().Square() + a176.Mul(&a88) + a220.SquareVal(&a176).Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square().Square() + a220.Square().Square().Square().Square() + a220.Mul(&a44) + a223.SquareVal(&a220).Square().Square() + a223.Mul(&a3) + + f.SquareVal(&a223).Square().Square().Square().Square() + f.Square().Square().Square().Square().Square() + f.Square().Square().Square().Square().Square() + f.Square().Square().Square().Square().Square() + f.Square().Square().Square() + f.Mul(&a22) + f.Square().Square().Square().Square().Square() + f.Mul(&a) + f.Square().Square().Square() + f.Mul(&a2) + f.Square().Square() + return f.Mul(&a) +} diff --git a/dcrec/secp256k1/field64_bench_test.go b/dcrec/secp256k1/field64_bench_test.go new file mode 100644 index 0000000000..6b63e3cb05 --- /dev/null +++ b/dcrec/secp256k1/field64_bench_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package secp256k1 + +import "testing" + +// These benchmarks mirror the FieldVal (field_bench_test.go) suite for +// FieldVal64 so the two implementations can be compared directly. + +// BenchmarkField64Sqrt benchmarks calculating the square root of an unsigned +// 256-bit big-endian integer modulo the field prime with the FieldVal64 type. +func BenchmarkField64Sqrt(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + f := new(FieldVal64).SetHex(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var result FieldVal64 + _ = result.SquareRootVal(f) + } +} + +// BenchmarkField64Inverse benchmarks calculating the multiplicative inverse of +// an unsigned 256-bit big-endian integer modulo the field prime with the +// FieldVal64 type. +func BenchmarkField64Inverse(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + f := new(FieldVal64).SetHex(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + f.Inverse() + } +} + +// BenchmarkField64IsGtOrEqPrimeMinusOrder benchmarks determining whether a value +// is greater than or equal to the field prime minus the group order with the +// FieldVal64 type. +func BenchmarkField64IsGtOrEqPrimeMinusOrder(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + f := new(FieldVal64).SetHex(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = f.IsGtOrEqPrimeMinusOrder() + } +} + +// BenchmarkField64Add benchmarks adding two field values. +func BenchmarkField64Add(b *testing.B) { + a := new(FieldVal64).SetHex("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab") + c := new(FieldVal64).SetHex("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var sum FieldVal64 + sum.Add2(a, c) + } +} + +// BenchmarkField64Mul benchmarks multiplying two field values. +func BenchmarkField64Mul(b *testing.B) { + a := new(FieldVal64).SetHex("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab") + c := new(FieldVal64).SetHex("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var prod FieldVal64 + prod.Mul2(a, c) + } +} + +// BenchmarkField64Square benchmarks squaring a field value. +func BenchmarkField64Square(b *testing.B) { + a := new(FieldVal64).SetHex("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var sq FieldVal64 + sq.SquareVal(a) + } +} diff --git a/dcrec/secp256k1/field64_test.go b/dcrec/secp256k1/field64_test.go new file mode 100644 index 0000000000..e8e5c611f4 --- /dev/null +++ b/dcrec/secp256k1/field64_test.go @@ -0,0 +1,1354 @@ +// Copyright (c) 2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package secp256k1 + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "math/rand" + "testing" + "time" +) + +// These tests mirror the FieldVal (field_test.go) suite for FieldVal64. +// FieldVal64 is always kept fully reduced, so the few FieldVal cases +// that depend on a denormalized internal representation are adjusted to reflect +// the reduced value instead. + +// SetHex decodes the passed big-endian hex string into the field value. Only the +// first 32 bytes are used. This is NOT constant time and is only used by tests. +func (f *FieldVal64) SetHex(hexString string) *FieldVal64 { + if len(hexString)%2 != 0 { + hexString = "0" + hexString + } + decoded, _ := hex.DecodeString(hexString) + f.SetByteSlice(decoded) + return f +} + +// randIntAndFieldVal64 returns a big integer and a field value both created from +// the same random value generated by the passed rng. +func randIntAndFieldVal64(t *testing.T, rng *rand.Rand) (*big.Int, *FieldVal64) { + t.Helper() + + var buf [32]byte + if _, err := rng.Read(buf[:]); err != nil { + t.Fatalf("failed to read random: %v", err) + } + + bigIntVal := new(big.Int).SetBytes(buf[:]) + bigIntVal.Mod(bigIntVal, curveParams.N) + var fv FieldVal64 + fv.SetBytes(&buf) + return bigIntVal, &fv +} + +// TestField64SetInt ensures that setting a field value to various native +// integers works as expected. +func TestField64SetInt(t *testing.T) { + tests := []struct { + name string // test description + in uint16 // test value + }{ + {name: "one", in: 1}, + {name: "five", in: 5}, + {name: "2^16 - 1", in: 65535}, + } + + for _, test := range tests { + f := new(FieldVal64).SetInt(test.in) + var want [32]byte + binary.BigEndian.PutUint16(want[30:], test.in) + if !bytes.Equal(f.Bytes()[:], want[:]) { + t.Errorf("%s: wrong result\ngot: %x\nwant: %x", test.name, + f.Bytes()[:], want[:]) + continue + } + } +} + +// TestField64SetBytes ensures that setting a field value to a 256-bit big-endian +// unsigned integer via both the slice and array methods works as expected for +// edge cases. Random cases are tested via the various other tests. +func TestField64SetBytes(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded test value + overflow bool // expected overflow result + }{{ + name: "zero", + in: "00", + overflow: false, + }, { + name: "one (only limb zero set)", + in: "01", + overflow: false, + }, { + name: "limb zero all ones (2^64 - 1)", + in: "ffffffffffffffff", + overflow: false, + }, { + name: "limb one low bit (2^64)", + in: "010000000000000000", + overflow: false, + }, { + name: "limbs zero and one all ones (2^128 - 1)", + in: "ffffffffffffffffffffffffffffffff", + overflow: false, + }, { + name: "limb two low bit (2^128)", + in: "0100000000000000000000000000000000", + overflow: false, + }, { + name: "limbs zero, one, and two all ones (2^192 - 1)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffff", + overflow: false, + }, { + name: "limb three low bit (2^192)", + in: "01000000000000000000000000000000000000000000000000", + overflow: false, + }, { + name: "limb three high bit (2^255)", + in: "8000000000000000000000000000000000000000000000000000000000000000", + overflow: false, + }, { + name: "distinct value in every limb (verifies limb ordering)", + in: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", + overflow: false, + }, { + name: "field prime", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + overflow: true, + }, { + name: "field prime - 1 (limb zero one below prime, upper limbs maxed)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + overflow: false, + }, { + name: "field prime + 1 (overflow in limb zero)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + overflow: true, + }, { + name: "(field prime - 1) * 2 NOT mod P, truncated >32 bytes", + in: "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffff85c", + overflow: false, + }, { + name: "2^256 - 1 (max input)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + overflow: true, + }, { + name: "alternating bits (spans all limbs)", + in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + overflow: false, + }, { + name: "alternating bits 2 (spans all limbs)", + in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + overflow: false, + }} + + for _, test := range tests { + inBytes := hexToBytes(test.in) + + // FieldVal64 only consumes the first 32 bytes and always reduces, so + // derive the expected reduced value from the truncated input. + truncated := inBytes + if len(truncated) > 32 { + truncated = truncated[:32] + } + wantInt := new(big.Int).SetBytes(truncated) + wantInt.Mod(wantInt, curveParams.P) + var wantBytes [32]byte + wantInt.FillBytes(wantBytes[:]) + + // Ensure setting the bytes via the slice method works as expected. + var f FieldVal64 + overflow := f.SetByteSlice(inBytes) + if !bytes.Equal(f.Bytes()[:], wantBytes[:]) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + f.Bytes()[:], wantBytes[:]) + continue + } + if overflow != test.overflow { + t.Errorf("%s: unexpected overflow -- got: %v, want: %v", test.name, + overflow, test.overflow) + continue + } + + // Ensure setting the bytes via the array method works as expected. + var f2 FieldVal64 + var b32 [32]byte + copy(b32[32-len(truncated):], truncated) + overflow = f2.SetBytes(&b32) != 0 + if !bytes.Equal(f2.Bytes()[:], wantBytes[:]) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + f2.Bytes()[:], wantBytes[:]) + continue + } + if overflow != test.overflow { + t.Errorf("%s: unexpected overflow -- got: %v, want: %v", test.name, + overflow, test.overflow) + continue + } + } +} + +// TestField64Bytes ensures that retrieving the bytes for a 256-bit big-endian +// unsigned integer via the various methods works as expected for edge cases. +// Random cases are tested via the various other tests. +func TestField64Bytes(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded test value + expected string // expected hex encoded bytes + }{{ + name: "zero", + in: "0", + expected: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "one (only limb zero set)", + in: "1", + expected: "0000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "limb zero all ones (2^64 - 1)", + in: "ffffffffffffffff", + expected: "000000000000000000000000000000000000000000000000ffffffffffffffff", + }, { + name: "limb one low bit (2^64)", + in: "10000000000000000", + expected: "0000000000000000000000000000000000000000000000010000000000000000", + }, { + name: "limbs zero and one all ones (2^128 - 1)", + in: "ffffffffffffffffffffffffffffffff", + expected: "00000000000000000000000000000000ffffffffffffffffffffffffffffffff", + }, { + name: "limb two low bit (2^128)", + in: "100000000000000000000000000000000", + expected: "0000000000000000000000000000000100000000000000000000000000000000", + }, { + name: "limbs zero, one, and two all ones (2^192 - 1)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffff", + expected: "0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "limb three low bit (2^192)", + in: "1000000000000000000000000000000000000000000000000", + expected: "0000000000000001000000000000000000000000000000000000000000000000", + }, { + name: "limb three high bit (2^255)", + in: "8000000000000000000000000000000000000000000000000000000000000000", + expected: "8000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "distinct value in every limb (verifies limb ordering)", + in: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", + expected: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", + }, { + name: "field prime (aka 0)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "field prime - 1 (limb zero one below prime, upper limbs maxed)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + }, { + name: "field prime + 1 (overflow reduces to one)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + expected: "0000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "2^256 - 1 (overflow reduces to 0x1000003d0)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expected: "00000000000000000000000000000000000000000000000000000001000003d0", + }, { + name: "alternating bits (spans all limbs)", + in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + expected: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + }, { + name: "alternating bits 2 (spans all limbs)", + in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + expected: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in) + expected := hexToBytes(test.expected) + + // Ensure getting the bytes works as expected. + gotBytes := f.Bytes() + if !bytes.Equal(gotBytes[:], expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + *gotBytes, expected) + continue + } + + // Ensure getting the bytes directly into an array works as expected. + var b32 [32]byte + f.PutBytes(&b32) + if !bytes.Equal(b32[:], expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + b32, expected) + continue + } + + // Ensure getting the bytes directly into a slice works as expected. + var buffer [64]byte + f.PutBytesUnchecked(buffer[:]) + if !bytes.Equal(buffer[:32], expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + buffer[:32], expected) + continue + } + } +} + +// TestField64Zero ensures that zeroing a field value works as expected. +func TestField64Zero(t *testing.T) { + var f FieldVal64 + f.SetHex("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") + f.Zero() + for idx, limb := range f.n { + if limb != 0 { + t.Errorf("limb at index #%d is not zero - got %d", idx, limb) + } + } +} + +// TestField64IsZero ensures that checking if a field is zero via IsZero and +// IsZeroBit works as expected. +func TestField64IsZero(t *testing.T) { + f := new(FieldVal64) + if !f.IsZero() { + t.Errorf("new field value is not zero - got %v (rawints %x)", f, f.n) + } + if f.IsZeroBit() != 1 { + t.Errorf("new field value is not zero - got %v (rawints %x)", f, f.n) + } + + f.SetInt(1) + if f.IsZero() { + t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + } + if f.IsZeroBit() == 1 { + t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + } + + f.Zero() + if !f.IsZero() { + t.Errorf("claims nonzero for zero field - got %v (rawints %x)", f, f.n) + } + if f.IsZeroBit() != 1 { + t.Errorf("claims nonzero for zero field - got %v (rawints %x)", f, f.n) + } + + f.SetInt(1) + f.Zero() + if !f.IsZero() { + t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + } + if f.IsZeroBit() != 1 { + t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + } +} + +// TestField64IsOne ensures that checking if a field is one via IsOne and IsOneBit +// works as expected. +func TestField64IsOne(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded test value + expected bool // expected result + }{{ + name: "zero", + in: "0", + expected: false, + }, { + name: "one", + in: "1", + expected: true, + }, { + name: "secp256k1 prime (aka 0)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: false, + }, { + name: "secp256k1 prime + 1 (aka 1)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + expected: true, + }, { + name: "2^63 (high bit of limb zero)", + in: "8000000000000000", + expected: false, + }, { + name: "2^64 (low bit of limb one)", + in: "10000000000000000", + expected: false, + }, { + name: "2^128 (low bit of limb two)", + in: "100000000000000000000000000000000", + expected: false, + }, { + name: "2^192 (low bit of limb three)", + in: "1000000000000000000000000000000000000000000000000", + expected: false, + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in) + result := f.IsOne() + if result != test.expected { + t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, + test.expected) + continue + } + + result2 := f.IsOneBit() == 1 + if result2 != test.expected { + t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, + result2, test.expected) + continue + } + } +} + +// TestField64IsOdd ensures that checking if a field value is odd via IsOdd and +// IsOddBit works as expected. Unlike FieldVal, FieldVal64 is always fully +// reduced, so the parity is that of the reduced value. +func TestField64IsOdd(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded value + expected bool // expected oddness + }{{ + name: "zero", + in: "0", + expected: false, + }, { + name: "one", + in: "1", + expected: true, + }, { + name: "two", + in: "2", + expected: false, + }, { + name: "2^32 - 1", + in: "ffffffff", + expected: true, + }, { + name: "2^64 - 2", + in: "fffffffffffffffe", + expected: false, + }, { + name: "secp256k1 prime (reduced to 0, so even)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: false, + }, { + name: "secp256k1 prime + 1 (reduced to 1, so odd)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + expected: true, + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in) + result := f.IsOdd() + if result != test.expected { + t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, + result, test.expected) + continue + } + + result2 := f.IsOddBit() == 1 + if result2 != test.expected { + t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, + result2, test.expected) + continue + } + } +} + +// TestField64Equals ensures that checking two field values for equality via +// Equals works as expected. +func TestField64Equals(t *testing.T) { + tests := []struct { + name string // test description + in1 string // hex encoded value + in2 string // hex encoded value + expected bool // expected equality + }{{ + name: "0 == 0?", + in1: "0", + in2: "0", + expected: true, + }, { + name: "0 == 1?", + in1: "0", + in2: "1", + expected: false, + }, { + name: "1 == 0?", + in1: "1", + in2: "0", + expected: false, + }, { + name: "2^32 - 1 == 2^32 - 1?", + in1: "ffffffff", + in2: "ffffffff", + expected: true, + }, { + name: "2^64 - 1 == 2^64 - 2?", + in1: "ffffffffffffffff", + in2: "fffffffffffffffe", + expected: false, + }, { + name: "0 == prime (mod prime)?", + in1: "0", + in2: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: true, + }, { + name: "1 == prime + 1 (mod prime)?", + in1: "1", + in2: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + expected: true, + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in1) + f2 := new(FieldVal64).SetHex(test.in2) + result := f.Equals(f2) + if result != test.expected { + t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, + test.expected) + continue + } + } +} + +// TestField64Negate ensures that negating field values via Negate works as +// expected. +func TestField64Negate(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded test value + expected string // hex encoded expected result + }{{ + name: "zero", + in: "0", + expected: "0", + }, { + name: "secp256k1 prime (direct val in with 0 out)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: "0", + }, { + name: "secp256k1 prime (0 in with direct val out)", + in: "0", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + }, { + name: "1 -> secp256k1 prime - 1", + in: "1", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + }, { + name: "secp256k1 prime - 1 -> 1", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + expected: "1", + }, { + name: "2 -> secp256k1 prime - 2", + in: "2", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + }, { + name: "secp256k1 prime - 2 -> 2", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + expected: "2", + }, { + name: "random sampling #1", + in: "b3d9aac9c5e43910b4385b53c7e78c21d4cd5f8e683c633aed04c233efc2e120", + expected: "4c2655363a1bc6ef4bc7a4ac381873de2b32a07197c39cc512fb3dcb103d1b0f", + }, { + name: "random sampling #2", + in: "f8a85984fee5a12a7c8dd08830d83423c937d77c379e4a958e447a25f407733f", + expected: "757a67b011a5ed583722f77cf27cbdc36c82883c861b56a71bb85d90bf888f0", + }, { + name: "random sampling #3", + in: "45ee6142a7fda884211e93352ed6cb2807800e419533be723a9548823ece8312", + expected: "ba119ebd5802577bdee16ccad12934d7f87ff1be6acc418dc56ab77cc131791d", + }, { + name: "random sampling #4", + in: "53c2a668f07e411a2e473e1c3b6dcb495dec1227af27673761d44afe5b43d22b", + expected: "ac3d59970f81bee5d1b8c1e3c49234b6a213edd850d898c89e2bb500a4bc2a04", + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in) + expected := new(FieldVal64).SetHex(test.expected) + + // Ensure negating another value produces the expected result. + result := new(FieldVal64).NegateVal(f) + if !result.Equals(expected) { + t.Errorf("%s: unexpected result -- got: %x, want: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + + // Ensure self negating also produces the expected result. + result2 := f.Negate() + if !result2.Equals(expected) { + t.Errorf("%s: unexpected result -- got: %x, want: %x", test.name, + result2.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64AddInt ensures that adding an integer to field values via AddInt +// works as expected. +func TestField64AddInt(t *testing.T) { + tests := []struct { + name string // test description + in1 string // hex encoded value + in2 uint16 // unsigned integer to add to the value above + expected string // expected hex encoded value + }{{ + name: "zero + one", + in1: "0", + in2: 1, + expected: "1", + }, { + name: "one + zero", + in1: "1", + in2: 0, + expected: "1", + }, { + name: "one + one", + in1: "1", + in2: 1, + expected: "2", + }, { + name: "secp256k1 prime-1 + 1", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: 1, + expected: "0", + }, { + name: "secp256k1 prime + 1", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + in2: 1, + expected: "1", + }, { + name: "random sampling #1", + in1: "ff95ad9315aff04ab4af0ce673620c7145dc85d03bab5ba4b09ca2c4dec2d6c1", + in2: 0x10f, + expected: "ff95ad9315aff04ab4af0ce673620c7145dc85d03bab5ba4b09ca2c4dec2d7d0", + }, { + name: "random sampling #2", + in1: "44bdae6b772e7987941f1ba314e6a5b7804a4c12c00961b57d20f41deea9cecf", + in2: 0x3196, + expected: "44bdae6b772e7987941f1ba314e6a5b7804a4c12c00961b57d20f41deeaa0065", + }, { + name: "random sampling #3", + in1: "88c3ecae67b591935fb1f6a9499c35315ffad766adca665c50b55f7105122c9c", + in2: 0x966f, + expected: "88c3ecae67b591935fb1f6a9499c35315ffad766adca665c50b55f710512c30b", + }, { + name: "random sampling #4", + in1: "8523e9edf360ca32a95aae4e57fcde5a542b471d08a974d94ea0ee09a015e2a6", + in2: 0xc54, + expected: "8523e9edf360ca32a95aae4e57fcde5a542b471d08a974d94ea0ee09a015eefa", + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in1) + expected := new(FieldVal64).SetHex(test.expected) + result := f.AddInt(test.in2) + if !result.Equals(expected) { + t.Errorf("%s: wrong result -- got: %x -- want: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64Add ensures that adding two field values together via Add and Add2 +// works as expected. +func TestField64Add(t *testing.T) { + tests := []struct { + name string // test description + in1 string // first hex encoded value + in2 string // second hex encoded value to add + expected string // expected hex encoded value + }{{ + name: "zero + one", + in1: "0", + in2: "1", + expected: "1", + }, { + name: "one + zero", + in1: "1", + in2: "0", + expected: "1", + }, { + name: "secp256k1 prime-1 + 1", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: "1", + expected: "0", + }, { + name: "secp256k1 prime + 1", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + in2: "1", + expected: "1", + }, { + name: "random sampling #1", + in1: "2b2012f975404e5065b4292fb8bed0a5d315eacf24c74d8b27e73bcc5430edcc", + in2: "2c3cefa4e4753e8aeec6ac4c12d99da4d78accefda3b7885d4c6bab46c86db92", + expected: "575d029e59b58cdb547ad57bcb986e4aaaa0b7beff02c610fcadf680c0b7c95e", + }, { + name: "random sampling #2", + in1: "8131e8722fe59bb189692b96c9f38de92885730f1dd39ab025daffb94c97f79c", + in2: "ff5454b765f0aab5f0977dcc629becc84cabeb9def48e79c6aadb2622c490fa9", + expected: "80863d2995d646677a00a9632c8f7ab175315ead0d1c824c9088b21c78e10b16", + }, { + name: "random sampling #3", + in1: "c7c95e93d0892b2b2cdd77e80eb646ea61be7a30ac7e097e9f843af73fad5c22", + in2: "3afe6f91a74dfc1c7f15c34907ee981656c37236d946767dd53ccad9190e437c", + expected: "2c7ce2577d72747abf33b3116a4df00b881ec6785c47ffc74c105d158bba36f", + }, { + name: "random sampling #4", + in1: "fd1c26f6a23381e5d785ba889494ec059369b888ad8431cd67d8c934b580dbe1", + in2: "a475aa5a31dcca90ef5b53c097d9133d6b7117474b41e7877bb199590fc0489c", + expected: "a191d150d4104c76c6e10e492c6dff42fedacfcff8c61954e38a628ec541284e", + }, { + name: "random sampling #5", + in1: "ad82b8d1cc136e23e9fd77fe2c7db1fe5a2ecbfcbde59ab3529758334f862d28", + in2: "4d6a4e95d6d61f4f46b528bebe152d408fd741157a28f415639347a84f6f574b", + expected: "faed0767a2e98d7330b2a0bcea92df3eea060d12380e8ec8b62a9fdb9ef58473", + }, { + name: "random sampling #6", + in1: "f3f43a2540054a86e1df98547ec1c0e157b193e5350fb4a3c3ea214b228ac5e7", + in2: "25706572592690ea3ddc951a1b48b504a4c83dc253756e1b96d56fdfb3199522", + expected: "19649f97992bdb711fbc2d6e9a0a75e5fc79d1a7888522bf5abf912bd5a45eda", + }, { + name: "random sampling #7", + in1: "6915bb94eef13ff1bb9b2633d997e13b9b1157c713363cc0e891416d6734f5b8", + in2: "11f90d6ac6fe1c4e8900b1c85fb575c251ec31b9bc34b35ada0aea1c21eded22", + expected: "7b0ec8ffb5ef5c40449bd7fc394d56fdecfd8980cf6af01bc29c2b898922e2da", + }, { + name: "random sampling #8", + in1: "48b0c9eae622eed9335b747968544eb3e75cb2dc8128388f948aa30f88cabde4", + in2: "0989882b52f85f9d524a3a3061a0e01f46d597839d2ba637320f4b9510c8d2d5", + expected: "523a5216391b4e7685a5aea9c9f52ed32e324a601e53dec6c699eea4999390b9", + }} + + for _, test := range tests { + f1 := new(FieldVal64).SetHex(test.in1) + f2 := new(FieldVal64).SetHex(test.in2) + expected := new(FieldVal64).SetHex(test.expected) + + // Ensure adding the two values with the result going to another + // variable produces the expected result. + result := new(FieldVal64).Add2(f1, f2) + if !result.Equals(expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + + // Ensure adding the value to an existing field value produces the + // expected result. + f1.Add(f2) + if !f1.Equals(expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + f1.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64MulByX ensures the dedicated small-constant multiply helpers +// (MulBy2, MulBy3, MulBy4, and MulBy8) produce the same results as the +// equivalent MulInt call. +func TestField64MulByX(t *testing.T) { + mulByFuncs := []struct { + factor uint8 + fn func(*FieldVal64) *FieldVal64 + }{ + {2, (*FieldVal64).MulBy2}, + {3, (*FieldVal64).MulBy3}, + {4, (*FieldVal64).MulBy4}, + {8, (*FieldVal64).MulBy8}, + } + + testCases := []string{ + "0", + "1", + "2", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", // p - 1 + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17", // p / 2 + "55555555555555555555555555555555555555555555555555555554fffffeba", // p / 3 + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffff0b", // p / 4 + "1fffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffff85", // p / 8 + "e8655154d386119205b642e6cf03b2e7d03ff59301d98bfebcf350a778014efa", // random sampling + "bb38f58fddc9bf462d4b9ba6503c142be374ecd423525c590ab8de77a6265676", // random sampling + "bdacc1032b224a43e0dd6ac5452037d9cdfbc3a8041e0ce8eee9d42516a1e3b1", // random sampling + "e3cbe002cc93029190181a906ed41af401c9726546dc19389a06290efdf563f1", // random sampling + } + for _, in := range testCases { + in := new(FieldVal64).SetHex(in) + for _, m := range mulByFuncs { + var want FieldVal64 + want.Set(in).MulInt(m.factor) + + var f FieldVal64 + got := m.fn(f.Set(in)) + if !got.Equals(&want) { + t.Errorf("MulBy%d(%x): wrong result -- got: %x -- want: %x", + m.factor, in.Bytes(), got.Bytes(), want.Bytes()) + } + } + } +} + +// TestField64MulInt ensures that multiplying an integer to field values via +// MulInt works as expected. +func TestField64MulInt(t *testing.T) { + tests := []struct { + name string // test description + in1 string // hex encoded value + in2 uint8 // unsigned integer to multiply with value above + expected string // expected hex encoded value + }{{ + name: "zero * zero", + in1: "0", + in2: 0, + expected: "0", + }, { + name: "one * zero", + in1: "1", + in2: 0, + expected: "0", + }, { + name: "zero * one", + in1: "0", + in2: 1, + expected: "0", + }, { + name: "one * one", + in1: "1", + in2: 1, + expected: "1", + }, { + name: "secp256k1 prime-1 * 2", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: 2, + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + }, { + name: "secp256k1 prime * 3", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + in2: 3, + expected: "0", + }, { + name: "secp256k1 prime-1 * 8", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: 8, + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc27", + }, { + name: "random sampling #1", + in1: "b75674dc9180d306c692163ac5e089f7cef166af99645c0c23568ab6d967288a", + in2: 6, + expected: "4c06bd2b6904f228a76c8560a3433bced9a8681d985a2848d407404d186b0280", + }, { + name: "random sampling #2", + in1: "54873298ac2b5ba8591c125ae54931f5ea72040aee07b208d6135476fb5b9c0e", + in2: 3, + expected: "fd9597ca048212f90b543710afdb95e1bf560c20ca17161a8239fd64f212d42a", + }, { + name: "random sampling #3", + in1: "7c30fbd363a74c17e1198f56b090b59bbb6c8755a74927a6cba7a54843506401", + in2: 5, + expected: "6cf4eb20f2447c77657fccb172d38c0aa91ea4ac446dc641fa463a6b5091fba7", + }, { + name: "random sampling #4", + in1: "fb4529be3e027a3d1587d8a500b72f2d312e3577340ef5175f96d113be4c2ceb", + in2: 8, + expected: "da294df1f013d1e8ac3ec52805b979698971abb9a077a8bafcb688a4f261820f", + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in1) + expected := new(FieldVal64).SetHex(test.expected) + result := f.MulInt(test.in2) + if !result.Equals(expected) { + t.Errorf("%s: wrong result -- got: %x -- want: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64Mul ensures that multiplying two field values via Mul and Mul2 +// works as expected. +func TestField64Mul(t *testing.T) { + tests := []struct { + name string // test description + in1 string // first hex encoded value + in2 string // second hex encoded value to multiply with + expected string // expected hex encoded value + }{{ + name: "zero * zero", + in1: "0", + in2: "0", + expected: "0", + }, { + name: "one * zero", + in1: "1", + in2: "0", + expected: "0", + }, { + name: "zero * one", + in1: "0", + in2: "1", + expected: "0", + }, { + name: "one * one", + in1: "1", + in2: "1", + expected: "1", + }, { + name: "slightly over prime", + in1: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff1ffff", + in2: "1000", + expected: "1ffff3d1", + }, { + name: "secp256k1 prime-1 * 2", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: "2", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + }, { + name: "secp256k1 prime * 3", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + in2: "3", + expected: "0", + }, { + name: "secp256k1 prime-1 * 8", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: "8", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc27", + }, { + name: "prime-(2^256-prime) * prime-1", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffff85e", + in2: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + expected: "1000003d1", + }, { + name: "random sampling #1", + in1: "cfb81753d5ef499a98ecc04c62cb7768c2e4f1740032946db1c12e405248137e", + in2: "58f355ad27b4d75fb7db0442452e732c436c1f7c5a7c4e214fa9cc031426a7d3", + expected: "1018cd2d7c2535235b71e18db9cd98027386328d2fa6a14b36ec663c4c87282b", + }, { + name: "random sampling #2", + in1: "26e9d61d1cdf3920e9928e85fa3df3e7556ef9ab1d14ec56d8b4fc8ed37235bf", + in2: "2dfc4bbe537afee979c644f8c97b31e58be5296d6dbc460091eae630c98511cf", + expected: "da85f48da2dc371e223a1ae63bd30b7e7ee45ae9b189ac43ff357e9ef8cf107a", + }, { + name: "random sampling #3", + in1: "5db64ed5afb71646c8b231585d5b2bf7e628590154e0854c4c29920b999ff351", + in2: "279cfae5eea5d09ade8e6a7409182f9de40981bc31c84c3d3dfe1d933f152e9a", + expected: "2c78fbae91792dd0b157abe3054920049b1879a7cc9d98cfda927d83be411b37", + }, { + name: "random sampling #4", + in1: "b66dfc1f96820b07d2bdbd559c19319a3a73c97ceb7b3d662f4fe75ecb6819e6", + in2: "bf774aba43e3e49eb63a6e18037d1118152568f1a3ac4ec8b89aeb6ff8008ae1", + expected: "c4f016558ca8e950c21c3f7fc15f640293a979c7b01754ee7f8b3340d4902ebb", + }} + + for _, test := range tests { + f1 := new(FieldVal64).SetHex(test.in1) + f2 := new(FieldVal64).SetHex(test.in2) + expected := new(FieldVal64).SetHex(test.expected) + + // Ensure multiplying the two values with the result going to another + // variable produces the expected result. + result := new(FieldVal64).Mul2(f1, f2) + if !result.Equals(expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + + // Ensure multiplying the value to an existing field value produces the + // expected result. + f1.Mul(f2) + if !f1.Equals(expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + f1.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64Square ensures that squaring field values via Square and SquareVal +// works as expected. +func TestField64Square(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded value + expected string // expected hex encoded value + }{{ + name: "zero", + in: "0", + expected: "0", + }, { + name: "secp256k1 prime (direct val in with 0 out)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: "0", + }, { + name: "secp256k1 prime (0 in with direct val out)", + in: "0", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + }, { + name: "secp256k1 prime - 1", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + expected: "1", + }, { + name: "secp256k1 prime - 2", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + expected: "4", + }, { + name: "random sampling #1", + in: "b0ba920360ea8436a216128047aab9766d8faf468895eb5090fc8241ec758896", + expected: "133896b0b69fda8ce9f648b9a3af38f345290c9eea3cbd35bafcadf7c34653d3", + }, { + name: "random sampling #2", + in: "c55d0d730b1d0285a1599995938b042a756e6e8857d390165ffab480af61cbd5", + expected: "cd81758b3f5877cbe7e5b0a10cebfa73bcbf0957ca6453e63ee8954ab7780bee", + }, { + name: "random sampling #3", + in: "e89c1f9a70d93651a1ba4bca5b78658f00de65a66014a25544d3365b0ab82324", + expected: "39ffc7a43e5dbef78fd5d0354fb82c6d34f5a08735e34df29da14665b43aa1f", + }, { + name: "random sampling #4", + in: "7dc26186079d22bcbe1614aa20ae627e62d72f9be7ad1e99cac0feb438956f05", + expected: "bf86bcfc4edb3d81f916853adfda80c07c57745b008b60f560b1912f95bce8ae", + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in) + expected := new(FieldVal64).SetHex(test.expected) + + // Ensure squaring the value with the result going to another variable + // produces the expected result. + result := new(FieldVal64).SquareVal(f) + if !result.Equals(expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + + // Ensure self squaring an existing field value produces the expected + // result. + f.Square() + if !f.Equals(expected) { + t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + f.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64SquareRoot ensures that calculating the square root of field values +// via SquareRootVal works as expected for edge cases. +func TestField64SquareRoot(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded value + valid bool // whether or not the value has a square root + want string // expected hex encoded value + }{{ + name: "secp256k1 prime (as 0 in and out)", + in: "0", + valid: true, + want: "0", + }, { + name: "secp256k1 prime (direct val with 0 out)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + valid: true, + want: "0", + }, { + name: "secp256k1 prime (as 0 in direct val out)", + in: "0", + valid: true, + want: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + }, { + name: "secp256k1 prime-1", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + valid: false, + want: "0000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "secp256k1 prime-2", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + valid: false, + want: "210c790573632359b1edb4302c117d8a132654692c3feeb7de3a86ac3f3b53f7", + }, { + name: "(secp256k1 prime-2)^2", + in: "0000000000000000000000000000000000000000000000000000000000000004", + valid: true, + want: "0000000000000000000000000000000000000000000000000000000000000002", + }, { + name: "value 1", + in: "0000000000000000000000000000000000000000000000000000000000000001", + valid: true, + want: "0000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "value 2", + in: "0000000000000000000000000000000000000000000000000000000000000002", + valid: true, + want: "210c790573632359b1edb4302c117d8a132654692c3feeb7de3a86ac3f3b53f7", + }, { + name: "random sampling 1", + in: "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca", + valid: false, + want: "6a27dcfca440cf7930a967be533b9620e397f122787c53958aaa7da7ad3d89a4", + }, { + name: "square of random sampling 1", + in: "f4a8c3738ace0a1c3abf77737ae737f07687b5e24c07a643398298bd96893a18", + valid: true, + want: "e90468feb8565338c9ab2b41dcc33b7478a31df5dedd2db0f8c2d641d77fa165", + }, { + name: "random sampling 2", + in: "69d1323ce9f1f7b3bd3c7320b0d6311408e30281e273e39a0d8c7ee1c8257919", + valid: true, + want: "61f4a7348274a52d75dfe176b8e3aaff61c1c833b6678260ba73def0fb2ad148", + }, { + name: "random sampling 3", + in: "e0debf988ae098ecda07d0b57713e97c6d213db19753e8c95aa12a2fc1cc5272", + valid: false, + want: "6e1cc9c311d33d901670135244f994b1ea39501f38002269b34ce231750cfbac", + }, { + name: "random sampling 4", + in: "dcd394f91f74c2ba16aad74a22bb0ed47fe857774b8f2d6c09e28bfb14642878", + valid: true, + want: "72b22fe6f173f8bcb21898806142ed4c05428601256eafce5d36c1b08fb82bab", + }} + + for _, test := range tests { + input := new(FieldVal64).SetHex(test.in) + want := new(FieldVal64).SetHex(test.want) + + // Calculate the square root and ensure the validity flag matches the + // expected value. + var result FieldVal64 + isValid := result.SquareRootVal(input) + if isValid != test.valid { + t.Errorf("%s: mismatched validity -- got %v, want %v", test.name, + isValid, test.valid) + continue + } + + // Ensure the calculated result matches the expected value. + if !result.Equals(want) { + t.Errorf("%s: wrong result\ngot: %x\nwant: %x", test.name, + result.Bytes(), want.Bytes()) + continue + } + } +} + +// TestField64SquareRootRandom ensures that calculating the square root for +// random field values works as expected by also performing the same operation +// with big ints and comparing the results. +func TestField64SquareRootRandom(t *testing.T) { + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().Unix() + rng := rand.New(rand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + for i := 0; i < 100; i++ { + // Generate big integer and field value with the same random value. + bigIntVal, fVal := randIntAndFieldVal64(t, rng) + + // Calculate the square root of the value using big ints. + bigIntResult := new(big.Int).ModSqrt(bigIntVal, curveParams.P) + bigIntHasSqrt := bigIntResult != nil + + // Calculate the square root of the value using a field value. + var fValResult FieldVal64 + fValHasSqrt := fValResult.SquareRootVal(fVal) + + // Ensure they match. + if bigIntHasSqrt != fValHasSqrt { + t.Fatalf("mismatched square root existence\nbig int in: %x\nfield "+ + "in: %x\nbig int result: %v\nfield result %v", bigIntVal, + fVal.Bytes(), bigIntHasSqrt, fValHasSqrt) + } + if !fValHasSqrt { + continue + } + bigIntResultHex := fmt.Sprintf("%064x", bigIntResult) + fieldValResultHex := hex.EncodeToString(fValResult.Bytes()[:]) + if bigIntResultHex != fieldValResultHex { + t.Fatalf("mismatched square root\nbig int in: %x\nfield in: %x\n"+ + "big int result: %x\nfield result %x", bigIntVal, fVal.Bytes(), + bigIntResult, fValResult.Bytes()) + } + } +} + +// TestField64Inverse ensures that finding the multiplicative inverse via Inverse +// works as expected. +func TestField64Inverse(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded value + expected string // expected hex encoded value + }{{ + name: "zero", + in: "0", + expected: "0", + }, { + name: "secp256k1 prime (direct val in with 0 out)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: "0", + }, { + name: "secp256k1 prime (0 in with direct val out)", + in: "0", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + }, { + name: "secp256k1 prime - 1", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + }, { + name: "secp256k1 prime - 2", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d", + expected: "7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17", + }, { + name: "random sampling #1", + in: "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca", + expected: "987aeb257b063df0c6d1334051c47092b6d8766c4bf10c463786d93f5bc54354", + }, { + name: "random sampling #2", + in: "69d1323ce9f1f7b3bd3c7320b0d6311408e30281e273e39a0d8c7ee1c8257919", + expected: "49340981fa9b8d3dad72de470b34f547ed9179c3953797d0943af67806f4bb6", + }, { + name: "random sampling #3", + in: "e0debf988ae098ecda07d0b57713e97c6d213db19753e8c95aa12a2fc1cc5272", + expected: "64f58077b68af5b656b413ea366863f7b2819f8d27375d9c4d9804135ca220c2", + }, { + name: "random sampling #4", + in: "dcd394f91f74c2ba16aad74a22bb0ed47fe857774b8f2d6c09e28bfb14642878", + expected: "fb848ec64d0be572a63c38fe83df5e7f3d032f60bf8c969ef67d36bf4ada22a9", + }} + + for _, test := range tests { + f := new(FieldVal64).SetHex(test.in) + expected := new(FieldVal64).SetHex(test.expected) + result := f.Inverse() + if !result.Equals(expected) { + t.Errorf("%s: wrong result\ngot: %x\nwant: %x", test.name, + result.Bytes(), expected.Bytes()) + continue + } + } +} + +// TestField64IsGtOrEqPrimeMinusOrder ensures that field values report whether or +// not they are greater than or equal to the field prime minus the group order +// as expected for edge cases. +func TestField64IsGtOrEqPrimeMinusOrder(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded test value + expected bool // expected result + }{{ + name: "zero", + in: "0", + expected: false, + }, { + name: "one", + in: "1", + expected: false, + }, { + name: "p - n - 1", + in: "14551231950b75fc4402da1722fc9baed", + expected: false, + }, { + name: "p - n", + in: "14551231950b75fc4402da1722fc9baee", + expected: true, + }, { + name: "p - n + 1", + in: "14551231950b75fc4402da1722fc9baef", + expected: true, + }, { + name: "greater in limb zero, limbs one and two equal", + in: "14551231950b75fc4ffffffffffffffff", + expected: true, + }, { + name: "greater in limb one (limb zero zero)", + in: "14551231950b75fc50000000000000000", + expected: true, + }, { + name: "smaller in limb one with max limb zero (below p - n)", + in: "14551231950b75fc3ffffffffffffffff", + expected: false, + }, { + name: "2^128 - 1 (limb two zero, below p - n)", + in: "ffffffffffffffffffffffffffffffff", + expected: false, + }, { + name: "greater in limb two (2 * 2^128)", + in: "200000000000000000000000000000000", + expected: true, + }, { + name: "limb three set (2^192)", + in: "1000000000000000000000000000000000000000000000000", + expected: true, + }, { + name: "limb three high bit (2^255)", + in: "8000000000000000000000000000000000000000000000000000000000000000", + expected: true, + }} + + for _, test := range tests { + result := new(FieldVal64).SetHex(test.in).IsGtOrEqPrimeMinusOrder() + if result != test.expected { + t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name, + result, test.expected) + continue + } + } +} + +// TestField64IsGtOrEqPrimeMinusOrderRandom ensures that field values report +// whether or not they are greater than or equal to the field prime minus the +// group order as expected by also performing the same operation with big ints +// and comparing the results. +func TestField64IsGtOrEqPrimeMinusOrderRandom(t *testing.T) { + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().Unix() + rng := rand.New(rand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + bigPMinusN := new(big.Int).Sub(curveParams.P, curveParams.N) + for i := 0; i < 100; i++ { + // Generate big integer and field value with the same random value. + bigIntVal, fVal := randIntAndFieldVal64(t, rng) + + // Determine the value is greater than or equal to the prime minus the + // order using big ints. + bigIntResult := bigIntVal.Cmp(bigPMinusN) >= 0 + + // Determine the value is greater than or equal to the prime minus the + // order using a field value. + fValResult := fVal.IsGtOrEqPrimeMinusOrder() + + // Ensure they match. + if bigIntResult != fValResult { + t.Fatalf("mismatched is gt or eq prime minus order\nbig int in: "+ + "%x\nscalar in: %x\nbig int result: %v\nscalar result %v", + bigIntVal, fVal.Bytes(), bigIntResult, fValResult) + } + } +} diff --git a/dcrec/secp256k1/modnscalar.go b/dcrec/secp256k1/modnscalar.go index 225016d8de..ec6443f543 100644 --- a/dcrec/secp256k1/modnscalar.go +++ b/dcrec/secp256k1/modnscalar.go @@ -215,6 +215,12 @@ func constantTimeEq(a, b uint32) uint32 { return uint32((uint64(a^b) - 1) >> 63) } +// constantTimeEq64 returns 1 if a == b or 0 otherwise in constant time. +func constantTimeEq64(a, b uint64) uint32 { + t := a ^ b + return uint32(((t | -t) >> 63) ^ 1) +} + // constantTimeNotEq returns 1 if a != b or 0 otherwise in constant time. func constantTimeNotEq(a, b uint32) uint32 { return ^uint32((uint64(a^b)-1)>>63) & 1