From b2d8048814fafe05e64e32ba79cb02abbb209070 Mon Sep 17 00:00:00 2001 From: "valery.osheter@coinbase.com" Date: Sun, 21 Jun 2026 09:25:20 +0300 Subject: [PATCH 1/4] secp256k1: Add pure Go FieldVal64 (4x64) field element Introduce FieldVal64, an alternative secp256k1 field element that packs the value into four uint64 limbs (base 2^64) and stays fully reduced after every operation. AI-assisted: developed with the help of an AI coding assistant; all code reviewed and tested by the author. --- dcrec/secp256k1/field64.go | 679 +++++++++++++++++++++++++++++++++++++ 1 file changed, 679 insertions(+) create mode 100644 dcrec/secp256k1/field64.go diff --git a/dcrec/secp256k1/field64.go b/dcrec/secp256k1/field64.go new file mode 100644 index 0000000000..64a13fd5e3 --- /dev/null +++ b/dcrec/secp256k1/field64.go @@ -0,0 +1,679 @@ +// 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" + "fmt" + "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 +) + +// SetBytes sets f to the 32-byte big-endian value and returns 1 if the input is +// >= p. +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) +} + +// Bytes returns the 32-byte big-endian encoding. +func (f *FieldVal64) Bytes() *[32]byte { + var b [32]byte + f.PutBytes(&b) + return &b +} + +// PutBytes writes the value to b in big-endian order. +func (f *FieldVal64) PutBytes(b *[32]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]) +} + +// Normalize fully reduces f modulo p. FieldVal64 values are always kept fully +// reduced, so this is a no-op kept for API parity with FieldVal. +func (f *FieldVal64) Normalize() *FieldVal64 { + return f +} + +// SetInt sets f to a small integer. +func (f *FieldVal64) SetInt(v uint16) *FieldVal64 { + f.n = [4]uint64{uint64(v), 0, 0, 0} + return f +} + +// Set sets f to val. +func (f *FieldVal64) Set(val *FieldVal64) *FieldVal64 { + f.n = val.n + return f +} + +// Equals reports whether two values are equal. +func (f *FieldVal64) Equals(val *FieldVal64) bool { + bits := (f.n[0] ^ val.n[0]) | (f.n[1] ^ val.n[1]) | + (f.n[2] ^ val.n[2]) | (f.n[3] ^ val.n[3]) + return bits == 0 +} + +// IsOdd reports whether f is odd. +func (f *FieldVal64) IsOdd() bool { + return f.n[0]&1 == 1 +} + +// Add2 sets f = a + b (mod p). +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. Since p = 2^256 - C, the low 256 bits of t - p are + // identical to t + C, i.e. the folded result for a 2^256 overflow. + 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 (= t - p when t >= p, = t + C folded + // when overflow occurred). + 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 +} + +// Add sets f = f + val (mod p). +func (f *FieldVal64) Add(val *FieldVal64) *FieldVal64 { + return f.Add2(f, val) +} + +// MulInt sets f = f * val (mod p). val is limited to the small constants used by +// the curve formulas (up to 8); larger values panic. FieldVal magnitude tracking +// is irrelevant since FieldVal64 stays fully reduced. Doublings (f.Add(f)) keep +// the addition count low. +func (f *FieldVal64) MulInt(val uint8) *FieldVal64 { + if val > 8 { + panic(fmt.Sprintf("FieldVal64.MulInt: val %d exceeds supported maximum of 8", val)) + } + + switch val { + case 0: + f.n = [4]uint64{} + return f + case 1: + return f + } + + var orig FieldVal64 + orig.Set(f) + switch val { + case 2: + f.Add(&orig) // 2 + case 3: + f.Add(&orig).Add(&orig) // 3 + case 4: + f.Add(&orig) // 2 + f.Add(f) // 4 + case 5: + f.Add(&orig) // 2 + f.Add(f) // 4 + f.Add(&orig) // 5 + case 6: + f.Add(&orig).Add(&orig) // 3 + f.Add(f) // 6 + case 7: + f.Add(&orig).Add(&orig) // 3 + f.Add(f) // 6 + f.Add(&orig) // 7 + case 8: + f.Add(&orig) // 2 + f.Add(f) // 4 + f.Add(f) // 8 + } + return f +} + +// NegateVal sets f = -val (mod p). The magnitude parameter exists for API +// parity with FieldVal and is ignored since FieldVal64 stays fully reduced. +func (f *FieldVal64) NegateVal(val *FieldVal64, magnitude uint32) *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 so (0 - val) + p = p - val (mod 2^256), + // while val == 0 stays 0. + 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 +} + +// Mul2 sets f = a * b (mod p). The 256x256 -> 256 modular multiply is provided +// by field64Mul, which uses native/hardware-optimized assembly on the supported +// platforms and falls back to a portable Go implementation elsewhere. +func (f *FieldVal64) Mul2(a, b *FieldVal64) *FieldVal64 { + field64Mul(&f.n, &a.n, &b.n) + return f +} + +// Mul sets f = f * val (mod p). +func (f *FieldVal64) Mul(val *FieldVal64) *FieldVal64 { + return f.Mul2(f, val) +} + +// SquareVal sets f = val^2 (mod p). The modular square is provided by +// field64Square, which uses native/hardware-optimized assembly on the supported +// platforms and falls back to a portable Go implementation elsewhere. +func (f *FieldVal64) SquareVal(val *FieldVal64) *FieldVal64 { + field64Square(&f.n, &val.n) + return f +} + +// Square sets f = f^2 (mod p). +func (f *FieldVal64) Square() *FieldVal64 { + return f.SquareVal(f) +} + +// p - n (field prime minus the group order) as little-endian 64-bit limbs. +const ( + field64PMinusN0 = 0x402da1722fc9baee + field64PMinusN1 = 0x4551231950b75fc4 + field64PMinusN2 = 0x0000000000000001 + field64PMinusN3 = 0x0000000000000000 +) + +// Negate sets f = -f (mod p). The magnitude parameter exists for API parity with +// FieldVal and is ignored since FieldVal64 stays fully reduced. +func (f *FieldVal64) Negate(magnitude uint32) *FieldVal64 { + return f.NegateVal(f, magnitude) +} + +// AddInt sets f = f + ui (mod p). +func (f *FieldVal64) AddInt(ui uint16) *FieldVal64 { + var t FieldVal64 + t.SetInt(ui) + return f.Add(&t) +} + +// Zero sets f to zero. +func (f *FieldVal64) Zero() { + f.n = [4]uint64{} +} + +// IsZero reports whether f is zero. +func (f *FieldVal64) IsZero() bool { + return (f.n[0] | f.n[1] | f.n[2] | f.n[3]) == 0 +} + +// IsZeroBit returns 1 if f is zero and 0 otherwise in constant time. See IsZero +// for the bool version. +func (f *FieldVal64) IsZeroBit() uint32 { + // (bits | -bits) >> 63 is 1 iff bits != 0; invert for is-zero. + bits := f.n[0] | f.n[1] | f.n[2] | f.n[3] + return uint32(((bits | -bits) >> 63) ^ 1) +} + +// IsOne reports whether f is one. +func (f *FieldVal64) IsOne() bool { + return f.n[0] == 1 && (f.n[1]|f.n[2]|f.n[3]) == 0 +} + +// IsOneBit returns 1 if f is one and 0 otherwise in constant time. See IsOne for +// the bool version. +func (f *FieldVal64) IsOneBit() uint32 { + // (bits | -bits) >> 63 is 1 iff bits != 0; invert for is-one. + bits := (f.n[0] ^ 1) | f.n[1] | f.n[2] | f.n[3] + return uint32(((bits | -bits) >> 63) ^ 1) +} + +// String returns the field value as a human-readable hex string. +func (f FieldVal64) String() string { + return hex.EncodeToString(f.Bytes()[:]) +} + +// IsOddBit returns 1 if f is odd and 0 otherwise. +func (f *FieldVal64) IsOddBit() uint32 { + return uint32(f.n[0] & 1) +} + +// SetByteSlice interprets b as a big-endian unsigned integer (truncated to the +// first 32 bytes), stores it modulo p, and returns whether the input overflowed +// p before reduction. +func (f *FieldVal64) SetByteSlice(b []byte) bool { + var b32 [32]byte + if len(b) > 32 { + b = b[:32] + } + copy(b32[32-len(b):], b) + return f.SetBytes(&b32) != 0 +} + +// PutBytesUnchecked writes the 32-byte big-endian encoding of f into b without +// bounds checking. +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]) +} + +// IsGtOrEqPrimeMinusOrder reports whether f >= p - n in constant time. f must be +// fully reduced, which FieldVal64 always is. +func (f *FieldVal64) IsGtOrEqPrimeMinusOrder() bool { + 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 +} + +// Inverse sets f = f^(-1) (mod p) via Fermat's little theorem (a^(p-2)). +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) +} + +// 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. +// +// Since the secp256k1 prime is ≡ 3 (mod 4), the square root is a^((p+1)/4), +// computed via the same addition chain used by FieldVal26 (254 squarings, 13 +// multiplications). +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) +} + +// 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] + + // Each row forms the 5-limb partial product a * b[j] in (q0..q4) with a + // single carry chain, then accumulates it into the running product with a + // second carry chain. Threading every carry through bits.Add64's carry-in + // (rather than summing hi + carries by value) lets the compiler emit + // efficient add-with-carry chains. The fresh top limb cannot overflow: the + // maximum hi + partialCarry + accumulateCarry is < 2^64. + 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) +} From d359660bf4579bae3d4b1c00d2aac75721e88ce4 Mon Sep 17 00:00:00 2001 From: "valery.osheter@coinbase.com" Date: Sun, 21 Jun 2026 09:25:20 +0300 Subject: [PATCH 2/4] secp256k1: Add tests for FieldVal64 Add a test suite for FieldVal64 mirroring the existing FieldVal tests: serialization round-trips, arithmetic (add, negate, multiply, square), normalization, inverse, square root, and the bit predicates, plus randomized checks. Cases that only apply to the unnormalized FieldVal representation are adjusted since FieldVal64 is always fully reduced. AI-assisted: developed with the help of an AI coding assistant; all code reviewed and tested by the author. --- dcrec/secp256k1/field64_test.go | 1364 +++++++++++++++++++++++++++++++ 1 file changed, 1364 insertions(+) create mode 100644 dcrec/secp256k1/field64_test.go diff --git a/dcrec/secp256k1/field64_test.go b/dcrec/secp256k1/field64_test.go new file mode 100644 index 0000000000..884b0cb47a --- /dev/null +++ b/dcrec/secp256k1/field64_test.go @@ -0,0 +1,1364 @@ +// 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 +} + +// randFieldVal sets f to a field value created from a random value generated by +// the passed rng and returns f to support chaining. +func (f *FieldVal64) randFieldVal(rng *rand.Rand) *FieldVal64 { + var buf [32]byte + rng.Read(buf[:]) + f.SetBytes(&buf) + 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: "field prime", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + overflow: true, + }, { + name: "field prime - 1", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + overflow: false, + }, { + name: "field prime + 1 (overflow in word zero)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + overflow: true, + }, { + name: "field prime first 32 bits", + in: "fffffc2f", + overflow: false, + }, { + name: "field prime word zero", + in: "03fffc2f", + overflow: false, + }, { + name: "field prime first 64 bits", + in: "fffffffefffffc2f", + overflow: false, + }, { + name: "field prime word zero and one", + in: "0ffffefffffc2f", + overflow: false, + }, { + name: "field prime first 96 bits", + in: "fffffffffffffffefffffc2f", + overflow: false, + }, { + name: "field prime word zero, one, and two", + in: "3ffffffffffefffffc2f", + overflow: false, + }, { + name: "overflow in word one (prime + 1<<26)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff03fffc2f", + overflow: true, + }, { + name: "(field prime - 1) * 2 NOT mod P, truncated >32 bytes", + in: "01fffffffffffffffffffffffffffffffffffffffffffffffffffffffdfffff85c", + overflow: false, + }, { + name: "2^256 - 1", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + overflow: true, + }, { + name: "alternating bits", + in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + overflow: false, + }, { + name: "alternating bits 2", + 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: "field prime (aka 0)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "field prime - 1", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + }, { + name: "field prime + 1 (aka 1, overflow in word zero)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + expected: "0000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "field prime first 32 bits", + in: "fffffc2f", + expected: "00000000000000000000000000000000000000000000000000000000fffffc2f", + }, { + name: "field prime word zero", + in: "03fffc2f", + expected: "0000000000000000000000000000000000000000000000000000000003fffc2f", + }, { + name: "field prime first 64 bits", + in: "fffffffefffffc2f", + expected: "000000000000000000000000000000000000000000000000fffffffefffffc2f", + }, { + name: "field prime word zero and one", + in: "0ffffefffffc2f", + expected: "000000000000000000000000000000000000000000000000000ffffefffffc2f", + }, { + name: "field prime first 96 bits", + in: "fffffffffffffffefffffc2f", + expected: "0000000000000000000000000000000000000000fffffffffffffffefffffc2f", + }, { + name: "field prime word zero, one, and two", + in: "3ffffffffffefffffc2f", + expected: "000000000000000000000000000000000000000000003ffffffffffefffffc2f", + }, { + name: "overflow in word one (prime + 1<<26)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff03fffc2f", + expected: "0000000000000000000000000000000000000000000000000000000004000000", + }, { + name: "2^256 - 1", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expected: "00000000000000000000000000000000000000000000000000000001000003d0", + }, { + name: "alternating bits", + in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + expected: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + }, { + name: "alternating bits 2", + 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() + if !f.IsZero() { + t.Errorf("field value is not zero - got %x", f.Bytes()) + } +} + +// TestField64IsZero ensures that checking if a field is zero via IsZero works as +// expected. +func TestField64IsZero(t *testing.T) { + f := new(FieldVal64) + if !f.IsZero() { + t.Errorf("new field value is not zero - got %x", f.Bytes()) + } + + f.SetInt(1) + if f.IsZero() { + t.Errorf("claims zero for nonzero field - got %x", f.Bytes()) + } + + f.Zero() + if !f.IsZero() { + t.Errorf("claims nonzero for zero field - got %x", f.Bytes()) + } + + f.SetInt(1) + f.Zero() + if !f.IsZero() { + t.Errorf("claims zero for nonzero field - got %x", f.Bytes()) + } +} + +// TestField64IsOne ensures that checking if a field is one via IsOne works as +// expected. Unlike FieldVal, FieldVal64 is always fully reduced, so values such +// as the prime + 1 report as one. +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^26", + in: "4000000", + expected: false, + }, { + name: "2^52", + in: "10000000000000", + expected: false, + }, { + name: "2^104", + in: "100000000000000000000000000", + expected: false, + }, { + name: "2^234", + in: "40000000000000000000000000000000000000000000000000000000000", + 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 + } + } +} + +// 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, 1) + 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(1) + 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 + } + } +} + +// 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 * 3", + in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + in2: "8", + expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc27", + }, { + 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: "over p - n word one", + in: "14551231950b75fc4402da17233c9baee", + expected: true, + }, { + name: "over p - n word two", + in: "14551231950b75fc4403da1722fc9baee", + expected: true, + }, { + name: "over p - n word three", + in: "14551231950b79fc4402da1722fc9baee", + expected: true, + }, { + name: "over p - n word four", + in: "14551241950b75fc4402da1722fc9baee", + expected: true, + }, { + name: "over p - n word five", + in: "54551231950b75fc4402da1722fc9baee", + expected: true, + }, { + name: "over p - n word six", + in: "100000014551231950b75fc4402da1722fc9baee", + expected: true, + }, { + name: "over p - n word seven", + in: "000000000000000000400000000000014551231950b75fc4402da1722fc9baee", + expected: true, + }, { + name: "over p - n word eight", + in: "000000000001000000000000000000014551231950b75fc4402da1722fc9baee", + expected: true, + }, { + name: "over p - n word nine", + in: "000004000000000000000000000000014551231950b75fc4402da1722fc9baee", + 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) + } + } +} + +// assertField64EqualsBig fails the test when got does not equal the 32-byte +// big-endian encoding of want. +func assertField64EqualsBig(t *testing.T, want *big.Int, got *FieldVal64) { + t.Helper() + + wantBytes := make([]byte, 32) + want.FillBytes(wantBytes) + if !bytes.Equal(wantBytes, got.Bytes()[:]) { + t.Fatalf("value mismatch\nwant: %x\ngot: %x", wantBytes, got.Bytes()) + } +} + +// TestField64ReductionCarry is a deterministic regression test for the Crandall +// reduction carry-out bug. When a Mul/Square product lands in [2^256, 2^256 + +// 2^66) the second reduction fold carries out of the top limb. That carry +// represents 2^256 ≡ 0x1000003d1 (mod p); a previous implementation dropped it +// and produced a result 0x1000003d1 too small. +// +// The inputs are values of the form p-k (all strictly < p, the documented +// precondition for Mul/Square), which drive the reduction into the carry path. +// p-65537 is the smallest case originally observed to trigger the bug. These +// are intentionally fixed rather than random so the regression is always +// covered. +func TestField64ReductionCarry(t *testing.T) { + t.Parallel() + + p := curveParams.P + + // fromBig builds a FieldVal64 from an integer that is known to be < p, so + // SetBytes stores it without modular reduction. + fromBig := func(v *big.Int) *FieldVal64 { + var b [32]byte + v.FillBytes(b[:]) + var f FieldVal64 + f.SetBytes(&b) + return &f + } + + ks := []int64{ + 1, 2, 3, 977, 978, 1953, 65536, 65537, 65538, + 0x1000003d0, 0x1000003d1, 0x1000003d2, + } + + pMinus1Int := new(big.Int).Sub(p, big.NewInt(1)) + pMinus1 := fromBig(pMinus1Int) + + for _, k := range ks { + aInt := new(big.Int).Sub(p, big.NewInt(k)) + a := fromBig(aInt) + + t.Run(fmt.Sprintf("square_p-%d", k), func(t *testing.T) { + want := new(big.Int).Mul(aInt, aInt) + want.Mod(want, p) + + var sq FieldVal64 + sq.SquareVal(a) + assertField64EqualsBig(t, want, &sq) + + // Squaring must also agree with multiplying the value by itself. + var prod FieldVal64 + prod.Mul2(a, a) + if !prod.Equals(&sq) { + t.Fatalf("square != mul(a, a)\nsquare: %x\nmul: %x", + sq.Bytes(), prod.Bytes()) + } + }) + + t.Run(fmt.Sprintf("mul_p-%d_x_p-1", k), func(t *testing.T) { + want := new(big.Int).Mul(aInt, pMinus1Int) + want.Mod(want, p) + + var prod FieldVal64 + prod.Mul2(a, pMinus1) + assertField64EqualsBig(t, want, &prod) + }) + } +} From 9db856e1f8c477255b2c6a3b1371adc2a9a822c8 Mon Sep 17 00:00:00 2001 From: "valery.osheter@coinbase.com" Date: Sun, 21 Jun 2026 09:25:21 +0300 Subject: [PATCH 3/4] secp256k1: Add benchmarks for FieldVal64 Add micro-benchmarks for the FieldVal64 multiply, square, inverse, and related operations to track the pure Go performance. AI-assisted: developed with the help of an AI coding assistant; all code reviewed and tested by the author. --- dcrec/secp256k1/field64_bench_test.go | 93 +++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 dcrec/secp256k1/field64_bench_test.go 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) + } +} From 5b6e6efdf69dc60c31611ff5db3a29ff20fb2aa2 Mon Sep 17 00:00:00 2001 From: "valery.osheter@coinbase.com" Date: Wed, 24 Jun 2026 18:19:36 +0300 Subject: [PATCH 4/4] secp256k1: Refine FieldVal64 API and tests. Reorder methods to mirror field.go and tidy the exported doc comments so the two field backends stay consistent. Drop Normalize and magnitude tracking since FieldVal64 is always fully reduced, add MulBy2, MulBy3, MulBy4, and MulBy8 for the common small-constant multiplications, and back them with a generic MulInt. Rework the edge-case tests to exercise the 4x64 limbs rather than the 10x26 word boundaries inherited from FieldVal, remove the inconsistent TestField64ReductionCarry, and ensure the operations remain constant time, along with assorted small fixes. --- dcrec/secp256k1/field64.go | 829 ++++++++++++++++++-------------- dcrec/secp256k1/field64_test.go | 374 +++++++------- dcrec/secp256k1/modnscalar.go | 6 + 3 files changed, 647 insertions(+), 562 deletions(-) diff --git a/dcrec/secp256k1/field64.go b/dcrec/secp256k1/field64.go index 64a13fd5e3..c751f2b07a 100644 --- a/dcrec/secp256k1/field64.go +++ b/dcrec/secp256k1/field64.go @@ -7,7 +7,6 @@ package secp256k1 import ( "encoding/binary" "encoding/hex" - "fmt" "math/bits" ) @@ -29,8 +28,48 @@ const ( field64Prime3 = 0xFFFFFFFFFFFFFFFF ) -// SetBytes sets f to the 32-byte big-endian value and returns 1 if the input is -// >= p. +// 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]) @@ -53,134 +92,142 @@ func (f *FieldVal64) SetBytes(b *[32]byte) uint32 { return uint32(1 - borrow) } -// Bytes returns the 32-byte big-endian encoding. -func (f *FieldVal64) Bytes() *[32]byte { - var b [32]byte - f.PutBytes(&b) - return &b +// 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 } -// PutBytes writes the value to b in big-endian order. -func (f *FieldVal64) PutBytes(b *[32]byte) { +// 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]) } -// Normalize fully reduces f modulo p. FieldVal64 values are always kept fully -// reduced, so this is a no-op kept for API parity with FieldVal. -func (f *FieldVal64) Normalize() *FieldVal64 { - return f +// 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[:]) } -// SetInt sets f to a small integer. -func (f *FieldVal64) SetInt(v uint16) *FieldVal64 { - f.n = [4]uint64{uint64(v), 0, 0, 0} - return f +// 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 } -// Set sets f to val. -func (f *FieldVal64) Set(val *FieldVal64) *FieldVal64 { - f.n = val.n - return f +// 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) } -// Equals reports whether two values are equal. -func (f *FieldVal64) Equals(val *FieldVal64) bool { - bits := (f.n[0] ^ val.n[0]) | (f.n[1] ^ val.n[1]) | - (f.n[2] ^ val.n[2]) | (f.n[3] ^ val.n[3]) - return bits == 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 } -// IsOdd reports whether f is odd. -func (f *FieldVal64) IsOdd() bool { - return f.n[0]&1 == 1 +// 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) } -// Add2 sets f = a + b (mod p). -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. Since p = 2^256 - C, the low 256 bits of t - p are - // identical to t + C, i.e. the folded result for a 2^256 overflow. - 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 (= t - p when t >= p, = t + C folded - // when overflow occurred). - 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 +// 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 } -// Add sets f = f + val (mod p). -func (f *FieldVal64) Add(val *FieldVal64) *FieldVal64 { - return f.Add2(f, val) +// 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) } -// MulInt sets f = f * val (mod p). val is limited to the small constants used by -// the curve formulas (up to 8); larger values panic. FieldVal magnitude tracking -// is irrelevant since FieldVal64 stays fully reduced. Doublings (f.Add(f)) keep -// the addition count low. -func (f *FieldVal64) MulInt(val uint8) *FieldVal64 { - if val > 8 { - panic(fmt.Sprintf("FieldVal64.MulInt: val %d exceeds supported maximum of 8", val)) - } - - switch val { - case 0: - f.n = [4]uint64{} - return f - case 1: - return f - } +// 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 +} - var orig FieldVal64 - orig.Set(f) - switch val { - case 2: - f.Add(&orig) // 2 - case 3: - f.Add(&orig).Add(&orig) // 3 - case 4: - f.Add(&orig) // 2 - f.Add(f) // 4 - case 5: - f.Add(&orig) // 2 - f.Add(f) // 4 - f.Add(&orig) // 5 - case 6: - f.Add(&orig).Add(&orig) // 3 - f.Add(f) // 6 - case 7: - f.Add(&orig).Add(&orig) // 3 - f.Add(f) // 6 - f.Add(&orig) // 7 - case 8: - f.Add(&orig) // 2 - f.Add(f) // 4 - f.Add(f) // 8 - } - return f +// 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 sets f = -val (mod p). The magnitude parameter exists for API -// parity with FieldVal and is ignored since FieldVal64 stays fully reduced. -func (f *FieldVal64) NegateVal(val *FieldVal64, magnitude uint32) *FieldVal64 { +// 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) @@ -192,8 +239,7 @@ func (f *FieldVal64) NegateVal(val *FieldVal64, magnitude uint32) *FieldVal64 { mask := -borrow maskedPrime0 := field64Prime0 & mask - // Pass 3: add the masked modulus so (0 - val) + p = p - val (mod 2^256), - // while val == 0 stays 0. + // 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) @@ -202,291 +248,154 @@ func (f *FieldVal64) NegateVal(val *FieldVal64, magnitude uint32) *FieldVal64 { return f } -// Mul2 sets f = a * b (mod p). The 256x256 -> 256 modular multiply is provided -// by field64Mul, which uses native/hardware-optimized assembly on the supported -// platforms and falls back to a portable Go implementation elsewhere. -func (f *FieldVal64) Mul2(a, b *FieldVal64) *FieldVal64 { - field64Mul(&f.n, &a.n, &b.n) - return f -} - -// Mul sets f = f * val (mod p). -func (f *FieldVal64) Mul(val *FieldVal64) *FieldVal64 { - return f.Mul2(f, val) -} - -// SquareVal sets f = val^2 (mod p). The modular square is provided by -// field64Square, which uses native/hardware-optimized assembly on the supported -// platforms and falls back to a portable Go implementation elsewhere. -func (f *FieldVal64) SquareVal(val *FieldVal64) *FieldVal64 { - field64Square(&f.n, &val.n) - return f -} - -// Square sets f = f^2 (mod p). -func (f *FieldVal64) Square() *FieldVal64 { - return f.SquareVal(f) -} - -// p - n (field prime minus the group order) as little-endian 64-bit limbs. -const ( - field64PMinusN0 = 0x402da1722fc9baee - field64PMinusN1 = 0x4551231950b75fc4 - field64PMinusN2 = 0x0000000000000001 - field64PMinusN3 = 0x0000000000000000 -) - -// Negate sets f = -f (mod p). The magnitude parameter exists for API parity with -// FieldVal and is ignored since FieldVal64 stays fully reduced. -func (f *FieldVal64) Negate(magnitude uint32) *FieldVal64 { - return f.NegateVal(f, magnitude) +// 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 sets f = f + ui (mod p). +// 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) } -// Zero sets f to zero. -func (f *FieldVal64) Zero() { - f.n = [4]uint64{} +// 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) } -// IsZero reports whether f is zero. -func (f *FieldVal64) IsZero() bool { - return (f.n[0] | f.n[1] | f.n[2] | f.n[3]) == 0 -} +// 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 -// IsZeroBit returns 1 if f is zero and 0 otherwise in constant time. See IsZero -// for the bool version. -func (f *FieldVal64) IsZeroBit() uint32 { - // (bits | -bits) >> 63 is 1 iff bits != 0; invert for is-zero. - bits := f.n[0] | f.n[1] | f.n[2] | f.n[3] - return uint32(((bits | -bits) >> 63) ^ 1) -} + // 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) -// IsOne reports whether f is one. -func (f *FieldVal64) IsOne() bool { - return f.n[0] == 1 && (f.n[1]|f.n[2]|f.n[3]) == 0 + // 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 } -// IsOneBit returns 1 if f is one and 0 otherwise in constant time. See IsOne for -// the bool version. -func (f *FieldVal64) IsOneBit() uint32 { - // (bits | -bits) >> 63 is 1 iff bits != 0; invert for is-one. - bits := (f.n[0] ^ 1) | f.n[1] | f.n[2] | f.n[3] - return uint32(((bits | -bits) >> 63) ^ 1) +// 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) } -// String returns the field value as a human-readable hex string. -func (f FieldVal64) String() string { - return hex.EncodeToString(f.Bytes()[:]) +// 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) } -// IsOddBit returns 1 if f is odd and 0 otherwise. -func (f *FieldVal64) IsOddBit() uint32 { - return uint32(f.n[0] & 1) +// 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() } -// SetByteSlice interprets b as a big-endian unsigned integer (truncated to the -// first 32 bytes), stores it modulo p, and returns whether the input overflowed -// p before reduction. -func (f *FieldVal64) SetByteSlice(b []byte) bool { - var b32 [32]byte - if len(b) > 32 { - b = b[:32] - } - copy(b32[32-len(b):], b) - return f.SetBytes(&b32) != 0 +// 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() } -// PutBytesUnchecked writes the 32-byte big-endian encoding of f into b without -// bounds checking. -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]) +// 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) } -// IsGtOrEqPrimeMinusOrder reports whether f >= p - n in constant time. f must be -// fully reduced, which FieldVal64 always is. -func (f *FieldVal64) IsGtOrEqPrimeMinusOrder() bool { - 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 +// 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) } -// Inverse sets f = f^(-1) (mod p) via Fermat's little theorem (a^(p-2)). -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) +// 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 +} - 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) +// 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) } -// 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. +// SquareVal squares the passed value and stores the result in f in constant +// time. // -// Since the secp256k1 prime is ≡ 3 (mod 4), the square root is a^((p+1)/4), -// computed via the same addition chain used by FieldVal26 (254 squarings, 13 -// multiplications). -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) +// 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 @@ -494,13 +403,6 @@ func (f *FieldVal64) SquareRootVal(val *FieldVal64) bool { 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] - - // Each row forms the 5-limb partial product a * b[j] in (q0..q4) with a - // single carry chain, then accumulates it into the running product with a - // second carry chain. Threading every carry through bits.Add64's carry-in - // (rather than summing hi + carries by value) lets the compiler emit - // efficient add-with-carry chains. The fresh top limb cannot overflow: the - // maximum hi + partialCarry + accumulateCarry is < 2^64. var c uint64 // Row 0: p0..p4 = a * b0. @@ -677,3 +579,190 @@ func field64Square(r *[4]uint64, a *[4]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_test.go b/dcrec/secp256k1/field64_test.go index 884b0cb47a..e8e5c611f4 100644 --- a/dcrec/secp256k1/field64_test.go +++ b/dcrec/secp256k1/field64_test.go @@ -31,15 +31,6 @@ func (f *FieldVal64) SetHex(hexString string) *FieldVal64 { return f } -// randFieldVal sets f to a field value created from a random value generated by -// the passed rng and returns f to support chaining. -func (f *FieldVal64) randFieldVal(rng *rand.Rand) *FieldVal64 { - var buf [32]byte - rng.Read(buf[:]) - f.SetBytes(&buf) - 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) { @@ -83,7 +74,7 @@ func TestField64SetInt(t *testing.T) { // 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. +// edge cases. Random cases are tested via the various other tests. func TestField64SetBytes(t *testing.T) { tests := []struct { name string // test description @@ -94,59 +85,67 @@ func TestField64SetBytes(t *testing.T) { in: "00", overflow: false, }, { - name: "field prime", - in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - overflow: true, + name: "one (only limb zero set)", + in: "01", + overflow: false, }, { - name: "field prime - 1", - in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + name: "limb zero all ones (2^64 - 1)", + in: "ffffffffffffffff", overflow: false, }, { - name: "field prime + 1 (overflow in word zero)", - in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", - overflow: true, + name: "limb one low bit (2^64)", + in: "010000000000000000", + overflow: false, }, { - name: "field prime first 32 bits", - in: "fffffc2f", + name: "limbs zero and one all ones (2^128 - 1)", + in: "ffffffffffffffffffffffffffffffff", overflow: false, }, { - name: "field prime word zero", - in: "03fffc2f", + name: "limb two low bit (2^128)", + in: "0100000000000000000000000000000000", overflow: false, }, { - name: "field prime first 64 bits", - in: "fffffffefffffc2f", + name: "limbs zero, one, and two all ones (2^192 - 1)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffff", overflow: false, }, { - name: "field prime word zero and one", - in: "0ffffefffffc2f", + name: "limb three low bit (2^192)", + in: "01000000000000000000000000000000000000000000000000", overflow: false, }, { - name: "field prime first 96 bits", - in: "fffffffffffffffefffffc2f", + name: "limb three high bit (2^255)", + in: "8000000000000000000000000000000000000000000000000000000000000000", overflow: false, }, { - name: "field prime word zero, one, and two", - in: "3ffffffffffefffffc2f", + name: "distinct value in every limb (verifies limb ordering)", + in: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", overflow: false, }, { - name: "overflow in word one (prime + 1<<26)", - in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff03fffc2f", + 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", + name: "2^256 - 1 (max input)", in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", overflow: true, }, { - name: "alternating bits", + name: "alternating bits (spans all limbs)", in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", overflow: false, }, { - name: "alternating bits 2", + name: "alternating bits 2 (spans all limbs)", in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", overflow: false, }} @@ -210,55 +209,63 @@ func TestField64Bytes(t *testing.T) { in: "0", expected: "0000000000000000000000000000000000000000000000000000000000000000", }, { - name: "field prime (aka 0)", - in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - expected: "0000000000000000000000000000000000000000000000000000000000000000", + name: "one (only limb zero set)", + in: "1", + expected: "0000000000000000000000000000000000000000000000000000000000000001", }, { - name: "field prime - 1", - in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", - expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + name: "limb zero all ones (2^64 - 1)", + in: "ffffffffffffffff", + expected: "000000000000000000000000000000000000000000000000ffffffffffffffff", }, { - name: "field prime + 1 (aka 1, overflow in word zero)", - in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", - expected: "0000000000000000000000000000000000000000000000000000000000000001", + name: "limb one low bit (2^64)", + in: "10000000000000000", + expected: "0000000000000000000000000000000000000000000000010000000000000000", }, { - name: "field prime first 32 bits", - in: "fffffc2f", - expected: "00000000000000000000000000000000000000000000000000000000fffffc2f", + name: "limbs zero and one all ones (2^128 - 1)", + in: "ffffffffffffffffffffffffffffffff", + expected: "00000000000000000000000000000000ffffffffffffffffffffffffffffffff", }, { - name: "field prime word zero", - in: "03fffc2f", - expected: "0000000000000000000000000000000000000000000000000000000003fffc2f", + name: "limb two low bit (2^128)", + in: "100000000000000000000000000000000", + expected: "0000000000000000000000000000000100000000000000000000000000000000", }, { - name: "field prime first 64 bits", - in: "fffffffefffffc2f", - expected: "000000000000000000000000000000000000000000000000fffffffefffffc2f", + name: "limbs zero, one, and two all ones (2^192 - 1)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffff", + expected: "0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff", }, { - name: "field prime word zero and one", - in: "0ffffefffffc2f", - expected: "000000000000000000000000000000000000000000000000000ffffefffffc2f", + name: "limb three low bit (2^192)", + in: "1000000000000000000000000000000000000000000000000", + expected: "0000000000000001000000000000000000000000000000000000000000000000", }, { - name: "field prime first 96 bits", - in: "fffffffffffffffefffffc2f", - expected: "0000000000000000000000000000000000000000fffffffffffffffefffffc2f", + name: "limb three high bit (2^255)", + in: "8000000000000000000000000000000000000000000000000000000000000000", + expected: "8000000000000000000000000000000000000000000000000000000000000000", }, { - name: "field prime word zero, one, and two", - in: "3ffffffffffefffffc2f", - expected: "000000000000000000000000000000000000000000003ffffffffffefffffc2f", + name: "distinct value in every limb (verifies limb ordering)", + in: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", + expected: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", }, { - name: "overflow in word one (prime + 1<<26)", - in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff03fffc2f", - expected: "0000000000000000000000000000000000000000000000000000000004000000", + 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: "2^256 - 1", + 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", + name: "alternating bits (spans all limbs)", in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", expected: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", }, { - name: "alternating bits 2", + name: "alternating bits 2 (spans all limbs)", in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", expected: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", }} @@ -300,39 +307,52 @@ func TestField64Zero(t *testing.T) { var f FieldVal64 f.SetHex("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") f.Zero() - if !f.IsZero() { - t.Errorf("field value is not zero - got %x", f.Bytes()) + 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 works as -// expected. +// 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 %x", f.Bytes()) + 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 %x", f.Bytes()) + 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 %x", f.Bytes()) + 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 %x", f.Bytes()) + 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 works as -// expected. Unlike FieldVal, FieldVal64 is always fully reduced, so values such -// as the prime + 1 report as one. +// 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 @@ -355,20 +375,20 @@ func TestField64IsOne(t *testing.T) { in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", expected: true, }, { - name: "2^26", - in: "4000000", + name: "2^63 (high bit of limb zero)", + in: "8000000000000000", expected: false, }, { - name: "2^52", - in: "10000000000000", + name: "2^64 (low bit of limb one)", + in: "10000000000000000", expected: false, }, { - name: "2^104", - in: "100000000000000000000000000", + name: "2^128 (low bit of limb two)", + in: "100000000000000000000000000000000", expected: false, }, { - name: "2^234", - in: "40000000000000000000000000000000000000000000000000000000000", + name: "2^192 (low bit of limb three)", + in: "1000000000000000000000000000000000000000000000000", expected: false, }} @@ -380,6 +400,13 @@ func TestField64IsOne(t *testing.T) { 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 + } } } @@ -554,7 +581,7 @@ func TestField64Negate(t *testing.T) { expected := new(FieldVal64).SetHex(test.expected) // Ensure negating another value produces the expected result. - result := new(FieldVal64).NegateVal(f, 1) + result := new(FieldVal64).NegateVal(f) if !result.Equals(expected) { t.Errorf("%s: unexpected result -- got: %x, want: %x", test.name, result.Bytes(), expected.Bytes()) @@ -562,7 +589,7 @@ func TestField64Negate(t *testing.T) { } // Ensure self negating also produces the expected result. - result2 := f.Negate(1) + result2 := f.Negate() if !result2.Equals(expected) { t.Errorf("%s: unexpected result -- got: %x, want: %x", test.name, result2.Bytes(), expected.Bytes()) @@ -733,6 +760,50 @@ func TestField64Add(t *testing.T) { } } +// 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) { @@ -854,10 +925,15 @@ func TestField64Mul(t *testing.T) { in2: "3", expected: "0", }, { - name: "secp256k1 prime * 3", + 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", @@ -1202,40 +1278,32 @@ func TestField64IsGtOrEqPrimeMinusOrder(t *testing.T) { in: "14551231950b75fc4402da1722fc9baef", expected: true, }, { - name: "over p - n word one", - in: "14551231950b75fc4402da17233c9baee", + name: "greater in limb zero, limbs one and two equal", + in: "14551231950b75fc4ffffffffffffffff", expected: true, }, { - name: "over p - n word two", - in: "14551231950b75fc4403da1722fc9baee", + name: "greater in limb one (limb zero zero)", + in: "14551231950b75fc50000000000000000", expected: true, }, { - name: "over p - n word three", - in: "14551231950b79fc4402da1722fc9baee", - expected: true, - }, { - name: "over p - n word four", - in: "14551241950b75fc4402da1722fc9baee", - expected: true, - }, { - name: "over p - n word five", - in: "54551231950b75fc4402da1722fc9baee", - expected: true, + name: "smaller in limb one with max limb zero (below p - n)", + in: "14551231950b75fc3ffffffffffffffff", + expected: false, }, { - name: "over p - n word six", - in: "100000014551231950b75fc4402da1722fc9baee", - expected: true, + name: "2^128 - 1 (limb two zero, below p - n)", + in: "ffffffffffffffffffffffffffffffff", + expected: false, }, { - name: "over p - n word seven", - in: "000000000000000000400000000000014551231950b75fc4402da1722fc9baee", + name: "greater in limb two (2 * 2^128)", + in: "200000000000000000000000000000000", expected: true, }, { - name: "over p - n word eight", - in: "000000000001000000000000000000014551231950b75fc4402da1722fc9baee", + name: "limb three set (2^192)", + in: "1000000000000000000000000000000000000000000000000", expected: true, }, { - name: "over p - n word nine", - in: "000004000000000000000000000000014551231950b75fc4402da1722fc9baee", + name: "limb three high bit (2^255)", + in: "8000000000000000000000000000000000000000000000000000000000000000", expected: true, }} @@ -1284,81 +1352,3 @@ func TestField64IsGtOrEqPrimeMinusOrderRandom(t *testing.T) { } } } - -// assertField64EqualsBig fails the test when got does not equal the 32-byte -// big-endian encoding of want. -func assertField64EqualsBig(t *testing.T, want *big.Int, got *FieldVal64) { - t.Helper() - - wantBytes := make([]byte, 32) - want.FillBytes(wantBytes) - if !bytes.Equal(wantBytes, got.Bytes()[:]) { - t.Fatalf("value mismatch\nwant: %x\ngot: %x", wantBytes, got.Bytes()) - } -} - -// TestField64ReductionCarry is a deterministic regression test for the Crandall -// reduction carry-out bug. When a Mul/Square product lands in [2^256, 2^256 + -// 2^66) the second reduction fold carries out of the top limb. That carry -// represents 2^256 ≡ 0x1000003d1 (mod p); a previous implementation dropped it -// and produced a result 0x1000003d1 too small. -// -// The inputs are values of the form p-k (all strictly < p, the documented -// precondition for Mul/Square), which drive the reduction into the carry path. -// p-65537 is the smallest case originally observed to trigger the bug. These -// are intentionally fixed rather than random so the regression is always -// covered. -func TestField64ReductionCarry(t *testing.T) { - t.Parallel() - - p := curveParams.P - - // fromBig builds a FieldVal64 from an integer that is known to be < p, so - // SetBytes stores it without modular reduction. - fromBig := func(v *big.Int) *FieldVal64 { - var b [32]byte - v.FillBytes(b[:]) - var f FieldVal64 - f.SetBytes(&b) - return &f - } - - ks := []int64{ - 1, 2, 3, 977, 978, 1953, 65536, 65537, 65538, - 0x1000003d0, 0x1000003d1, 0x1000003d2, - } - - pMinus1Int := new(big.Int).Sub(p, big.NewInt(1)) - pMinus1 := fromBig(pMinus1Int) - - for _, k := range ks { - aInt := new(big.Int).Sub(p, big.NewInt(k)) - a := fromBig(aInt) - - t.Run(fmt.Sprintf("square_p-%d", k), func(t *testing.T) { - want := new(big.Int).Mul(aInt, aInt) - want.Mod(want, p) - - var sq FieldVal64 - sq.SquareVal(a) - assertField64EqualsBig(t, want, &sq) - - // Squaring must also agree with multiplying the value by itself. - var prod FieldVal64 - prod.Mul2(a, a) - if !prod.Equals(&sq) { - t.Fatalf("square != mul(a, a)\nsquare: %x\nmul: %x", - sq.Bytes(), prod.Bytes()) - } - }) - - t.Run(fmt.Sprintf("mul_p-%d_x_p-1", k), func(t *testing.T) { - want := new(big.Int).Mul(aInt, pMinus1Int) - want.Mod(want, p) - - var prod FieldVal64 - prod.Mul2(a, pMinus1) - assertField64EqualsBig(t, want, &prod) - }) - } -} 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