diff --git a/dcrec/secp256k1/README.md b/dcrec/secp256k1/README.md index 53f3839117..4fcfa18616 100644 --- a/dcrec/secp256k1/README.md +++ b/dcrec/secp256k1/README.md @@ -16,8 +16,8 @@ structures and functions for working with public and private secp256k1 keys. In addition, subpackages are provided to produce, verify, parse, and serialize ECDSA signatures and EC-Schnorr-DCRv0 (a custom Schnorr-based signature scheme -specific to Decred) signatures. See the README.md files in the relevant sub -packages for more details about those aspects. +specific to Decred) signatures. See the README.md files in the relevant +subpackages for more details about those aspects. ## Features @@ -95,6 +95,21 @@ secp256k1 elliptic curve cryptography. The secp256k1 domain parameters are specified in [SEC 2: Recommended Elliptic Curve Domain Parameters](https://www.secg.org/sec2-v2.pdf). +## Caveats + +Constant time implementations are designed to eliminate data dependent timing +variation at the algorithmic level. This property depends on the Go compiler +continuing to compile the relevant operations into constant-time machine +instructions. + +This package also consistently uses temporary stack allocated buffers which it +attempts to clear in order to reduce the likelihood of sensitive data lingering +in memory, but Go does not guarantee such writes are never optimized away. + +Significant effort has been made to ensure correctness including careful review +of the generated assembly, but it is impossible to guarantee exact behavior of +future versions of the Go compiler. + ## secp256k1 use in Decred At the time of this writing, the primary public key cryptography in widespread diff --git a/dcrec/secp256k1/common.go b/dcrec/secp256k1/common.go deleted file mode 100644 index b45e013bd5..0000000000 --- a/dcrec/secp256k1/common.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2020-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 - -// constantTimeEq returns 1 if a == b or 0 otherwise in constant time. -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 -} - -// constantTimeLess returns 1 if a < b or 0 otherwise in constant time. -func constantTimeLess(a, b uint32) uint32 { - return uint32((uint64(a) - uint64(b)) >> 63) -} - -// constantTimeLessOrEq returns 1 if a <= b or 0 otherwise in constant time. -func constantTimeLessOrEq(a, b uint32) uint32 { - return uint32((uint64(a) - uint64(b) - 1) >> 63) -} - -// constantTimeGreater returns 1 if a > b or 0 otherwise in constant time. -func constantTimeGreater(a, b uint32) uint32 { - return constantTimeLess(b, a) -} - -// constantTimeGreaterOrEq returns 1 if a >= b or 0 otherwise in constant time. -func constantTimeGreaterOrEq(a, b uint32) uint32 { - return constantTimeLessOrEq(b, a) -} - -// constantTimeMin returns min(a,b) in constant time. -func constantTimeMin(a, b uint32) uint32 { - return b ^ ((a ^ b) & -constantTimeLess(a, b)) -} - -// constantTimeSelect64 returns a if cond == 1 or b if cond == 0 in constant -// time. -// -// WARNING: The behavior is undefined if cond is anything other than 0 or 1. -func constantTimeSelect64(cond, a, b uint64) uint64 { - mask := -cond - return b ^ (a^b)&mask -} diff --git a/dcrec/secp256k1/common_test.go b/dcrec/secp256k1/common_test.go deleted file mode 100644 index cde06019ff..0000000000 --- a/dcrec/secp256k1/common_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2026 The Decred developers -// Copyright (c) 2013-2026 Dave Collins -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package secp256k1 - -import "testing" - -// TestConstantTimeSelect64 ensures that the 64-bit constant time selection -// function works as expected in terms of behavior. -func TestConstantTimeSelect64(t *testing.T) { - tests := []struct { - name string // test description - cond uint64 // condition value - a uint64 // first value - b uint64 // second value - want uint64 // expected selected value - }{ - {"sel(0,1,2)==2", 0, 1, 2, 2}, - {"sel(1,1,2)==1", 1, 1, 2, 1}, - {"sel(0,1<<64-1,1)==1", 0, 1<<64 - 1, 1, 1}, - {"sel(1,1<<64-1,1)==1<<64-1", 1, 1<<64 - 1, 1, 1<<64 - 1}, - {"sel(0,1,1<<64-1)==1<<64-1", 0, 1, 1<<64 - 1, 1<<64 - 1}, - {"sel(1,1,1<<64-1)==1", 1, 1, 1<<64 - 1, 1}, - {"sel(0,1<<64-1,1<<64-2)==1<<64-2", 0, 1<<64 - 1, 1<<64 - 2, 1<<64 - 2}, - {"sel(1,1<<64-1,1<<64-2)==1<<64-1", 1, 1<<64 - 1, 1<<64 - 2, 1<<64 - 1}, - } - - for _, test := range tests { - got := constantTimeSelect64(test.cond, test.a, test.b) - if got != test.want { - t.Errorf("%q: unexpected result -- got %d, want %d", test.name, - got, test.want) - } - } -} diff --git a/dcrec/secp256k1/curve.go b/dcrec/secp256k1/curve.go index 84c6d0c414..fb9da3d265 100644 --- a/dcrec/secp256k1/curve.go +++ b/dcrec/secp256k1/curve.go @@ -720,16 +720,6 @@ func mulAdd64Carry(digit1, digit2, m, c uint64) (hi, lo uint64) { // because it is used for replacing division with multiplication and thus the // intermediate results must be done via a field extension to a larger field. func mul512Rsh320Round(n1, n2 *ModNScalar) ModNScalar { - // Convert n1 and n2 to base 2^64 digits. - n1Digit0 := uint64(n1.n[0]) | uint64(n1.n[1])<<32 - n1Digit1 := uint64(n1.n[2]) | uint64(n1.n[3])<<32 - n1Digit2 := uint64(n1.n[4]) | uint64(n1.n[5])<<32 - n1Digit3 := uint64(n1.n[6]) | uint64(n1.n[7])<<32 - n2Digit0 := uint64(n2.n[0]) | uint64(n2.n[1])<<32 - n2Digit1 := uint64(n2.n[2]) | uint64(n2.n[3])<<32 - n2Digit2 := uint64(n2.n[4]) | uint64(n2.n[5])<<32 - n2Digit3 := uint64(n2.n[6]) | uint64(n2.n[7])<<32 - // Compute the full 512-bit product n1*n2. var r0, r1, r2, r3, r4, r5, r6, r7, c uint64 @@ -738,40 +728,40 @@ func mul512Rsh320Round(n1, n2 *ModNScalar) ModNScalar { // // Note that r0 is ignored because it is not needed to compute the higher // terms and it is shifted out below anyway. - c, _ = bits.Mul64(n2Digit0, n1Digit0) - c, r1 = mulAdd64(n2Digit0, n1Digit1, c) - c, r2 = mulAdd64(n2Digit0, n1Digit2, c) - r4, r3 = mulAdd64(n2Digit0, n1Digit3, c) + c, _ = bits.Mul64(n2.n[0], n1.n[0]) + c, r1 = mulAdd64(n2.n[0], n1.n[1], c) + c, r2 = mulAdd64(n2.n[0], n1.n[2], c) + r4, r3 = mulAdd64(n2.n[0], n1.n[3], c) // Terms resulting from the product of the second digit of the second number // by all digits of the first number. // // Note that r1 is ignored because it is no longer needed to compute the // higher terms and it is shifted out below anyway. - c, _ = mulAdd64(n2Digit1, n1Digit0, r1) - c, r2 = mulAdd64Carry(n2Digit1, n1Digit1, r2, c) - c, r3 = mulAdd64Carry(n2Digit1, n1Digit2, r3, c) - r5, r4 = mulAdd64Carry(n2Digit1, n1Digit3, r4, c) + c, _ = mulAdd64(n2.n[1], n1.n[0], r1) + c, r2 = mulAdd64Carry(n2.n[1], n1.n[1], r2, c) + c, r3 = mulAdd64Carry(n2.n[1], n1.n[2], r3, c) + r5, r4 = mulAdd64Carry(n2.n[1], n1.n[3], r4, c) // Terms resulting from the product of the third digit of the second number // by all digits of the first number. // // Note that r2 is ignored because it is no longer needed to compute the // higher terms and it is shifted out below anyway. - c, _ = mulAdd64(n2Digit2, n1Digit0, r2) - c, r3 = mulAdd64Carry(n2Digit2, n1Digit1, r3, c) - c, r4 = mulAdd64Carry(n2Digit2, n1Digit2, r4, c) - r6, r5 = mulAdd64Carry(n2Digit2, n1Digit3, r5, c) + c, _ = mulAdd64(n2.n[2], n1.n[0], r2) + c, r3 = mulAdd64Carry(n2.n[2], n1.n[1], r3, c) + c, r4 = mulAdd64Carry(n2.n[2], n1.n[2], r4, c) + r6, r5 = mulAdd64Carry(n2.n[2], n1.n[3], r5, c) // Terms resulting from the product of the fourth digit of the second number // by all digits of the first number. // // Note that r3 is ignored because it is no longer needed to compute the // higher terms and it is shifted out below anyway. - c, _ = mulAdd64(n2Digit3, n1Digit0, r3) - c, r4 = mulAdd64Carry(n2Digit3, n1Digit1, r4, c) - c, r5 = mulAdd64Carry(n2Digit3, n1Digit2, r5, c) - r7, r6 = mulAdd64Carry(n2Digit3, n1Digit3, r6, c) + c, _ = mulAdd64(n2.n[3], n1.n[0], r3) + c, r4 = mulAdd64Carry(n2.n[3], n1.n[1], r4, c) + c, r5 = mulAdd64Carry(n2.n[3], n1.n[2], r5, c) + r7, r6 = mulAdd64Carry(n2.n[3], n1.n[3], r6, c) // At this point the upper 256 bits of the full 512-bit product n1*n2 are in // r4..r7 (recall the low order results were discarded as noted above). @@ -796,14 +786,10 @@ func mul512Rsh320Round(n1, n2 *ModNScalar) ModNScalar { // less than the group order given the group order is > 2^255 and the // maximum possible value of the result is 2^192. var result ModNScalar - result.n[0] = uint32(r0) - result.n[1] = uint32(r0 >> 32) - result.n[2] = uint32(r1) - result.n[3] = uint32(r1 >> 32) - result.n[4] = uint32(r2) - result.n[5] = uint32(r2 >> 32) - result.n[6] = uint32(r3) - result.n[7] = uint32(r3 >> 32) + result.n[0] = r0 + result.n[1] = r1 + result.n[2] = r2 + result.n[3] = r3 return result } @@ -957,140 +943,337 @@ func splitK(k *ModNScalar) (ModNScalar, ModNScalar) { return k1, k2 } -// nafScalar represents a positive integer up to a maximum value of 2^256 - 1 -// encoded in non-adjacent form. +// wNAFWidth is the window width of the NAF representation used in scalar +// multiplication. +// +// It is important to note that this constant primarily exists to improve code +// readability, to parameterize parts of the scalar multiplication +// implementation, and to allow tests to assert invariants. +// +// However, the overall implementation is intentionally not fully parameterized +// based on this constant because it is carefully optimized for this specific +// window width and those optimizations involve taking advantage of known bounds +// that an arbitrary window size would violate. +// +// Choosing a new window size involves a variety of tradeoffs that must be +// carefully analyzed. For example, recoding costs, precomputation costs, and +// average density. +const wNAFWidth = 5 + +// wnaf5RecodeCodes maps the low five bits of an integer to the encoded +// representative of the width-5 wNAF digit that should be emitted. +// +// These values are not the signed digits themselves. Rather, each is an +// encoded representative that serves directly as an index into precomputed +// tables. See [wnafScalar] for the encoding details. // -// NAF is a signed-digit representation where each digit can be +1, 0, or -1. +// For each odd residue, the represented signed digit is the unique value in +// {±1, ±3, ±5, ±7, ±9, ±11, ±13, ±15} that is congruent to the residue modulo +// 32: // -// In order to efficiently encode that information, this type uses two arrays, a -// "positive" array where set bits represent the +1 signed digits and a -// "negative" array where set bits represent the -1 signed digits. 0 is -// represented by neither array having a bit set in that position. +// residue ≡ digit (mod 32) // -// The Pos and Neg methods return the aforementioned positive and negative -// arrays, respectively. -type nafScalar struct { - // pos houses the positive portion of the representation. An additional - // byte is required for the positive portion because the NAF encoding can be - // up to 1 bit longer than the normal binary encoding of the value. - // - // neg houses the negative portion of the representation. Even though the - // additional byte is not required for the negative portion, since it can - // never exceed the length of the normal binary encoding of the value, - // keeping the same length for positive and negative portions simplifies - // working with the representation and allows extra conditional branches to - // be avoided. - // - // start and end specify the starting and ending index to use within the pos - // and neg arrays, respectively. This allows fixed size arrays to be used - // versus needing to dynamically allocate space on the heap. - // - // NOTE: The fields are defined in the order that they are to minimize the - // padding on 32-bit and 64-bit platforms. - pos [33]byte - start, end uint8 - neg [33]byte +// Thus, subtracting the represented signed digit always produces an integer +// that is divisible by 32. This allows the optimized recoding algorithm to +// skip five one-bit iterations of the canonical algorithm at once. +// +// Even residues always map to zero because they are already divisible by two +// and therefore correspond only to implicit zero digits. +// +// The corresponding arithmetic adjustment for each encoded representative is +// provided by [wnaf5RecodeAdjustments]. +var wnaf5RecodeCodes = [32]uint8{ + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 16, 0, 15, 0, 14, 0, 13, 0, 12, 0, 11, 0, 10, 0, 9, } -// Pos returns the bytes of the encoded value with bits set in the positions -// that represent a signed digit of +1. -func (s *nafScalar) Pos() []byte { - return s.pos[s.start:s.end] +// wnaf5RecodeAdjustments maps the low five bits of an integer to the signed +// adjustment that must be added to the working integer before shifting during +// the width-5 wNAF recoding algorithm. +// +// While [wnaf5RecodeCodes] produces the encoded representative that is stored +// in the resulting wNAF encoding, this table provides the corresponding +// arithmetic adjustment. The represented signed digit d satisfies: +// +// residue ≡ d (mod 32) +// +// and the recoding algorithm updates the working integer via: +// +// n = (n - d) / 2 +// +// Rather than subtracting d directly, this table stores -d encoded as an +// unsigned 64-bit two's complement value. Negative adjustments therefore +// appear as values such as: +// +// -1 -> ^uint64(0) +// -3 -> ^uint64(2) +// -5 -> ^uint64(4) +// -7 -> ^uint64(6) +// -9 -> ^uint64(8) +// -11 -> ^uint64(10) +// -13 -> ^uint64(12) +// -15 -> ^uint64(14) +// +// The adjustment is sign-extended across the upper limbs during the multiword +// addition which allows the hot loop to remain branch free. +var wnaf5RecodeAdjustments = [32]uint64{ + 0, ^uint64(0), 0, ^uint64(2), 0, ^uint64(4), 0, ^uint64(6), + 0, ^uint64(8), 0, ^uint64(10), 0, ^uint64(12), 0, ^uint64(14), + 0, 15, 0, 13, 0, 11, 0, 9, 0, 7, 0, 5, 0, 3, 0, 1, } -// Neg returns the bytes of the encoded value with bits set in the positions -// that represent a signed digit of -1. -func (s *nafScalar) Neg() []byte { - return s.neg[s.start:s.end] +// wnafScalar represents a non-negative integer less than 2^129 encoded in +// width-5 windowed non-adjacent form (wNAF). +// +// wNAF is a signed-digit representation where the valid digits are 0, ±1, ±3, +// ±5, ±7, ±9, ±11, ±13, and ±15. +// +// Each entry corresponds to one bit position and encodes the signed digit. +// +// The encoding from code to digit is: +// 0 -> 0 +// 1 -> +1 +// 2 -> +3 +// 3 -> +5 +// 4 -> +7 +// 5 -> +9 +// 6 -> +11 +// 7 -> +13 +// 8 -> +15 +// 9 -> -1 +// 10 -> -3 +// 11 -> -5 +// 12 -> -7 +// 13 -> -9 +// 14 -> -11 +// 15 -> -13 +// 16 -> -15 +// +// The encoding is intentionally not using a more compact form so that it serves +// directly as an index into precomputed tables which allows simplification of +// hot paths. +// +// Formally, letting c denote the stored code value, the decoded signed digit +// d(c) is given by the piecewise function: +// +// {0, where c = 0 +// d(c) = {2c - 1, where 0 < c ≤ 8 +// {17 - 2c, where 8 < c ≤ 16 +// +// The array contains one extra entry because a recoding may produce a carry +// into an additional most-significant digit. +type wnafScalar struct { + codes [130]uint8 + bits uint8 } -// naf takes a positive integer up to a maximum value of 2^256 - 1 and returns -// its non-adjacent form (NAF), which is a unique signed-digit representation -// such that no two consecutive digits are nonzero. See the documentation for -// the returned type for details on how the representation is encoded -// efficiently and how to interpret it +// wnaf takes a non-negative integer less than 2^129 and returns its width-5 +// windowed non-adjacent form (wNAF) which is a unique signed-digit +// representation such that non-zero digits are separated by at least 4 zeroes. +// See [wnafScalar] for details on how the representation is encoded and how to +// interpret it. // -// NAF is useful in that it has the fewest nonzero digits of any signed digit -// representation, only 1/3rd of its digits are nonzero on average, and at least -// half of the digits will be 0. +// Width-5 wNAF is useful because, on average, only about one in every six +// digits is non-zero. // -// The aforementioned properties are particularly beneficial for optimizing -// elliptic curve point multiplication because they effectively minimize the -// number of required point additions in exchange for needing to perform a mix -// of fewer point additions and subtractions and possibly one additional point -// doubling. This is an excellent tradeoff because subtraction of points has +// This property is particularly beneficial for optimizing elliptic curve point +// multiplication because it greatly reduces the number of point additions at +// the cost of precomputing a few odd multiples of the point and, in the worst +// case, one additional point doubling due to a carry introduced by the +// recoding. This is an excellent tradeoff because subtraction of points has // the same computational complexity as addition of points and point doubling is // faster than both. -func naf(k []byte) nafScalar { - // Strip leading zero bytes. - for len(k) > 0 && k[0] == 0x00 { - k = k[1:] +func wnaf(k *ModNScalar) wnafScalar { + const ( + // This is intentionally not using the package constant [wNAFWidth] for + // the window size for the reasons stated by its documentation. + // + // In particular, this implementation is carefully optimized for this + // specific width and involves assumptions that an arbitrary window size + // might violate. + // + // Using a separate constant helps make it clear that updating the + // window size here requires extra care to assert correctness. + windowSize = 5 + windowMask = (1 << windowSize) - 1 + ) + + // The arithmetic for wNAF recoding is performed over the ordinary integers, + // not modulo the curve order. Moreover, this method is required to be + // called with the scalar's balanced integer representative, which is known + // to fit within 129 bits. + // + // Consequently, the scalar is first converted to a uint192 with three + // 64-bit limbs where the top limb is at most 1. + t0, t1, t2 := k.n[0], k.n[1], k.n[2] + + // This implementation is based on the standard width-w NAF recoding + // algorithm presented as Algorithm 3.35 in [GECC]. However, it has been + // modified to skip runs of zero digits instead of processing one bit at a + // time. + // + // The optimizations exploit the fact that runs of zero digits are implicit + // in the output representation. + // + // Also, note that the last non-zero bit is initialized to the maximum value + // so that adding 1 at the end to account for the exclusive endpoint wraps + // around to 0 when no digits are emitted. + var result wnafScalar + var c uint64 + var bit uint8 + var lastNonZeroBit = ^uint8(0) + for t0|t1|t2 != 0 { + // The next five iterations of the canonical bit-by-bit algorithm would + // emit only zero when the low window is zero, so skip directly to the + // next window boundary in that case. + if residue := uint8(t0 & windowMask); residue != 0 { + // Skip the zero digits that the canonical bit-by-bit algorithm + // would emit before reaching the next odd value. + // + // Since 0 < residue < 32, shift is guaranteed to satisfy the + // following bounds: + // + // 0 ≤ shift < 5 + shift := uint8(bits.TrailingZeros8(residue)) + t0 = t0>>shift | t1<<(64-shift) + t1 = t1>>shift | t2<<(64-shift) + t2 >>= shift + bit += shift + + residue = uint8(t0 & windowMask) + result.codes[bit] = wnaf5RecodeCodes[residue] + lastNonZeroBit = bit + + // The selected digit is congruent to the low five bits modulo 32. + // Therefore, adding the stored adjustment (which represents the + // negation of that digit) makes the value divisible by 32. + // + // Negative adjustments are stored in two's complement. Sign + // extending the value across the upper limbs allows the multiword + // addition to operate without branches. + adjustment := wnaf5RecodeAdjustments[residue] + signExtended := uint64(int64(adjustment) >> 63) + t0, c = bits.Add64(t0, adjustment, 0) + t1, c = bits.Add64(t1, signExtended, c) + t2, _ = bits.Add64(t2, signExtended, c) + } + + // Divide by 32 to prepare for the next window. + // + // The adjustment above guarantees the current value is divisible by 32. + t0 = t0>>windowSize | t1<<(64-windowSize) + t1 = t1>>windowSize | t2<<(64-windowSize) + t2 >>= windowSize + bit += windowSize } - // The non-adjacent form (NAF) of a positive integer k is an expression - // k = ∑_(i=0, l-1) k_i * 2^i where k_i ∈ {0,±1}, k_(l-1) != 0, and no two - // consecutive digits k_i are nonzero. - // - // The traditional method of computing the NAF of a positive integer is - // given by algorithm 3.30 in [GECC]. It consists of repeatedly dividing k - // by 2 and choosing the remainder so that the quotient (k−r)/2 is even - // which ensures the next NAF digit is 0. This requires log_2(k) steps. - // - // However, in [BRID], Prodinger notes that a closed form expression for the - // NAF representation is the bitwise difference 3k/2 - k/2. This is more - // efficient as it can be computed in O(1) versus the O(log(n)) of the - // traditional approach. - // - // The following code makes use of that formula to compute the NAF more - // efficiently. - // - // To understand the logic here, observe that the only way the NAF has a - // nonzero digit at a given bit is when either 3k/2 or k/2 has a bit set in - // that position, but not both. In other words, the result of a bitwise - // xor. This can be seen simply by considering that when the bits are the - // same, the subtraction is either 0-0 or 1-1, both of which are 0. - // - // Further, observe that the "+1" digits in the result are contributed by - // 3k/2 while the "-1" digits are from k/2. So, they can be determined by - // taking the bitwise and of each respective value with the result of the - // xor which identifies which bits are nonzero. - // - // Using that information, this loops backwards from the least significant - // byte to the most significant byte while performing the aforementioned - // calculations by propagating the potential carry and high order bit from - // the next word during the right shift. - kLen := len(k) - var result nafScalar - var carry uint8 - for byteNum := kLen - 1; byteNum >= 0; byteNum-- { - // Calculate k/2. Notice the carry from the previous word is added and - // the low order bit from the next word is shifted in accordingly. - kc := uint16(k[byteNum]) + uint16(carry) - var nextWord uint8 - if byteNum > 0 { - nextWord = k[byteNum-1] - } - halfK := kc>>1 | uint16(nextWord<<7) + result.bits = lastNonZeroBit + 1 + return result +} - // Calculate 3k/2 and determine the non-zero digits in the result. - threeHalfK := kc + halfK - nonZeroResultDigits := threeHalfK ^ halfK +// wNAFPrecompTableSize is the size of the wNAF precomputed point table. +const wNAFPrecompTableSize = 1 << (wNAFWidth - 1) - // Determine the signed digits {0, ±1}. - result.pos[byteNum+1] = uint8(threeHalfK & nonZeroResultDigits) - result.neg[byteNum+1] = uint8(halfK & nonZeroResultDigits) +// wNAFPrecompTable houses the precomputed odd point multiples used during wNAF +// scalar multiplication. +// +// The first half of the table (starting at index 1) contains the positive odd +// multiples: +// +// P, 3P, 5P, ... +// +// While the second half of the table contains the corresponding negatives: +// +// -P, -3P, -5P, ... +// +// Entry 0 is intentionally left unused so the encoded wNAF digit can be used +// directly as an array index. +type wNAFPrecompTable [wNAFPrecompTableSize + 1]JacobianPoint - // Propagate the potential carry from the 3k/2 calculation. - carry = uint8(threeHalfK >> 8) +// wNAFNegateStart returns the index of the half of a precomputed table whose +// points must be negated in order to match the sign adjustment made to the +// corresponding scalar. +// +// When the scalar has been negated, the first half is negated so it represents +// the negative odd multiples while the second half remains positive. +// Otherwise, the opposite arrangement is used. +func wNAFNegateStart(isScalarNegated bool) int { + if isScalarNegated { + return 1 } - result.pos[0] = carry + return wNAFPrecompTableSize/2 + 1 +} - // Set the starting and ending positions within the fixed size arrays to - // identify the bytes that are actually used. This is important since the - // encoding is big endian and thus trailing zero bytes changes its value. - result.start = 1 - carry - result.end = uint8(kLen + 1) - return result +// buildOddPrecomputeTables constructs the odd multiple tables for both P and +// φ(P) and arranges the positive and negative representatives so they can be +// indexed directly by encoded wNAF digits. +func buildOddPrecomputeTables(p *JacobianPoint, pTable, phiTable *wNAFPrecompTable, k1Neg, k2Neg bool) { + // oddMultiplesPerHalf is the total number of odd multiples in each half of + // the table. + const oddMultiplesPerHalf = wNAFPrecompTableSize / 2 + + // Build the positive odd multiples of P. + // + // Width-w wNAF only ever emits odd signed digits, so only odd multiples of + // the point need to be precomputed. + // + // Both positive and negative entries are ultimately needed. The second + // half is initially a copy of the positive half and the appropriate half is + // negated below depending on the sign of k1. + // + // The negation is deferred until after the φ(P) table is built because that + // table is constructed by applying φ to the corresponding positive odd + // multiples of P. + var twoP JacobianPoint + pTable[1].Set(p) + DoubleNonConst(&pTable[1], &twoP) + for i := 1; i < oddMultiplesPerHalf; i++ { + AddNonConst(&twoP, &pTable[i], &pTable[i+1]) + } + copy(pTable[oddMultiplesPerHalf+1:], pTable[1:oddMultiplesPerHalf+1]) + + // Build the corresponding odd multiples of φ(P). + // + // As above, both positive and negative entries are ultimately needed. The + // sign of this table is chosen independently from the P table because it + // depends only on the sign of k2. + // + // Note that φ is a group homomorphism, so: + // + // φ(n*P) = n*φ(P) + // + // This allows the second table to be obtained by applying φ to each entry + // of the first table instead of computing each odd multiple independently. + // + // NOTE: φ(x,y) = (βx,y). The Jacobian z coordinates are the same, so this + // math goes through. + for i := 1; i <= oddMultiplesPerHalf; i++ { + phiTable[i].Set(&pTable[i]) + phiTable[i].X.Mul(endoBeta).Normalize() + } + copy(phiTable[oddMultiplesPerHalf+1:], phiTable[1:oddMultiplesPerHalf+1]) + + // Negate whichever half of the table corresponds to the sign adjustment + // that was applied to k1. + // + // This maintains the invariant: + // + // k1*P = -k1*-P + negateStart := wNAFNegateStart(k1Neg) + for i := negateStart; i < negateStart+oddMultiplesPerHalf; i++ { + pTable[i].Y.Negate(1).Normalize() + } + + // Apply the same transformation independently to the φ(P) table based on + // the sign adjustment that was applied to k2. + // + // Similarly, this maintains the invariant: + // + // k2*φ(P) = -k2*-φ(P) + negateStart = wNAFNegateStart(k2Neg) + for i := negateStart; i < negateStart+oddMultiplesPerHalf; i++ { + phiTable[i].Y.Negate(1).Normalize() + } } // ScalarMultNonConst multiplies k*P where k is a scalar modulo the curve order @@ -1140,25 +1323,6 @@ func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) { // See section 3.5 in [GECC] for a more rigorous treatment. // ------------------------------------------------------------------------- - // Per above, the main equation here to remember is: - // k*P = k1*P + k2*φ(P) - // - // p1 below is P in the equation while p2 is φ(P) in the equation. - // - // NOTE: φ(x,y) = (β*x,y). The Jacobian z coordinates are the same, so this - // math goes through. - // - // Also, calculate -p1 and -p2 for use in the NAF optimization. - p1, p1Neg := new(JacobianPoint), new(JacobianPoint) - p1.Set(point) - p1Neg.Set(p1) - p1Neg.Y.Negate(1).Normalize() - p2, p2Neg := new(JacobianPoint), new(JacobianPoint) - p2.Set(p1) - p2.X.Mul(endoBeta).Normalize() - p2Neg.Set(p2) - p2Neg.Y.Negate(1).Normalize() - // Decompose k into k1 and k2 such that k = k1 + k2*λ (mod n) where k1 and // k2 are around half the bit length of k in order to halve the number of EC // operations. @@ -1170,94 +1334,91 @@ func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) { // which means that when they would otherwise be a small negative magnitude // they will instead be a large positive magnitude. Since the goal is for // the scalars to have a small magnitude to achieve a performance boost, use - // their negation when they are greater than the half order of the group and - // flip the positive and negative values of the corresponding point that - // will be multiplied by to compensate. + // their negation when they are greater than the half order of the group. + // + // In order to compensate, the positive and negative values of the + // corresponding points that will be multiplied by are flipped later. // // In other words, transform the calc when k1 is over the half order to: // k1*P = -k1*-P // // Similarly, transform the calc when k2 is over the half order to: // k2*φ(P) = -k2*-φ(P) + var k1Neg, k2Neg bool k1, k2 := splitK(k) if k1.IsOverHalfOrder() { k1.Negate() - p1, p1Neg = p1Neg, p1 + k1Neg = true } if k2.IsOverHalfOrder() { k2.Negate() - p2, p2Neg = p2Neg, p2 + k2Neg = true } - // Convert k1 and k2 into their NAF representations since NAF has a lot more - // zeros overall on average which minimizes the number of required point - // additions in exchange for a mix of fewer point additions and subtractions - // at the cost of one additional point doubling. + // Per above, the main equation here to remember is: + // k*P = k1*P + k2*φ(P) + // + // The simultaneous multiplication therefore needs two sets of precomputed + // odd multiples: + // + // P, 3P, 5P, ... + // + // and: + // + // φ(P), 3φ(P), 5φ(P), ... + var pPrecomps, phiPrecomps wNAFPrecompTable + buildOddPrecomputeTables(point, &pPrecomps, &phiPrecomps, k1Neg, k2Neg) + + // Convert k1 and k2 into their windowed NAF representations since they have + // a lot more zeroes overall on average which greatly reduces the number of + // point additions at the cost of precomputing a few odd multiples of the + // point and, in the worst case, one additional point doubling due to a + // carry introduced by the recoding. // // This is an excellent tradeoff because subtraction of points has the same // computational complexity as addition of points and point doubling is // faster than both. // // Concretely, on average, 1/2 of all bits will be non-zero with the normal - // binary representation whereas only 1/3rd of the bits will be non-zero - // with NAF. - // - // The Pos version of the bytes contain the +1s and the Neg versions contain - // the -1s. - k1Bytes, k2Bytes := k1.Bytes(), k2.Bytes() - k1NAF, k2NAF := naf(k1Bytes[:]), naf(k2Bytes[:]) - k1PosNAF, k1NegNAF := k1NAF.Pos(), k1NAF.Neg() - k2PosNAF, k2NegNAF := k2NAF.Pos(), k2NAF.Neg() - k1Len, k2Len := len(k1PosNAF), len(k2PosNAF) + // binary representation whereas only 1/6 of the bits will be non-zero with + // width-5 wNAF. + k1NAF, k2NAF := wnaf(&k1), wnaf(&k2) - // Add left-to-right using the NAF optimization. See algorithm 3.77 from - // [GECC]. + // Add left-to-right using the endomorphism and wNAF optimizations. See + // algorithms 3.36 and 3.77 from [GECC]. // // Point Q = ∞ (point at infinity). + // + // Like ordinary binary scalar multiplication, the accumulator is doubled + // once per processed bit position. However, instead of each bit + // contributing either 0 or 1 copies of the point, each non-zero wNAF digit + // contributes a precomputed odd multiple while the preceding doublings + // supply the required power-of-two scaling. + // + // ±P, ±3P, ±5P, ... + // + // Since non-zero digits are sparse and separated by several zero digits, + // relatively few point additions are required compared to the ordinary + // binary representation. var q JacobianPoint - m := k1Len - if m < k2Len { - m = k2Len + maxBits := k1NAF.bits + if maxBits < k2NAF.bits { + maxBits = k2NAF.bits } - for i := 0; i < m; i++ { - // Since k1 and k2 are potentially different lengths and the calculation - // is being done left to right, pad the front of the shorter one with - // 0s. - var k1BytePos, k1ByteNeg, k2BytePos, k2ByteNeg byte - if i >= m-k1Len { - k1BytePos, k1ByteNeg = k1PosNAF[i-(m-k1Len)], k1NegNAF[i-(m-k1Len)] - } - if i >= m-k2Len { - k2BytePos, k2ByteNeg = k2PosNAF[i-(m-k2Len)], k2NegNAF[i-(m-k2Len)] + for bit := maxBits; bit > 0; bit-- { + // Q = 2 * Q + DoubleNonConst(&q, &q) + + // The encoded wNAF digit of k1 serves directly as an index into the + // precomputed odd multiples of P. + if code := k1NAF.codes[bit-1]; code != 0 { + AddNonConst(&q, &pPrecomps[code], &q) } - for mask := uint8(1 << 7); mask > 0; mask >>= 1 { - // Q = 2 * Q - DoubleNonConst(&q, &q) - - // Add or subtract the first point based on the signed digit of the - // NAF representation of k1 at this bit position. - // - // +1: Q = Q + p1 - // -1: Q = Q - p1 - // 0: Q = Q (no change) - if k1BytePos&mask == mask { - AddNonConst(&q, p1, &q) - } else if k1ByteNeg&mask == mask { - AddNonConst(&q, p1Neg, &q) - } - - // Add or subtract the second point based on the signed digit of the - // NAF representation of k2 at this bit position. - // - // +1: Q = Q + p2 - // -1: Q = Q - p2 - // 0: Q = Q (no change) - if k2BytePos&mask == mask { - AddNonConst(&q, p2, &q) - } else if k2ByteNeg&mask == mask { - AddNonConst(&q, p2Neg, &q) - } + // Likewise, the encoded wNAF digit of k2 serves directly as an index + // into the precomputed odd multiples of φ(P). + if code := k2NAF.codes[bit-1]; code != 0 { + AddNonConst(&q, &phiPrecomps[code], &q) } } diff --git a/dcrec/secp256k1/curve_bench_test.go b/dcrec/secp256k1/curve_bench_test.go index 5100ed8966..7d8bf3089a 100644 --- a/dcrec/secp256k1/curve_bench_test.go +++ b/dcrec/secp256k1/curve_bench_test.go @@ -216,16 +216,15 @@ func BenchmarkScalarMultNonConst(b *testing.B) { } } -// BenchmarkNAF benchmarks conversion of a positive integer into its -// non-adjacent form representation. -func BenchmarkNAF(b *testing.B) { - k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575") - kBytes := k.Bytes() +// BenchmarkWNAF benchmarks conversion of a non-negative integer into its +// width-w windowed non-adjacent form representation, where w is [wNAFWidth]. +func BenchmarkWNAF(b *testing.B) { + k := mustModNScalar("8447e288d34b360bc885cb8ce7c00575") b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - naf(kBytes) + wnaf(k) } } diff --git a/dcrec/secp256k1/curve_test.go b/dcrec/secp256k1/curve_test.go index b1b4bec009..6ddb60fdbb 100644 --- a/dcrec/secp256k1/curve_test.go +++ b/dcrec/secp256k1/curve_test.go @@ -61,6 +61,34 @@ func (p *JacobianPoint) Rescale(s *FieldVal) { p.Z.Mul(s).Normalize() } +// mustFieldValWithOverflow converts the passed hex string into a [FieldVal] and +// will panic if there is an error. Values that overflow are NOT treated as an +// error. +// +// This is only provided for the hard-coded constants so errors in the source +// code can be detected. It will only (and must only) be called with hard-coded +// values. +func mustFieldValWithOverflow(s string) *FieldVal { + return mustFieldValInternal(s, true) +} + +// randFieldVal returns a field value created from a random value generated by +// the passed rng. +func randFieldVal(t *testing.T, rng *rand.Rand) *FieldVal { + t.Helper() + + var buf [32]byte + if _, err := rng.Read(buf[:]); err != nil { + t.Fatalf("failed to read random: %v", err) + } + + // Create and return a field value. + var fv FieldVal + fv.SetBytes(&buf) + fv.Normalize() + return &fv +} + // randJacobian returns a Jacobian point created from a point generated by the // passed rng. func randJacobian(t *testing.T, rng *rand.Rand) *JacobianPoint { @@ -634,72 +662,107 @@ func TestDoubleJacobian(t *testing.T) { } } -// checkNAFEncoding returns an error if the provided positive and negative -// portions of an overall NAF encoding do not adhere to the requirements or they -// do not sum back to the provided original value. -func checkNAFEncoding(pos, neg []byte, origValue *big.Int) error { - // NAF must not have a leading zero byte and the number of negative - // bytes must not exceed the positive portion. - if len(pos) > 0 && pos[0] == 0 { - return fmt.Errorf("positive has leading zero -- got %x", pos) +// decodeWNAFDigit converts the provided wNAF code to the signed digit it +// represents. +func decodeWNAFDigit(code uint8) int8 { + const ( + positiveCodeLimit = 1 << (wNAFWidth - 2) + maxCode = 1 << (wNAFWidth - 1) + ) + switch { + case code == 0: + return 0 + case code <= positiveCodeLimit: + return 2*int8(code) - 1 } - if len(neg) > len(pos) { - return fmt.Errorf("negative has len %d > pos len %d", len(neg), - len(pos)) + return (maxCode + 1) - 2*int8(code) +} + +// checkWNAFEncoding returns an error if the provided windowed NAF encoding does +// not adhere to the encoding requirements or does not reconstruct the provided +// original value. +func checkWNAFEncoding(s *wnafScalar, origValue *big.Int) error { + // Ensure the reported number of used bits does not exceed the size of the + // array. + if int(s.bits) > len(s.codes) { + return fmt.Errorf("bits %d > %d", s.bits, len(s.codes)) } - // Ensure the result doesn't have any adjacent non-zero digits. - gotPos := new(big.Int).SetBytes(pos) - gotNeg := new(big.Int).SetBytes(neg) - posOrNeg := new(big.Int).Or(gotPos, gotNeg) - prevBit := posOrNeg.Bit(0) - for bit := 1; bit < posOrNeg.BitLen(); bit++ { - thisBit := posOrNeg.Bit(bit) - if prevBit == 1 && thisBit == 1 { - return fmt.Errorf("adjacent non-zero digits found at bit pos %d", - bit-1) + // wNAF must not have a leading zero and the individual codes must not + // exceed the max allowed code for the window width. + codes := s.codes[:s.bits] + if len(codes) > 0 && codes[len(codes)-1] == 0 { + return fmt.Errorf("leading zero in encoding: %v", codes) + } + const maxCode = 1 << (wNAFWidth - 1) + for _, code := range codes { + if code > maxCode { + return fmt.Errorf("found code %d > %d", code, maxCode) + } + } + + // Ensure each non-zero digit is separated by at least the width of the + // window. + const minDistance = wNAFWidth + prevNonZeroBit := -minDistance + for bit, code := range codes { + if code == 0 { + continue + } + + if distance := bit - prevNonZeroBit; distance < minDistance { + return fmt.Errorf("non-zero digits at bit pos %d and %d are only "+ + "%d bits apart", prevNonZeroBit, bit, distance) } - prevBit = thisBit + + prevNonZeroBit = bit } - // Ensure the resulting positive and negative portions of the overall - // NAF representation sum back to the original value. - gotValue := new(big.Int).Sub(gotPos, gotNeg) - if origValue.Cmp(gotValue) != 0 { - return fmt.Errorf("pos-neg is not original value: got %x, want %x", - gotValue, origValue) + // Reconstruct the represented value and ensure it matches the original + // value. + sum := new(big.Int) + for bit, code := range codes { + digit := decodeWNAFDigit(code) + term := big.NewInt(int64(digit)) + term.Lsh(term, uint(bit)) + sum.Add(sum, term) + } + if origValue.Cmp(sum) != 0 { + return fmt.Errorf("failed to reconstruct orig value: got %v, want %v", + sum, origValue) } return nil } -// TestNAF ensures encoding various edge cases and values to non-adjacent form -// produces valid results. -func TestNAF(t *testing.T) { - tests := []struct { +// TestWNAF ensures encoding various edge cases and values to width-w windowed +// non-adjacent form, where w is [wNAFWidth], produces valid results. +func TestWNAF(t *testing.T) { + type wnafTest struct { name string // test description in string // hex encoded test value - }{{ - name: "empty is zero", - in: "", + } + extraTests := []wnafTest{{ + name: "leading zeroes", + in: "002f20569b90697ad471c1be6107814f", }, { - name: "zero", - in: "00", + name: "highest allowed bit only", + in: "100000000000000000000000000000000", }, { - name: "just before first carry", - in: "aa", + name: "largest 129-bit value", + in: "1ffffffffffffffffffffffffffffffff", }, { - name: "first carry", - in: "ab", + name: "130 bits when NAF encoded", + in: "1f0000000000000000000000000000001", }, { - name: "leading zeroes", - in: "002f20569b90697ad471c1be6107814f53f47446be298a3a2a6b686b97d35cf9", + name: "alternating bits (0xa5)", + in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", }, { - name: "257 bits when NAF encoded", - in: "c000000000000000000000000000000000000000000000000000000000000001", + name: "alternating bits (0x5a)", + in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", }, { - name: "32-byte scalar", - in: "6df2b5d30854069ccdec40ae022f5c948936324a4e9ebed8eb82cfd5a6b6d766", + name: "all ones", + in: "ffffffffffffffffffffffffffffffff", }, { name: "first term of balanced length-two representation #1", in: "b776e53fb55f6b006a270d42d64ec2b1", @@ -714,21 +777,32 @@ func TestNAF(t *testing.T) { in: "a2e79d200f27f2360fba57619936159b", }} + // Add tests for the first small values since they collectively exercise + // every possible residue modulo 2^w, where w is the window width, multiple + // times. Then append the extra deterministic tests for edge cases. + const smallValues = 2*(1< 0 { - return uint16(bits.Len32(w)) + 224 - } - if w := s.n[6]; w > 0 { - return uint16(bits.Len32(w)) + 192 - } - if w := s.n[5]; w > 0 { - return uint16(bits.Len32(w)) + 160 - } - if w := s.n[4]; w > 0 { - return uint16(bits.Len32(w)) + 128 - } if w := s.n[3]; w > 0 { - return uint16(bits.Len32(w)) + 96 + return uint16(bits.Len64(w)) + 192 } if w := s.n[2]; w > 0 { - return uint16(bits.Len32(w)) + 64 + return uint16(bits.Len64(w)) + 128 } if w := s.n[1]; w > 0 { - return uint16(bits.Len32(w)) + 32 + return uint16(bits.Len64(w)) + 64 } - return uint16(bits.Len32(s.n[0])) + return uint16(bits.Len64(s.n[0])) } // checkLambdaDecomposition returns an error if the provided decomposed scalars diff --git a/dcrec/secp256k1/doc.go b/dcrec/secp256k1/doc.go index d946650d73..69121d1164 100644 --- a/dcrec/secp256k1/doc.go +++ b/dcrec/secp256k1/doc.go @@ -50,6 +50,21 @@ use optimized secp256k1 elliptic curve cryptography. Finally, a comprehensive suite of tests is provided to provide a high level of quality assurance. +## Caveats + +Constant time implementations are designed to eliminate data dependent timing +variation at the algorithmic level. This property depends on the Go compiler +continuing to compile the relevant operations into constant time machine +instructions. + +This package also consistently uses temporary stack allocated buffers which it +attempts to clear in order to reduce the likelihood of sensitive data lingering +in memory, but Go does not guarantee such writes are never optimized away. + +Significant effort has been made to ensure correctness, including careful review +of the generated assembly, but it is impossible to guarantee exact behavior of +future versions of the Go compiler. + # Use of secp256k1 in Decred At the time of this writing, the primary public key cryptography in widespread diff --git a/dcrec/secp256k1/field10x26/README.md b/dcrec/secp256k1/field10x26/README.md new file mode 100644 index 0000000000..a5e499e8b9 --- /dev/null +++ b/dcrec/secp256k1/field10x26/README.md @@ -0,0 +1,62 @@ +field10x26 +========== + +[![Build Status](https://github.com/decred/dcrd/workflows/Build%20and%20Test/badge.svg)](https://github.com/decred/dcrd/actions) +[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4/field10x26) + +Package field10x26 provides highly optimized pure-Go arithmetic over the +secp256k1 finite field using a 10x26 representation. + +It is designed for correctness, performance, security, and high assurance +through specialized arithmetic, constant time engineering, differential testing, +and multiple complementary testing techniques. + +This package exposes the underlying implementation directly and is primarily +intended for specialized use cases. Most consumers should prefer the +`secp256k1` package, which provides the stable public `FieldVal` type and +automatically selects the appropriate backend implementation for the target +architecture. + +## Design + +The implementation represents field elements using ten base-2^26 limbs with +carefully bounded intermediate overflow between normalizations. This approach +minimizes carry propagation during common arithmetic operations and enables +efficient constant-time implementations. + +Unlike implementations that always maintain canonical field elements, this +representation intentionally permits bounded overflow between normalizations. +Consequently, callers are responsible for observing the documented +normalization, magnitude, and operation preconditions described by the `Element` +type. + +The semantics are a deliberate design choice that eliminates unnecessary work +from performance-critical arithmetic by allowing callers to normalize only when +required. + +## Assurance + +This package emphasizes correctness and implementation assurance through +multiple complementary validation techniques. + +Key implementation characteristics include: + +- Highly optimized arithmetic specialized specifically for the secp256k1 field +- Constant time implementation suitable for secret-dependent operations +- Differential testing against independent secp256k1 implementations +- Deterministic test vectors +- Property-based testing +- Randomized-input testing +- Coverage-guided fuzz testing +- Manual review of security-critical implementation details + +## Installation and Updating + +This package is part of the `github.com/decred/dcrd/dcrec/secp256k1/v4` module. +Use the standard go tooling for working with modules to incorporate it. + +## License + +Package field10x26 is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/dcrec/secp256k1/field10x26/common_test.go b/dcrec/secp256k1/field10x26/common_test.go new file mode 100644 index 0000000000..d474680725 --- /dev/null +++ b/dcrec/secp256k1/field10x26/common_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2026 The Decred developers +// Copyright (c) 2013-2026 Dave Collins +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package field10x26 + +// References: +// [SECG]: Recommended Elliptic Curve Domain Parameters +// https://www.secg.org/sec2-v2.pdf +// + +import ( + "encoding/hex" + "math/big" +) + +// Curve parameters taken from [SECG] section 2.4.1. +var curveParams = struct { + P *big.Int + N *big.Int +}{ + P: fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"), + N: fromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), +} + +// hexToBytes converts the passed hex string into bytes and will panic if there +// is an error. This is only provided for the hard-coded constants so errors in +// the source code can be detected. It will only (and must only) be called with +// hard-coded values. +func hexToBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic("invalid hex in source file: " + s) + } + return b +} + +// fromHex converts the passed hex string into a big integer pointer and will +// panic is there is an error. This is only provided for the hard-coded +// constants so errors in the source code can bet detected. It will only (and +// must only) be called for initialization purposes. +func fromHex(s string) *big.Int { + if s == "" { + return big.NewInt(0) + } + r, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("invalid hex in source file: " + s) + } + return r +} + +// mustElementInternal converts the passed hex string into an [Element] and will +// panic if there is an error. Values that overflow are treated as an error +// unless the allow overflow flag is set. +// +// This is only provided for the hard-coded constants so errors in the source +// code can be detected. It will only (and must only) be called with hard-coded +// values. +func mustElementInternal(s string, allowOverflow bool) *Element { + if len(s)%2 != 0 { + s = "0" + s + } + b, err := hex.DecodeString(s) + if err != nil { + panic("invalid hex in source file: " + s) + } + if len(b) > 32 { + panic("hex in source file overflows uint256: " + s) + } + var e Element + if overflow := e.SetByteSlice(b); overflow && !allowOverflow { + panic("hex in source file overflows mod N scalar: " + s) + } + return &e +} + +// mustElement converts the passed hex string into an [Element] and will panic +// if there is an error. Values that overflow are treated as an error. +// +// This is only provided for the hard-coded constants so errors in the source +// code can be detected. It will only (and must only) be called with hard-coded +// values. +func mustElement(s string) *Element { + return mustElementInternal(s, false) +} + +// mustElementWithOverflow converts the passed hex string into an [Element] and +// will panic if there is an error. Values that overflow are NOT treated as an +// error. +// +// This is only provided for the hard-coded constants so errors in the source +// code can be detected. It will only (and must only) be called with hard-coded +// values. +func mustElementWithOverflow(s string) *Element { + return mustElementInternal(s, true) +} diff --git a/dcrec/secp256k1/field.go b/dcrec/secp256k1/field10x26/element.go similarity index 58% rename from dcrec/secp256k1/field.go rename to dcrec/secp256k1/field10x26/element.go index 9c7c9367f0..0682b8604e 100644 --- a/dcrec/secp256k1/field.go +++ b/dcrec/secp256k1/field10x26/element.go @@ -4,7 +4,16 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package secp256k1 +// Package field10x26 implements highly optimized, constant-time arithmetic +// over the secp256k1 finite field using a 10x26 representation with bounded +// overflow between normalizations. +package field10x26 + +import ( + "encoding/hex" + + "github.com/decred/dcrd/dcrec/secp256k1/v4/internal/arith" +) // References: // [HAC]: Handbook of Applied Cryptography Menezes, van Oorschot, Vanstone. @@ -20,9 +29,9 @@ package secp256k1 // optimizations not available to arbitrary-precision arithmetic and generic // modular arithmetic algorithms. // -// There are various ways to internally represent each finite field element. -// For example, the most obvious representation would be to use an array of 4 -// uint64s (aka 4x64: 64 bits * 4 = 256 bits). However, at the time this field +// There are various internal representations for finite field elements. For +// example, the most obvious representation would be to use an array of 4 +// uint64s (aka 4x64: 64 bits * 4 = 256 bits). However, at the time this // implementation was written, Go did not have access to hardware intrinsics, so // that representation suffered from a couple of issues. First, there is no // native Go type large enough to handle the intermediate results while adding @@ -33,7 +42,7 @@ package secp256k1 // While both of those things are still true without intrinsics, modern Go now // provides access to intrinsics that permit the hardware to perform both full // 128-bit products and addition with carry which entirely mitigates those -// limitations. As a result, there is now an alternative [FieldVal64] +// limitations. As a result, there is now an alternative [field4x64.Element] // implementation that uses the aforementioned 4x64 representation with // intrinsics. // @@ -57,11 +66,7 @@ package secp256k1 // // Since it is so important that the field arithmetic is extremely fast for high // performance crypto, this type does not perform any validation where it -// ordinarily would. See the documentation for [FieldVal] for more details. - -import ( - "encoding/hex" -) +// ordinarily would. See the documentation for [Element] for more details. // Constants used to make the code more readable. const ( @@ -71,48 +76,48 @@ const ( eightBitsMask = 0xff ) -// Constants related to the field representation. +// Constants related to the internal representation. const ( - // fieldWords is the number of words used to internally represent the + // fieldLimbs is the number of limbs used to internally represent the // 256-bit value. - fieldWords = 10 + fieldLimbs = 10 - // fieldBase is the exponent used to form the numeric base of each word. - // 2^(fieldBase*i) where i is the word position. + // fieldBase is the exponent used to form the numeric base of each limb. + // 2^(fieldBase*i) where i is the limb position. fieldBase = 26 - // fieldBaseMask is the mask for the bits in each word needed to - // represent the numeric base of each word (except the most significant - // word). + // fieldBaseMask is the mask for the bits in each limb needed to + // represent the numeric base of each limb (except the most significant + // limb). fieldBaseMask = (1 << fieldBase) - 1 - // fieldMSBBits is the number of bits in the most significant word used + // fieldMSBBits is the number of bits in the most significant limb used // to represent the value. - fieldMSBBits = 256 - (fieldBase * (fieldWords - 1)) + fieldMSBBits = 256 - (fieldBase * (fieldLimbs - 1)) - // fieldMSBMask is the mask for the bits in the most significant word + // fieldMSBMask is the mask for the bits in the most significant limb // needed to represent the value. fieldMSBMask = (1 << fieldMSBBits) - 1 - // These fields provide convenient access to each of the words of the - // secp256k1 prime in the internal field representation to improve code + // These fields provide convenient access to each of the limbs of the + // secp256k1 prime in the internal representation to improve code // readability. - fieldPrimeWordZero = 0x03fffc2f - fieldPrimeWordOne = 0x03ffffbf - fieldPrimeWordTwo = 0x03ffffff - fieldPrimeWordThree = 0x03ffffff - fieldPrimeWordFour = 0x03ffffff - fieldPrimeWordFive = 0x03ffffff - fieldPrimeWordSix = 0x03ffffff - fieldPrimeWordSeven = 0x03ffffff - fieldPrimeWordEight = 0x03ffffff - fieldPrimeWordNine = 0x003fffff + fieldPrimeLimb0 = 0x03fffc2f + fieldPrimeLimb1 = 0x03ffffbf + fieldPrimeLimb2 = 0x03ffffff + fieldPrimeLimb3 = 0x03ffffff + fieldPrimeLimb4 = 0x03ffffff + fieldPrimeLimb5 = 0x03ffffff + fieldPrimeLimb6 = 0x03ffffff + fieldPrimeLimb7 = 0x03ffffff + fieldPrimeLimb8 = 0x03ffffff + fieldPrimeLimb9 = 0x003fffff ) -// FieldVal implements optimized fixed-precision arithmetic over the -// secp256k1 finite field. This means all arithmetic is performed modulo +// Element implements optimized fixed-precision arithmetic over the secp256k1 +// finite field. This means all arithmetic is performed modulo // -// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f. +// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f // // WARNING: Since it is so important for the field arithmetic to be extremely // fast for high performance crypto, this type does not perform any validation @@ -120,38 +125,38 @@ const ( // IMPERATIVE for callers to understand some key concepts that are described // below and ensure the methods are called with the necessary preconditions that // each method is documented with. For example, some methods only give the -// correct result if the field value is normalized and others require the field -// values involved to have a maximum magnitude and THERE ARE NO EXPLICIT CHECKS -// TO ENSURE THOSE PRECONDITIONS ARE SATISFIED. This does, unfortunately, make -// the type more difficult to use correctly and while I typically prefer to +// correct result if the element is normalized and others require the elements +// involved to have a maximum magnitude and THERE ARE NO EXPLICIT CHECKS TO +// ENSURE THOSE PRECONDITIONS ARE SATISFIED. This does, unfortunately, make the +// type more difficult to use correctly and while it is typically preferable to // ensure all state and input is valid for most code, this is a bit of an // exception because those extra checks really add up in what ends up being // critical hot paths. // // The first key concept when working with this type is normalization. In order // to avoid the need to propagate a ton of carries, the internal representation -// provides additional overflow bits for each word of the overall 256-bit value. +// provides additional overflow bits for each limb of the overall 256-bit value. // This means that there are multiple internal representations for the same // value and, as a result, any methods that rely on comparison of the value, // such as equality and oddness determination, require the caller to provide a -// normalized value. +// normalized field element. // // The second key concept when working with this type is magnitude. As // previously mentioned, the internal representation provides additional // overflow bits which means that the more math operations that are performed on -// the field value between normalizations, the more those overflow bits -// accumulate. The magnitude is effectively that maximum possible number of -// those overflow bits that could possibly be required as a result of a given -// operation. Since there are only a limited number of overflow bits available, -// this implies that the max possible magnitude MUST be tracked by the caller -// and the caller MUST normalize the field value if a given operation would -// cause the magnitude of the result to exceed the max allowed value. -// -// IMPORTANT: The max allowed magnitude of a field value is 32. -type FieldVal struct { +// the element between normalizations, the more those overflow bits accumulate. +// The magnitude is effectively that maximum possible number of those overflow +// bits that could possibly be required as a result of a given operation. Since +// there are only a limited number of overflow bits available, this implies that +// the max possible magnitude MUST be tracked by the caller and the caller MUST +// normalize the element if a given operation would cause the magnitude of the +// result to exceed the max allowed value. +// +// IMPORTANT: The max allowed magnitude of an element is 32. +type Element struct { // Each 256-bit value is represented as 10 32-bit integers in base 2^26. - // This provides 6 bits of overflow in each word (10 bits in the most - // significant word) for a total of 64 bits of overflow (9*6 + 10 = 64). It + // This provides 6 bits of overflow in each limb (10 bits in the most + // significant limb) for a total of 64 bits of overflow (9*6 + 10 = 64). It // only implements the arithmetic needed for elliptic curve operations. // // The following depicts the internal representation: @@ -179,129 +184,133 @@ type FieldVal struct { n [10]uint32 } -// String returns the field value as a normalized human-readable hex string. +// String returns the element as a normalized human-readable hex string. // // Preconditions: None -// Output Normalized: Field is not modified -- same as input value -// Output Max Magnitude: Field is not modified -- same as input value -func (f FieldVal) String() string { - // f is a copy, so it's safe to normalize it without mutating the original. - f.Normalize() - return hex.EncodeToString(f.Bytes()[:]) +// Output Normalized: Element is not modified -- same as input element +// Output Max Magnitude: Element is not modified -- same as input element +func (e Element) String() string { + // e is a copy, so it's safe to normalize it without mutating the original. + e.Normalize() + return hex.EncodeToString(e.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. +// Zero sets the element to zero in constant time. A newly created element is +// already set to zero. This function can be useful to clear an existing +// element for reuse. // // Preconditions: None // Output Normalized: Yes // Output Max Magnitude: 1 -func (f *FieldVal) Zero() { - f.n = [10]uint32{} +func (e *Element) Zero() { + e.n = [10]uint32{} } -// Set sets the field value equal to the passed value in constant time. The -// normalization and magnitude of the two fields will be identical. +// Set sets the element equal to the passed element in constant time. The +// normalization and magnitude of the two elements will be identical. // -// 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. +// The element is returned to support chaining. This enables syntax like: +// e := new(Element).Set(e2).Add(1) so that e = e2 + 1 where e2 is not modified. // // Preconditions: None -// Output Normalized: Same as input value -// Output Max Magnitude: Same as input value -func (f *FieldVal) Set(val *FieldVal) *FieldVal { - *f = *val - return f +// Output Normalized: Same as input element +// Output Max Magnitude: Same as input element +func (e *Element) Set(val *Element) *Element { + *e = *val + return e } -// 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. +// SetInt sets the element to the passed integer in constant time. This is a +// convenience function since it is fairly common to perform 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. +// The element is returned to support chaining. This enables syntax such +// as e := new(Element).SetInt(2).Mul(e2) so that e = 2 * e2. // // Preconditions: None // Output Normalized: Yes // Output Max Magnitude: 1 -func (f *FieldVal) SetInt(ui uint16) *FieldVal { - f.Zero() - f.n[0] = uint32(ui) - return f +func (e *Element) SetInt(ui uint16) *Element { + e.Zero() + e.n[0] = uint32(ui) + return e } -// SetBytes packs the passed 32-byte big-endian value into the internal field -// value representation in constant time. It 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. +// SetBytes packs the passed 32-byte big-endian value into the internal +// representation in constant time. It interprets the provided array as a +// 256-bit big-endian unsigned integer, packs it, 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. // // Preconditions: None -// Output Normalized: Yes if no overflow, no otherwise +// Output Normalized: Yes when no overflow, No otherwise // Output Max Magnitude: 1 -func (f *FieldVal) SetBytes(b *[32]byte) uint32 { - // Pack the 256 total bits across the 10 uint32 words with a max of - // 26-bits per word. This could be done with a couple of for loops, +func (e *Element) SetBytes(b *[32]byte) uint32 { + // Pack the 256 total bits across the 10 uint32 limbs with at most + // 26-bits per limb. This could be done with a couple of for loops, // but this unrolled version is significantly faster. Benchmarks show // this is about 34 times faster than the variant which uses loops. - f.n[0] = uint32(b[31]) | uint32(b[30])<<8 | uint32(b[29])<<16 | + e.n[0] = uint32(b[31]) | uint32(b[30])<<8 | uint32(b[29])<<16 | (uint32(b[28])&twoBitsMask)<<24 - f.n[1] = uint32(b[28])>>2 | uint32(b[27])<<6 | uint32(b[26])<<14 | + e.n[1] = uint32(b[28])>>2 | uint32(b[27])<<6 | uint32(b[26])<<14 | (uint32(b[25])&fourBitsMask)<<22 - f.n[2] = uint32(b[25])>>4 | uint32(b[24])<<4 | uint32(b[23])<<12 | + e.n[2] = uint32(b[25])>>4 | uint32(b[24])<<4 | uint32(b[23])<<12 | (uint32(b[22])&sixBitsMask)<<20 - f.n[3] = uint32(b[22])>>6 | uint32(b[21])<<2 | uint32(b[20])<<10 | + e.n[3] = uint32(b[22])>>6 | uint32(b[21])<<2 | uint32(b[20])<<10 | uint32(b[19])<<18 - f.n[4] = uint32(b[18]) | uint32(b[17])<<8 | uint32(b[16])<<16 | + e.n[4] = uint32(b[18]) | uint32(b[17])<<8 | uint32(b[16])<<16 | (uint32(b[15])&twoBitsMask)<<24 - f.n[5] = uint32(b[15])>>2 | uint32(b[14])<<6 | uint32(b[13])<<14 | + e.n[5] = uint32(b[15])>>2 | uint32(b[14])<<6 | uint32(b[13])<<14 | (uint32(b[12])&fourBitsMask)<<22 - f.n[6] = uint32(b[12])>>4 | uint32(b[11])<<4 | uint32(b[10])<<12 | + e.n[6] = uint32(b[12])>>4 | uint32(b[11])<<4 | uint32(b[10])<<12 | (uint32(b[9])&sixBitsMask)<<20 - f.n[7] = uint32(b[9])>>6 | uint32(b[8])<<2 | uint32(b[7])<<10 | + e.n[7] = uint32(b[9])>>6 | uint32(b[8])<<2 | uint32(b[7])<<10 | uint32(b[6])<<18 - f.n[8] = uint32(b[5]) | uint32(b[4])<<8 | uint32(b[3])<<16 | + e.n[8] = uint32(b[5]) | uint32(b[4])<<8 | uint32(b[3])<<16 | (uint32(b[2])&twoBitsMask)<<24 - f.n[9] = uint32(b[2])>>2 | uint32(b[1])<<6 | uint32(b[0])<<14 + e.n[9] = uint32(b[2])>>2 | uint32(b[1])<<6 | uint32(b[0])<<14 - // The intuition here is that the field value is greater than the prime if - // one of the higher individual words is greater than corresponding word of - // the prime and all higher words in the field value are equal to their - // corresponding word of the prime. Since this type is modulo the prime, + // The intuition here is that the element is greater than the prime if one + // of the higher individual limbs is greater than corresponding limb of the + // prime and all higher limbs in the element are equal to their + // corresponding limb of the prime. Since this type is modulo the prime, // being equal is also an overflow back to 0. // // Note that because the input is 32 bytes and it was just packed into the - // field representation, the only words that can possibly be greater are + // internal representation, the only limbs that can possibly be greater are // zero and one, because ceil(log_2(2^256 - 1 - P)) = 33 bits max and the - // internal field representation encodes 26 bits with each word. + // internal representation encodes 26 bits with each limb. // - // Thus, there is no need to test if the upper words of the field value - // exceeds them, hence, only equality is checked for them. - highWordsEq := constantTimeEq(f.n[9], fieldPrimeWordNine) - highWordsEq &= constantTimeEq(f.n[8], fieldPrimeWordEight) - highWordsEq &= constantTimeEq(f.n[7], fieldPrimeWordSeven) - highWordsEq &= constantTimeEq(f.n[6], fieldPrimeWordSix) - highWordsEq &= constantTimeEq(f.n[5], fieldPrimeWordFive) - highWordsEq &= constantTimeEq(f.n[4], fieldPrimeWordFour) - highWordsEq &= constantTimeEq(f.n[3], fieldPrimeWordThree) - highWordsEq &= constantTimeEq(f.n[2], fieldPrimeWordTwo) - overflow := highWordsEq & constantTimeGreater(f.n[1], fieldPrimeWordOne) - highWordsEq &= constantTimeEq(f.n[1], fieldPrimeWordOne) - overflow |= highWordsEq & constantTimeGreaterOrEq(f.n[0], fieldPrimeWordZero) + // Thus, there is no need to test if the upper limbs of the element exceeds + // them, hence, only equality is checked for them. + highLimbsEq := arith.ConstantTimeEq(e.n[9], fieldPrimeLimb9) + highLimbsEq &= arith.ConstantTimeEq(e.n[8], fieldPrimeLimb8) + highLimbsEq &= arith.ConstantTimeEq(e.n[7], fieldPrimeLimb7) + highLimbsEq &= arith.ConstantTimeEq(e.n[6], fieldPrimeLimb6) + highLimbsEq &= arith.ConstantTimeEq(e.n[5], fieldPrimeLimb5) + highLimbsEq &= arith.ConstantTimeEq(e.n[4], fieldPrimeLimb4) + highLimbsEq &= arith.ConstantTimeEq(e.n[3], fieldPrimeLimb3) + highLimbsEq &= arith.ConstantTimeEq(e.n[2], fieldPrimeLimb2) + overflow := highLimbsEq & arith.ConstantTimeGreater(e.n[1], fieldPrimeLimb1) + highLimbsEq &= arith.ConstantTimeEq(e.n[1], fieldPrimeLimb1) + overflow |= highLimbsEq & arith.ConstantTimeGreaterOrEq(e.n[0], fieldPrimeLimb0) return overflow } +// zeroArray32 zeroes the provided 32-byte buffer. +func zeroArray32(b *[32]byte) { + *b = [32]byte{} +} + // 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 +// internal 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 @@ -312,35 +321,34 @@ func (f *FieldVal) SetBytes(b *[32]byte) uint32 { // overflow behavior. // // Preconditions: None -// Output Normalized: Yes if no overflow, no otherwise +// Output Normalized: Yes when no overflow, No otherwise // Output Max Magnitude: 1 -func (f *FieldVal) SetByteSlice(b []byte) bool { +func (e *Element) SetByteSlice(b []byte) bool { var b32 [32]byte - b = b[:constantTimeMin(uint32(len(b)), 32)] + b = b[:arith.ConstantTimeMin(uint32(len(b)), 32)] copy(b32[:], b32[:32-len(b)]) copy(b32[32-len(b):], b) - result := f.SetBytes(&b32) + result := e.SetBytes(&b32) zeroArray32(&b32) return result != 0 } -// Normalize normalizes the internal field words into the desired range and -// performs fast modular reduction over the secp256k1 prime by making use of the -// special form of the prime in constant time. +// Normalize normalizes the internal limbs into the desired range and performs +// fast modular reduction over the secp256k1 prime by making use of the special +// form of the prime in constant time. // // Preconditions: None // Output Normalized: Yes // Output Max Magnitude: 1 -func (f *FieldVal) Normalize() *FieldVal { - // The field representation leaves 6 bits of overflow in each word so - // intermediate calculations can be performed without needing to - // propagate the carry to each higher word during the calculations. In - // order to normalize, we need to "compact" the full 256-bit value to - // the right while propagating any carries through to the high order - // word. +func (e *Element) Normalize() *Element { + // The internal representation leaves 6 bits of overflow in each limb so + // intermediate calculations can be performed without needing to propagate + // the carry to each higher limb during the calculations. In order to + // normalize, we need to "compact" the full 256-bit value to the right while + // propagating any carries through to the high order limb. // - // Since this field is doing arithmetic modulo the secp256k1 prime, we - // also need to perform modular reduction over the prime. + // Since this is doing arithmetic modulo the secp256k1 prime, we also need + // to perform modular reduction over the prime. // // Per [HAC] section 14.3.4: Reduction method of moduli of special form, // when the modulus is of the special form m = b^t - c, highly efficient @@ -349,53 +357,53 @@ func (f *FieldVal) Normalize() *FieldVal { // The secp256k1 prime is equivalent to 2^256 - 4294968273, so it fits // this criteria. // - // 4294968273 in field representation (base 2^26) is: + // 4294968273 in internal representation (base 2^26) is: // n[0] = 977 // n[1] = 64 // That is to say (2^26 * 64) + 977 = 4294968273 // - // The algorithm presented in the referenced section typically repeats - // until the quotient is zero. However, due to our field representation - // we already know to within one reduction how many times we would need - // to repeat as it's the uppermost bits of the high order word. Thus we - // can simply multiply the magnitude by the field representation of the - // prime and do a single iteration. After this step there might be an - // additional carry to bit 256 (bit 22 of the high order word). - t9 := f.n[9] + // The algorithm presented in the referenced section typically repeats until + // the quotient is zero. However, due to our internal representation we + // already know to within one reduction how many times we would need to + // repeat as it's the uppermost bits of the high order limb. Thus we can + // simply multiply the magnitude by the internal representation of the prime + // and do a single iteration. After this step there might be an additional + // carry to bit 256 (bit 22 of the high order limb). + t9 := e.n[9] m := t9 >> fieldMSBBits t9 &= fieldMSBMask - t0 := f.n[0] + m*977 - t1 := (t0 >> fieldBase) + f.n[1] + (m << 6) + t0 := e.n[0] + m*977 + t1 := (t0 >> fieldBase) + e.n[1] + (m << 6) t0 &= fieldBaseMask - t2 := (t1 >> fieldBase) + f.n[2] + t2 := (t1 >> fieldBase) + e.n[2] t1 &= fieldBaseMask - t3 := (t2 >> fieldBase) + f.n[3] + t3 := (t2 >> fieldBase) + e.n[3] t2 &= fieldBaseMask - t4 := (t3 >> fieldBase) + f.n[4] + t4 := (t3 >> fieldBase) + e.n[4] t3 &= fieldBaseMask - t5 := (t4 >> fieldBase) + f.n[5] + t5 := (t4 >> fieldBase) + e.n[5] t4 &= fieldBaseMask - t6 := (t5 >> fieldBase) + f.n[6] + t6 := (t5 >> fieldBase) + e.n[6] t5 &= fieldBaseMask - t7 := (t6 >> fieldBase) + f.n[7] + t7 := (t6 >> fieldBase) + e.n[7] t6 &= fieldBaseMask - t8 := (t7 >> fieldBase) + f.n[8] + t8 := (t7 >> fieldBase) + e.n[8] t7 &= fieldBaseMask t9 = (t8 >> fieldBase) + t9 t8 &= fieldBaseMask // At this point, the magnitude is guaranteed to be one, however, the - // value could still be greater than the prime if there was either a - // carry through to bit 256 (bit 22 of the higher order word) or the - // value is greater than or equal to the field characteristic. The + // element could still be greater than the prime if there was either a + // carry through to bit 256 (bit 22 of the higher order limb) or the + // element is greater than or equal to the field characteristic. The // following determines if either or these conditions are true and does // the final reduction in constant time. // // Also note that 'm' will be zero when neither of the aforementioned - // conditions are true and the value will not be changed when 'm' is zero. - m = constantTimeEq(t9, fieldMSBMask) - m &= constantTimeEq(t8&t7&t6&t5&t4&t3&t2, fieldBaseMask) - m &= constantTimeGreater(t1+64+((t0+977)>>fieldBase), fieldBaseMask) + // conditions are true and the element will not be changed when 'm' is zero. + m = arith.ConstantTimeEq(t9, fieldMSBMask) + m &= arith.ConstantTimeEq(t8&t7&t6&t5&t4&t3&t2, fieldBaseMask) + m &= arith.ConstantTimeGreater(t1+64+((t0+977)>>fieldBase), fieldBaseMask) m |= t9 >> fieldMSBBits t0 += m * 977 t1 = (t0 >> fieldBase) + t1 + (m << 6) @@ -418,487 +426,482 @@ func (f *FieldVal) Normalize() *FieldVal { t8 &= fieldBaseMask t9 &= fieldMSBMask // Remove potential multiple of 2^256. - // Finally, set the normalized and reduced words. - f.n[0] = t0 - f.n[1] = t1 - f.n[2] = t2 - f.n[3] = t3 - f.n[4] = t4 - f.n[5] = t5 - f.n[6] = t6 - f.n[7] = t7 - f.n[8] = t8 - f.n[9] = t9 - return f + // Finally, set the normalized and reduced limbs. + e.n[0] = t0 + e.n[1] = t1 + e.n[2] = t2 + e.n[3] = t3 + e.n[4] = t4 + e.n[5] = t5 + e.n[6] = t6 + e.n[7] = t7 + e.n[8] = t8 + e.n[9] = t9 + return e } -// 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. +// PutBytesUnchecked unpacks the element 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, [FieldVal.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 +// There is a similar function, [Element.PutBytes], which unpacks the element +// 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. // // Preconditions: -// - The field value MUST be normalized +// - The element MUST be normalized // - The target slice MUST have at least 32 bytes available -func (f *FieldVal) PutBytesUnchecked(b []byte) { - // Unpack the 256 total bits from the 10 uint32 words with a max of - // 26-bits per word. This could be done with a couple of for loops, +func (e *Element) PutBytesUnchecked(b []byte) { + // Unpack the 256 total bits from the 10 uint32 limbs with at most + // 26-bits per limb. This could be done with a couple of for loops, // but this unrolled version is a bit faster. Benchmarks show this is // about 10 times faster than the variant which uses loops. - b[31] = byte(f.n[0] & eightBitsMask) - b[30] = byte((f.n[0] >> 8) & eightBitsMask) - b[29] = byte((f.n[0] >> 16) & eightBitsMask) - b[28] = byte((f.n[0]>>24)&twoBitsMask | (f.n[1]&sixBitsMask)<<2) - b[27] = byte((f.n[1] >> 6) & eightBitsMask) - b[26] = byte((f.n[1] >> 14) & eightBitsMask) - b[25] = byte((f.n[1]>>22)&fourBitsMask | (f.n[2]&fourBitsMask)<<4) - b[24] = byte((f.n[2] >> 4) & eightBitsMask) - b[23] = byte((f.n[2] >> 12) & eightBitsMask) - b[22] = byte((f.n[2]>>20)&sixBitsMask | (f.n[3]&twoBitsMask)<<6) - b[21] = byte((f.n[3] >> 2) & eightBitsMask) - b[20] = byte((f.n[3] >> 10) & eightBitsMask) - b[19] = byte((f.n[3] >> 18) & eightBitsMask) - b[18] = byte(f.n[4] & eightBitsMask) - b[17] = byte((f.n[4] >> 8) & eightBitsMask) - b[16] = byte((f.n[4] >> 16) & eightBitsMask) - b[15] = byte((f.n[4]>>24)&twoBitsMask | (f.n[5]&sixBitsMask)<<2) - b[14] = byte((f.n[5] >> 6) & eightBitsMask) - b[13] = byte((f.n[5] >> 14) & eightBitsMask) - b[12] = byte((f.n[5]>>22)&fourBitsMask | (f.n[6]&fourBitsMask)<<4) - b[11] = byte((f.n[6] >> 4) & eightBitsMask) - b[10] = byte((f.n[6] >> 12) & eightBitsMask) - b[9] = byte((f.n[6]>>20)&sixBitsMask | (f.n[7]&twoBitsMask)<<6) - b[8] = byte((f.n[7] >> 2) & eightBitsMask) - b[7] = byte((f.n[7] >> 10) & eightBitsMask) - b[6] = byte((f.n[7] >> 18) & eightBitsMask) - b[5] = byte(f.n[8] & eightBitsMask) - b[4] = byte((f.n[8] >> 8) & eightBitsMask) - b[3] = byte((f.n[8] >> 16) & eightBitsMask) - b[2] = byte((f.n[8]>>24)&twoBitsMask | (f.n[9]&sixBitsMask)<<2) - b[1] = byte((f.n[9] >> 6) & eightBitsMask) - b[0] = byte((f.n[9] >> 14) & eightBitsMask) + b[31] = byte(e.n[0] & eightBitsMask) + b[30] = byte((e.n[0] >> 8) & eightBitsMask) + b[29] = byte((e.n[0] >> 16) & eightBitsMask) + b[28] = byte((e.n[0]>>24)&twoBitsMask | (e.n[1]&sixBitsMask)<<2) + b[27] = byte((e.n[1] >> 6) & eightBitsMask) + b[26] = byte((e.n[1] >> 14) & eightBitsMask) + b[25] = byte((e.n[1]>>22)&fourBitsMask | (e.n[2]&fourBitsMask)<<4) + b[24] = byte((e.n[2] >> 4) & eightBitsMask) + b[23] = byte((e.n[2] >> 12) & eightBitsMask) + b[22] = byte((e.n[2]>>20)&sixBitsMask | (e.n[3]&twoBitsMask)<<6) + b[21] = byte((e.n[3] >> 2) & eightBitsMask) + b[20] = byte((e.n[3] >> 10) & eightBitsMask) + b[19] = byte((e.n[3] >> 18) & eightBitsMask) + b[18] = byte(e.n[4] & eightBitsMask) + b[17] = byte((e.n[4] >> 8) & eightBitsMask) + b[16] = byte((e.n[4] >> 16) & eightBitsMask) + b[15] = byte((e.n[4]>>24)&twoBitsMask | (e.n[5]&sixBitsMask)<<2) + b[14] = byte((e.n[5] >> 6) & eightBitsMask) + b[13] = byte((e.n[5] >> 14) & eightBitsMask) + b[12] = byte((e.n[5]>>22)&fourBitsMask | (e.n[6]&fourBitsMask)<<4) + b[11] = byte((e.n[6] >> 4) & eightBitsMask) + b[10] = byte((e.n[6] >> 12) & eightBitsMask) + b[9] = byte((e.n[6]>>20)&sixBitsMask | (e.n[7]&twoBitsMask)<<6) + b[8] = byte((e.n[7] >> 2) & eightBitsMask) + b[7] = byte((e.n[7] >> 10) & eightBitsMask) + b[6] = byte((e.n[7] >> 18) & eightBitsMask) + b[5] = byte(e.n[8] & eightBitsMask) + b[4] = byte((e.n[8] >> 8) & eightBitsMask) + b[3] = byte((e.n[8] >> 16) & eightBitsMask) + b[2] = byte((e.n[8]>>24)&twoBitsMask | (e.n[9]&sixBitsMask)<<2) + b[1] = byte((e.n[9] >> 6) & eightBitsMask) + b[0] = byte((e.n[9] >> 14) & eightBitsMask) } -// PutBytes unpacks the field value to a 32-byte big-endian value using the -// passed byte array in constant time. +// PutBytes unpacks the element to a 32-byte big-endian value using the passed +// byte array in constant time. // -// There is a similar function, [FieldVal.PutBytesUnchecked], which unpacks the -// field value into a slice that must have at least 32 bytes available. This +// There is a similar function, [Element.PutBytesUnchecked], which unpacks the +// element 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 [FieldVal.Bytes], which unpacks the field value -// into a new array and returns that which can sometimes be more ergonomic in +// Alternatively, there is also [Element.Bytes], which unpacks the element into +// a new array and returns that which can sometimes be more ergonomic in // applications that aren't concerned about an additional copy. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) PutBytes(b *[32]byte) { - f.PutBytesUnchecked(b[:]) +// - The element MUST be normalized +func (e *Element) PutBytes(b *[32]byte) { + e.PutBytesUnchecked(b[:]) } -// Bytes unpacks the field value to a 32-byte big-endian value in constant time. +// Bytes unpacks the element to a 32-byte big-endian value in constant time. // -// See [FieldVal.PutBytes] and [FieldVal.PutBytesUnchecked] for variants that +// See [Element.PutBytes] and [Element.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. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) Bytes() *[32]byte { +// - The element MUST be normalized +func (e *Element) Bytes() *[32]byte { b := new([32]byte) - f.PutBytesUnchecked(b[:]) + e.PutBytesUnchecked(b[:]) return b } -// IsZeroBit returns 1 when the field value is equal to zero or 0 otherwise in +// IsZeroBit returns 1 when the element 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 [FieldVal.IsZero] for the version +// operations require a numeric value. See [Element.IsZero] for the version // that returns a bool. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) IsZeroBit() uint32 { - // The value can only be zero if no bits are set in any of the words. +// - The element MUST be normalized +func (e *Element) IsZeroBit() uint32 { + // The element can only be zero if no bits are set in any of the limbs. // This is a constant time implementation. - bits := f.n[0] | f.n[1] | f.n[2] | f.n[3] | f.n[4] | - f.n[5] | f.n[6] | f.n[7] | f.n[8] | f.n[9] + bits := e.n[0] | e.n[1] | e.n[2] | e.n[3] | e.n[4] | + e.n[5] | e.n[6] | e.n[7] | e.n[8] | e.n[9] - return constantTimeEq(bits, 0) + return arith.ConstantTimeEq(bits, 0) } -// IsZero returns whether or not the field value is equal to zero in constant -// time. +// IsZero returns whether or not the element is equal to zero in constant time. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) IsZero() bool { - // The value can only be zero if no bits are set in any of the words. +// - The element MUST be normalized +func (e *Element) IsZero() bool { + // The element can only be zero if no bits are set in any of the limbs. // This is a constant time implementation. - bits := f.n[0] | f.n[1] | f.n[2] | f.n[3] | f.n[4] | - f.n[5] | f.n[6] | f.n[7] | f.n[8] | f.n[9] + bits := e.n[0] | e.n[1] | e.n[2] | e.n[3] | e.n[4] | + e.n[5] | e.n[6] | e.n[7] | e.n[8] | e.n[9] return bits == 0 } -// IsOneBit returns 1 when the field value is equal to one or 0 otherwise in +// IsOneBit returns 1 when the element 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 [FieldVal.IsOne] for the version +// operations require a numeric value. See [Element.IsOne] for the version // that returns a bool. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) 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. +// - The element MUST be normalized +func (e *Element) IsOneBit() uint32 { + // The element can only be one if the single lowest significant bit is set in + // the first limb and no other bits are set in any of the other limbs. // This is a constant time implementation. - bits := (f.n[0] ^ 1) | f.n[1] | f.n[2] | f.n[3] | f.n[4] | f.n[5] | - f.n[6] | f.n[7] | f.n[8] | f.n[9] + bits := (e.n[0] ^ 1) | e.n[1] | e.n[2] | e.n[3] | e.n[4] | e.n[5] | + e.n[6] | e.n[7] | e.n[8] | e.n[9] - return constantTimeEq(bits, 0) + return arith.ConstantTimeEq(bits, 0) } -// IsOne returns whether or not the field value is equal to one in constant -// time. +// IsOne returns whether or not the element is equal to one in constant time. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) 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. +// - The element MUST be normalized +func (e *Element) IsOne() bool { + // The element can only be one if the single lowest significant bit is set + // in the first limb and no other bits are set in any of the other limbs. // This is a constant time implementation. - bits := (f.n[0] ^ 1) | f.n[1] | f.n[2] | f.n[3] | f.n[4] | f.n[5] | - f.n[6] | f.n[7] | f.n[8] | f.n[9] + bits := (e.n[0] ^ 1) | e.n[1] | e.n[2] | e.n[3] | e.n[4] | e.n[5] | + e.n[6] | e.n[7] | e.n[8] | e.n[9] return bits == 0 } -// IsOddBit returns 1 when the field value is an odd number or 0 otherwise in +// IsOddBit returns 1 when the element 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 [FieldVal.IsOdd] for the version +// operations require a numeric value. See [Element.IsOdd] for the version // that returns a bool. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) IsOddBit() uint32 { +// - The element MUST be normalized +func (e *Element) IsOddBit() uint32 { // Only odd numbers have the bottom bit set. - return f.n[0] & 1 + return e.n[0] & 1 } -// IsOdd returns whether or not the field value is an odd number in constant -// time. +// IsOdd returns whether or not the element is an odd number in constant time. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) IsOdd() bool { +// - The element MUST be normalized +func (e *Element) IsOdd() bool { // Only odd numbers have the bottom bit set. - return f.n[0]&1 == 1 + return e.n[0]&1 == 1 } -// Equals returns whether or not the two field values are the same in constant -// time. +// Equals returns whether or not the two elements are the same in constant time. // // Preconditions: -// - Both field values being compared MUST be normalized -func (f *FieldVal) Equals(val *FieldVal) 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. - 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]) | (f.n[4] ^ val.n[4]) | (f.n[5] ^ val.n[5]) | - (f.n[6] ^ val.n[6]) | (f.n[7] ^ val.n[7]) | (f.n[8] ^ val.n[8]) | - (f.n[9] ^ val.n[9]) +// - Both elements being compared MUST be normalized +func (e *Element) Equals(val *Element) bool { + // Xor only sets bits when they are different, so the two elements can only + // be the same if no bits are set after xoring each limb. This is a + // constant time implementation. + bits := (e.n[0] ^ val.n[0]) | (e.n[1] ^ val.n[1]) | (e.n[2] ^ val.n[2]) | + (e.n[3] ^ val.n[3]) | (e.n[4] ^ val.n[4]) | (e.n[5] ^ val.n[5]) | + (e.n[6] ^ val.n[6]) | (e.n[7] ^ val.n[7]) | (e.n[8] ^ val.n[8]) | + (e.n[9] ^ val.n[9]) return bits == 0 } -// NegateVal negates the passed value and stores the result in f in constant -// time. The caller must provide the maximum magnitude of the passed value for -// a correct result. +// NegateVal negates the passed element and stores the result in e in constant +// time. The caller must provide the maximum magnitude of the passed element +// for a correct result. // -// The field value is returned to support chaining. This enables syntax like: -// f.NegateVal(f2).AddInt(1) so that f = -f2 + 1. +// The element is returned to support chaining. This enables syntax like: +// e.NegateVal(e2).AddInt(1) so that e = -e2 + 1. // // Preconditions: // - The max magnitude MUST be 31 // Output Normalized: No // Output Max Magnitude: Input magnitude + 1 -func (f *FieldVal) NegateVal(val *FieldVal, magnitude uint32) *FieldVal { - // Negation in the field is just the prime minus the value. However, - // in order to allow negation against a field value without having to - // normalize/reduce it first, multiply by the magnitude (that is how - // "far" away it is from the normalized value) to adjust. Also, since - // negating a value pushes it one more order of magnitude away from the +func (e *Element) NegateVal(val *Element, magnitude uint32) *Element { + // Negation in the field is just the prime minus the element. However, in + // order to allow negation against an element without having to + // normalize/reduce it first, multiply by the magnitude (that is how "far" + // away it is from its normalized representation) to adjust. Also, since + // negating an element pushes it one more order of magnitude away from the // normalized range, add 1 to compensate. // // For some intuition here, imagine you're performing mod 12 arithmetic - // (picture a clock) and you are negating the number 7. So you start at - // 12 (which is of course 0 under mod 12) and count backwards (left on - // the clock) 7 times to arrive at 5. Notice this is just 12-7 = 5. - // Now, assume you're starting with 19, which is a number that is - // already larger than the modulus and congruent to 7 (mod 12). When a - // value is already in the desired range, its magnitude is 1. Since 19 - // is an additional "step", its magnitude (mod 12) is 2. Since any - // multiple of the modulus is congruent to zero (mod m), the answer can - // be shortcut by simply multiplying the magnitude by the modulus and - // subtracting. Keeping with the example, this would be (2*12)-19 = 5. - f.n[0] = (magnitude+1)*fieldPrimeWordZero - val.n[0] - f.n[1] = (magnitude+1)*fieldPrimeWordOne - val.n[1] - f.n[2] = (magnitude+1)*fieldBaseMask - val.n[2] - f.n[3] = (magnitude+1)*fieldBaseMask - val.n[3] - f.n[4] = (magnitude+1)*fieldBaseMask - val.n[4] - f.n[5] = (magnitude+1)*fieldBaseMask - val.n[5] - f.n[6] = (magnitude+1)*fieldBaseMask - val.n[6] - f.n[7] = (magnitude+1)*fieldBaseMask - val.n[7] - f.n[8] = (magnitude+1)*fieldBaseMask - val.n[8] - f.n[9] = (magnitude+1)*fieldMSBMask - val.n[9] - - return f + // (picture a clock) and you are negating the number 7. So you start at 12 + // (which is of course 0 under mod 12) and count backwards (left on the + // clock) 7 times to arrive at 5. Notice this is just 12-7 = 5. Now, + // assume you're starting with 19, which is a number that is already larger + // than the modulus and congruent to 7 (mod 12). When an element is already + // in the desired range, its magnitude is 1. Since 19 is an additional + // "step", its magnitude (mod 12) is 2. Since any multiple of the modulus + // is congruent to zero (mod m), the answer can be shortcut by simply + // multiplying the magnitude by the modulus and subtracting. Keeping with + // the example, this would be (2*12)-19 = 5. + e.n[0] = (magnitude+1)*fieldPrimeLimb0 - val.n[0] + e.n[1] = (magnitude+1)*fieldPrimeLimb1 - val.n[1] + e.n[2] = (magnitude+1)*fieldBaseMask - val.n[2] + e.n[3] = (magnitude+1)*fieldBaseMask - val.n[3] + e.n[4] = (magnitude+1)*fieldBaseMask - val.n[4] + e.n[5] = (magnitude+1)*fieldBaseMask - val.n[5] + e.n[6] = (magnitude+1)*fieldBaseMask - val.n[6] + e.n[7] = (magnitude+1)*fieldBaseMask - val.n[7] + e.n[8] = (magnitude+1)*fieldBaseMask - val.n[8] + e.n[9] = (magnitude+1)*fieldMSBMask - val.n[9] + + return e } -// Negate negates the field value in constant time. The existing field value is -// modified. The caller must provide the maximum magnitude of the field value -// for a correct result. +// Negate negates the element in constant time. The existing element is +// modified. The caller must provide the maximum magnitude of the element for a +// correct result. // -// The field value is returned to support chaining. This enables syntax like: -// f.Negate().AddInt(1) so that f = -f + 1. +// The element is returned to support chaining. This enables syntax like: +// e.Negate().AddInt(1) so that e = -e + 1. // // Preconditions: // - The max magnitude MUST be 31 // Output Normalized: No // Output Max Magnitude: Input magnitude + 1 -func (f *FieldVal) Negate(magnitude uint32) *FieldVal { - return f.NegateVal(f, magnitude) +func (e *Element) Negate(magnitude uint32) *Element { + return e.NegateVal(e, magnitude) } -// 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. +// AddInt adds the passed integer to the existing element and stores the result +// in e 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. +// The element is returned to support chaining. This enables syntax like: +// e.AddInt(1).Add(e2) so that e = e + 1 + e2. // // Preconditions: -// - The field value MUST have a max magnitude of 31 -// - The integer MUST be a max of 32767 +// - The element MUST have a max magnitude of 31 +// - The integer MUST be at most 32767 // Output Normalized: No -// Output Max Magnitude: Existing field magnitude + 1 -func (f *FieldVal) AddInt(ui uint16) *FieldVal { - // Since the field representation intentionally provides overflow bits, - // it's ok to use carryless addition as the carry bit is safely part of - // the word and will be normalized out. - f.n[0] += uint32(ui) - - return f +// Output Max Magnitude: Existing element magnitude + 1 +func (e *Element) AddInt(ui uint16) *Element { + // Since the internal representation intentionally provides overflow bits, + // it's ok to use carryless addition as the carry bit is safely part of the + // limb and will be normalized out. + e.n[0] += uint32(ui) + + return e } -// Add adds the passed value to the existing field value and stores the result -// in f in constant time. +// Add adds the passed element to the existing element and stores the result in +// e 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. +// The element is returned to support chaining. This enables syntax like: +// e.Add(e2).AddInt(1) so that e = e + e2 + 1. // // Preconditions: -// - The sum of the magnitudes of the two field values MUST be a max of 32 +// - The sum of the magnitudes of the two elements MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Sum of the magnitude of the two individual field values -func (f *FieldVal) Add(val *FieldVal) *FieldVal { - // Since the field representation intentionally provides overflow bits, - // it's ok to use carryless addition as the carry bit is safely part of - // each word and will be normalized out. This could obviously be done - // in a loop, but the unrolled version is faster. - f.n[0] += val.n[0] - f.n[1] += val.n[1] - f.n[2] += val.n[2] - f.n[3] += val.n[3] - f.n[4] += val.n[4] - f.n[5] += val.n[5] - f.n[6] += val.n[6] - f.n[7] += val.n[7] - f.n[8] += val.n[8] - f.n[9] += val.n[9] - - return f +// Output Max Magnitude: Sum of the magnitude of the two individual elements +func (e *Element) Add(val *Element) *Element { + // Since the internal representation intentionally provides overflow bits, + // it's ok to use carryless addition as the carry bit is safely part of each + // limb and will be normalized out. This could obviously be done in a loop, + // but the unrolled version is faster. + e.n[0] += val.n[0] + e.n[1] += val.n[1] + e.n[2] += val.n[2] + e.n[3] += val.n[3] + e.n[4] += val.n[4] + e.n[5] += val.n[5] + e.n[6] += val.n[6] + e.n[7] += val.n[7] + e.n[8] += val.n[8] + e.n[9] += val.n[9] + + return e } -// Add2 adds the passed two field values together and stores the result in f in +// Add2 adds the passed two elements together and stores the result in e 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. +// The element is returned to support chaining. This enables syntax like: +// e3.Add2(e, e2).AddInt(1) so that e3 = e + e2 + 1. // // Preconditions: -// - The sum of the magnitudes of the two field values MUST be a max of 32 +// - The sum of the magnitudes of the two elements MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Sum of the magnitude of the two field values -func (f *FieldVal) Add2(val *FieldVal, val2 *FieldVal) *FieldVal { - // Since the field representation intentionally provides overflow bits, - // it's ok to use carryless addition as the carry bit is safely part of - // each word and will be normalized out. This could obviously be done - // in a loop, but the unrolled version is faster. - f.n[0] = val.n[0] + val2.n[0] - f.n[1] = val.n[1] + val2.n[1] - f.n[2] = val.n[2] + val2.n[2] - f.n[3] = val.n[3] + val2.n[3] - f.n[4] = val.n[4] + val2.n[4] - f.n[5] = val.n[5] + val2.n[5] - f.n[6] = val.n[6] + val2.n[6] - f.n[7] = val.n[7] + val2.n[7] - f.n[8] = val.n[8] + val2.n[8] - f.n[9] = val.n[9] + val2.n[9] - - return f +// Output Max Magnitude: Sum of the magnitude of the two elements +func (e *Element) Add2(val *Element, val2 *Element) *Element { + // Since the internal representation intentionally provides overflow bits, + // it's ok to use carryless addition as the carry bit is safely part of each + // limb and will be normalized out. This could obviously be done in a loop, + // but the unrolled version is faster. + e.n[0] = val.n[0] + val2.n[0] + e.n[1] = val.n[1] + val2.n[1] + e.n[2] = val.n[2] + val2.n[2] + e.n[3] = val.n[3] + val2.n[3] + e.n[4] = val.n[4] + val2.n[4] + e.n[5] = val.n[5] + val2.n[5] + e.n[6] = val.n[6] + val2.n[6] + e.n[7] = val.n[7] + val2.n[7] + e.n[8] = val.n[8] + val2.n[8] + e.n[9] = val.n[9] + val2.n[9] + + return e } -// MulBy2 multiplies the field value by 2 and stores the result in f in constant -// time. Note that this function can overflow if multiplying the value by any -// of the individual words exceeds a max uint32. Therefore it is important that -// the caller ensures no overflows will occur before using this function. +// MulBy2 multiplies the element by 2 and stores the result in e in constant +// time. Note that this function can overflow if multiplying the element causes +// any individual limb to overflow uint32. Therefore it is important that the +// caller ensures no overflows will occur before using this function. // -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy2().Add(f2) so that f = 2 * f + f2. +// The element is returned to support chaining. This enables syntax like: +// e.MulBy2().Add(e2) so that e = 2 * e + e2. // // Preconditions: -// - The field value magnitude multiplied by 2 val MUST be a max of 32 +// - The element magnitude multiplied by 2 MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Existing field magnitude times 2 -func (f *FieldVal) MulBy2() *FieldVal { - return f.MulInt(2) +// Output Max Magnitude: Existing element magnitude times 2 +func (e *Element) MulBy2() *Element { + return e.MulInt(2) } -// MulBy3 multiplies the field value by 3 and stores the result in f in constant -// time. Note that this function can overflow if multiplying the value by any -// of the individual words exceeds a max uint32. Therefore it is important that -// the caller ensures no overflows will occur before using this function. +// MulBy3 multiplies the element by 3 and stores the result in e in constant +// time. Note that this function can overflow if multiplying the element causes +// any individual limb to overflow uint32. Therefore it is important that the +// caller ensures no overflows will occur before using this function. // -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy3().Add(f2) so that f = 3 * f + f2. +// The element is returned to support chaining. This enables syntax like: +// e.MulBy3().Add(e2) so that e = 3 * e + e2. // // Preconditions: -// - The field value magnitude multiplied by 3 val MUST be a max of 32 +// - The element magnitude multiplied by 3 MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Existing field magnitude times 3 -func (f *FieldVal) MulBy3() *FieldVal { - return f.MulInt(3) +// Output Max Magnitude: Existing element magnitude times 3 +func (e *Element) MulBy3() *Element { + return e.MulInt(3) } -// MulBy4 multiplies the field value by 4 and stores the result in f in constant -// time. Note that this function can overflow if multiplying the value by any -// of the individual words exceeds a max uint32. Therefore it is important that -// the caller ensures no overflows will occur before using this function. +// MulBy4 multiplies the element by 4 and stores the result in e in constant +// time. Note that this function can overflow if multiplying the element causes +// any individual limb to overflow uint32. Therefore it is important that the +// caller ensures no overflows will occur before using this function. // -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy4().Add(f2) so that f = 4 * f + f2. +// The element is returned to support chaining. This enables syntax like: +// e.MulBy4().Add(e2) so that e = 4 * e + e2. // // Preconditions: -// - The field value magnitude multiplied by 4 val MUST be a max of 32 +// - The element magnitude multiplied by 4 MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Existing field magnitude times 4 -func (f *FieldVal) MulBy4() *FieldVal { - return f.MulInt(4) +// Output Max Magnitude: Existing element magnitude times 4 +func (e *Element) MulBy4() *Element { + return e.MulInt(4) } -// MulBy8 multiplies the field value by 8 and stores the result in f in constant -// time. Note that this function can overflow if multiplying the value by any -// of the individual words exceeds a max uint32. Therefore it is important that -// the caller ensures no overflows will occur before using this function. +// MulBy8 multiplies the element by 8 and stores the result in e in constant +// time. Note that this function can overflow if multiplying the element causes +// any individual limb to overflow uint32. Therefore it is important that the +// caller ensures no overflows will occur before using this function. // -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy8().Add(f2) so that f = 8 * f + f2. +// The element is returned to support chaining. This enables syntax like: +// e.MulBy8().Add(e2) so that e = 8 * e + e2. // // Preconditions: -// - The field value magnitude multiplied by 8 val MUST be a max of 32 +// - The element magnitude multiplied by 8 MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Existing field magnitude times 8 -func (f *FieldVal) MulBy8() *FieldVal { - return f.MulInt(8) +// Output Max Magnitude: Existing element times 8 +func (e *Element) MulBy8() *Element { + return e.MulInt(8) } -// MulInt multiplies the field value by the passed int and stores the result in -// f in constant time. Note that this function can overflow if multiplying the -// value by any of the individual words exceeds a max uint32. Therefore it is +// MulInt multiplies the element by the passed int and stores the result in e in +// constant time. Note that this function can overflow if multiplying the +// element causes any individual limb to overflow uint32. Therefore it is // important that the caller ensures no overflows will occur before using this // function. // // Callers should prefer using the specialized methods for multiplying by 2, 3, // 4, and 8, as they are commonly used in curve equations. // -// See [FieldVal.MulBy2], [FieldVal.MulBy3], [FieldVal.MulBy4], and -// [FieldVal.MulBy8] for the aforementioned specialized methods. +// See [Element.MulBy2], [Element.MulBy3], [Element.MulBy4], and +// [Element.MulBy8] for the aforementioned specialized methods. // -// The field value is returned to support chaining. This enables syntax like: -// f.MulInt(2).Add(f2) so that f = 2 * f + f2. +// The element is returned to support chaining. This enables syntax like: +// e.MulInt(2).Add(e2) so that e = 2 * e + e2. // // Preconditions: -// - The field value magnitude multiplied by given val MUST be a max of 32 +// - The element magnitude multiplied by given val MUST be at most 32 // Output Normalized: No -// Output Max Magnitude: Existing field magnitude times the provided integer val -func (f *FieldVal) MulInt(val uint8) *FieldVal { - // Since each word of the field representation can hold up to - // 32 - fieldBase extra bits which will be normalized out, it's safe - // to multiply each word without using a larger type or carry - // propagation so long as the values won't overflow a uint32. This - // could obviously be done in a loop, but the unrolled version is - // faster. +// Output Max Magnitude: Existing element magnitude times the provided integer val +func (e *Element) MulInt(val uint8) *Element { + // Since each limb of the internal representation can hold up to 32 - + // [fieldBase] extra bits which will be normalized out, it's safe to + // multiply each limb without using a larger type or carry propagation so + // long as the values won't overflow a uint32. This could obviously be done + // in a loop, but the unrolled version is faster. ui := uint32(val) - f.n[0] *= ui - f.n[1] *= ui - f.n[2] *= ui - f.n[3] *= ui - f.n[4] *= ui - f.n[5] *= ui - f.n[6] *= ui - f.n[7] *= ui - f.n[8] *= ui - f.n[9] *= ui - - return f + e.n[0] *= ui + e.n[1] *= ui + e.n[2] *= ui + e.n[3] *= ui + e.n[4] *= ui + e.n[5] *= ui + e.n[6] *= ui + e.n[7] *= ui + e.n[8] *= ui + e.n[9] *= ui + + return e } -// Mul multiplies the passed value to the existing field value and stores the -// result in f in constant time. Note that this function can overflow if -// multiplying any of the individual words exceeds a max uint32. In practice, -// this means the magnitude of either value involved in the multiplication must -// be a max of 8. +// Mul multiplies the passed element to the existing element and stores the +// result in e in constant time. Note that this function can overflow if +// multiplying causes any individual limb to overflow uint32. In practice, this +// means the magnitude of either element involved in the multiplication must be +// at most 8. // -// The field value is returned to support chaining. This enables syntax like: -// f.Mul(f2).AddInt(1) so that f = (f * f2) + 1. +// The element is returned to support chaining. This enables syntax like: +// e.Mul(e2).AddInt(1) so that e = (e * e2) + 1. // // Preconditions: -// - Both field values MUST have a max magnitude of 8 +// - Both elements MUST have a max magnitude of 8 // Output Normalized: No // Output Max Magnitude: 1 -func (f *FieldVal) Mul(val *FieldVal) *FieldVal { - return f.Mul2(f, val) +func (e *Element) Mul(val *Element) *Element { + return e.Mul2(e, val) } -// Mul2 multiplies the passed two field values together and stores the result in -// f in constant time. Note that this function can overflow if multiplying any -// of the individual words exceeds a max uint32. In practice, this means the -// magnitude of either value involved in the multiplication must be a max of 8. +// Mul2 multiplies the passed two elements together and stores the result in e +// in constant time. Note that this function can overflow if multiplying any of +// the individual limbs exceeds a max uint32. In practice, this means the +// magnitude of either element involved in the multiplication must be at most 8. // -// The field value is returned to support chaining. This enables syntax like: -// f3.Mul2(f, f2).AddInt(1) so that f3 = (f * f2) + 1. +// The element is returned to support chaining. This enables syntax like: +// e3.Mul2(e, e2).AddInt(1) so that e3 = (e * e2) + 1. // // Preconditions: -// - Both input field values MUST have a max magnitude of 8 +// - Both input elements MUST have a max magnitude of 8 // Output Normalized: No // Output Max Magnitude: 1 -func (f *FieldVal) Mul2(val *FieldVal, val2 *FieldVal) *FieldVal { +func (e *Element) Mul2(val *Element, val2 *Element) *Element { // This could be done with a couple of for loops and an array to store // the intermediate terms, but this unrolled version is significantly // faster. @@ -1090,24 +1093,24 @@ func (f *FieldVal) Mul2(val *FieldVal, val2 *FieldVal) *FieldVal { // The secp256k1 prime is equivalent to 2^256 - 4294968273, so it fits // this criteria. // - // 4294968273 in field representation (base 2^26) is: + // 4294968273 in the internal representation (base 2^26) is: // n[0] = 977 // n[1] = 64 // That is to say (2^26 * 64) + 977 = 4294968273 // - // Since each word is in base 26, the upper terms (t10 and up) start - // at 260 bits (versus the final desired range of 256 bits), so the - // field representation of 'c' from above needs to be adjusted for the - // extra 4 bits by multiplying it by 2^4 = 16. 4294968273 * 16 = - // 68719492368. Thus, the adjusted field representation of 'c' is: + // Since each limb is in base 26, the upper terms (t10 and up) start at 260 + // bits (versus the final desired range of 256 bits), so the internal + // representation of 'c' from above needs to be adjusted for the extra 4 + // bits by multiplying it by 2^4 = 16. 4294968273 * 16 = 68719492368. + // Thus, the adjusted internal representation of 'c' is: // n[0] = 977 * 16 = 15632 // n[1] = 64 * 16 = 1024 // That is to say (2^26 * 1024) + 15632 = 68719492368 // - // To reduce the final term, t19, the entire 'c' value is needed instead - // of only n[0] because there are no more terms left to handle n[1]. - // This means there might be some magnitude left in the upper bits that - // is handled below. + // To reduce the final term, t19, the entire 'c' value is needed instead of + // only n[0] because there are no more terms left to handle n[1]. This + // means there might be some magnitude left in the upper bits that is + // handled below. m = t0 + t10*15632 t0 = m & fieldBaseMask m = (m >> fieldBase) + t1 + t10*1024 + t11*15632 @@ -1130,7 +1133,7 @@ func (f *FieldVal) Mul2(val *FieldVal, val2 *FieldVal) *FieldVal { t9 = m & fieldMSBMask m >>= fieldMSBBits - // At this point, if the magnitude is greater than 0, the overall value + // At this point, if the magnitude is greater than 0, the overall element // is greater than the max possible 256-bit value. In particular, it is // "how many times larger" than the max value it is. // @@ -1138,46 +1141,46 @@ func (f *FieldVal) Mul2(val *FieldVal, val2 *FieldVal) *FieldVal { // quotient is zero. However, due to the above, we already know at // least how many times we would need to repeat as it's the value // currently in m. Thus we can simply multiply the magnitude by the - // field representation of the prime and do a single iteration. Notice + // internal representation of the prime and do a single iteration. Notice // that nothing will be changed when the magnitude is zero, so we could // skip this in that case, however always running regardless allows it // to run in constant time. The final result will be in the range // 0 <= result <= prime + (2^64 - c), so it is guaranteed to have a // magnitude of 1, but it is denormalized. d := t0 + m*977 - f.n[0] = uint32(d & fieldBaseMask) + e.n[0] = uint32(d & fieldBaseMask) d = (d >> fieldBase) + t1 + m*64 - f.n[1] = uint32(d & fieldBaseMask) - f.n[2] = uint32((d >> fieldBase) + t2) - f.n[3] = uint32(t3) - f.n[4] = uint32(t4) - f.n[5] = uint32(t5) - f.n[6] = uint32(t6) - f.n[7] = uint32(t7) - f.n[8] = uint32(t8) - f.n[9] = uint32(t9) - - return f + e.n[1] = uint32(d & fieldBaseMask) + e.n[2] = uint32((d >> fieldBase) + t2) + e.n[3] = uint32(t3) + e.n[4] = uint32(t4) + e.n[5] = uint32(t5) + e.n[6] = uint32(t6) + e.n[7] = uint32(t7) + e.n[8] = uint32(t8) + e.n[9] = uint32(t9) + + return e } -// 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. +// SquareRootVal either calculates the square root of the passed element when it +// exists or the square root of the negation of the element when it does not +// exist and stores the result in e in constant time. The return flag is true +// when the calculated square root is for the passed element itself and false +// when it is for its negation. // // Note that this function can overflow if multiplying any of the individual -// words exceeds a max uint32. In practice, this means the magnitude of the -// field must be a max of 8 to prevent overflow. The magnitude of the result +// limbs exceeds a max uint32. In practice, this means the magnitude of the +// element must be at most 8 to prevent overflow. The magnitude of the result // will be 1. // // Preconditions: -// - The input field value MUST have a max magnitude of 8 +// - The input element MUST have a max magnitude of 8 // Output Normalized: No // Output Max Magnitude: 1 -func (f *FieldVal) SquareRootVal(val *FieldVal) bool { +func (e *Element) SquareRootVal(val *Element) bool { // This uses the Tonelli-Shanks method for calculating the square root of - // the value when it exists. The key principles of the method follow. + // the element when it exists. The key principles of the method follow. // // Fermat's little theorem states that for a nonzero number 'a' and prime // 'p', a^(p-1) ≡ 1 (mod p). @@ -1199,15 +1202,15 @@ func (f *FieldVal) SquareRootVal(val *FieldVal) bool { // The Tonelli-Shanks method uses these facts along with factoring out // powers of two to solve a congruence that results in either the solution // when the square root exists or the square root of the negation of the - // value when it does not. In the case of primes that are ≡ 3 (mod 4), the - // possible solutions are r = ±a^((p+1)/4) (mod p). Therefore, either r^2 ≡ - // a (mod p) is true in which case ±r are the two solutions, or r^2 ≡ -a - // (mod p) in which case 'a' is a non-residue and there are no solutions. + // element when it does not. In the case of primes that are ≡ 3 (mod 4), + // the possible solutions are r = ±a^((p+1)/4) (mod p). Therefore, either + // r^2 ≡ a (mod p) is true in which case ±r are the two solutions, or r^2 ≡ + // -a (mod p) in which case 'a' is a non-residue and there are no solutions. // // The secp256k1 prime is ≡ 3 (mod 4), so this result applies. // // In other words, calculate a^((p+1)/4) and then square it and check it - // against the original value to determine if it is actually the square + // against the original element to determine if it is actually the square // root. // // In order to efficiently compute a^((p+1)/4), (p+1)/4 needs to be split @@ -1243,7 +1246,7 @@ func (f *FieldVal) SquareRootVal(val *FieldVal) bool { // => 2^1 2^[2] 2^3 2^6 2^9 2^11 2^[22] 2^44 2^88 2^176 2^220 2^[223] // // This has a cost of 254 field squarings and 13 field multiplications. - var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 FieldVal + var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 Element a.Set(val) a2.SquareVal(&a).Mul(&a) // a2 = a^(2^2 - 1) a3.SquareVal(&a2).Mul(&a) // a3 = a^(2^3 - 1) @@ -1305,54 +1308,54 @@ func (f *FieldVal) SquareRootVal(val *FieldVal) bool { 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 - 2^8 - 2^3 - 2^2) + e.SquareVal(&a223).Square().Square().Square().Square() // e = a^(2^228 - 2^5) + e.Square().Square().Square().Square().Square() // e = a^(2^233 - 2^10) + e.Square().Square().Square().Square().Square() // e = a^(2^238 - 2^15) + e.Square().Square().Square().Square().Square() // e = a^(2^243 - 2^20) + e.Square().Square().Square() // e = a^(2^246 - 2^23) + e.Mul(&a22) // e = a^(2^246 - 2^22 - 1) + e.Square().Square().Square().Square().Square() // e = a^(2^251 - 2^27 - 2^5) + e.Square() // e = a^(2^252 - 2^28 - 2^6) + e.Mul(&a2) // e = a^(2^252 - 2^28 - 2^6 - 2^1 - 1) + e.Square().Square() // e = a^(2^254 - 2^30 - 2^8 - 2^3 - 2^2) // // = a^(2^254 - 2^30 - 244) // // = a^((p+1)/4) // Ensure the calculated result is actually the square root by squaring it - // and checking against the original value. - var sqr FieldVal - return sqr.SquareVal(f).Normalize().Equals(val.Normalize()) + // and checking against the original element. + var sqr Element + return sqr.SquareVal(e).Normalize().Equals(val.Normalize()) } -// Square squares the field value in constant time. The existing field value is +// Square squares the element in constant time. The existing element is // modified. Note that this function can overflow if multiplying any of the -// individual words exceeds a max uint32. In practice, this means the magnitude -// of the field must be a max of 8 to prevent overflow. +// individual limbs exceeds a max uint32. In practice, this means the magnitude +// of the element must be at most 8 to prevent overflow. // -// The field value is returned to support chaining. This enables syntax like: -// f.Square().Mul(f2) so that f = f^2 * f2. +// The element is returned to support chaining. This enables syntax like: +// e.Square().Mul(e2) so that e = e^2 * e2. // // Preconditions: -// - The field value MUST have a max magnitude of 8 +// - The element MUST have a max magnitude of 8 // Output Normalized: No // Output Max Magnitude: 1 -func (f *FieldVal) Square() *FieldVal { - return f.SquareVal(f) +func (e *Element) Square() *Element { + return e.SquareVal(e) } -// SquareVal squares the passed value and stores the result in f in constant +// SquareVal squares the passed element and stores the result in e in constant // time. Note that this function can overflow if multiplying any of the -// individual words exceeds a max uint32. In practice, this means the magnitude -// of the field being squared must be a max of 8 to prevent overflow. +// individual limbs exceeds a max uint32. In practice, this means the magnitude +// of the element being squared must be at most 8 to prevent overflow. // -// 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. +// The element is returned to support chaining. This enables syntax like: +// e3.SquareVal(e).Mul(e) so that e3 = e^2 * e = e^3. // // Preconditions: -// - The input field value MUST have a max magnitude of 8 +// - The input element MUST have a max magnitude of 8 // Output Normalized: No // Output Max Magnitude: 1 -func (f *FieldVal) SquareVal(val *FieldVal) *FieldVal { +func (e *Element) SquareVal(val *Element) *Element { // This could be done with a couple of for loops and an array to store // the intermediate terms, but this unrolled version is significantly // faster. @@ -1497,16 +1500,16 @@ func (f *FieldVal) SquareVal(val *FieldVal) *FieldVal { // The secp256k1 prime is equivalent to 2^256 - 4294968273, so it fits // this criteria. // - // 4294968273 in field representation (base 2^26) is: + // 4294968273 in the internal representation (base 2^26) is: // n[0] = 977 // n[1] = 64 // That is to say (2^26 * 64) + 977 = 4294968273 // - // Since each word is in base 26, the upper terms (t10 and up) start - // at 260 bits (versus the final desired range of 256 bits), so the - // field representation of 'c' from above needs to be adjusted for the - // extra 4 bits by multiplying it by 2^4 = 16. 4294968273 * 16 = - // 68719492368. Thus, the adjusted field representation of 'c' is: + // Since each limb is in base 26, the upper terms (t10 and up) start at 260 + // bits (versus the final desired range of 256 bits), so the internal + // representation of 'c' from above needs to be adjusted for the extra 4 + // bits by multiplying it by 2^4 = 16. 4294968273 * 16 = 68719492368. + // Thus, the adjusted internal representation of 'c' is: // n[0] = 977 * 16 = 15632 // n[1] = 64 * 16 = 1024 // That is to say (2^26 * 1024) + 15632 = 68719492368 @@ -1537,47 +1540,47 @@ func (f *FieldVal) SquareVal(val *FieldVal) *FieldVal { t9 = m & fieldMSBMask m >>= fieldMSBBits - // At this point, if the magnitude is greater than 0, the overall value + // At this point, if the magnitude is greater than 0, the overall element // is greater than the max possible 256-bit value. In particular, it is // "how many times larger" than the max value it is. // // The algorithm presented in [HAC] section 14.3.4 repeats until the // quotient is zero. However, due to the above, we already know at - // least how many times we would need to repeat as it's the value + // least how many times we would need to repeat as it's the quantity // currently in m. Thus we can simply multiply the magnitude by the - // field representation of the prime and do a single iteration. Notice + // internal representation of the prime and do a single iteration. Notice // that nothing will be changed when the magnitude is zero, so we could // skip this in that case, however always running regardless allows it // to run in constant time. The final result will be in the range // 0 <= result <= prime + (2^64 - c), so it is guaranteed to have a // magnitude of 1, but it is denormalized. n := t0 + m*977 - f.n[0] = uint32(n & fieldBaseMask) + e.n[0] = uint32(n & fieldBaseMask) n = (n >> fieldBase) + t1 + m*64 - f.n[1] = uint32(n & fieldBaseMask) - f.n[2] = uint32((n >> fieldBase) + t2) - f.n[3] = uint32(t3) - f.n[4] = uint32(t4) - f.n[5] = uint32(t5) - f.n[6] = uint32(t6) - f.n[7] = uint32(t7) - f.n[8] = uint32(t8) - f.n[9] = uint32(t9) - - return f + e.n[1] = uint32(n & fieldBaseMask) + e.n[2] = uint32((n >> fieldBase) + t2) + e.n[3] = uint32(t3) + e.n[4] = uint32(t4) + e.n[5] = uint32(t5) + e.n[6] = uint32(t6) + e.n[7] = uint32(t7) + e.n[8] = uint32(t8) + e.n[9] = uint32(t9) + + return e } -// Inverse finds the modular multiplicative inverse of the field value in -// constant time. The existing field value is modified. +// Inverse finds the modular multiplicative inverse of the element in constant +// time. The existing element is modified. // -// The field value is returned to support chaining. This enables syntax like: -// f.Inverse().Mul(f2) so that f = f^-1 * f2. +// The element is returned to support chaining. This enables syntax like: +// e.Inverse().Mul(e2) so that e = e^-1 * e2. // // Preconditions: -// - The field value MUST have a max magnitude of 8 +// - The element MUST have a max magnitude of 8 // Output Normalized: No // Output Max Magnitude: 1 -func (f *FieldVal) Inverse() *FieldVal { +func (e *Element) Inverse() *Element { // Fermat's little theorem states that for a nonzero number 'a' and prime // 'p', a^(p-1) ≡ 1 (mod p). Multiplying both sides of the equation by the // multiplicative inverse a^-1 yields a^(p-2) ≡ a^-1 (mod p). Thus, a^(p-2) @@ -1619,8 +1622,8 @@ func (f *FieldVal) Inverse() *FieldVal { // => 2^[1] 2^[2] 2^3 2^6 2^9 2^11 2^[22] 2^44 2^88 2^176 2^220 2^[223] // // This has a cost of 255 field squarings and 15 field multiplications. - var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 FieldVal - a.Set(f) + var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 Element + a.Set(e) 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) @@ -1681,27 +1684,26 @@ func (f *FieldVal) Inverse() *FieldVal { 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 - 4194305) - f.Square().Square().Square().Square().Square() // f = a^(2^251 - 134217760) - f.Mul(&a) // f = a^(2^251 - 134217759) - f.Square().Square().Square() // f = a^(2^254 - 1073742072) - f.Mul(&a2) // f = a^(2^254 - 1073742069) - f.Square().Square() // f = a^(2^256 - 4294968276) - return f.Mul(&a) // f = a^(2^256 - 4294968275) = a^(p-2) + e.SquareVal(&a223).Square().Square().Square().Square() // e = a^(2^228 - 2^5) + e.Square().Square().Square().Square().Square() // e = a^(2^233 - 2^10) + e.Square().Square().Square().Square().Square() // e = a^(2^238 - 2^15) + e.Square().Square().Square().Square().Square() // e = a^(2^243 - 2^20) + e.Square().Square().Square() // e = a^(2^246 - 2^23) + e.Mul(&a22) // e = a^(2^246 - 4194305) + e.Square().Square().Square().Square().Square() // e = a^(2^251 - 134217760) + e.Mul(&a) // e = a^(2^251 - 134217759) + e.Square().Square().Square() // e = a^(2^254 - 1073742072) + e.Mul(&a2) // e = a^(2^254 - 1073742069) + e.Square().Square() // e = a^(2^256 - 4294968276) + return e.Mul(&a) // e = a^(2^256 - 4294968275) = a^(p-2) } -// 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. +// IsGtOrEqPrimeMinusOrder returns whether or not the element is greater than or +// equal to the field prime minus the secp256k1 group order in constant time. // // Preconditions: -// - The field value MUST be normalized -func (f *FieldVal) IsGtOrEqPrimeMinusOrder() bool { +// - The element MUST be normalized +func (e *Element) IsGtOrEqPrimeMinusOrder() bool { // The secp256k1 prime is equivalent to 2^256 - 4294968273 and the group // order is 2^256 - 432420386565659656852420866394968145599. Thus, // the prime minus the group order is: @@ -1710,7 +1712,7 @@ func (f *FieldVal) IsGtOrEqPrimeMinusOrder() bool { // In hex that is: // 0x00000000 00000000 00000000 00000001 45512319 50b75fc4 402da172 2fc9baee // - // Converting that to field representation (base 2^26) is: + // Converting that to the internal representation (base 2^26) is: // // n[0] = 0x03c9baee // n[1] = 0x03685c8b @@ -1720,46 +1722,46 @@ func (f *FieldVal) IsGtOrEqPrimeMinusOrder() bool { // // This can be verified with the following test code: // pMinusN := new(big.Int).Sub(curveParams.P, curveParams.N) - // var fv FieldVal - // fv.SetByteSlice(pMinusN.Bytes()) - // t.Logf("%x", fv.n) + // var v Element + // v.SetByteSlice(pMinusN.Bytes()) + // t.Logf("%x", v.n) // // Outputs: [3c9baee 3685c8b 1fc4402 6542dd 1455123 0 0 0 0 0] const ( - pMinusNWordZero = 0x03c9baee - pMinusNWordOne = 0x03685c8b - pMinusNWordTwo = 0x01fc4402 - pMinusNWordThree = 0x006542dd - pMinusNWordFour = 0x01455123 - pMinusNWordFive = 0x00000000 - pMinusNWordSix = 0x00000000 - pMinusNWordSeven = 0x00000000 - pMinusNWordEight = 0x00000000 - pMinusNWordNine = 0x00000000 + pMinusNLimb0 = 0x03c9baee + pMinusNLimb1 = 0x03685c8b + pMinusNLimb2 = 0x01fc4402 + pMinusNLimb3 = 0x006542dd + pMinusNLimb4 = 0x01455123 + pMinusNLimb5 = 0x00000000 + pMinusNLimb6 = 0x00000000 + pMinusNLimb7 = 0x00000000 + pMinusNLimb8 = 0x00000000 + pMinusNLimb9 = 0x00000000 ) - // The intuition here is that the value is greater than field prime minus - // the group order if one of the higher individual words is greater than the - // corresponding word and all higher words in the value are equal. - result := constantTimeGreater(f.n[9], pMinusNWordNine) - highWordsEqual := constantTimeEq(f.n[9], pMinusNWordNine) - result |= highWordsEqual & constantTimeGreater(f.n[8], pMinusNWordEight) - highWordsEqual &= constantTimeEq(f.n[8], pMinusNWordEight) - result |= highWordsEqual & constantTimeGreater(f.n[7], pMinusNWordSeven) - highWordsEqual &= constantTimeEq(f.n[7], pMinusNWordSeven) - result |= highWordsEqual & constantTimeGreater(f.n[6], pMinusNWordSix) - highWordsEqual &= constantTimeEq(f.n[6], pMinusNWordSix) - result |= highWordsEqual & constantTimeGreater(f.n[5], pMinusNWordFive) - highWordsEqual &= constantTimeEq(f.n[5], pMinusNWordFive) - result |= highWordsEqual & constantTimeGreater(f.n[4], pMinusNWordFour) - highWordsEqual &= constantTimeEq(f.n[4], pMinusNWordFour) - result |= highWordsEqual & constantTimeGreater(f.n[3], pMinusNWordThree) - highWordsEqual &= constantTimeEq(f.n[3], pMinusNWordThree) - result |= highWordsEqual & constantTimeGreater(f.n[2], pMinusNWordTwo) - highWordsEqual &= constantTimeEq(f.n[2], pMinusNWordTwo) - result |= highWordsEqual & constantTimeGreater(f.n[1], pMinusNWordOne) - highWordsEqual &= constantTimeEq(f.n[1], pMinusNWordOne) - result |= highWordsEqual & constantTimeGreaterOrEq(f.n[0], pMinusNWordZero) + // The intuition here is that the element is greater than field prime minus + // the group order if one of the higher individual limbs is greater than the + // corresponding limb and all higher limbs in the element are equal. + result := arith.ConstantTimeGreater(e.n[9], pMinusNLimb9) + highLimbsEqual := arith.ConstantTimeEq(e.n[9], pMinusNLimb9) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[8], pMinusNLimb8) + highLimbsEqual &= arith.ConstantTimeEq(e.n[8], pMinusNLimb8) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[7], pMinusNLimb7) + highLimbsEqual &= arith.ConstantTimeEq(e.n[7], pMinusNLimb7) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[6], pMinusNLimb6) + highLimbsEqual &= arith.ConstantTimeEq(e.n[6], pMinusNLimb6) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[5], pMinusNLimb5) + highLimbsEqual &= arith.ConstantTimeEq(e.n[5], pMinusNLimb5) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[4], pMinusNLimb4) + highLimbsEqual &= arith.ConstantTimeEq(e.n[4], pMinusNLimb4) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[3], pMinusNLimb3) + highLimbsEqual &= arith.ConstantTimeEq(e.n[3], pMinusNLimb3) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[2], pMinusNLimb2) + highLimbsEqual &= arith.ConstantTimeEq(e.n[2], pMinusNLimb2) + result |= highLimbsEqual & arith.ConstantTimeGreater(e.n[1], pMinusNLimb1) + highLimbsEqual &= arith.ConstantTimeEq(e.n[1], pMinusNLimb1) + result |= highLimbsEqual & arith.ConstantTimeGreaterOrEq(e.n[0], pMinusNLimb0) return result != 0 } diff --git a/dcrec/secp256k1/field_bench_test.go b/dcrec/secp256k1/field10x26/element_bench_test.go similarity index 64% rename from dcrec/secp256k1/field_bench_test.go rename to dcrec/secp256k1/field10x26/element_bench_test.go index bdf2c16b12..d795c9cc82 100644 --- a/dcrec/secp256k1/field_bench_test.go +++ b/dcrec/secp256k1/field10x26/element_bench_test.go @@ -2,18 +2,18 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package secp256k1 +package field10x26 import ( "math/big" "testing" ) -// BenchmarkFieldNormalize benchmarks how long it takes the internal field -// to perform normalization (which includes modular reduction) with [FieldVal]. -func BenchmarkFieldNormalize(b *testing.B) { +// BenchmarkElementNormalize benchmarks how long it takes the internal field +// to perform normalization (which includes modular reduction) with [Element]. +func BenchmarkElementNormalize(b *testing.B) { // The function is constant time so any value is fine. - f := &FieldVal{n: [10]uint32{ + e := &Element{n: [10]uint32{ 0x000148f6, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x00000007, }} @@ -21,7 +21,7 @@ func BenchmarkFieldNormalize(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - f.Normalize() + e.Normalize() } } @@ -40,18 +40,18 @@ func BenchmarkBigIntNegateModP(b *testing.B) { } } -// BenchmarkFieldNegate benchmarks calculating the additive inverse of an -// unsigned 256-bit big-endian integer modulo the field prime with [FieldVal]. -func BenchmarkFieldNegate(b *testing.B) { +// BenchmarkElementNegate benchmarks calculating the additive inverse of an +// unsigned 256-bit big-endian integer modulo the field prime with [Element]. +func BenchmarkElementNegate(b *testing.B) { // The function is constant time so any value is fine. valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(valHex) + e := mustElement(valHex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - var result FieldVal - _ = result.NegateVal(f, 1) + var result Element + _ = result.NegateVal(e, 1) } } @@ -71,85 +71,85 @@ func BenchmarkBigIntAddModP(b *testing.B) { } } -// BenchmarkFieldAdd benchmarks adding two unsigned 256-bit big-endian integers -// modulo the field prime with [FieldVal]. -func BenchmarkFieldAdd(b *testing.B) { +// BenchmarkElementAdd benchmarks adding two unsigned 256-bit big-endian +// integers modulo the field prime with [Element]. +func BenchmarkElementAdd(b *testing.B) { // The function is constant time so any values are fine. f1Hex := "d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab" f2Hex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f1 := mustFieldVal(f1Hex) - f2 := mustFieldVal(f2Hex) + f1 := mustElement(f1Hex) + f2 := mustElement(f2Hex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - var sum FieldVal + var sum Element sum.Add2(f1, f2) } } -// BenchmarkFieldMulBy2 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 2 with [FieldVal.MulBy2]. -func BenchmarkFieldMulBy2(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(fHex) +// BenchmarkElementMulBy2 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 2 with [Element.MulBy2]. +func BenchmarkElementMulBy2(b *testing.B) { + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - f.MulBy2() + e.MulBy2() } } -// BenchmarkFieldMulBy3 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 3 with [FieldVal.MulBy3]. -func BenchmarkFieldMulBy3(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(fHex) +// BenchmarkElementMulBy3 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 3 with [Element.MulBy3]. +func BenchmarkElementMulBy3(b *testing.B) { + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - f.MulBy3() + e.MulBy3() } } -// BenchmarkFieldMulBy4 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 4 with [FieldVal.MulBy4]. -func BenchmarkFieldMulBy4(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(fHex) +// BenchmarkElementMulBy4 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 4 with [Element.MulBy4]. +func BenchmarkElementMulBy4(b *testing.B) { + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - f.MulBy4() + e.MulBy4() } } -// BenchmarkFieldMulBy8 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 8 with [FieldVal.MulBy8]. -func BenchmarkFieldMulBy8(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(fHex) +// BenchmarkElementMulBy8 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 8 with [Element.MulBy8]. +func BenchmarkElementMulBy8(b *testing.B) { + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - f.MulBy8() + e.MulBy8() } } -// BenchmarkFieldMulInt benchmarks multiplying an unsigned 256-bit big-endian -// integer by small integers with [FieldVal.MulInt]. -func BenchmarkFieldMulInt(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(fHex) +// BenchmarkElementMulInt benchmarks multiplying an unsigned 256-bit big-endian +// integer by small integers with [Element.MulInt]. +func BenchmarkElementMulInt(b *testing.B) { + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - f.MulInt(2) + e.MulInt(2) } } @@ -169,19 +169,19 @@ func BenchmarkBigIntMulModP(b *testing.B) { } } -// BenchmarkFieldMul benchmarks multiplying two unsigned 256-bit big-endian -// integers modulo the field prime with [FieldVal]. -func BenchmarkFieldMul(b *testing.B) { +// BenchmarkElementMul benchmarks multiplying two unsigned 256-bit big-endian +// integers modulo the field prime with [Element]. +func BenchmarkElementMul(b *testing.B) { // The function is constant time so any values are fine. f1Hex := "d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab" f2Hex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f1 := mustFieldVal(f1Hex) - f2 := mustFieldVal(f2Hex) + f1 := mustElement(f1Hex) + f2 := mustElement(f2Hex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - var prod FieldVal + var prod Element prod.Mul2(f1, f2) } } @@ -199,18 +199,18 @@ func BenchmarkBigIntSqrtModP(b *testing.B) { } } -// BenchmarkFieldSqrt benchmarks calculating the square root of an unsigned -// 256-bit big-endian integer modulo the field prime with [FieldVal]. -func BenchmarkFieldSqrt(b *testing.B) { +// BenchmarkElementSqrt benchmarks calculating the square root of an unsigned +// 256-bit big-endian integer modulo the field prime with [Element]. +func BenchmarkElementSqrt(b *testing.B) { // The function is constant time so any value is fine. valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(valHex) + e := mustElement(valHex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - var result FieldVal - _ = result.SquareRootVal(f) + var result Element + _ = result.SquareRootVal(e) } } @@ -228,18 +228,18 @@ func BenchmarkBigIntSquareModP(b *testing.B) { } } -// BenchmarkFieldSquare benchmarks squaring a 256-bit big-endian integer modulo -// the field prime with [FieldVal]. -func BenchmarkFieldSquare(b *testing.B) { +// BenchmarkElementSquare benchmarks squaring a 256-bit big-endian integer +// modulo the field prime with [Element]. +func BenchmarkElementSquare(b *testing.B) { // The function is constant time so any values are fine. - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(fHex) + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - var sq FieldVal - sq.SquareVal(f) + var sq Element + sq.SquareVal(e) } } @@ -257,17 +257,17 @@ func BenchmarkBigIntInverseModP(b *testing.B) { } } -// BenchmarkFieldInverse calculating the multiplicative inverse of an unsigned -// 256-bit big-endian integer modulo the field prime with [FieldVal]. -func BenchmarkFieldInverse(b *testing.B) { +// BenchmarkElementInverse calculating the multiplicative inverse of an unsigned +// 256-bit big-endian integer modulo the field prime with [Element]. +func BenchmarkElementInverse(b *testing.B) { // The function is constant time so any value is fine. valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(valHex) + e := mustElement(valHex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - f.Inverse() + e.Inverse() } } @@ -290,17 +290,17 @@ func BenchmarkBigIntIsGtOrEqPrimeMinusOrder(b *testing.B) { } } -// BenchmarkFieldIsGtOrEqPrimeMinusOrder benchmarks determining whether a value +// BenchmarkElementIsGtOrEqPrimeMinusOrder benchmarks determining whether a value // is greater than or equal to the field prime minus the group order with the // specialized type. -func BenchmarkFieldIsGtOrEqPrimeMinusOrder(b *testing.B) { +func BenchmarkElementIsGtOrEqPrimeMinusOrder(b *testing.B) { // The function is constant time so any value is fine. valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal(valHex) + e := mustElement(valHex) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - _ = f.IsGtOrEqPrimeMinusOrder() + _ = e.IsGtOrEqPrimeMinusOrder() } } diff --git a/dcrec/secp256k1/field_test.go b/dcrec/secp256k1/field10x26/element_test.go similarity index 84% rename from dcrec/secp256k1/field_test.go rename to dcrec/secp256k1/field10x26/element_test.go index a1200b8dd8..dafd95ae5b 100644 --- a/dcrec/secp256k1/field_test.go +++ b/dcrec/secp256k1/field10x26/element_test.go @@ -4,7 +4,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package secp256k1 +package field10x26 import ( "bytes" @@ -16,37 +16,9 @@ import ( "time" ) -// mustFieldValWithOverflow converts the passed hex string into a [FieldVal] and -// will panic if there is an error. Values that overflow are NOT treated as an -// error. -// -// This is only provided for the hard-coded constants so errors in the source -// code can be detected. It will only (and must only) be called with hard-coded -// values. -func mustFieldValWithOverflow(s string) *FieldVal { - return mustFieldValInternal(s, true) -} - -// randFieldVal returns a field value created from a random value generated by -// the passed rng. -func randFieldVal(t *testing.T, rng *rand.Rand) *FieldVal { - t.Helper() - - var buf [32]byte - if _, err := rng.Read(buf[:]); err != nil { - t.Fatalf("failed to read random: %v", err) - } - - // Create and return a field value. - var fv FieldVal - fv.SetBytes(&buf) - fv.Normalize() - return &fv -} - -// randIntAndFieldVal returns a big integer and a field value both created from -// the same random value generated by the passed rng. -func randIntAndFieldVal(t *testing.T, rng *rand.Rand) (*big.Int, *FieldVal) { +// randIntAndElement returns a [big.Int] and [Element] both created from the +// same random value generated by the passed rng. +func randIntAndElement(t *testing.T, rng *rand.Rand) (*big.Int, *Element) { t.Helper() var buf [32]byte @@ -54,17 +26,17 @@ func randIntAndFieldVal(t *testing.T, rng *rand.Rand) (*big.Int, *FieldVal) { t.Fatalf("failed to read random: %v", err) } - // Create and return both a big integer and a field value. + // Create and return both a big integer and a field element. bigIntVal := new(big.Int).SetBytes(buf[:]) bigIntVal.Mod(bigIntVal, curveParams.P) - var fv FieldVal + var fv Element fv.SetBytes(&buf) return bigIntVal, &fv } -// TestFieldSetInt ensures that setting a field value to various native -// integers works as expected. -func TestFieldSetInt(t *testing.T) { +// TestElementSetInt ensures that setting an element to various native integers +// works as expected. +func TestElementSetInt(t *testing.T) { tests := []struct { name string // test description in uint16 // test value @@ -84,19 +56,19 @@ func TestFieldSetInt(t *testing.T) { }} for _, test := range tests { - f := new(FieldVal).SetInt(test.in) - if !reflect.DeepEqual(f.n, test.expected) { - t.Errorf("%s: wrong result\ngot: %v\nwant: %v", test.name, f.n, + e := new(Element).SetInt(test.in) + if !reflect.DeepEqual(e.n, test.expected) { + t.Errorf("%s: wrong result\ngot: %v\nwant: %v", test.name, e.n, test.expected) continue } } } -// TestFieldSetBytes ensures that setting a field value to a 256-bit big-endian +// TestElementSetBytes ensures that setting an element 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 TestFieldSetBytes(t *testing.T) { +func TestElementSetBytes(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -225,7 +197,7 @@ func TestFieldSetBytes(t *testing.T) { inBytes := hexToBytes(test.in) // Ensure setting the bytes via the slice method works as expected. - var f FieldVal + var f Element overflow := f.SetByteSlice(inBytes) if !reflect.DeepEqual(f.n, test.expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, f.n, @@ -242,7 +214,7 @@ func TestFieldSetBytes(t *testing.T) { } // Ensure setting the bytes via the array method works as expected. - var f2 FieldVal + var f2 Element var b32 [32]byte truncatedInBytes := inBytes if len(truncatedInBytes) > 32 { @@ -266,10 +238,10 @@ func TestFieldSetBytes(t *testing.T) { } } -// TestFieldBytes ensures that retrieving the bytes for a 256-bit big-endian +// TestElementBytes 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 TestFieldBytes(t *testing.T) { +func TestElementBytes(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -333,11 +305,11 @@ func TestFieldBytes(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in).Normalize() + e := mustElementWithOverflow(test.in).Normalize() expected := hexToBytes(test.expected) // Ensure getting the bytes works as expected. - gotBytes := f.Bytes() + gotBytes := e.Bytes() if !bytes.Equal(gotBytes[:], expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, *gotBytes, expected) @@ -346,7 +318,7 @@ func TestFieldBytes(t *testing.T) { // Ensure getting the bytes directly into an array works as expected. var b32 [32]byte - f.PutBytes(&b32) + e.PutBytes(&b32) if !bytes.Equal(b32[:], expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, b32, expected) @@ -355,7 +327,7 @@ func TestFieldBytes(t *testing.T) { // Ensure getting the bytes directly into a slice works as expected. var buffer [64]byte - f.PutBytesUnchecked(buffer[:]) + e.PutBytesUnchecked(buffer[:]) if !bytes.Equal(buffer[:32], expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, buffer[:32], expected) @@ -364,11 +336,12 @@ func TestFieldBytes(t *testing.T) { } } -// TestFieldZero ensures that zeroing a field value works as expected. -func TestFieldZero(t *testing.T) { - f := mustFieldVal("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") - f.Zero() - for idx, rawInt := range f.n { +// TestElementZero ensures that zeroing an element via [Element.Zero] works as +// expected. +func TestElementZero(t *testing.T) { + e := mustElement("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") + e.Zero() + for idx, rawInt := range e.n { if rawInt != 0 { t.Errorf("internal integer at index #%d is not zero - got %d", idx, rawInt) @@ -376,46 +349,46 @@ func TestFieldZero(t *testing.T) { } } -// TestFieldIsZero ensures that checking if a field is zero via -// [FieldVal.IsZero] and [FieldVal.IsZeroBit] works as expected. -func TestFieldIsZero(t *testing.T) { - f := new(FieldVal) - if !f.IsZero() { - t.Errorf("new field value is not zero - got %v (rawints %x)", f, f.n) +// TestElementIsZero ensures that checking if an element is zero via +// [Element.IsZero] and [Element.IsZeroBit] works as expected. +func TestElementIsZero(t *testing.T) { + e := new(Element) + if !e.IsZero() { + t.Errorf("new element is not zero - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() != 1 { - t.Errorf("new field value is not zero - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() != 1 { + t.Errorf("new element is not zero - got %v (rawints %x)", e, e.n) } - f.SetInt(1) - if f.IsZero() { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + e.SetInt(1) + if e.IsZero() { + t.Errorf("claims zero for nonzero element- got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() == 1 { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() == 1 { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } - f.Zero() - if !f.IsZero() { - t.Errorf("claims nonzero for zero field - got %v (rawints %x)", f, f.n) + e.Zero() + if !e.IsZero() { + t.Errorf("claims nonzero for zero element - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() != 1 { - t.Errorf("claims nonzero for zero field - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() != 1 { + t.Errorf("claims nonzero for zero element - got %v (rawints %x)", e, e.n) } - f.SetInt(1) - f.Zero() - if !f.IsZero() { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + e.SetInt(1) + e.Zero() + if !e.IsZero() { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() != 1 { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() != 1 { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } } -// TestFieldIsOne ensures that checking if a field is one via [FieldVal.IsOne] -// and [FieldVal.IsOneBit] works as expected. -func TestFieldIsOne(t *testing.T) { +// TestElementIsOne ensures that checking if an element is one via +// [Element.IsOne] and [Element.IsOneBit] works as expected. +func TestElementIsOne(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -452,65 +425,65 @@ func TestFieldIsOne(t *testing.T) { normalize: false, expected: false, }, { - name: "2^26 (one bit in second internal field word", + name: "2^26 (one bit in second internal limb)", in: "4000000", normalize: false, expected: false, }, { - name: "2^52 (one bit in third internal field word", + name: "2^52 (one bit in third internal limb)", in: "10000000000000", normalize: false, expected: false, }, { - name: "2^78 (one bit in fourth internal field word", + name: "2^78 (one bit in fourth internal limb)", in: "40000000000000000000", normalize: false, expected: false, }, { - name: "2^104 (one bit in fifth internal field word", + name: "2^104 (one bit in fifth internal limb)", in: "100000000000000000000000000", normalize: false, expected: false, }, { - name: "2^130 (one bit in sixth internal field word", + name: "2^130 (one bit in sixth internal limb)", in: "400000000000000000000000000000000", normalize: false, expected: false, }, { - name: "2^156 (one bit in seventh internal field word", + name: "2^156 (one bit in seventh internal limb)", in: "1000000000000000000000000000000000000000", normalize: false, expected: false, }, { - name: "2^182 (one bit in eighth internal field word", + name: "2^182 (one bit in eighth internal limb)", in: "4000000000000000000000000000000000000000000000", normalize: false, expected: false, }, { - name: "2^208 (one bit in ninth internal field word", + name: "2^208 (one bit in ninth internal limb)", in: "10000000000000000000000000000000000000000000000000000", normalize: false, expected: false, }, { - name: "2^234 (one bit in tenth internal field word", + name: "2^234 (one bit in tenth internal limb)", in: "40000000000000000000000000000000000000000000000000000000000", normalize: false, expected: false, }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in) + e := mustElementWithOverflow(test.in) if test.normalize { - f.Normalize() + e.Normalize() } - result := f.IsOne() + result := e.IsOne() if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) continue } - result2 := f.IsOneBit() == 1 + result2 := e.IsOneBit() == 1 if result2 != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result2, test.expected) @@ -519,8 +492,8 @@ func TestFieldIsOne(t *testing.T) { } } -// TestFieldStringer ensures the stringer returns the appropriate hex string. -func TestFieldStringer(t *testing.T) { +// TestElementStringer ensures the stringer returns the appropriate hex string. +func TestElementStringer(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -604,8 +577,8 @@ func TestFieldStringer(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in) - result := f.String() + e := mustElementWithOverflow(test.in) + result := e.String() if result != test.expected { t.Errorf("%s: wrong result\ngot: %v\nwant: %v", test.name, result, test.expected) @@ -614,9 +587,9 @@ func TestFieldStringer(t *testing.T) { } } -// TestFieldNormalize ensures that normalizing the internal field words works as +// TestElementNormalize ensures that normalizing the internal limbs works as // expected. -func TestFieldNormalize(t *testing.T) { +func TestElementNormalize(t *testing.T) { tests := []struct { name string // test description raw [10]uint32 // Intentionally denormalized value @@ -694,8 +667,8 @@ func TestFieldNormalize(t *testing.T) { raw: [10]uint32{0xffffffff, 0xffffffc0, 0xffffffc0, 0xffffffc0, 0xffffffc0, 0xffffffc0, 0xffffffc0, 0xffffffc0, 0xffffffc0, 0x3fffc0}, normalized: [10]uint32{0x000003d0, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000}, }, { - // Prime with field representation such that the initial reduction does - // not result in a carry to bit 256. + // Prime with internal representation such that the initial reduction + // does not result in a carry to bit 256. // // 2^256 - 4294968273 (secp256k1 prime) name: "2^256 - 4294968273 (secp256k1 prime)", @@ -724,7 +697,7 @@ func TestFieldNormalize(t *testing.T) { // than P when it has a magnitude of 1 due to a carry to bit 256, but // would not be without the carry. These values come from the fact that // P is 2^256 - 4294968273 and 977 is the low order word in the internal - // field representation. + // representation. // // 2^256 * 5 - ((4294968273 - (977+1)) * 4) name: "2^256 * 5 - ((4294968273 - (977+1)) * 4)", @@ -821,20 +794,20 @@ func TestFieldNormalize(t *testing.T) { }} for _, test := range tests { - f := new(FieldVal) - f.n = test.raw - f.Normalize() - if !reflect.DeepEqual(f.n, test.normalized) { + e := new(Element) + e.n = test.raw + e.Normalize() + if !reflect.DeepEqual(e.n, test.normalized) { t.Errorf("%s: wrong normalized result\ngot: %x\nwant: %x", - test.name, f.n, test.normalized) + test.name, e.n, test.normalized) continue } } } -// TestFieldIsOdd ensures that checking if a field value is odd via -// [FieldVal.IsOdd] and [FieldVal.IsOddBit] works as expected. -func TestFieldIsOdd(t *testing.T) { +// TestElementIsOdd ensures that checking if an element is odd via +// [Element.IsOdd] and [Element.IsOddBit] works as expected. +func TestElementIsOdd(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -870,15 +843,15 @@ func TestFieldIsOdd(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in) - result := f.IsOdd() + e := mustElementWithOverflow(test.in) + result := e.IsOdd() if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) continue } - result2 := f.IsOddBit() == 1 + result2 := e.IsOddBit() == 1 if result2 != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result2, test.expected) @@ -887,9 +860,9 @@ func TestFieldIsOdd(t *testing.T) { } } -// TestFieldEquals ensures that checking two field values for equality via -// [FieldVal.Equals] works as expected. -func TestFieldEquals(t *testing.T) { +// TestElementEquals ensures that checking two elements for equality via +// [Element.Equals] works as expected. +func TestElementEquals(t *testing.T) { tests := []struct { name string // test description in1 string // hex encoded value @@ -933,9 +906,9 @@ func TestFieldEquals(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in1).Normalize() - f2 := mustFieldValWithOverflow(test.in2).Normalize() - result := f.Equals(f2) + e := mustElementWithOverflow(test.in1).Normalize() + e2 := mustElementWithOverflow(test.in2).Normalize() + result := e.Equals(e2) if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) @@ -944,9 +917,9 @@ func TestFieldEquals(t *testing.T) { } } -// TestFieldNegate ensures that negating field values via [FieldVal.Negate] -// works as expected. -func TestFieldNegate(t *testing.T) { +// TestElementNegate ensures that negating elements via [Element.Negate] works +// as expected. +func TestElementNegate(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -994,11 +967,11 @@ func TestFieldNegate(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in).Normalize() - expected := mustFieldVal(test.expected).Normalize() + e := mustElementWithOverflow(test.in).Normalize() + expected := mustElement(test.expected).Normalize() - // Ensure negating another value produces the expected result. - result := new(FieldVal).NegateVal(f, 1).Normalize() + // Ensure negating another element produces the expected result. + result := new(Element).NegateVal(e, 1).Normalize() if !result.Equals(expected) { t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name, result, expected) @@ -1006,7 +979,7 @@ func TestFieldNegate(t *testing.T) { } // Ensure self negating also produces the expected result. - result2 := f.Negate(1).Normalize() + result2 := e.Negate(1).Normalize() if !result2.Equals(expected) { t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name, result2, expected) @@ -1015,9 +988,9 @@ func TestFieldNegate(t *testing.T) { } } -// TestFieldAddInt ensures that adding an integer to field values via -// [FieldVal.AddInt] works as expected. -func TestFieldAddInt(t *testing.T) { +// TestElementAddInt ensures that adding an integer to elements via +// [Element.AddInt] works as expected. +func TestElementAddInt(t *testing.T) { tests := []struct { name string // test description in1 string // hex encoded value @@ -1071,9 +1044,9 @@ func TestFieldAddInt(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in1).Normalize() - expected := mustFieldVal(test.expected).Normalize() - result := f.AddInt(test.in2).Normalize() + e := mustElementWithOverflow(test.in1).Normalize() + expected := mustElement(test.expected).Normalize() + result := e.AddInt(test.in2).Normalize() if !result.Equals(expected) { t.Errorf("%s: wrong result -- got: %v -- want: %v", test.name, result, expected) @@ -1082,9 +1055,11 @@ func TestFieldAddInt(t *testing.T) { } } -// TestFieldAdd ensures that adding two field values together via [FieldVal.Add] -// and [FieldVal.Add2] works as expected. -func TestFieldAdd(t *testing.T) { +// TestElementAdd ensures that adding two elements together via [Element.Add] +// and [Element.Add2] works as expected. +// +// nolint: dupl +func TestElementAdd(t *testing.T) { tests := []struct { name string // test description in1 string // first hex encoded value @@ -1154,21 +1129,21 @@ func TestFieldAdd(t *testing.T) { for _, test := range tests { // Parse test hex. - f1 := mustFieldValWithOverflow(test.in1).Normalize() - f2 := mustFieldValWithOverflow(test.in2).Normalize() - expected := mustFieldVal(test.expected).Normalize() + f1 := mustElementWithOverflow(test.in1).Normalize() + f2 := mustElementWithOverflow(test.in2).Normalize() + expected := mustElement(test.expected).Normalize() // Ensure adding the two values with the result going to another // variable produces the expected result. - result := new(FieldVal).Add2(f1, f2).Normalize() + result := new(Element).Add2(f1, f2).Normalize() if !result.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %v\nwant: %v", test.name, result, expected) continue } - // Ensure adding the value to an existing field value produces the - // expected result. + // Ensure adding the value to an existing element produces the expected + // result. f1.Add(f2).Normalize() if !f1.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %v\nwant: %v", test.name, @@ -1178,18 +1153,18 @@ func TestFieldAdd(t *testing.T) { } } -// TestFieldMulByX ensures [FieldVal.MulBy2], [FieldVal.MulBy3], -// [FieldVal.MulBy4], and [FieldVal.MulBy8] produce the same results as the -// equivalent [FieldVal.MulInt]. -func TestFieldMulByX(t *testing.T) { +// TestElementMulByX ensures [Element.MulBy2], [Element.MulBy3], +// [Element.MulBy4], and [Element.MulBy8] produce the same results as the +// equivalent [Element.MulInt]. +func TestElementMulByX(t *testing.T) { mulByFuncs := []struct { factor uint8 - fn func(*FieldVal) *FieldVal + fn func(*Element) *Element }{ - {2, (*FieldVal).MulBy2}, - {3, (*FieldVal).MulBy3}, - {4, (*FieldVal).MulBy4}, - {8, (*FieldVal).MulBy8}, + {2, (*Element).MulBy2}, + {3, (*Element).MulBy3}, + {4, (*Element).MulBy4}, + {8, (*Element).MulBy8}, } tests := []struct { @@ -1233,10 +1208,10 @@ func TestFieldMulByX(t *testing.T) { in: "e3cbe002cc93029190181a906ed41af401c9726546dc19389a06290efdf563f1", }} for _, test := range tests { - in := mustFieldVal(test.in) + in := mustElement(test.in) for _, m := range mulByFuncs { - want := new(FieldVal).Set(in).MulInt(m.factor) - got := m.fn(new(FieldVal).Set(in)) + want := new(Element).Set(in).MulInt(m.factor) + got := m.fn(new(Element).Set(in)) if !got.Equals(want) { t.Errorf("%q: MulBy%d: wrong result -- got: %v, want: %v", test.name, m.factor, got, want) @@ -1245,9 +1220,9 @@ func TestFieldMulByX(t *testing.T) { } } -// TestFieldMulInt ensures that multiplying an integer to field values via -// [FieldVal.MulInt] works as expected. -func TestFieldMulInt(t *testing.T) { +// TestElementMulInt ensures that multiplying an integer to elements via +// [Element.MulInt] works as expected. +func TestElementMulInt(t *testing.T) { tests := []struct { name string // test description in1 string // hex encoded value @@ -1307,16 +1282,16 @@ func TestFieldMulInt(t *testing.T) { in2: 5, expected: "6cf4eb20f2447c77657fccb172d38c0aa91ea4ac446dc641fa463a6b5091fba7", }, { - name: "random sampling #3", + name: "random sampling #4", in1: "fb4529be3e027a3d1587d8a500b72f2d312e3577340ef5175f96d113be4c2ceb", in2: 8, expected: "da294df1f013d1e8ac3ec52805b979698971abb9a077a8bafcb688a4f261820f", }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in1).Normalize() - expected := mustFieldVal(test.expected).Normalize() - result := f.MulInt(test.in2).Normalize() + e := mustElementWithOverflow(test.in1).Normalize() + expected := mustElement(test.expected).Normalize() + result := e.MulInt(test.in2).Normalize() if !result.Equals(expected) { t.Errorf("%s: wrong result -- got: %v -- want: %v", test.name, result, expected) @@ -1325,9 +1300,11 @@ func TestFieldMulInt(t *testing.T) { } } -// TestFieldMul ensures that multiplying two field values via [FieldVal.Mul] and -// [FieldVal.Mul2] works as expected. -func TestFieldMul(t *testing.T) { +// TestElementMul ensures that multiplying two elements via [Element.Mul] and +// [Element.Mul2] works as expected. +// +// nolint: dupl +func TestElementMul(t *testing.T) { tests := []struct { name string // test description in1 string // first hex encoded value @@ -1369,7 +1346,7 @@ func TestFieldMul(t *testing.T) { in2: "3", expected: "0", }, { - name: "secp256k1 prime * 3", + name: "secp256k1 prime * 8", in1: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", in2: "8", expected: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc27", @@ -1396,20 +1373,20 @@ func TestFieldMul(t *testing.T) { }} for _, test := range tests { - f1 := mustFieldValWithOverflow(test.in1).Normalize() - f2 := mustFieldValWithOverflow(test.in2).Normalize() - expected := mustFieldVal(test.expected).Normalize() + f1 := mustElementWithOverflow(test.in1).Normalize() + f2 := mustElementWithOverflow(test.in2).Normalize() + expected := mustElement(test.expected).Normalize() // Ensure multiplying the two values with the result going to another // variable produces the expected result. - result := new(FieldVal).Mul2(f1, f2).Normalize() + result := new(Element).Mul2(f1, f2).Normalize() if !result.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %v\nwant: %v", test.name, result, expected) continue } - // Ensure multiplying the value to an existing field value produces the + // Ensure multiplying the value to an existing element produces the // expected result. f1.Mul(f2).Normalize() if !f1.Equals(expected) { @@ -1420,9 +1397,9 @@ func TestFieldMul(t *testing.T) { } } -// TestFieldSquare ensures that squaring field values via [FieldVal.Square] and -// [FieldVal.SqualVal] works as expected. -func TestFieldSquare(t *testing.T) { +// TestElementSquare ensures that squaring elements via [Element.Square] and +// [Element.SqualVal] works as expected. +func TestElementSquare(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -1462,32 +1439,32 @@ func TestFieldSquare(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in).Normalize() - expected := mustFieldVal(test.expected).Normalize() + e := mustElementWithOverflow(test.in).Normalize() + expected := mustElement(test.expected).Normalize() // Ensure squaring the value with the result going to another variable // produces the expected result. - result := new(FieldVal).SquareVal(f).Normalize() + result := new(Element).SquareVal(e).Normalize() if !result.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %v\nwant: %v", test.name, result, expected) continue } - // Ensure self squaring an existing field value produces the expected + // Ensure self squaring an existing element produces the expected // result. - f.Square().Normalize() - if !f.Equals(expected) { + e.Square().Normalize() + if !e.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %v\nwant: %v", test.name, - f, expected) + e, expected) continue } } } -// TestFieldSquareRoot ensures that calculating the square root of field values -// via [FieldVal.SquareRootVal] works as expected for edge cases. -func TestFieldSquareRoot(t *testing.T) { +// TestElementSquareRoot ensures that calculating the square root of elements +// via [Element.SquareRootVal] works as expected for edge cases. +func TestElementSquareRoot(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -1556,12 +1533,12 @@ func TestFieldSquareRoot(t *testing.T) { }} for _, test := range tests { - input := mustFieldValWithOverflow(test.in).Normalize() - want := mustFieldVal(test.want).Normalize() + input := mustElementWithOverflow(test.in).Normalize() + want := mustElement(test.want).Normalize() // Calculate the square root and enusre the validity flag matches the // expected value. - var result FieldVal + var result Element isValid := result.SquareRootVal(input) if isValid != test.valid { t.Errorf("%s: mismatched validity -- got %v, want %v", test.name, @@ -1579,10 +1556,10 @@ func TestFieldSquareRoot(t *testing.T) { } } -// TestFieldSquareRootRandom 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 TestFieldSquareRootRandom(t *testing.T) { +// TestElementSquareRootRandom ensures that calculating the square root for +// random elements works as expected by also performing the same operation with +// big ints and comparing the results. +func TestElementSquareRootRandom(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)) @@ -1593,39 +1570,39 @@ func TestFieldSquareRootRandom(t *testing.T) { }(t, seed) for i := 0; i < 100; i++ { - // Generate big integer and field value with the same random value. - bigIntVal, fVal := randIntAndFieldVal(t, rng) + // Generate big integer and element with the same random value. + bigIntVal, elem := randIntAndElement(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 FieldVal - fValHasSqrt := fValResult.SquareRootVal(fVal) + // Calculate the square root of the value using an element. + var elemResult Element + elemHasSqrt := elemResult.SquareRootVal(elem) // Ensure they match. - if bigIntHasSqrt != fValHasSqrt { - t.Fatalf("mismatched square root existence\nbig int in: %x\nfield "+ - "in: %v\nbig int result: %v\nfield result %v", bigIntVal, fVal, - bigIntHasSqrt, fValHasSqrt) + if bigIntHasSqrt != elemHasSqrt { + t.Fatalf("mismatched square root existence\nbig int in: %x\nelem "+ + "in: %v\nbig int result: %v\nelem result %v", bigIntVal, elem, + bigIntHasSqrt, elemHasSqrt) } - if !fValHasSqrt { + if !elemHasSqrt { continue } bigIntResultHex := fmt.Sprintf("%064x", bigIntResult) - fieldValResultHex := fmt.Sprintf("%v", fValResult) - if bigIntResultHex != fieldValResultHex { - t.Fatalf("mismatched square root\nbig int in: %x\nfield in: %v\n"+ - "big int result: %x\nfield result %v", bigIntVal, fVal, - bigIntResult, fValResult) + elemResultHex := fmt.Sprintf("%v", elemResult) + if bigIntResultHex != elemResultHex { + t.Fatalf("mismatched square root\nbig int in: %x\nelem in: %v\n"+ + "big int result: %x\nelem result %v", bigIntVal, elem, + bigIntResult, elemResult) } } } -// TestFieldInverse ensures that finding the multiplicative inverse via -// [FieldVal.Inverse] works as expected. -func TestFieldInverse(t *testing.T) { +// TestElementInverse ensures that finding the multiplicative inverse via +// [Element.Inverse] works as expected. +func TestElementInverse(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -1665,9 +1642,9 @@ func TestFieldInverse(t *testing.T) { }} for _, test := range tests { - f := mustFieldValWithOverflow(test.in).Normalize() - expected := mustFieldVal(test.expected).Normalize() - result := f.Inverse().Normalize() + e := mustElementWithOverflow(test.in).Normalize() + expected := mustElement(test.expected).Normalize() + result := e.Inverse().Normalize() if !result.Equals(expected) { t.Errorf("%s: d wrong result\ngot: %v\nwant: %v", test.name, result, expected) @@ -1676,10 +1653,10 @@ func TestFieldInverse(t *testing.T) { } } -// TestFieldIsGtOrEqPrimeMinusOrder ensures that field values report whether or +// TestElementIsGtOrEqPrimeMinusOrder ensures that elements report whether or // not they are greater than or equal to the field prime minus the group order // as expected for edge cases. -func TestFieldIsGtOrEqPrimeMinusOrder(t *testing.T) { +func TestElementIsGtOrEqPrimeMinusOrder(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -1743,7 +1720,7 @@ func TestFieldIsGtOrEqPrimeMinusOrder(t *testing.T) { }} for _, test := range tests { - result := mustFieldVal(test.in).IsGtOrEqPrimeMinusOrder() + result := mustElement(test.in).IsGtOrEqPrimeMinusOrder() if result != test.expected { t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name, result, test.expected) @@ -1752,11 +1729,11 @@ func TestFieldIsGtOrEqPrimeMinusOrder(t *testing.T) { } } -// TestFieldIsGtOrEqPrimeMinusOrderRandom 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 TestFieldIsGtOrEqPrimeMinusOrderRandom(t *testing.T) { +// TestElementIsGtOrEqPrimeMinusOrderRandom ensures that elements 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 TestElementIsGtOrEqPrimeMinusOrderRandom(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)) @@ -1768,22 +1745,22 @@ func TestFieldIsGtOrEqPrimeMinusOrderRandom(t *testing.T) { 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 := randIntAndFieldVal(t, rng) + // Generate big integer and element with the same random value. + bigIntVal, elem := randIntAndElement(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() + // order using an element. + fValResult := elem.IsGtOrEqPrimeMinusOrder() // Ensure they match. if bigIntResult != fValResult { t.Fatalf("mismatched is gt or eq prime minus order\nbig int in: "+ - "%x\nscalar in: %v\nbig int result: %v\nscalar result %v", - bigIntVal, fVal, bigIntResult, fValResult) + "%x\nelement in: %v\nbig int result: %v\nelement result %v", + bigIntVal, elem, bigIntResult, fValResult) } } } diff --git a/dcrec/secp256k1/field4x64/README.md b/dcrec/secp256k1/field4x64/README.md new file mode 100644 index 0000000000..917788a7a7 --- /dev/null +++ b/dcrec/secp256k1/field4x64/README.md @@ -0,0 +1,66 @@ +field4x64 +========= + +[![Build Status](https://github.com/decred/dcrd/workflows/Build%20and%20Test/badge.svg)](https://github.com/decred/dcrd/actions) +[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4/field4x64) + +Package field4x64 provides highly optimized pure-Go arithmetic over the +secp256k1 finite field using a 4x64 representation. + +It is designed for correctness, performance, security, and high assurance +through specialized arithmetic, constant time engineering, formal verification +of critical arithmetic routines, differential testing, and multiple +complementary testing techniques. + +This package exposes the underlying implementation directly and is primarily +intended for specialized use cases. Most consumers should prefer the +`secp256k1` package, which provides the stable public `FieldVal` type and +automatically selects the appropriate backend implementation for the target +architecture. + +## Design + +The implementation represents field elements with four 64-bit limbs using a +canonical 256-bit representation in the range [0, p-1]. Arithmetic is +specialized for the secp256k1 prime and uses optimized pure-Go implementations +together with architecture-specific assembly where available. + +Unlike the 10x26 backend, this representation fully reduces every arithmetic +operation. Consequently, field elements are always maintained in canonical form +and callers are not required to manually track normalization or magnitude. + +The semantics simplify both the API and implementation reasoning while remaining +highly efficient on modern 64-bit processors. + +## Assurance + +This package emphasizes correctness and implementation assurance through +multiple complementary validation techniques. + +Key implementation characteristics include: + +- Highly optimized arithmetic specialized specifically for the secp256k1 field +- Constant time implementations suitable for secret-dependent operations +- Formal verification of critical arithmetic operations using the Z3 theorem + prover +- Differential testing against independent secp256k1 implementations +- Deterministic test vectors +- Property-based testing +- Randomized-input testing +- Coverage-guided fuzz testing +- Manual review of security-critical implementation details + +## Disabling Assembler Optimizations + +The `purego` build tag may be used to disable all assembly code. + +## Installation and Updating + +This package is part of the `github.com/decred/dcrd/dcrec/secp256k1/v4` module. +Use the standard go tooling for working with modules to incorporate it. + +## License + +Package field4x64 is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/dcrec/secp256k1/field4x64/common_test.go b/dcrec/secp256k1/field4x64/common_test.go new file mode 100644 index 0000000000..f279d619d8 --- /dev/null +++ b/dcrec/secp256k1/field4x64/common_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2026 The Decred developers +// Copyright (c) 2013-2026 Dave Collins +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package field4x64 + +// References: +// [SECG]: Recommended Elliptic Curve Domain Parameters +// https://www.secg.org/sec2-v2.pdf +// + +import ( + "encoding/hex" + "math/big" +) + +// Curve parameters taken from [SECG] section 2.4.1. +var curveParams = struct { + P *big.Int + N *big.Int +}{ + P: fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"), + N: fromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), +} + +// hexToBytes converts the passed hex string into bytes and will panic if there +// is an error. This is only provided for the hard-coded constants so errors in +// the source code can be detected. It will only (and must only) be called with +// hard-coded values. +func hexToBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic("invalid hex in source file: " + s) + } + return b +} + +// fromHex converts the passed hex string into a big integer pointer and will +// panic is there is an error. This is only provided for the hard-coded +// constants so errors in the source code can bet detected. It will only (and +// must only) be called for initialization purposes. +func fromHex(s string) *big.Int { + if s == "" { + return big.NewInt(0) + } + r, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("invalid hex in source file: " + s) + } + return r +} diff --git a/dcrec/secp256k1/field4x64/element.go b/dcrec/secp256k1/field4x64/element.go new file mode 100644 index 0000000000..c42071e22b --- /dev/null +++ b/dcrec/secp256k1/field4x64/element.go @@ -0,0 +1,831 @@ +// 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 field4x64 implements highly optimized, constant-time arithmetic over +// the secp256k1 finite field using a dense 4x64 representation. +package field4x64 + +import ( + "encoding/binary" + "encoding/hex" + "math/bits" + + "github.com/decred/dcrd/dcrec/secp256k1/v4/internal/arith" +) + +// References: +// [HAC]: Handbook of Applied Cryptography Menezes, van Oorschot, Vanstone. +// https://cacr.uwaterloo.ca/hac/ + +// This file provides an alternate implementation of the secp256k1 finite field. +// It uses tight 256-bit packing with four little-endian uint64s and fully +// reduces after each operation. Hardware intrinsics are used when available. + +// Element implements optimized fixed-precision arithmetic over the secp256k1 +// finite field. This means all arithmetic is performed modulo +// +// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f. +// +// This fully reduces after each operation and therefore does not require +// normalization or manual magnitude tracking. It is also quite a bit faster +// than [field10x26.Element] on all modern 64-bit hardware. +type Element struct { + // Each 256-bit value is represented as 4 64-bit integers in base 2^64. + // It only implements the arithmetic needed for elliptic curve operations. + // + // The following depicts the internal representation: + // -------------------------------------------------------------------- + // | n[3] | n[2] | n[1] | n[0] | + // | 64 bits | 64 bits | 64 bits | 64 bits | + // | Mult: 2^(64*3) | Mult: 2^(64*2) | Mult: 2^(64*1) | Mult: 2^(64*0) | + // -------------------------------------------------------------------- + // + // For example, consider the number 2^87 + 1. It would be represented as: + // n[0] = 1 + // n[1] = 2^23 + // n[2] = n[3] = 0 + // + // The full 256-bit value is then calculated by looping i from 3..0 and + // performing sum(n[i] * 2^(64i)) as follows: + // n[3] * 2^(64*3) = 0 * 2^192 = 0 + // n[2] * 2^(64*2) = 0 * 2^128 = 0 + // n[1] * 2^(64*1) = 2^23 * 2^64 = 2^87 + // n[0] * 2^(64*0) = 1 * 2^0 = 1 + // Sum: 0 + 0 + 2^87 + 1 = 2^87 + 1 + n [4]uint64 +} + +// Constants related to the internal representation. +const ( + // fieldPrimeComplement is the two's complement of the secp256k1 prime. + fieldPrimeComplement = 0x1000003d1 // 2^32 + 977 + + // These fields provide convenient access to each of the limbs of the + // secp256k1 prime in the internal representation to improve code + // readability. + fieldPrimeLimb0 = 0xfffffffefffffc2f + fieldPrimeLimb1 = 0xffffffffffffffff + fieldPrimeLimb2 = 0xffffffffffffffff + fieldPrimeLimb3 = 0xffffffffffffffff +) + +// String returns the element as a human-readable hex string. +func (e Element) String() string { + return hex.EncodeToString(e.Bytes()[:]) +} + +// Zero sets the element to zero in constant time. A newly created element is +// already set to zero. This function can be useful to clear an existing +// element for reuse. +func (e *Element) Zero() { + e.n = [4]uint64{} +} + +// Set sets the element equal to the passed element in constant time. +// +// The element is returned to support chaining. This enables syntax like: +// e := new(Element).Set(e2).Add(1) so that e = e2 + 1 where e2 is not +// modified. +func (e *Element) Set(val *Element) *Element { + e.n = val.n + return e +} + +// SetInt sets the element to the passed integer in constant time. This is a +// convenience function since it is fairly common to perform arithmetic with +// small native integers. +// +// The element is returned to support chaining. This enables syntax such +// as e := new(Element).SetInt(2).Mul(e2) so that e = 2 * e2. +func (e *Element) SetInt(v uint16) *Element { + e.n = [4]uint64{uint64(v), 0, 0, 0} + return e +} + +// SetBytes packs the passed 32-byte big-endian value into the internal +// representation in constant time. It interprets the provided array as a +// 256-bit big-endian unsigned integer, packs it, 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 (e *Element) SetBytes(b *[32]byte) uint32 { + // Pack the 256 total bits across the 4 uint64 limbs. + e.n[0] = binary.BigEndian.Uint64(b[24:32]) + e.n[1] = binary.BigEndian.Uint64(b[16:24]) + e.n[2] = binary.BigEndian.Uint64(b[8:16]) + e.n[3] = binary.BigEndian.Uint64(b[0:8]) + + // Since e < 2^256 < 2p (where p is the secp256k1 prime), the max possible + // number of reductions required is one. Therefore, in the case a reduction + // is needed, it can be performed with a single subtraction of p. + // + // Since p must only conditionally be subtracted when e ≥ p, the following + // handles it in constant time by always calculating s = e - p and selecting + // the correct case via a constant time select. + + // Subtract p with borrow propagation. borrow is set iff e < p. + // + // In other words, the input overflowed (≥ p) when e - p does NOT borrow. + // + // s = e - p + var s0, s1, s2, s3, borrow uint64 + s0, borrow = bits.Sub64(e.n[0], fieldPrimeLimb0, 0) + s1, borrow = bits.Sub64(e.n[1], fieldPrimeLimb1, borrow) + s2, borrow = bits.Sub64(e.n[2], fieldPrimeLimb2, borrow) + s3, borrow = bits.Sub64(e.n[3], fieldPrimeLimb3, borrow) + + // Constant-time select. + // + // Set e = e when e < p (aka borrow is set). Otherwise e = s = e - p. + e.n[0] = arith.ConstantTimeSelect64(borrow, e.n[0], s0) + e.n[1] = arith.ConstantTimeSelect64(borrow, e.n[1], s1) + e.n[2] = arith.ConstantTimeSelect64(borrow, e.n[2], s2) + e.n[3] = arith.ConstantTimeSelect64(borrow, e.n[3], s3) + return uint32(1 - borrow) +} + +// zeroArray32 zeroes the provided 32-byte buffer. +func zeroArray32(b *[32]byte) { + *b = [32]byte{} +} + +// 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 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 (e *Element) SetByteSlice(b []byte) bool { + var b32 [32]byte + b = b[:arith.ConstantTimeMin(uint32(len(b)), 32)] + copy(b32[:], b32[:32-len(b)]) + copy(b32[32-len(b):], b) + result := e.SetBytes(&b32) + zeroArray32(&b32) + return result != 0 +} + +// Normalize is a no-op. It is provided to keep API parity with the other field +// element implementations. +func (e *Element) Normalize() *Element { + return e +} + +// PutBytesUnchecked unpacks the element 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, [Element.PutBytes], which unpacks the element +// 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 (e *Element) PutBytesUnchecked(b []byte) { + // Unpack the 256 total bits from the 4 uint64 limbs. + binary.BigEndian.PutUint64(b[0:8], e.n[3]) + binary.BigEndian.PutUint64(b[8:16], e.n[2]) + binary.BigEndian.PutUint64(b[16:24], e.n[1]) + binary.BigEndian.PutUint64(b[24:32], e.n[0]) +} + +// PutBytes unpacks the element to a 32-byte big-endian value using the passed +// byte array in constant time. +// +// There is a similar function, [Element.PutBytesUnchecked], which unpacks the +// element 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 [Element.Bytes], which unpacks the element into +// a new array and returns that which can sometimes be more ergonomic in +// applications that aren't concerned about an additional copy. +func (e *Element) PutBytes(b *[32]byte) { + e.PutBytesUnchecked(b[:]) +} + +// Bytes unpacks the element to a 32-byte big-endian value in constant time. +// +// See [Element.PutBytes] and [Element.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 (e *Element) Bytes() *[32]byte { + var b [32]byte + e.PutBytesUnchecked(b[:]) + return &b +} + +// IsZeroBit returns 1 when the element 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 [Element.IsZero] for the version +// that returns a bool. +func (e *Element) IsZeroBit() uint32 { + return arith.ConstantTimeEq64(e.n[0]|e.n[1]|e.n[2]|e.n[3], 0) +} + +// IsZero returns whether or not the element is equal to zero in constant time. +func (e *Element) IsZero() bool { + return (e.n[0] | e.n[1] | e.n[2] | e.n[3]) == 0 +} + +// IsOneBit returns 1 when the element 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 [Element.IsOne] for the version +// that returns a bool. +func (e *Element) IsOneBit() uint32 { + // The element can only be one if the single lowest significant bit is set + // in the first limb and no other bits are set in any of the other limbs. + // This is a constant time implementation. + return arith.ConstantTimeEq64((e.n[0]^1)|e.n[1]|e.n[2]|e.n[3], 0) +} + +// IsOne returns whether or not the element is equal to one in constant time. +func (e *Element) IsOne() bool { + // The element can only be one if the single lowest significant bit is set + // in the first limb and no other bits are set in any of the other limbs. + // This is a constant time implementation. + return ((e.n[0] ^ 1) | e.n[1] | e.n[2] | e.n[3]) == 0 +} + +// IsOddBit returns 1 when the element 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 [Element.IsOdd] for the version that +// returns a bool. +func (e *Element) IsOddBit() uint32 { + // Only odd numbers have the bottom bit set. + return uint32(e.n[0] & 1) +} + +// IsOdd returns whether or not the element is an odd number in constant time. +func (e *Element) IsOdd() bool { + // Only odd numbers have the bottom bit set. + return e.n[0]&1 == 1 +} + +// Equals returns whether or not the two elements are the same in constant time. +func (e *Element) Equals(val *Element) bool { + // Xor only sets bits when they are different, so the two elements can only + // be the same if no bits are set after xoring each limb. This is a + // constant time implementation. + return ((e.n[0] ^ val.n[0]) | (e.n[1] ^ val.n[1]) | (e.n[2] ^ val.n[2]) | + (e.n[3] ^ val.n[3])) == 0 +} + +// NegateVal negates the passed element and stores the result in e in constant +// time. The ignored parameter exists to keep API parity with the field element +// implementations. +// +// The element is returned to support chaining. This enables syntax like: +// e.NegateVal(e2).AddInt(1) so that e = -e2 + 1. +func (e *Element) NegateVal(val *Element, _ uint32) *Element { + // Since the element is already in the range 0 ≤ val < p, where p is the + // secp256k1 prime, negation modulo p is just p - val. This implies that + // the result will always be in the desired range with the sole exception of + // 0 because p - 0 = p itself. + // + // The following handles that case in constant time by creating a mask that + // is all 0s in the case the element being negated is 0 and all 1s otherwise + // and then bitwise ands that mask with each limb of the prime. + + // Subtract val from 0. borrow is set iff val != 0. + // + // t = 0 - val = -val + 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) + + // Mask the prime with the borrow (p when val != 0, else 0). + // + // The upper limbs of the prime are all 1s, so there is no need to mask them + // given they are equal to the mask for both cases. + mask := -borrow + maskedPrime0 := fieldPrimeLimb0 & mask + + // Add 0 when val == 0 or p when val != 0. The result is either: + // + // val == 0: e = 0 + 0 = 0 + // val != 0: e = -val + p = p - val + var carry uint64 + e.n[0], carry = bits.Add64(t0, maskedPrime0, 0) + e.n[1], carry = bits.Add64(t1, mask, carry) + e.n[2], carry = bits.Add64(t2, mask, carry) + e.n[3], _ = bits.Add64(t3, mask, carry) + return e +} + +// Negate negates the element in constant time. The existing element is +// modified. The ignored parameter exists to keep API parity with the field +// element implementations. +// +// The element is returned to support chaining. This enables syntax like: +// e.Negate().AddInt(1) so that e = -e + 1. +func (e *Element) Negate(_ uint32) *Element { + return e.NegateVal(e, 0) +} + +// AddInt adds the passed integer to the existing element and stores the result +// in e in constant time. This is a convenience function since it is fairly +// common to perform some arithmetic with small native integers. +// +// The element is returned to support chaining. This enables syntax like: +// e.AddInt(1).Add(e2) so that e = e + 1 + e2. +func (e *Element) AddInt(ui uint16) *Element { + return e.Add(new(Element).SetInt(ui)) +} + +// Add adds the passed element to the existing element and stores the result in +// e in constant time. +// +// The element is returned to support chaining. This enables syntax like: +// e.Add(e2).AddInt(1) so that e = e + e2 + 1. +func (e *Element) Add(val *Element) *Element { + return e.Add2(e, val) +} + +// Add2 adds the passed two elements together and stores the result in e in +// constant time. +// +// The element is returned to support chaining. This enables syntax like: +// e3.Add2(e, e2).AddInt(1) so that e3 = e + e2 + 1. +func (e *Element) Add2(a, b *Element) *Element { + // Since both elements are already in the range 0 ≤ val < p (where p is the + // secp256k1 prime), the maximum possible result is < 2p - 1. So a maximum + // of one subtraction of p is required in the worst case. + // + // Since p must only conditionally be subtracted when a+b ≥ p, the following + // handles it in constant time by calculating both t = a+b and s = a+b - p + // and selecting the correct case via a constant time select. + + // Add with carry propagation. overflow is set iff t = a+b ≥ 2^256. + // + // t = a + b + var t0, t1, t2, t3, overflow, carry uint64 + 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) + + // Subtract p with borrow propagation. borrow is set iff t = a+b < p. + // + // s = t - p = a+b - p + var s0, s1, s2, s3, borrow uint64 + s0, borrow = bits.Sub64(t0, fieldPrimeLimb0, 0) + s1, borrow = bits.Sub64(t1, fieldPrimeLimb1, borrow) + s2, borrow = bits.Sub64(t2, fieldPrimeLimb2, borrow) + s3, borrow = bits.Sub64(t3, fieldPrimeLimb3, borrow) + + // Constant-time select. + // + // Set e = t = a+b only when there was no overflow and t < p (borrow set). + // Otherwise e = s = a+b - p. + cond := (1 - overflow) & borrow + e.n[0] = arith.ConstantTimeSelect64(cond, t0, s0) + e.n[1] = arith.ConstantTimeSelect64(cond, t1, s1) + e.n[2] = arith.ConstantTimeSelect64(cond, t2, s2) + e.n[3] = arith.ConstantTimeSelect64(cond, t3, s3) + return e +} + +// MulBy2 multiplies the element by 2 and stores the result in e in constant +// time. +// +// This method is optimized to provide a significant speed advantage over the +// more general [Element.MulInt]. +// +// The element is returned to support chaining. This enables syntax like: +// e.MulBy2().Add(e2) so that e = 2 * e + e2. +func (e *Element) MulBy2() *Element { + return e.Add(e) +} + +// MulBy3 multiplies the element by 3 and stores the result in e in constant +// time. +// +// This method is optimized to provide a significant speed advantage over the +// more general [Element.MulInt]. +// +// The element is returned to support chaining. This enables syntax like: +// e.MulBy3().Add(e2) so that e = 3 * e + e2. +func (e *Element) MulBy3() *Element { + var orig Element + orig.Set(e) + return e.MulBy2().Add(&orig) +} + +// MulBy4 multiplies the element by 4 and stores the result in e in constant +// time. +// +// This method is optimized to provide a significant speed advantage over the +// more general [Element.MulInt]. +// +// The element is returned to support chaining. This enables syntax like: +// e.MulBy4().Add(e2) so that e = 4 * e + e2. +func (e *Element) MulBy4() *Element { + return e.MulBy2().MulBy2() +} + +// MulBy8 multiplies the element by 8 and stores the result in e in constant +// time. +// +// This method is optimized to provide a significant speed advantage over the +// more general [Element.MulInt]. +// +// The element is returned to support chaining. This enables syntax like: +// e.MulBy8().Add(e2) so that e = 8 * e + e2. +func (e *Element) MulBy8() *Element { + return e.MulBy4().MulBy2() +} + +// MulInt multiplies the element by the passed int and stores the result in e in +// constant time. +// +// Callers should prefer using the faster specialized methods for multiplying by +// 2, 3, 4, and 8, as they are commonly used in curve equations. +// +// See [Element.MulBy2], [Element.MulBy3], [Element.MulBy4], and +// [Element.MulBy8] for the aforementioned optimized methods. +// +// The element is returned to support chaining. This enables syntax like: +// e.MulInt(15).Add(e2) so that e = 15 * e + e2. +func (e *Element) MulInt(val uint8) *Element { + return e.Mul(new(Element).SetInt(uint16(val))) +} + +// Mul multiplies the passed element to the existing element and stores the +// result in e in constant time. +// +// The element is returned to support chaining. This enables syntax like: +// e.Mul(e2).AddInt(1) so that e = (e * e2) + 1. +func (e *Element) Mul(val *Element) *Element { + return e.Mul2(e, val) +} + +// Mul2 multiplies the passed two elements together and stores the result in e +// in constant time. +// +// The element is returned to support chaining. This enables syntax like: +// e3.Mul2(e, e2).AddInt(1) so that e3 = (e * e2) + 1. +func (e *Element) Mul2(a, b *Element) *Element { + mulReduce(&e.n, &a.n, &b.n) + return e +} + +// SquareRootVal either calculates the square root of the passed element when it +// exists or the square root of the negation of the element when it does not +// exist and stores the result in e in constant time. The return flag is true +// when the calculated square root is for the passed element itself and false +// when it is for its negation. +func (e *Element) SquareRootVal(val *Element) bool { + // This uses the Tonelli-Shanks method for calculating the square root of + // the element when it exists. The key principles of the method follow. + // + // Fermat's little theorem states that for a nonzero number 'a' and prime + // 'p', a^(p-1) ≡ 1 (mod p). + // + // Further, Euler's criterion states that an integer 'a' has a square root + // (aka is a quadratic residue) modulo a prime if a^((p-1)/2) ≡ 1 (mod p) + // and, conversely, when it does NOT have a square root (aka 'a' is a + // non-residue) a^((p-1)/2) ≡ -1 (mod p). + // + // This can be seen by considering that Fermat's little theorem can be + // written as (a^((p-1)/2) - 1)(a^((p-1)/2) + 1) ≡ 0 (mod p). Therefore, + // one of the two factors must be 0. Then, when a ≡ x^2 (aka 'a' is a + // quadratic residue), (x^2)^((p-1)/2) ≡ x^(p-1) ≡ 1 (mod p) which implies + // the first factor must be zero. Finally, per Lagrange's theorem, the + // non-residues are the only remaining possible solutions and thus must make + // the second factor zero to satisfy Fermat's little theorem implying that + // a^((p-1)/2) ≡ -1 (mod p) for that case. + // + // The Tonelli-Shanks method uses these facts along with factoring out + // powers of two to solve a congruence that results in either the solution + // when the square root exists or the square root of the negation of the + // element when it does not. In the case of primes that are ≡ 3 (mod 4), + // the possible solutions are r = ±a^((p+1)/4) (mod p). Therefore, either + // r^2 ≡ a (mod p) is true in which case ±r are the two solutions, or r^2 ≡ + // -a (mod p) in which case 'a' is a non-residue and there are no solutions. + // + // The secp256k1 prime is ≡ 3 (mod 4), so this result applies. + // + // In other words, calculate a^((p+1)/4) and then square it and check it + // against the original element to determine if it is actually the square + // root. + // + // In order to efficiently compute a^((p+1)/4), (p+1)/4 needs to be split + // into a sequence of squares and multiplications that minimizes the number + // of multiplications needed (since they are more costly than squarings). + // + // The secp256k1 prime + 1 / 4 is 2^254 - 2^30 - 244. In binary, that is: + // + // 00111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 10111111 11111111 11111111 00001100 + // + // Notice that can be broken up into three windows of consecutive 1s (in + // order of least to most significant) as: + // + // 6-bit window with two bits set (bits 4, 5, 6, 7 unset) + // 23-bit window with 22 bits set (bit 30 unset) + // 223-bit window with all 223 bits set + // + // Thus, the groups of 1 bits in each window forms the set: + // S = {2, 22, 223}. + // + // The strategy is to calculate a^(2^n - 1) for each grouping via an + // addition chain with a sliding window. + // + // The addition chain used is (credits to Peter Dettman): + // (0,0),(1,0),(2,2),(3,2),(4,1),(5,5),(6,6),(7,7),(8,8),(9,7),(10,2) + // => 2^1 2^[2] 2^3 2^6 2^9 2^11 2^[22] 2^44 2^88 2^176 2^220 2^[223] + // + // This has a cost of 254 field squarings and 13 field multiplications. + var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 Element + 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) + + e.SquareVal(&a223).Square().Square().Square().Square() // e = a^(2^228 - 2^5) + e.Square().Square().Square().Square().Square() // e = a^(2^233 - 2^10) + e.Square().Square().Square().Square().Square() // e = a^(2^238 - 2^15) + e.Square().Square().Square().Square().Square() // e = a^(2^243 - 2^20) + e.Square().Square().Square() // e = a^(2^246 - 2^23) + e.Mul(&a22) // e = a^(2^246 - 2^22 - 1) + e.Square().Square().Square().Square().Square() // e = a^(2^251 - 2^27 - 2^5) + e.Square() // e = a^(2^252 - 2^28 - 2^6) + e.Mul(&a2) // e = a^(2^252 - 2^28 - 2^6 - 2^1 - 1) + e.Square().Square() // e = 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 element. + var sqr Element + return sqr.SquareVal(e).Equals(val) +} + +// Square squares the element in constant time. The existing element is +// modified. +// +// The element is returned to support chaining. This enables syntax like: +// e.Square().Mul(e2) so that e = e^2 * e2. +func (e *Element) Square() *Element { + return e.SquareVal(e) +} + +// SquareVal squares the passed element and stores the result in e in constant +// time. +// +// The element is returned to support chaining. This enables syntax like: +// e3.SquareVal(e).Mul(e) so that e3 = e^2 * e = e^3. +func (e *Element) SquareVal(val *Element) *Element { + squareReduce(&e.n, &val.n) + return e +} + +// Inverse finds the modular multiplicative inverse of the element in constant +// time. The existing element is modified. +// +// The element is returned to support chaining. This enables syntax like: +// e.Inverse().Mul(e2) so that e = e^-1 * e2. +func (e *Element) Inverse() *Element { + // Fermat's little theorem states that for a nonzero number 'a' and prime + // 'p', a^(p-1) ≡ 1 (mod p). Multiplying both sides of the equation by the + // multiplicative inverse a^-1 yields a^(p-2) ≡ a^-1 (mod p). Thus, a^(p-2) + // is the multiplicative inverse. + // + // In order to efficiently compute a^(p-2), p-2 needs to be split into a + // sequence of squares and multiplications that minimizes the number of + // multiplications needed (since they are more costly than squarings). + // Intermediate results are saved and reused as well. + // + // The secp256k1 prime - 2 is 2^256 - 4294968275. In binary, that is: + // + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111111 + // 11111111 11111111 11111111 11111110 + // 11111111 11111111 11111100 00101101 + // + // Notice that can be broken up into five windows of consecutive 1s (in + // order of least to most significant) as: + // + // 2-bit window with 1 bit set (bit 1 unset) + // 3-bit window with 2 bits set (bit 4 unset) + // 5-bit window with 1 bit set (bits 6, 7, 8, 9 unset) + // 23-bit window with 22 bits set (bit 32 unset) + // 223-bit window with all 223 bits set + // + // Thus, the groups of 1 bits in each window forms the set: + // S = {1, 2, 22, 223}. + // + // The strategy is to calculate a^(2^n - 1) for each grouping via an + // addition chain with a sliding window. + // + // The addition chain used is (credits to Peter Dettman): + // (0,0),(1,0),(2,2),(3,2),(4,1),(5,5),(6,6),(7,7),(8,8),(9,7),(10,2) + // => 2^[1] 2^[2] 2^3 2^6 2^9 2^11 2^[22] 2^44 2^88 2^176 2^220 2^[223] + // + // This has a cost of 255 field squarings and 15 field multiplications. + var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 Element + a.Set(e) + 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) + + e.SquareVal(&a223).Square().Square().Square().Square() // e = a^(2^228 - 2^5) + e.Square().Square().Square().Square().Square() // e = a^(2^233 - 2^10) + e.Square().Square().Square().Square().Square() // e = a^(2^238 - 2^15) + e.Square().Square().Square().Square().Square() // e = a^(2^243 - 2^20) + e.Square().Square().Square() // e = a^(2^246 - 2^23) + e.Mul(&a22) // e = a^(2^246 - 4194305) + e.Square().Square().Square().Square().Square() // e = a^(2^251 - 134217760) + e.Mul(&a) // e = a^(2^251 - 134217759) + e.Square().Square().Square() // e = a^(2^254 - 1073742072) + e.Mul(&a2) // e = a^(2^254 - 1073742069) + e.Square().Square() // e = a^(2^256 - 4294968276) + return e.Mul(&a) // e = a^(2^256 - 4294968275) = a^(p-2) +} + +// IsGtOrEqPrimeMinusOrder returns whether or not the element is greater than or +// equal to the field prime minus the secp256k1 group order in constant time. +func (e *Element) IsGtOrEqPrimeMinusOrder() bool { + // The secp256k1 prime is equivalent to 2^256 - 4294968273 and the group + // order is 2^256 - 432420386565659656852420866394968145599. Thus, the + // prime minus the group order is: + // 432420386565659656852420866390673177326 + // + // In hex that is: + // 0x00000000 00000000 00000000 00000001 45512319 50b75fc4 402da172 2fc9baee + // + // Converting that to the internal representation (base 2^64) is: + // + // n[0] = 0x402da1722fc9baee + // n[1] = 0x4551231950b75fc4 + // n[2] = 0x0000000000000001 + // n[3] = 0x0000000000000000 + // + // This can be verified with the following test code: + // pMinusN := new(big.Int).Sub(curveParams.P, curveParams.N) + // var v Element + // v.SetByteSlice(pMinusN.Bytes()) + // t.Logf("%x", v.n) + // + // Outputs: [402da1722fc9baee 4551231950b75fc4 1 0] + const ( + pMinusNLimb0 = 0x402da1722fc9baee + pMinusNLimb1 = 0x4551231950b75fc4 + pMinusNLimb2 = 0x0000000000000001 + pMinusNLimb3 = 0x0000000000000000 + ) + + // The goal is to return true when the element is greater than or equal to + // the field prime minus the group order. That is, return true when e ≥ p - + // n, which is trivially rearranged to e - (p - n) ≥ 0. + // + // In other words, the condition is met iff subtracting (p - n) from e is + // non-negative (aka there was no borrow). + var borrow uint64 + _, borrow = bits.Sub64(e.n[0], pMinusNLimb0, 0) + _, borrow = bits.Sub64(e.n[1], pMinusNLimb1, borrow) + _, borrow = bits.Sub64(e.n[2], pMinusNLimb2, borrow) + _, borrow = bits.Sub64(e.n[3], pMinusNLimb3, borrow) + return borrow == 0 +} diff --git a/dcrec/secp256k1/field4x64/element_amd64.go b/dcrec/secp256k1/field4x64/element_amd64.go new file mode 100644 index 0000000000..108e77cb17 --- /dev/null +++ b/dcrec/secp256k1/field4x64/element_amd64.go @@ -0,0 +1,24 @@ +// 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. + +//go:build !purego + +package field4x64 + +import ( + "github.com/decred/dcrd/dcrec/secp256k1/v4/internal/cpufeat" +) + +// useBMI2AndADX is enabled when the CPU supports both BMI2 (MULX) and ADX +// (ADCX/ADOX). +var useBMI2AndADX = func() bool { + f := cpufeat.Supported() + return f.BMI2 && f.ADX +}() + +//go:noescape +func mulReduceADX(r *[4]uint64, a, b *[4]uint64) + +//go:noescape +func squareReduceADX(r *[4]uint64, a *[4]uint64) diff --git a/dcrec/secp256k1/field64_amd64.s b/dcrec/secp256k1/field4x64/element_amd64.s similarity index 90% rename from dcrec/secp256k1/field64_amd64.s rename to dcrec/secp256k1/field4x64/element_amd64.s index aeb851ae5d..8b98035b72 100644 --- a/dcrec/secp256k1/field64_amd64.s +++ b/dcrec/secp256k1/field4x64/element_amd64.s @@ -58,8 +58,8 @@ MOVQ R14, 16(AX) \ MOVQ R15, 24(AX) -// func field64MulADX(r *[4]uint64, a, b *[4]uint64) -TEXT ·field64MulADX(SB), NOSPLIT, $0-24 +// func mulReduceADX(r *[4]uint64, a, b *[4]uint64) +TEXT ·mulReduceADX(SB), NOSPLIT, $0-24 MOVQ a+8(FP), SI MOVQ b+16(FP), DI XORQ BX, BX @@ -126,8 +126,8 @@ TEXT ·field64MulADX(SB), NOSPLIT, $0-24 REDUCE() RET -// func field64SquareADX(r *[4]uint64, a *[4]uint64) -TEXT ·field64SquareADX(SB), NOSPLIT, $0-16 +// func squareReduceADX(r *[4]uint64, a *[4]uint64) +TEXT ·squareReduceADX(SB), NOSPLIT, $0-16 MOVQ a+8(FP), AX MOVQ 0(AX), DX // a0 MOVQ 8(AX), SI // a1 @@ -188,14 +188,3 @@ TEXT ·field64SquareADX(SB), NOSPLIT, $0-16 REDUCE() RET - -// func field64CPUID(eaxIn, ecxIn uint32) (eax, ebx, ecx, edx uint32) -TEXT ·field64CPUID(SB), NOSPLIT, $0-24 - MOVL eaxIn+0(FP), AX - MOVL ecxIn+4(FP), CX - CPUID - MOVL AX, eax+8(FP) - MOVL BX, ebx+12(FP) - MOVL CX, ecx+16(FP) - MOVL DX, edx+20(FP) - RET diff --git a/dcrec/secp256k1/field4x64/element_amd64_test.go b/dcrec/secp256k1/field4x64/element_amd64_test.go new file mode 100644 index 0000000000..cfdffa7885 --- /dev/null +++ b/dcrec/secp256k1/field4x64/element_amd64_test.go @@ -0,0 +1,66 @@ +// 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 field4x64 + +import "testing" + +// TestElementAMD64 ensures each of the specialized amd64 [Element.Mul] and +// [Element.Square] implementations return the expected results. +// +// Note that any tests which require instruction sets that aren't available on +// the system executing the tests are skipped. +func TestElementAMD64(t *testing.T) { + // NOTE: This is intentionally not made parallel because it modifies the + // global feature flags to ensure all supported variants are tested. + + pureGo := true + type mulVariantTest struct { + name string + featureFlag *bool + } + variants := []mulVariantTest{ + {name: "Pure Go", featureFlag: &pureGo}, + {name: "BMI2/ADX", featureFlag: &useBMI2AndADX}, + } + + // Restore the feature flags after the tests complete. + origFlags := make(map[string]bool) + for _, variant := range variants { + origFlags[variant.name] = *variant.featureFlag + } + defer func() { + for _, variant := range variants { + *variant.featureFlag = origFlags[variant.name] + } + }() + + for _, variant := range variants { + t.Run(variant.name, func(t *testing.T) { + // Skip any features that the hardware does not support or have + // explicitly been disabled. + // + // Note that this is intentionally not using t.Skipf because tinygo + // does not support it. + if !origFlags[variant.name] { + t.Logf("Skipping %s tests (disabled or no instruction set "+ + "support)", variant.name) + return + } + + // Ensure only the specific feature flag for this test is enabled. + for _, variant := range variants { + *variant.featureFlag = false + } + *variant.featureFlag = true + + t.Run("Mul", func(t *testing.T) { + testElementMul(t) + }) + t.Run("Square", func(t *testing.T) { + testElementSquare(t) + }) + }) + } +} diff --git a/dcrec/secp256k1/field4x64/element_bench_amd64_test.go b/dcrec/secp256k1/field4x64/element_bench_amd64_test.go new file mode 100644 index 0000000000..f6c652e9f9 --- /dev/null +++ b/dcrec/secp256k1/field4x64/element_bench_amd64_test.go @@ -0,0 +1,83 @@ +// 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. + +//go:build amd64 && !purego + +package field4x64 + +import ( + "testing" +) + +// BenchmarkMulReduceAMD64 benchmarks how long it takes to multiply two field +// elements together and reduce them modulo the field prime with each of the +// specialized amd64 implementations along with the number of allocations +// needed. +func BenchmarkMulReduceAMD64(b *testing.B) { + benches := []struct { + name string + fn func(r *[4]uint64, a, b *[4]uint64) + supported bool + }{ + {name: "Generic", fn: mulReduceGeneric, supported: true}, + {name: "BMI2/ADX", fn: mulReduceADX, supported: useBMI2AndADX}, + } + + a := mustElement("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab").n + c := mustElement("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca").n + var r [4]uint64 + + for _, bench := range benches { + b.Run(bench.name, func(b *testing.B) { + if !bench.supported { + // Note that this is intentionally not using b.Skipf because + // tinygo does not support it. + b.Logf("Skipping %s bench (disabled or no instruction set "+ + "support)", bench.name) + return + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bench.fn(&r, &a, &c) + } + }) + } +} + +// BenchmarkSquareReduceAMD64 benchmarks how long it takes to square a field +// element and reduce it modulo the field prime with each of the specialized +// amd64 implementations along with the number of allocations needed. +func BenchmarkSquareReduceAMD64(b *testing.B) { + benches := []struct { + name string + fn func(r *[4]uint64, a *[4]uint64) + supported bool + }{ + {name: "Generic", fn: squareReduceGeneric, supported: true}, + {name: "BMI2/ADX", fn: squareReduceADX, supported: useBMI2AndADX}, + } + + a := mustElement("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca").n + var r [4]uint64 + + for _, bench := range benches { + b.Run(bench.name, func(b *testing.B) { + if !bench.supported { + // Note that this is intentionally not using b.Skipf because + // tinygo does not support it. + b.Logf("Skipping %s bench (disabled or no instruction set "+ + "support)", bench.name) + return + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bench.fn(&r, &a) + } + }) + } +} diff --git a/dcrec/secp256k1/field4x64/element_bench_test.go b/dcrec/secp256k1/field4x64/element_bench_test.go new file mode 100644 index 0000000000..96579a079c --- /dev/null +++ b/dcrec/secp256k1/field4x64/element_bench_test.go @@ -0,0 +1,175 @@ +// 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 field4x64 + +import ( + "testing" +) + +// BenchmarkElementNegate benchmarks calculating the additive inverse of an +// unsigned 256-bit big-endian integer modulo the field prime with [Element]. +func BenchmarkElementNegate(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var result Element + _ = result.NegateVal(e, 0) + } +} + +// BenchmarkElementAdd benchmarks adding two unsigned 256-bit big-endian +// integers modulo the field prime with [Element]. +func BenchmarkElementAdd(b *testing.B) { + a := mustElement("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab") + c := mustElement("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var sum Element + sum.Add2(a, c) + } +} + +// BenchmarkElementMulBy2 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 2 with [Element.MulBy2]. +func BenchmarkElementMulBy2(b *testing.B) { + eHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(eHex) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + e.MulBy2() + } +} + +// BenchmarkElementMulBy3 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 3 with [Element.MulBy3]. +func BenchmarkElementMulBy3(b *testing.B) { + eHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(eHex) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + e.MulBy3() + } +} + +// BenchmarkElementMulBy4 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 4 with [Element.MulBy4]. +func BenchmarkElementMulBy4(b *testing.B) { + eHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(eHex) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + e.MulBy4() + } +} + +// BenchmarkElementMulBy8 benchmarks multiplying an unsigned 256-bit big-endian +// integer by 8 with [Element.MulBy8]. +func BenchmarkElementMulBy8(b *testing.B) { + eHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(eHex) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + e.MulBy8() + } +} + +// BenchmarkFieldMulInt benchmarks multiplying an unsigned 256-bit big-endian +// integer by small integers with [Element.MulInt]. +func BenchmarkElementMulInt(b *testing.B) { + eHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(eHex) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + e.MulInt(2) + } +} + +// BenchmarkElementMul benchmarks multiplying two unsigned 256-bit big-endian +// integers modulo the field prime with [Element]. +func BenchmarkElementMul(b *testing.B) { + a := mustElement("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab") + c := mustElement("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var prod Element + prod.Mul2(a, c) + } +} + +// BenchmarkElementSqrt benchmarks calculating the square root of an unsigned +// 256-bit big-endian integer modulo the field prime with [Element]. +func BenchmarkElementSqrt(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var result Element + _ = result.SquareRootVal(e) + } +} + +// BenchmarkElementSquare benchmarks squaring a 256-bit big-endian integer +// modulo the field prime with [Element]. +func BenchmarkElementSquare(b *testing.B) { + a := mustElement("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + var sq Element + sq.SquareVal(a) + } +} + +// BenchmarkElementInverse benchmarks calculating the multiplicative inverse of +// an unsigned 256-bit big-endian integer modulo the field prime with +// [Element]. +func BenchmarkElementInverse(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + e.Inverse() + } +} + +// BenchmarkElementIsGtOrEqPrimeMinusOrder benchmarks determining whether a +// value is greater than or equal to the field prime minus the group order with +// [Element]. +func BenchmarkElementIsGtOrEqPrimeMinusOrder(b *testing.B) { + // The function is constant time so any value is fine. + valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" + e := mustElement(valHex) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = e.IsGtOrEqPrimeMinusOrder() + } +} diff --git a/dcrec/secp256k1/field4x64/element_generic.go b/dcrec/secp256k1/field4x64/element_generic.go new file mode 100644 index 0000000000..d5b10840f2 --- /dev/null +++ b/dcrec/secp256k1/field4x64/element_generic.go @@ -0,0 +1,124 @@ +// 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 field4x64 + +import ( + "math/bits" + + "github.com/decred/dcrd/dcrec/secp256k1/v4/internal/arith" +) + +// reduce512 reduces a 512-bit little-endian limb array modulo p in constant +// time and stores the result in r using pure Go. +func reduce512(r *[4]uint64, x *[8]uint64) { + // This algorithm has been formally verified, including its intermediate + // bounds, carry assumptions, and functional correctness. The verification + // artifacts are available in internal/proofs. + + // Per [HAC] section 14.3.4: Reduction method of moduli of special form, + // when the modulus is of the special form m = b^t - c, highly efficient + // reduction can be achieved. While [HAC] only presents the algorithm and + // does not call it out by name or provide the mathematical justification, + // the underlying technique is known as Crandall reduction and is often + // presented as 2^k - c. It is easy to see they are equivalent by setting + // b = 2 and t = k. + // + // The secp256k1 prime is 2^256 - 4294968273, so it fits this criteria where + // k=256, and c = 4294968273 = 2^32 + 977. + // + // Crandall reduction works by taking advantage of the fact that if a prime + // is of the form 2^k - c, then 2^k - c ≡ 0 (mod p), so 2^k ≡ c (mod p). In + // other words, every multiple of 2^k is equivalent to adding c when working + // modulo p. + // + // Since the 512-bit value to reduce is tightly packed into uint64s, the + // upper 4 limbs are all multiples of 2^256. Therefore, reducing modulo the + // prime is equivalent to multiplying those upper limbs by c and adding the + // result to the corresponding lower 4 limbs while propagating the carries. + // + // For the specific case of the secp256k1 prime, a max of 3 reductions are + // required because c is 33 bits and so the first round will reduce from 512 + // bits to a max of 256 + 33 = 289 bits and the second round will reduce to + // within 2p. Then, a conditional subtraction of p handles the final + // reduction. + + var t0, t1, t2, t3, t4, h, lo, hi, carry uint64 + + h, t0 = bits.Mul64(x[4], fieldPrimeComplement) + + // Note that since hi is the upper 64 bits of the product of a uint64 with + // c and c < 2^33: + // hi ≤ floor((2^64-1)(2^33 - 1) / 2^64) = 2^33 - 2 + // + // Then, because carry ≤ 1, a loose bound for h is: + // h ≤ hi + 1 = 2^33 - 1 < 2^64 + // + // Therefore, it is safe to discard the carry and the same applies to the + // next two limbs (second h and first t4). + hi, lo = bits.Mul64(x[5], fieldPrimeComplement) + t1, carry = bits.Add64(lo, h, 0) + h, _ = bits.Add64(hi, 0, carry) + + hi, lo = bits.Mul64(x[6], fieldPrimeComplement) + t2, carry = bits.Add64(lo, h, 0) + h, _ = bits.Add64(hi, 0, carry) + + hi, lo = bits.Mul64(x[7], fieldPrimeComplement) + t3, carry = bits.Add64(lo, h, 0) + t4, _ = bits.Add64(hi, 0, carry) + + // The carryless add into t4 below is safe because, per the bound above, + // t4 ≤ 2^33 - 1 and carry ≤ 1, so: + // t4 ≤ (2^33 - 1) + 1 = 2^33 < 2^64 + 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 + + // The value now fits in 289 bits, so reduce it again. Only the fifth limb + // (t4) needs to be considered since all of the higher limbs are ≥ 320 bits + // and thus guaranteed to be 0. + h, t4 = bits.Mul64(t4, fieldPrimeComplement) + + 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, borrow uint64 + s0, borrow = bits.Sub64(t0, fieldPrimeLimb0, 0) + s1, borrow = bits.Sub64(t1, fieldPrimeLimb1, borrow) + s2, borrow = bits.Sub64(t2, fieldPrimeLimb2, borrow) + s3, borrow = bits.Sub64(t3, fieldPrimeLimb3, borrow) + _, borrow = bits.Sub64(t4, 0, borrow) + r[0] = arith.ConstantTimeSelect64(borrow, t0, s0) + r[1] = arith.ConstantTimeSelect64(borrow, t1, s1) + r[2] = arith.ConstantTimeSelect64(borrow, t2, s2) + r[3] = arith.ConstantTimeSelect64(borrow, t3, s3) +} + +// mulReduceGeneric sets r = a * b (mod p). This is a generic implementation +// that performs the multiplication and reduction steps separately without any +// reliance on specific hardware extensions. +func mulReduceGeneric(r *[4]uint64, a, b *[4]uint64) { + var product [8]uint64 + arith.Mul512(&product, a, b) + reduce512(r, &product) +} + +// squareReduceGeneric sets r = a^2 (mod p). This is a generic implementation +// that performs the squaring and reduction steps separately without any +// reliance on specific hardware extensions. +func squareReduceGeneric(r *[4]uint64, a *[4]uint64) { + var product [8]uint64 + arith.Square512(&product, a) + reduce512(r, &product) +} diff --git a/dcrec/secp256k1/field4x64/element_noasm.go b/dcrec/secp256k1/field4x64/element_noasm.go new file mode 100644 index 0000000000..c94b4e8bfc --- /dev/null +++ b/dcrec/secp256k1/field4x64/element_noasm.go @@ -0,0 +1,23 @@ +// 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. + +//go:build !amd64 || purego + +package field4x64 + +// useBMI2AndADX depends on hardware support and access to assembly +// instructions. +var useBMI2AndADX = false + +// mulReduce sets r = a * b (mod p). This defers to the generic +// implementation that does not rely on the optimized assembly implementations. +func mulReduce(r *[4]uint64, a, b *[4]uint64) { + mulReduceGeneric(r, a, b) +} + +// squareReduce sets r = a^2 (mod p). This defers to the generic implementation +// that does not rely on the optimized assembly implementations. +func squareReduce(r *[4]uint64, a *[4]uint64) { + squareReduceGeneric(r, a) +} diff --git a/dcrec/secp256k1/field64_test.go b/dcrec/secp256k1/field4x64/element_test.go similarity index 75% rename from dcrec/secp256k1/field64_test.go rename to dcrec/secp256k1/field4x64/element_test.go index a3589130ac..4d18442339 100644 --- a/dcrec/secp256k1/field64_test.go +++ b/dcrec/secp256k1/field4x64/element_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package secp256k1 +package field4x64 import ( "bytes" @@ -15,14 +15,14 @@ import ( "time" ) -// mustFieldVal64Internal converts the passed hex string into a [FieldVal64] and -// will panic if there is an error. Values that overflow are treated as an -// error unless the allow overflow flag is set. +// mustElementInternal converts the passed hex string into an [Element] and will +// panic if there is an error. Values that overflow are treated as an error +// unless the allow overflow flag is set. // // This is only provided for the hard-coded constants so errors in the source // code can be detected. It will only (and must only) be called with hard-coded // values. -func mustFieldVal64Internal(s string, allowOverflow bool) *FieldVal64 { +func mustElementInternal(s string, allowOverflow bool) *Element { if len(s)%2 != 0 { s = "0" + s } @@ -33,37 +33,37 @@ func mustFieldVal64Internal(s string, allowOverflow bool) *FieldVal64 { if len(b) > 32 { panic("hex in source file overflows uint256: " + s) } - var f FieldVal64 + var f Element if overflow := f.SetByteSlice(b); overflow && !allowOverflow { - panic("hex in source file overflows mod N scalar: " + s) + panic("hex in source file overflows field prime: " + s) } return &f } -// mustFieldVal64 converts the passed hex string into a [FieldVal64] and will -// panic if there is an error. Values that overflow are treated as an error. +// mustElement converts the passed hex string into an [Element] and will panic if +// there is an error. Values that overflow are treated as an error. // // This is only provided for the hard-coded constants so errors in the source // code can be detected. It will only (and must only) be called with hard-coded // values. -func mustFieldVal64(s string) *FieldVal64 { - return mustFieldVal64Internal(s, false) +func mustElement(s string) *Element { + return mustElementInternal(s, false) } -// mustFieldVal64WithOverflow converts the passed hex string into a [FieldVal64] -// and will panic if there is an error. Values that overflow are NOT treated as -// an error. +// mustElementWithOverflow converts the passed hex string into an [Element] and +// will panic if there is an error. Values that overflow are NOT treated as an +// error. // // This is only provided for the hard-coded constants so errors in the source // code can be detected. It will only (and must only) be called with hard-coded // values. -func mustFieldVal64WithOverflow(s string) *FieldVal64 { - return mustFieldVal64Internal(s, true) +func mustElementWithOverflow(s string) *Element { + return mustElementInternal(s, true) } -// 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) { +// randIntAndElement returns a [big.Int] and [Element] both created from the +// same random value generated by the passed rng. +func randIntAndElement(t *testing.T, rng *rand.Rand) (*big.Int, *Element) { t.Helper() var buf [32]byte @@ -73,14 +73,14 @@ func randIntAndFieldVal64(t *testing.T, rng *rand.Rand) (*big.Int, *FieldVal64) bigIntVal := new(big.Int).SetBytes(buf[:]) bigIntVal.Mod(bigIntVal, curveParams.P) - var fv FieldVal64 + var fv Element 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) { +// TestElementSetInt ensures that setting an element to various native integers +// works as expected. +func TestElementSetInt(t *testing.T) { tests := []struct { name string // test description in uint16 // test value @@ -91,21 +91,21 @@ func TestField64SetInt(t *testing.T) { } for _, test := range tests { - f := new(FieldVal64).SetInt(test.in) + e := new(Element).SetInt(test.in) var want [32]byte binary.BigEndian.PutUint16(want[30:], test.in) - if !bytes.Equal(f.Bytes()[:], want[:]) { + if !bytes.Equal(e.Bytes()[:], want[:]) { t.Errorf("%s: wrong result\ngot: %x\nwant: %x", test.name, - f.Bytes()[:], want[:]) + e.Bytes()[:], want[:]) continue } } } -// TestField64SetBytes ensures that setting a field value to a 256-bit big-endian +// TestElementSetBytes ensures that setting an element 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) { +func TestElementSetBytes(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -183,7 +183,7 @@ func TestField64SetBytes(t *testing.T) { for _, test := range tests { inBytes := hexToBytes(test.in) - // FieldVal64 only consumes the first 32 bytes and always reduces, so + // [Element] 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 { @@ -195,7 +195,7 @@ func TestField64SetBytes(t *testing.T) { wantInt.FillBytes(wantBytes[:]) // Ensure setting the bytes via the slice method works as expected. - var f FieldVal64 + var f Element overflow := f.SetByteSlice(inBytes) if !bytes.Equal(f.Bytes()[:], wantBytes[:]) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, @@ -209,7 +209,7 @@ func TestField64SetBytes(t *testing.T) { } // Ensure setting the bytes via the array method works as expected. - var f2 FieldVal64 + var f2 Element var b32 [32]byte copy(b32[32-len(truncated):], truncated) overflow = f2.SetBytes(&b32) != 0 @@ -226,10 +226,10 @@ func TestField64SetBytes(t *testing.T) { } } -// TestField64Bytes ensures that retrieving the bytes for a 256-bit big-endian +// TestElementBytes 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) { +func TestElementBytes(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -301,11 +301,11 @@ func TestField64Bytes(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in) + e := mustElementWithOverflow(test.in) expected := hexToBytes(test.expected) // Ensure getting the bytes works as expected. - gotBytes := f.Bytes() + gotBytes := e.Bytes() if !bytes.Equal(gotBytes[:], expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, *gotBytes, expected) @@ -314,7 +314,7 @@ func TestField64Bytes(t *testing.T) { // Ensure getting the bytes directly into an array works as expected. var b32 [32]byte - f.PutBytes(&b32) + e.PutBytes(&b32) if !bytes.Equal(b32[:], expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, b32, expected) @@ -323,7 +323,7 @@ func TestField64Bytes(t *testing.T) { // Ensure getting the bytes directly into a slice works as expected. var buffer [64]byte - f.PutBytesUnchecked(buffer[:]) + e.PutBytesUnchecked(buffer[:]) if !bytes.Equal(buffer[:32], expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, buffer[:32], expected) @@ -332,57 +332,57 @@ func TestField64Bytes(t *testing.T) { } } -// TestField64Zero ensures that zeroing a field value works as expected. -func TestField64Zero(t *testing.T) { - f := mustFieldVal64("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") - f.Zero() - for idx, limb := range f.n { +// TestElementZero ensures that zeroing an element works as expected. +func TestElementZero(t *testing.T) { + e := mustElement("a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5") + e.Zero() + for idx, limb := range e.n { if limb != 0 { t.Errorf("limb at index #%d is not zero - got %d", idx, limb) } } } -// TestField64IsZero ensures that checking if a field is zero via IsZero and -// IsZeroBit works as expected. -func TestField64IsZero(t *testing.T) { - f := new(FieldVal64) - if !f.IsZero() { - t.Errorf("new field value is not zero - got %v (rawints %x)", f, f.n) +// TestElementIsZero ensures that checking if an [Element] is zero via +// [Element.IsZero] and [Element.IsZeroBit] works as expected. +func TestElementIsZero(t *testing.T) { + e := new(Element) + if !e.IsZero() { + t.Errorf("new element is not zero - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() != 1 { - t.Errorf("new field value is not zero - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() != 1 { + t.Errorf("new element is not zero - got %v (rawints %x)", e, e.n) } - f.SetInt(1) - if f.IsZero() { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + e.SetInt(1) + if e.IsZero() { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() == 1 { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() == 1 { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } - f.Zero() - if !f.IsZero() { - t.Errorf("claims nonzero for zero field - got %v (rawints %x)", f, f.n) + e.Zero() + if !e.IsZero() { + t.Errorf("claims nonzero for zero element - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() != 1 { - t.Errorf("claims nonzero for zero field - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() != 1 { + t.Errorf("claims nonzero for zero element - got %v (rawints %x)", e, e.n) } - f.SetInt(1) - f.Zero() - if !f.IsZero() { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + e.SetInt(1) + e.Zero() + if !e.IsZero() { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } - if f.IsZeroBit() != 1 { - t.Errorf("claims zero for nonzero field - got %v (rawints %x)", f, f.n) + if e.IsZeroBit() != 1 { + t.Errorf("claims zero for nonzero element - got %v (rawints %x)", e, e.n) } } -// TestField64IsOne ensures that checking if a field is one via IsOne and IsOneBit -// works as expected. -func TestField64IsOne(t *testing.T) { +// TestElementIsOne ensures that checking if an [Element] is one via +// [Element.IsOne] and [Element.IsOneBit] works as expected. +func TestElementIsOne(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -422,15 +422,15 @@ func TestField64IsOne(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in) - result := f.IsOne() + e := mustElementWithOverflow(test.in) + result := e.IsOne() if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) continue } - result2 := f.IsOneBit() == 1 + result2 := e.IsOneBit() == 1 if result2 != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result2, test.expected) @@ -439,10 +439,104 @@ func TestField64IsOne(t *testing.T) { } } -// 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) { +// TestElementStringer ensures the stringer returns the appropriate hex string. +func TestElementStringer(t *testing.T) { + tests := []struct { + name string // test description + in string // hex encoded test value + expected string // expected result + }{{ + name: "zero", + in: "0", + expected: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "one", + in: "1", + expected: "0000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "ten", + in: "a", + expected: "000000000000000000000000000000000000000000000000000000000000000a", + }, { + name: "eleven", + in: "b", + expected: "000000000000000000000000000000000000000000000000000000000000000b", + }, { + name: "twelve", + in: "c", + expected: "000000000000000000000000000000000000000000000000000000000000000c", + }, { + name: "thirteen", + in: "d", + expected: "000000000000000000000000000000000000000000000000000000000000000d", + }, { + name: "fourteen", + in: "e", + expected: "000000000000000000000000000000000000000000000000000000000000000e", + }, { + name: "fifteen", + in: "f", + expected: "000000000000000000000000000000000000000000000000000000000000000f", + }, { + name: "240", + in: "f0", + expected: "00000000000000000000000000000000000000000000000000000000000000f0", + }, { + name: "2^26 - 1", + in: "3ffffff", + expected: "0000000000000000000000000000000000000000000000000000000003ffffff", + }, { + name: "2^32 - 1", + in: "ffffffff", + expected: "00000000000000000000000000000000000000000000000000000000ffffffff", + }, { + name: "2^64 - 1", + in: "ffffffffffffffff", + expected: "000000000000000000000000000000000000000000000000ffffffffffffffff", + }, { + name: "2^96 - 1", + in: "ffffffffffffffffffffffff", + expected: "0000000000000000000000000000000000000000ffffffffffffffffffffffff", + }, { + name: "2^128 - 1", + in: "ffffffffffffffffffffffffffffffff", + expected: "00000000000000000000000000000000ffffffffffffffffffffffffffffffff", + }, { + name: "2^160 - 1", + in: "ffffffffffffffffffffffffffffffffffffffff", + expected: "000000000000000000000000ffffffffffffffffffffffffffffffffffffffff", + }, { + name: "2^192 - 1", + in: "ffffffffffffffffffffffffffffffffffffffffffffffff", + expected: "0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "2^224 - 1", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expected: "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "2^256-4294968273 (the secp256k1 prime, so should result in 0)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + expected: "0000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "2^256-4294968274 (the secp256k1 prime+1, so should result in 1)", + in: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30", + expected: "0000000000000000000000000000000000000000000000000000000000000001", + }} + + for _, test := range tests { + e := mustElementWithOverflow(test.in) + result := e.String() + if result != test.expected { + t.Errorf("%s: wrong result\ngot: %v\nwant: %v", test.name, result, + test.expected) + continue + } + } +} + +// TestElementIsOdd ensures that checking if an element is odd via +// [Element.IsOdd] and [Element.IsOddBit] works as expected. +func TestElementIsOdd(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -478,15 +572,15 @@ func TestField64IsOdd(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in) - result := f.IsOdd() + e := mustElementWithOverflow(test.in) + result := e.IsOdd() if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) continue } - result2 := f.IsOddBit() == 1 + result2 := e.IsOddBit() == 1 if result2 != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result2, test.expected) @@ -495,9 +589,9 @@ func TestField64IsOdd(t *testing.T) { } } -// TestField64Equals ensures that checking two field values for equality via -// Equals works as expected. -func TestField64Equals(t *testing.T) { +// TestElementEquals ensures that checking two elements for equality via +// [Element.Equals] works as expected. +func TestElementEquals(t *testing.T) { tests := []struct { name string // test description in1 string // hex encoded value @@ -541,9 +635,9 @@ func TestField64Equals(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in1) - f2 := mustFieldVal64WithOverflow(test.in2) - result := f.Equals(f2) + e := mustElementWithOverflow(test.in1) + e2 := mustElementWithOverflow(test.in2) + result := e.Equals(e2) if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) @@ -552,9 +646,9 @@ func TestField64Equals(t *testing.T) { } } -// TestField64Negate ensures that negating field values via Negate works as -// expected. -func TestField64Negate(t *testing.T) { +// TestElementNegate ensures that negating elements via [Element.Negate] works +// as expected. +func TestElementNegate(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -602,11 +696,11 @@ func TestField64Negate(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in) - expected := mustFieldVal64(test.expected) + e := mustElementWithOverflow(test.in) + expected := mustElement(test.expected) // Ensure negating another value produces the expected result. - result := new(FieldVal64).NegateVal(f, 0) + result := new(Element).NegateVal(e, 0) if !result.Equals(expected) { t.Errorf("%s: unexpected result -- got: %x, want: %x", test.name, result.Bytes(), expected.Bytes()) @@ -614,7 +708,7 @@ func TestField64Negate(t *testing.T) { } // Ensure self negating also produces the expected result. - result2 := f.Negate(0) + result2 := e.Negate(0) if !result2.Equals(expected) { t.Errorf("%s: unexpected result -- got: %x, want: %x", test.name, result2.Bytes(), expected.Bytes()) @@ -623,9 +717,9 @@ func TestField64Negate(t *testing.T) { } } -// TestField64AddInt ensures that adding an integer to field values via AddInt -// works as expected. -func TestField64AddInt(t *testing.T) { +// TestElementAddInt ensures that adding an integer to elements via +// [Element.AddInt] works as expected. +func TestElementAddInt(t *testing.T) { tests := []struct { name string // test description in1 string // hex encoded value @@ -679,9 +773,9 @@ func TestField64AddInt(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in1) - expected := mustFieldVal64(test.expected) - result := f.AddInt(test.in2) + e := mustElementWithOverflow(test.in1) + expected := mustElement(test.expected) + result := e.AddInt(test.in2) if !result.Equals(expected) { t.Errorf("%s: wrong result -- got: %x -- want: %x", test.name, result.Bytes(), expected.Bytes()) @@ -690,9 +784,9 @@ func TestField64AddInt(t *testing.T) { } } -// TestField64Add ensures that adding two field values together via Add and Add2 -// works as expected. -func TestField64Add(t *testing.T) { +// TestElementAdd ensures that adding two elements together via [Element.Add] +// and [Element.Add2] works as expected. +func TestElementAdd(t *testing.T) { tests := []struct { name string // test description in1 string // first hex encoded value @@ -761,21 +855,21 @@ func TestField64Add(t *testing.T) { }} for _, test := range tests { - f1 := mustFieldVal64WithOverflow(test.in1) - f2 := mustFieldVal64WithOverflow(test.in2) - expected := mustFieldVal64(test.expected) + f1 := mustElementWithOverflow(test.in1) + f2 := mustElementWithOverflow(test.in2) + expected := mustElement(test.expected) // Ensure adding the two values with the result going to another // variable produces the expected result. - result := new(FieldVal64).Add2(f1, f2) + result := new(Element).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. + // Ensure adding the value to an existing element produces the expected + // result. f1.Add(f2) if !f1.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, @@ -785,18 +879,18 @@ func TestField64Add(t *testing.T) { } } -// TestField64MulByX ensures the dedicated small-constant multiply helpers +// TestElementMulByX 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) { +func TestElementMulByX(t *testing.T) { mulByFuncs := []struct { factor uint8 - fn func(*FieldVal64) *FieldVal64 + fn func(*Element) *Element }{ - {2, (*FieldVal64).MulBy2}, - {3, (*FieldVal64).MulBy3}, - {4, (*FieldVal64).MulBy4}, - {8, (*FieldVal64).MulBy8}, + {2, (*Element).MulBy2}, + {3, (*Element).MulBy3}, + {4, (*Element).MulBy4}, + {8, (*Element).MulBy8}, } testCases := []string{ @@ -814,12 +908,12 @@ func TestField64MulByX(t *testing.T) { "e3cbe002cc93029190181a906ed41af401c9726546dc19389a06290efdf563f1", // random sampling } for _, in := range testCases { - in := mustFieldVal64(in) + in := mustElement(in) for _, m := range mulByFuncs { - var want FieldVal64 + var want Element want.Set(in).MulInt(m.factor) - var f FieldVal64 + var f Element got := m.fn(f.Set(in)) if !got.Equals(&want) { t.Errorf("MulBy%d(%x): wrong result -- got: %x -- want: %x", @@ -829,9 +923,9 @@ func TestField64MulByX(t *testing.T) { } } -// TestField64MulInt ensures that multiplying an integer to field values via -// MulInt works as expected. -func TestField64MulInt(t *testing.T) { +// TestElementMulInt ensures that multiplying an integer to elements via +// [Element.MulInt] works as expected. +func TestElementMulInt(t *testing.T) { tests := []struct { name string // test description in1 string // hex encoded value @@ -895,9 +989,9 @@ func TestField64MulInt(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in1) - expected := mustFieldVal64(test.expected) - result := f.MulInt(test.in2) + e := mustElementWithOverflow(test.in1) + expected := mustElement(test.expected) + result := e.MulInt(test.in2) if !result.Equals(expected) { t.Errorf("%s: wrong result -- got: %x -- want: %x", test.name, result.Bytes(), expected.Bytes()) @@ -906,9 +1000,12 @@ func TestField64MulInt(t *testing.T) { } } -// TestField64Mul ensures that multiplying two field values via Mul and Mul2 -// works as expected. -func TestField64Mul(t *testing.T) { +// testElementMul ensures that multiplying two elements via [Element.Mul] and +// [Element.Mul2] works as expected. This unexported version exists so it can +// be invoked with different run-time achitecture variants. +func testElementMul(t *testing.T) { + t.Helper() + tests := []struct { name string // test description in1 string // first hex encoded value @@ -982,20 +1079,20 @@ func TestField64Mul(t *testing.T) { }} for _, test := range tests { - f1 := mustFieldVal64WithOverflow(test.in1) - f2 := mustFieldVal64WithOverflow(test.in2) - expected := mustFieldVal64(test.expected) + f1 := mustElementWithOverflow(test.in1) + f2 := mustElementWithOverflow(test.in2) + expected := mustElement(test.expected) // Ensure multiplying the two values with the result going to another // variable produces the expected result. - result := new(FieldVal64).Mul2(f1, f2) + result := new(Element).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 + // Ensure multiplying the value to an existing element produces the // expected result. f1.Mul(f2) if !f1.Equals(expected) { @@ -1006,9 +1103,18 @@ func TestField64Mul(t *testing.T) { } } -// TestField64Square ensures that squaring field values via Square and SquareVal -// works as expected. -func TestField64Square(t *testing.T) { +// TestElementMul ensures that multiplying two elements via [Element.Mul] and +// [Element.Mul2] works as expected. +func TestElementMul(t *testing.T) { + testElementMul(t) +} + +// testElementSquare ensures that squaring elements via [Element.Square] and +// [Element.Square] works as expected. This unexported version exists so it can +// be invoked with different run-time achitecture variants. +func testElementSquare(t *testing.T) { + t.Helper() + tests := []struct { name string // test description in string // hex encoded value @@ -1048,32 +1154,38 @@ func TestField64Square(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in) - expected := mustFieldVal64(test.expected) + e := mustElementWithOverflow(test.in) + expected := mustElement(test.expected) // Ensure squaring the value with the result going to another variable // produces the expected result. - result := new(FieldVal64).SquareVal(f) + result := new(Element).SquareVal(e) 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 + // Ensure self squaring an existing element produces the expected // result. - f.Square() - if !f.Equals(expected) { + e.Square() + if !e.Equals(expected) { t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, - f.Bytes(), expected.Bytes()) + e.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) { +// TestElementSquare ensures that squaring elements via [Element.Square] and +// [Element.SquareVal] works as expected. +func TestElementSquare(t *testing.T) { + testElementSquare(t) +} + +// TestElementSquareRoot ensures that calculating the square root of elements +// via [Element.SquareRootVal] works as expected for edge cases. +func TestElementSquareRoot(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -1142,12 +1254,12 @@ func TestField64SquareRoot(t *testing.T) { }} for _, test := range tests { - input := mustFieldVal64WithOverflow(test.in) - want := mustFieldVal64(test.want) + input := mustElementWithOverflow(test.in) + want := mustElement(test.want) // Calculate the square root and ensure the validity flag matches the // expected value. - var result FieldVal64 + var result Element isValid := result.SquareRootVal(input) if isValid != test.valid { t.Errorf("%s: mismatched validity -- got %v, want %v", test.name, @@ -1164,10 +1276,10 @@ func TestField64SquareRoot(t *testing.T) { } } -// 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) { +// TestElementSquareRootRandom ensures that calculating the square root for +// random elements works as expected by also performing the same operation with +// big ints and comparing the results. +func TestElementSquareRootRandom(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)) @@ -1178,39 +1290,39 @@ func TestField64SquareRootRandom(t *testing.T) { }(t, seed) for i := 0; i < 100; i++ { - // Generate big integer and field value with the same random value. - bigIntVal, fVal := randIntAndFieldVal64(t, rng) + // Generate big integer and element with the same random value. + bigIntVal, fVal := randIntAndElement(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 + // Calculate the square root of the value using an element. + var fValResult Element 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, + t.Fatalf("mismatched square root existence\nbig int in: %x\nelem "+ + "in: %x\nbig int result: %v\nelem 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(), + elemResultHex := hex.EncodeToString(fValResult.Bytes()[:]) + if bigIntResultHex != elemResultHex { + t.Fatalf("mismatched square root\nbig int in: %x\nelem in: %x\n"+ + "big int result: %x\nelem 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) { +// TestElementInverse ensures that finding the multiplicative inverse via +// [Element.Inverse] works as expected. +func TestElementInverse(t *testing.T) { tests := []struct { name string // test description in string // hex encoded value @@ -1250,9 +1362,9 @@ func TestField64Inverse(t *testing.T) { }} for _, test := range tests { - f := mustFieldVal64WithOverflow(test.in) - expected := mustFieldVal64(test.expected) - result := f.Inverse() + e := mustElementWithOverflow(test.in) + expected := mustElement(test.expected) + result := e.Inverse() if !result.Equals(expected) { t.Errorf("%s: wrong result\ngot: %x\nwant: %x", test.name, result.Bytes(), expected.Bytes()) @@ -1261,10 +1373,10 @@ func TestField64Inverse(t *testing.T) { } } -// TestField64IsGtOrEqPrimeMinusOrder ensures that field values report whether or +// TestElementIsGtOrEqPrimeMinusOrder ensures that elements 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) { +func TestElementIsGtOrEqPrimeMinusOrder(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value @@ -1320,7 +1432,7 @@ func TestField64IsGtOrEqPrimeMinusOrder(t *testing.T) { }} for _, test := range tests { - result := mustFieldVal64(test.in).IsGtOrEqPrimeMinusOrder() + result := mustElement(test.in).IsGtOrEqPrimeMinusOrder() if result != test.expected { t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name, result, test.expected) @@ -1329,11 +1441,11 @@ func TestField64IsGtOrEqPrimeMinusOrder(t *testing.T) { } } -// 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) { +// TestElementIsGtOrEqPrimeMinusOrderRandom ensures that elements 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 TestElementIsGtOrEqPrimeMinusOrderRandom(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)) @@ -1345,22 +1457,22 @@ func TestField64IsGtOrEqPrimeMinusOrderRandom(t *testing.T) { 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) + // Generate big integer and element with the same random value. + bigIntVal, elem := randIntAndElement(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() + // order using an element. + fValResult := elem.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) + "%x\nelem in: %x\nbig int result: %v\nelem result %v", + bigIntVal, elem.Bytes(), bigIntResult, fValResult) } } } diff --git a/dcrec/secp256k1/field4x64/elementisa_amd64.go b/dcrec/secp256k1/field4x64/elementisa_amd64.go new file mode 100644 index 0000000000..7a1215f64a --- /dev/null +++ b/dcrec/secp256k1/field4x64/elementisa_amd64.go @@ -0,0 +1,27 @@ +// 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. + +//go:build !purego + +package field4x64 + +// mulReduce sets r = a * b (mod p) using processor-specific hardware extensions +// when available. +func mulReduce(r *[4]uint64, a, b *[4]uint64) { + if useBMI2AndADX { + mulReduceADX(r, a, b) + return + } + mulReduceGeneric(r, a, b) +} + +// squareReduce sets r = a^2 (mod p) using processor-specific hardware +// extensions when available. +func squareReduce(r *[4]uint64, a *[4]uint64) { + if useBMI2AndADX { + squareReduceADX(r, a) + return + } + squareReduceGeneric(r, a) +} diff --git a/dcrec/secp256k1/field64.go b/dcrec/secp256k1/field64.go deleted file mode 100644 index 5be951eb0b..0000000000 --- a/dcrec/secp256k1/field64.go +++ /dev/null @@ -1,1120 +0,0 @@ -// Copyright (c) 2026 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package secp256k1 - -import ( - "encoding/binary" - "encoding/hex" - "math/bits" -) - -// References: -// [HAC]: Handbook of Applied Cryptography Menezes, van Oorschot, Vanstone. -// https://cacr.uwaterloo.ca/hac/ - -// This file provides an alternate implementation of the secp256k1 finite field. -// It uses tight 256-bit packing with four little-endian uint64s and fully -// reduces after each operation. Hardware intrinsics are used when available. - -// FieldVal64 implements optimized fixed-precision arithmetic over the -// secp256k1 finite field. This means all arithmetic is performed modulo -// -// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f. -// -// Unlike [FieldVal], this fully reduces after each operation and therefore does -// not require normalization or manual magnitude tracking. It is also quite a -// bit faster than [FieldVal] on all modern 64-bit hardware. -type FieldVal64 struct { - // Each 256-bit value is represented as 4 64-bit integers in base 2^64. - // It only implements the arithmetic needed for elliptic curve operations. - // - // The following depicts the internal representation: - // -------------------------------------------------------------------- - // | n[3] | n[2] | n[1] | n[0] | - // | 64 bits | 64 bits | 64 bits | 64 bits | - // | Mult: 2^(64*3) | Mult: 2^(64*2) | Mult: 2^(64*1) | Mult: 2^(64*0) | - // -------------------------------------------------------------------- - // - // For example, consider the number 2^87 + 1. It would be represented as: - // n[0] = 1 - // n[1] = 2^23 - // n[2] = n[3] = 0 - // - // The full 256-bit value is then calculated by looping i from 3..0 and - // performing sum(n[i] * 2^(64i)) as follows: - // n[3] * 2^(64*3) = 0 * 2^192 = 0 - // n[2] * 2^(64*2) = 0 * 2^128 = 0 - // n[1] * 2^(64*1) = 2^23 * 2^64 = 2^87 - // n[0] * 2^(64*0) = 1 * 2^0 = 1 - // Sum: 0 + 0 + 2^87 + 1 = 2^87 + 1 - n [4]uint64 -} - -// Constants related to the field representation. -const ( - // field64PrimeComplement is the two's complement of the secp256k1 prime. - field64PrimeComplement = 0x1000003d1 // 2^32 + 977 - - // These fields provide convenient access to each of the limbs of the - // secp256k1 prime in the internal field representation to improve code - // readability. - field64Prime0 = 0xfffffffefffffc2f - field64Prime1 = 0xffffffffffffffff - field64Prime2 = 0xffffffffffffffff - field64Prime3 = 0xffffffffffffffff -) - -// String returns the field value as a human-readable hex string. -func (f FieldVal64) String() string { - return hex.EncodeToString(f.Bytes()[:]) -} - -// Zero sets the field value to zero in constant time. A newly created field -// value is already set to zero. This function can be useful to clear an -// existing field value for reuse. -func (f *FieldVal64) Zero() { - f.n = [4]uint64{} -} - -// Set sets the field value equal to the passed value in constant time. -// -// The field value is returned to support chaining. This enables syntax like: -// f := new(FieldVal).Set(f2).Add(1) so that f = f2 + 1 where f2 is not -// modified. -func (f *FieldVal64) Set(val *FieldVal64) *FieldVal64 { - f.n = val.n - return f -} - -// SetInt sets the field value to the passed integer in constant time. This is -// a convenience function since it is fairly common to perform some arithmetic -// with small native integers. -// -// The field value is returned to support chaining. This enables syntax such -// as f := new(FieldVal).SetInt(2).Mul(f2) so that f = 2 * f2. -func (f *FieldVal64) SetInt(v uint16) *FieldVal64 { - f.n = [4]uint64{uint64(v), 0, 0, 0} - return f -} - -// SetBytes packs the passed 32-byte big-endian value into the internal field -// value representation in constant time. It 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 { - // Pack the 256 total bits across the 4 uint64 limbs. - 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]) - - // Since f < 2^256 < 2p (where p is the secp256k1 prime), the max possible - // number of reductions required is one. Therefore, in the case a reduction - // is needed, it can be performed with a single subtraction of p. - // - // Since p must only conditionally be subtracted when f ≥ p, the following - // handles it in constant time by always calculating s = f - p and selecting - // the correct case via a constant time select. - - // Subtract p with borrow propagation. borrow is set iff f < p. - // - // In other words, the input overflowed (≥ p) when f - p does NOT borrow. - // - // s = f - p - 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) - - // Constant-time select. - // - // Set f = f when f < p (aka borrow is set). Otherwise f = s = f - p. - f.n[0] = constantTimeSelect64(borrow, f.n[0], s0) - f.n[1] = constantTimeSelect64(borrow, f.n[1], s1) - f.n[2] = constantTimeSelect64(borrow, f.n[2], s2) - f.n[3] = constantTimeSelect64(borrow, f.n[3], s3) - return uint32(1 - borrow) -} - -// SetByteSlice interprets the provided slice as a 256-bit big-endian unsigned -// integer (meaning it is truncated to the first 32 bytes), packs it into the -// internal field value representation, and returns whether or not the resulting -// truncated 256-bit integer is greater than or equal to the field prime (aka it -// overflowed) in constant time. -// -// Note that since passing a slice with more than 32 bytes is truncated, it is -// possible that the truncated value is less than the field prime and hence it -// will not be reported as having overflowed in that case. It is up to the -// caller to decide whether it needs to provide numbers of the appropriate size -// or it if is acceptable to use this function with the described truncation and -// overflow behavior. -func (f *FieldVal64) SetByteSlice(b []byte) bool { - var b32 [32]byte - b = b[:constantTimeMin(uint32(len(b)), 32)] - copy(b32[:], b32[:32-len(b)]) - copy(b32[32-len(b):], b) - result := f.SetBytes(&b32) - zeroArray32(&b32) - return result != 0 -} - -// Normalize is a no-op. It is provided to keep API parity with [FieldVal]. -func (f *FieldVal64) Normalize() {} - -// 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, [FieldVal64.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) { - // Unpack the 256 total bits from the 4 uint64 limbs. - binary.BigEndian.PutUint64(b[0:8], f.n[3]) - binary.BigEndian.PutUint64(b[8:16], f.n[2]) - binary.BigEndian.PutUint64(b[16:24], f.n[1]) - binary.BigEndian.PutUint64(b[24:32], f.n[0]) -} - -// PutBytes unpacks the field value to a 32-byte big-endian value using the -// passed byte array in constant time. -// -// There is a similar function, [FieldVal64.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 [FieldVal64.Bytes], which unpacks the field -// value into a new array and returns that which can sometimes be more ergonomic -// in applications that aren't concerned about an additional copy. -func (f *FieldVal64) PutBytes(b *[32]byte) { - f.PutBytesUnchecked(b[:]) -} - -// Bytes unpacks the field value to a 32-byte big-endian value in constant time. -// -// See [FieldVal64.PutBytes] and [FieldVal64.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.PutBytesUnchecked(b[:]) - return &b -} - -// IsZeroBit returns 1 when the field value is equal to zero or 0 otherwise in -// constant time. -// -// Note that a bool is not used here because it is not possible in Go to convert -// from a bool to numeric value in constant time and many constant-time -// operations require a numeric value. See IsZero for the version that returns -// a bool. -func (f *FieldVal64) IsZeroBit() uint32 { - return constantTimeEq64(f.n[0]|f.n[1]|f.n[2]|f.n[3], 0) -} - -// IsZero returns whether or not the field value is equal to zero in constant -// time. -func (f *FieldVal64) IsZero() bool { - return (f.n[0] | f.n[1] | f.n[2] | f.n[3]) == 0 -} - -// IsOneBit returns 1 when the field value is equal to one or 0 otherwise in -// constant time. -// -// Note that a bool is not used here because it is not possible in Go to convert -// from a bool to numeric value in constant time and many constant-time -// operations require a numeric value. See [FieldVal64.IsOne] for the version -// that returns a bool. -func (f *FieldVal64) IsOneBit() uint32 { - // The value can only be one if the single lowest significant bit is set in - // the first word and no other bits are set in any of the other words. - // This is a constant time implementation. - return constantTimeEq64((f.n[0]^1)|f.n[1]|f.n[2]|f.n[3], 0) -} - -// IsOne returns whether or not the field value is equal to one in constant -// time. -func (f *FieldVal64) IsOne() bool { - // The value can only be one if the single lowest significant bit is set in - // the first word and no other bits are set in any of the other words. - // This is a constant time implementation. - return ((f.n[0] ^ 1) | f.n[1] | f.n[2] | f.n[3]) == 0 -} - -// IsOddBit returns 1 when the field value is an odd number or 0 otherwise in -// constant time. -// -// Note that a bool is not used here because it is not possible in Go to convert -// from a bool to numeric value in constant time and many constant-time -// operations require a numeric value. See [FieldVal64.IsOdd] for the version -// that returns a bool. -func (f *FieldVal64) IsOddBit() uint32 { - // Only odd numbers have the bottom bit set. - return uint32(f.n[0] & 1) -} - -// IsOdd returns whether or not the field value is an odd number in constant -// time. -func (f *FieldVal64) IsOdd() bool { - // Only odd numbers have the bottom bit set. - return f.n[0]&1 == 1 -} - -// Equals returns whether or not the two field values are the same in constant -// time. -func (f *FieldVal64) Equals(val *FieldVal64) bool { - // Xor only sets bits when they are different, so the two field values - // can only be the same if no bits are set after xoring each word. - // This is a constant time implementation. - return ((f.n[0] ^ val.n[0]) | (f.n[1] ^ val.n[1]) | (f.n[2] ^ val.n[2]) | - (f.n[3] ^ val.n[3])) == 0 -} - -// NegateVal negates the passed value and stores the result in f in constant -// time. The ignored parameter exists to keep API parity with [FieldVal]. -// -// 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, _ uint32) *FieldVal64 { - // Since the value is already in the range 0 ≤ val < p, where p is the - // secp256k1 prime, negation modulo p is just p - val. This implies that - // the result will always be in the desired range with the sole exception of - // 0 because p - 0 = p itself. - // - // The following handles that case in constant time by creating a mask that - // is all 0s in the case the value being negated is 0 and all 1s otherwise - // and then bitwise ands that mask with each word of the prime. - - // Subtract val from 0. borrow is set iff val != 0. - // - // t = 0 - val = -val - 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) - - // Mask the prime with the borrow (p when val != 0, else 0). - // - // The upper limbs of the prime are all 1s, so there is no need to mask them - // given they are equal to the mask for both cases. - mask := -borrow - maskedPrime0 := field64Prime0 & mask - - // Add 0 when val == 0 or p when val != 0. The result is either: - // - // val == 0: f = 0 + 0 = 0 - // val != 0: f = -val + p = p - val - var carry uint64 - f.n[0], carry = bits.Add64(t0, maskedPrime0, 0) - f.n[1], carry = bits.Add64(t1, mask, carry) - f.n[2], carry = bits.Add64(t2, mask, carry) - f.n[3], _ = bits.Add64(t3, mask, carry) - return f -} - -// Negate negates the field value in constant time. The existing field value is -// modified. The ignored parameter exists to keep API parity with [FieldVal]. -// -// 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(_ uint32) *FieldVal64 { - return f.NegateVal(f, 0) -} - -// 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 { - return f.Add(new(FieldVal64).SetInt(ui)) -} - -// Add adds the passed value to the existing field value and stores the result -// in f in constant time. -// -// The field value is returned to support chaining. This enables syntax like: -// f.Add(f2).AddInt(1) so that f = f + f2 + 1. -func (f *FieldVal64) Add(val *FieldVal64) *FieldVal64 { - return f.Add2(f, val) -} - -// Add2 adds the passed two field values together and stores the result in f in -// constant time. -// -// The field value is returned to support chaining. This enables syntax like: -// f3.Add2(f, f2).AddInt(1) so that f3 = f + f2 + 1. -func (f *FieldVal64) Add2(a, b *FieldVal64) *FieldVal64 { - // Since both values are already in the range 0 ≤ val < p (where p is the - // secp256k1 prime), the maximum possible result is < 2p - 1. So a maximum - // of one subtraction of p is required in the worst case. - // - // Since p must only conditionally be subtracted when a+b ≥ p, the following - // handles it in constant time by calculating both t = a+b and s = a+b - p - // and selecting the correct case via a constant time select. - - // Add with carry propagation. overflow is set iff t = a+b ≥ 2^256. - // - // t = a + b - var t0, t1, t2, t3, overflow, carry uint64 - 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) - - // Subtract p with borrow propagation. borrow is set iff t = a+b < p. - // - // s = t - p = a+b - 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) - - // Constant-time select. - // - // Set f = t = a+b only when there was no overflow and t < p (borrow set). - // Otherwise f = s = a+b - p. - cond := (1 - overflow) & borrow - f.n[0] = constantTimeSelect64(cond, t0, s0) - f.n[1] = constantTimeSelect64(cond, t1, s1) - f.n[2] = constantTimeSelect64(cond, t2, s2) - f.n[3] = constantTimeSelect64(cond, t3, s3) - return f -} - -// MulBy2 multiplies the field value by 2 and stores the result in f in constant -// time. -// -// This method is optimized to provide a significant speed advantage over the -// more general [FieldVal64.MulInt]. -// -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy2().Add(f2) so that f = 2 * f + f2. -func (f *FieldVal64) MulBy2() *FieldVal64 { - return f.Add(f) -} - -// MulBy3 multiplies the field value by 3 and stores the result in f in constant -// time. -// -// This method is optimized to provide a significant speed advantage over the -// more general [FieldVal64.MulInt]. -// -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy3().Add(f2) so that f = 3 * f + f2. -func (f *FieldVal64) MulBy3() *FieldVal64 { - var orig FieldVal64 - orig.Set(f) - return f.MulBy2().Add(&orig) -} - -// MulBy4 multiplies the field value by 4 and stores the result in f in constant -// time. -// -// This method is optimized to provide a significant speed advantage over the -// more general [FieldVal64.MulInt]. -// -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy4().Add(f2) so that f = 4 * f + f2. -func (f *FieldVal64) MulBy4() *FieldVal64 { - return f.MulBy2().MulBy2() -} - -// MulBy8 multiplies the field value by 8 and stores the result in f in constant -// time. -// -// This method is optimized to provide a significant speed advantage over the -// more general [FieldVal64.MulInt]. -// -// The field value is returned to support chaining. This enables syntax like: -// f.MulBy8().Add(f2) so that f = 8 * f + f2. -func (f *FieldVal64) MulBy8() *FieldVal64 { - return f.MulBy4().MulBy2() -} - -// MulInt multiplies the field value by the passed int and stores the result in -// f in constant time. -// -// Callers should prefer using the faster specialized methods for multiplying by -// 2, 3, 4, and 8, as they are commonly used in curve equations. -// -// See [FieldVal64.MulBy2], [FieldVal64.MulBy3], [FieldVal64.MulBy4], and -// [FieldVal64.MulBy8] for the aforementioned optimized methods. -// -// The field value is returned to support chaining. This enables syntax like: -// f.MulInt(15).Add(f2) so that f = 15 * f + f2. -func (f *FieldVal64) MulInt(val uint8) *FieldVal64 { - return f.Mul(new(FieldVal64).SetInt(uint16(val))) -} - -// Mul multiplies the passed value to the existing field value and stores the -// result in f in constant time. -// -// The field value is returned to support chaining. This enables syntax like: -// f.Mul(f2).AddInt(1) so that f = (f * f2) + 1. -func (f *FieldVal64) Mul(val *FieldVal64) *FieldVal64 { - return f.Mul2(f, val) -} - -// Mul2 multiplies the passed two field values together and stores the result in -// f in constant time. -// -// The field value is returned to support chaining. This enables syntax like: -// f3.Mul2(f, f2).AddInt(1) so that f3 = (f * f2) + 1. -func (f *FieldVal64) Mul2(a, b *FieldVal64) *FieldVal64 { - field64Mul(&f.n, &a.n, &b.n) - return f -} - -// 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 { - // This uses the Tonelli-Shanks method for calculating the square root of - // the value when it exists. The key principles of the method follow. - // - // Fermat's little theorem states that for a nonzero number 'a' and prime - // 'p', a^(p-1) ≡ 1 (mod p). - // - // Further, Euler's criterion states that an integer 'a' has a square root - // (aka is a quadratic residue) modulo a prime if a^((p-1)/2) ≡ 1 (mod p) - // and, conversely, when it does NOT have a square root (aka 'a' is a - // non-residue) a^((p-1)/2) ≡ -1 (mod p). - // - // This can be seen by considering that Fermat's little theorem can be - // written as (a^((p-1)/2) - 1)(a^((p-1)/2) + 1) ≡ 0 (mod p). Therefore, - // one of the two factors must be 0. Then, when a ≡ x^2 (aka 'a' is a - // quadratic residue), (x^2)^((p-1)/2) ≡ x^(p-1) ≡ 1 (mod p) which implies - // the first factor must be zero. Finally, per Lagrange's theorem, the - // non-residues are the only remaining possible solutions and thus must make - // the second factor zero to satisfy Fermat's little theorem implying that - // a^((p-1)/2) ≡ -1 (mod p) for that case. - // - // The Tonelli-Shanks method uses these facts along with factoring out - // powers of two to solve a congruence that results in either the solution - // when the square root exists or the square root of the negation of the - // value when it does not. In the case of primes that are ≡ 3 (mod 4), the - // possible solutions are r = ±a^((p+1)/4) (mod p). Therefore, either r^2 ≡ - // a (mod p) is true in which case ±r are the two solutions, or r^2 ≡ -a - // (mod p) in which case 'a' is a non-residue and there are no solutions. - // - // The secp256k1 prime is ≡ 3 (mod 4), so this result applies. - // - // In other words, calculate a^((p+1)/4) and then square it and check it - // against the original value to determine if it is actually the square - // root. - // - // In order to efficiently compute a^((p+1)/4), (p+1)/4 needs to be split - // into a sequence of squares and multiplications that minimizes the number - // of multiplications needed (since they are more costly than squarings). - // - // The secp256k1 prime + 1 / 4 is 2^254 - 2^30 - 244. In binary, that is: - // - // 00111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 10111111 11111111 11111111 00001100 - // - // Notice that can be broken up into three windows of consecutive 1s (in - // order of least to most significant) as: - // - // 6-bit window with two bits set (bits 4, 5, 6, 7 unset) - // 23-bit window with 22 bits set (bit 30 unset) - // 223-bit window with all 223 bits set - // - // Thus, the groups of 1 bits in each window forms the set: - // S = {2, 22, 223}. - // - // The strategy is to calculate a^(2^n - 1) for each grouping via an - // addition chain with a sliding window. - // - // The addition chain used is (credits to Peter Dettman): - // (0,0),(1,0),(2,2),(3,2),(4,1),(5,5),(6,6),(7,7),(8,8),(9,7),(10,2) - // => 2^1 2^[2] 2^3 2^6 2^9 2^11 2^[22] 2^44 2^88 2^176 2^220 2^[223] - // - // This has a cost of 254 field squarings and 13 field multiplications. - 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) -} - -// Square squares the field value in constant time. The existing field value is -// modified. -// -// The field value is returned to support chaining. This enables syntax like: -// f.Square().Mul(f2) so that f = f^2 * f2. -func (f *FieldVal64) Square() *FieldVal64 { - return f.SquareVal(f) -} - -// SquareVal squares the passed value and stores the result in f in constant -// time. -// -// The field value is returned to support chaining. This enables syntax like: -// f3.SquareVal(f).Mul(f) so that f3 = f^2 * f = f^3. -func (f *FieldVal64) SquareVal(val *FieldVal64) *FieldVal64 { - field64Square(&f.n, &val.n) - return f -} - -// field64Mul512 sets t = x * y as an unreduced 512-bit product via a row-by-row -// schoolbook multiply. -func field64Mul512(t *[8]uint64, x, y *[4]uint64) { - // The intermediate bounds and carry assumptions used by this algorithm have - // been formally verified. The verification artifacts are available in - // internal/proofs. - - a0, a1, a2, a3 := x[0], x[1], x[2], x[3] - b0, b1, b2, b3 := y[0], y[1], y[2], y[3] - - var c uint64 - - // Row 0: p0..p4 = a * b0. - // - // Note that since h3 is the upper 64 bits of the product of two uint64s: - // h3 ≤ floor((2^64-1)^2 / 2^64) = 2^64 - 2 - // - // Without any other considerations, c ≤ 1, so a loose bound is: - // p4 ≤ h3 + 1 = 2^64 - 1 < 2^64 - // - // This already shows that the carryless add in p4 is safe, however, a tight - // upper bound is more useful to prove no overflow is possible in the upper - // words of the subsequent rows. - // - // Claim: p4 ≤ 2^64 - 2 - // - // Consider the row product A*b, where A ≤ 2^256 - 1, b ≤ 2^64 - 1, then: - // A*b ≤ (2^256 - 1)(2^64 - 1) = 2^320 - 2^256 - 2^64 + 1 - // - // Next, expressing the product in base 2^256 gives: - // A*b = p4*2^256 + qlow - // - // Where qlow is the low 256 bits of the product and p4 is the integer - // quotient: - // p4 = floor(A*b / 2^256) - // qlow = A*b (mod 2^256) - // - // Finally, bound the quotient: - // p4 = floor(A*b / 2^256) - // ≤ floor((2^320 - 2^256 - 2^64 + 1) / 2^256) - // = floor(2^64 - 1 - 2^(-192) + 2^(-256)) - // ≤ 2^64 - 2 - // - // So, p4 ≤ 2^64 - 2. - 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. - // - // Per row 0 above, the tight bound on q4 for this row is: - // q4 ≤ 2^64 - 2 - // - // Since c ≤ 1: - // p5 ≤ q4 + 1 = 2^64 - 1 < 2^64 - // - // So, the carryless add in p5 is safe. - 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. - // - // The same bounds calculation as row 1 applies. - 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. - // - // The same bounds calculation as row 1 applies. - 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) { - // The intermediate bounds and carry assumptions used by this algorithm have - // been formally verified. The verification artifacts are available in - // internal/proofs. - - a0, a1, a2, a3 := a[0], a[1], a[2], a[3] - - var c uint64 - - // Off-diagonal upper-triangle products (not yet doubled). - // - // Note that since h03 is the upper 64 bits of the product of two uint64s: - // h03 ≤ floor((2^64-1)^2 / 2^64) = 2^64 - 2 - // - // Then, because c ≤ 1, a loose bound is: - // p4 ≤ h03 + 1 = 2^64 - 1 < 2^64 - // - // Therefore, it is safe to discard the carry. - p2, p1 := bits.Mul64(a0, a1) - h02, l02 := bits.Mul64(a0, a2) - h03, l03 := bits.Mul64(a0, a3) - 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 - - // The p5 carry is safe to discard because p5 + h13 + c ≤ 2^64 - 1 (where c - // is the carry from p4 + l13). - // - // A full proof involves case analysis that is omitted here since the - // impossibility of the carry is formally proven in internal/proofs, but the - // key point is that the only way the final add could have a carry is if all - // 3 of the following conditions were simultaneously true: - // - // 1) p5_old = 1 (the carry from the earlier chain, so ≤ 1) - // 2) h13 = 2^64 - 2 (h13 ≤ 2^64 - 2 as proven previously) - // 3) c = 1 (implies p4 + l13 ≥ 2^64) - // - // However, that combination of conditions is impossible because in order - // for condition 2 to be true, a1 = a3 = 2^64 - 1, in which case l13 = 1 - // and so in order for condition 3 to also be true, p4 = 2^64 - 1. But then - // the combination of those conditions forces p5_old = 0. - h13, l13 := bits.Mul64(a1, a3) - p4, c = bits.Add64(p4, l13, 0) - p5, _ = bits.Add64(p5, h13, c) - - // Similarly, the p6 carry is safe to discard because, per above: - // h23 ≤ 2^64 - 2 - // - // Then, again c ≤ 1, so the same loose bound applies: - // p6 ≤ h23 + 1 = 2^64 - 1 < 2^64 - 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. - // - // The carry on the final add is safe to discard because a < p < 2^256, so: - // (2^256 - 1)^2 = 2^512 - 2^257 + 1 < 2^512 - 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 and stores the result in r. -func field64Reduce512(r *[4]uint64, x *[8]uint64) { - // This algorithm has been formally verified, including its intermediate - // bounds, carry assumptions, and functional correctness. The verification - // artifacts are available in internal/proofs. - - // Per [HAC] section 14.3.4: Reduction method of moduli of special form, - // when the modulus is of the special form m = b^t - c, highly efficient - // reduction can be achieved. While [HAC] only presents the algorithm and - // does not call it out by name or provide the mathematical justification, - // the underlying technique is known as Crandall reduction and is often - // presented as 2^k - c. It is easy to see they are equivalent by setting - // b = 2 and t = k. - // - // The secp256k1 prime is 2^256 - 4294968273, so it fits this criteria where - // k=256, and c = 4294968273 = 2^32 + 977. - // - // Crandall reduction works by taking advantage of the fact that if a prime - // is of the form 2^k - c, then 2^k - c ≡ 0 (mod p), so 2^k ≡ c (mod p). In - // other words, every multiple of 2^k is equivalent to adding c when working - // modulo p. - // - // Since the 512-bit value to reduce is tightly packed into uint64s, the - // upper 4 limbs are all multiples of 2^256. Therefore, reducing modulo the - // prime is equivalent to multiplying those upper limbs by c and adding the - // result to the corresponding lower 4 limbs while propagating the carries. - // - // For the specific case of the secp256k1 prime, a max of 3 reductions are - // required because c is 33 bits and so the first round will reduce from 512 - // bits to a max of 256 + 33 = 289 bits and the second round will reduce to - // within 2p. Then, a conditional subtraction of p handles the final - // reduction. - - var t0, t1, t2, t3, t4, h, lo, hi, carry uint64 - - h, t0 = bits.Mul64(x[4], field64PrimeComplement) - - // Note that since hi is the upper 64 bits of the product of a uint64 with - // c and c < 2^33: - // hi ≤ floor((2^64-1)(2^33 - 1) / 2^64) = 2^33 - 2 - // - // Then, because carry ≤ 1, a loose bound for h is: - // h ≤ hi + 1 = 2^33 - 1 < 2^64 - // - // Therefore, it is safe to discard the carry and the same applies to the - // next two limbs (second h and first t4). - 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) - - // The carryless add into t4 below is safe because, per the bound above, - // t4 ≤ 2^33 - 1 and carry ≤ 1, so: - // t4 ≤ (2^33 - 1) + 1 = 2^33 < 2^64 - 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 - - // The value now fits in 289 bits, so reduce it again. Only the fifth limb - // (t4) needs to be considered since all of the higher limbs are ≥ 320 bits - // and thus guaranteed to be 0. - 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, 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) - r[0] = constantTimeSelect64(borrow, t0, s0) - r[1] = constantTimeSelect64(borrow, t1, s1) - r[2] = constantTimeSelect64(borrow, t2, s2) - r[3] = constantTimeSelect64(borrow, t3, s3) -} - -// 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 { - // Fermat's little theorem states that for a nonzero number 'a' and prime - // 'p', a^(p-1) ≡ 1 (mod p). Multiplying both sides of the equation by the - // multiplicative inverse a^-1 yields a^(p-2) ≡ a^-1 (mod p). Thus, a^(p-2) - // is the multiplicative inverse. - // - // In order to efficiently compute a^(p-2), p-2 needs to be split into a - // sequence of squares and multiplications that minimizes the number of - // multiplications needed (since they are more costly than squarings). - // Intermediate results are saved and reused as well. - // - // The secp256k1 prime - 2 is 2^256 - 4294968275. In binary, that is: - // - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111111 - // 11111111 11111111 11111111 11111110 - // 11111111 11111111 11111100 00101101 - // - // Notice that can be broken up into five windows of consecutive 1s (in - // order of least to most significant) as: - // - // 2-bit window with 1 bit set (bit 1 unset) - // 3-bit window with 2 bits set (bit 4 unset) - // 5-bit window with 1 bit set (bits 6, 7, 8, 9 unset) - // 23-bit window with 22 bits set (bit 32 unset) - // 223-bit window with all 223 bits set - // - // Thus, the groups of 1 bits in each window forms the set: - // S = {1, 2, 22, 223}. - // - // The strategy is to calculate a^(2^n - 1) for each grouping via an - // addition chain with a sliding window. - // - // The addition chain used is (credits to Peter Dettman): - // (0,0),(1,0),(2,2),(3,2),(4,1),(5,5),(6,6),(7,7),(8,8),(9,7),(10,2) - // => 2^[1] 2^[2] 2^3 2^6 2^9 2^11 2^[22] 2^44 2^88 2^176 2^220 2^[223] - // - // This has a cost of 255 field squarings and 15 field multiplications. - var a, a2, a3, a6, a9, a11, a22, a44, a88, a176, a220, a223 FieldVal64 - a.Set(f) - 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 - 4194305) - f.Square().Square().Square().Square().Square() // f = a^(2^251 - 134217760) - f.Mul(&a) // f = a^(2^251 - 134217759) - f.Square().Square().Square() // f = a^(2^254 - 1073742072) - f.Mul(&a2) // f = a^(2^254 - 1073742069) - f.Square().Square() // f = a^(2^256 - 4294968276) - return f.Mul(&a) // f = a^(2^256 - 4294968275) = a^(p-2) -} - -// 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 { - // The secp256k1 prime is equivalent to 2^256 - 4294968273 and the group - // order is 2^256 - 432420386565659656852420866394968145599. Thus, the - // prime minus the group order is: - // 432420386565659656852420866390673177326 - // - // In hex that is: - // 0x00000000 00000000 00000000 00000001 45512319 50b75fc4 402da172 2fc9baee - // - // Converting that to field representation (base 2^64) is: - // - // n[0] = 0x402da1722fc9baee - // n[1] = 0x4551231950b75fc4 - // n[2] = 0x0000000000000001 - // n[3] = 0x0000000000000000 - // - // This can be verified with the following test code: - // pMinusN := new(big.Int).Sub(curveParams.P, curveParams.N) - // var fv FieldVal64 - // fv.SetByteSlice(pMinusN.Bytes()) - // t.Logf("%x", fv.n) - // - // Outputs: [402da1722fc9baee 4551231950b75fc4 1 0] - const ( - field64PMinusN0 = 0x402da1722fc9baee - field64PMinusN1 = 0x4551231950b75fc4 - field64PMinusN2 = 0x0000000000000001 - field64PMinusN3 = 0x0000000000000000 - ) - - // The goal is to return true when the value is greater than or equal to the - // field prime minus the group order. That is, return true when f ≥ p - n, - // which is trivially rearranged to f - (p - n) ≥ 0. - // - // In other words, the condition is met iff subtracting (p - n) from f is - // non-negative (aka there was no borrow). - 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 -} diff --git a/dcrec/secp256k1/field64_amd64.go b/dcrec/secp256k1/field64_amd64.go deleted file mode 100644 index bf068120f4..0000000000 --- a/dcrec/secp256k1/field64_amd64.go +++ /dev/null @@ -1,71 +0,0 @@ -// 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. - -//go:build amd64 && !purego - -package secp256k1 - -// field64UseADX reports whether the CPU supports both BMI2 (MULX) and ADX (ADCX/ADOX). -var field64UseADX = func() bool { - const ( - eaxMax = 0 - eaxExt = 7 - bmi2Bit = 8 - adxBit = 19 - ) - - // Leaf 0x07 is only valid when the CPU reports it within the max input. - eax, _, _, _ := field64CPUID(eaxMax, 0) - if eax < eaxExt { - return false - } - - _, ebx, _, _ := field64CPUID(eaxExt, 0) - hasBMI2 := ebx>>bmi2Bit&1 == 1 - hasADX := ebx>>adxBit&1 == 1 - return hasBMI2 && hasADX -}() - -//go:noescape -func field64MulADX(r *[4]uint64, a, b *[4]uint64) - -//go:noescape -func field64SquareADX(r *[4]uint64, a *[4]uint64) - -// field64CPUID provides access to the CPUID opcode. -// -//go:noescape -func field64CPUID(eaxIn, ecxIn uint32) (eax, ebx, ecx, edx uint32) - -// field64Mul sets r = a * b (mod p) -func field64Mul(r *[4]uint64, a, b *[4]uint64) { - if field64UseADX { - field64MulADX(r, a, b) - return - } - field64MulGeneric(r, a, b) -} - -// field64Square sets r = a^2 (mod p) -func field64Square(r *[4]uint64, a *[4]uint64) { - if field64UseADX { - field64SquareADX(r, a) - return - } - field64SquareGeneric(r, a) -} - -// field64MulGeneric sets r = a * b (mod p) -func field64MulGeneric(r *[4]uint64, a, b *[4]uint64) { - var product [8]uint64 - field64Mul512(&product, a, b) - field64Reduce512(r, &product) -} - -// field64SquareGeneric sets r = a^2 (mod p) -func field64SquareGeneric(r *[4]uint64, a *[4]uint64) { - var product [8]uint64 - field64Square512(&product, a) - field64Reduce512(r, &product) -} diff --git a/dcrec/secp256k1/field64_bench_amd64_test.go b/dcrec/secp256k1/field64_bench_amd64_test.go deleted file mode 100644 index adb4c5225c..0000000000 --- a/dcrec/secp256k1/field64_bench_amd64_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// 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. - -//go:build amd64 && !purego - -package secp256k1 - -import ( - "testing" -) - -func BenchmarkField64MulAMD64(b *testing.B) { - a := mustFieldVal64("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab").n - c := mustFieldVal64("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca").n - var r [4]uint64 - - b.Run("Generic", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - field64MulGeneric(&r, &a, &c) - } - }) - if field64UseADX { - b.Run("MULX/ADX", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - field64MulADX(&r, &a, &c) - } - }) - } else { - b.Log("Skipping MULX/ADX bench (disabled or no instruction set support)") - } -} - -func BenchmarkField64SquareAMD64(b *testing.B) { - a := mustFieldVal64("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca").n - var r [4]uint64 - - b.Run("Generic", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - field64SquareGeneric(&r, &a) - } - }) - if field64UseADX { - b.Run("MULX/ADX", func(b *testing.B) { - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - field64SquareADX(&r, &a) - } - }) - } else { - b.Log("Skipping MULX/ADX bench (disabled or no instruction set support)") - } -} diff --git a/dcrec/secp256k1/field64_bench_test.go b/dcrec/secp256k1/field64_bench_test.go deleted file mode 100644 index cf24ca9846..0000000000 --- a/dcrec/secp256k1/field64_bench_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// 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" -) - -// BenchmarkField64Negate benchmarks calculating the additive inverse of an -// unsigned 256-bit big-endian integer modulo the field prime with [FieldVal64]. -func BenchmarkField64Negate(b *testing.B) { - // The function is constant time so any value is fine. - valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(valHex) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var result FieldVal64 - _ = result.NegateVal(f, 0) - } -} - -// BenchmarkField64Add benchmarks adding two unsigned 256-bit big-endian -// integers modulo the field prime with [FieldVal64]. -func BenchmarkField64Add(b *testing.B) { - a := mustFieldVal64("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab") - c := mustFieldVal64("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var sum FieldVal64 - sum.Add2(a, c) - } -} - -// BenchmarkField64MulBy2 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 2 with [FieldVal64.MulBy2]. -func BenchmarkField64MulBy2(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(fHex) - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - f.MulBy2() - } -} - -// BenchmarkField64MulBy3 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 3 with [FieldVal64.MulBy3]. -func BenchmarkField64MulBy3(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(fHex) - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - f.MulBy3() - } -} - -// BenchmarkField64MulBy4 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 4 with [FieldVal64.MulBy4]. -func BenchmarkField64MulBy4(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(fHex) - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - f.MulBy4() - } -} - -// BenchmarkField64MulBy8 benchmarks multiplying an unsigned 256-bit big-endian -// integer by 8 with [FieldVal64.MulBy8]. -func BenchmarkField64MulBy8(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(fHex) - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - f.MulBy8() - } -} - -// BenchmarkFieldMulInt benchmarks multiplying an unsigned 256-bit big-endian -// integer by small integers with [FieldVal64.MulInt]. -func BenchmarkField64MulInt(b *testing.B) { - fHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(fHex) - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - f.MulInt(2) - } -} - -// BenchmarkField64Mul benchmarks multiplying two unsigned 256-bit big-endian -// integers modulo the field prime with [FieldVal64]. -func BenchmarkField64Mul(b *testing.B) { - a := mustFieldVal64("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab") - c := mustFieldVal64("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var prod FieldVal64 - prod.Mul2(a, c) - } -} - -// 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 := mustFieldVal64(valHex) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var result FieldVal64 - _ = result.SquareRootVal(f) - } -} - -// BenchmarkField64Square benchmarks squaring a 256-bit big-endian integer -// modulo the field prime with [FieldVal64]. -func BenchmarkField64Square(b *testing.B) { - a := mustFieldVal64("16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca") - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - var sq FieldVal64 - sq.SquareVal(a) - } -} - -// BenchmarkField64Inverse benchmarks calculating the multiplicative inverse of -// an unsigned 256-bit big-endian integer modulo the field prime with -// [FieldVal64]. -func BenchmarkField64Inverse(b *testing.B) { - // The function is constant time so any value is fine. - valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(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 -// [FieldVal64]. -func BenchmarkField64IsGtOrEqPrimeMinusOrder(b *testing.B) { - // The function is constant time so any value is fine. - valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca" - f := mustFieldVal64(valHex) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = f.IsGtOrEqPrimeMinusOrder() - } -} diff --git a/dcrec/secp256k1/field64_generic.go b/dcrec/secp256k1/field64_generic.go deleted file mode 100644 index bea665bc69..0000000000 --- a/dcrec/secp256k1/field64_generic.go +++ /dev/null @@ -1,21 +0,0 @@ -// 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. - -//go:build !amd64 || purego - -package secp256k1 - -// 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) -} diff --git a/dcrec/secp256k1/fieldwrap.go b/dcrec/secp256k1/fieldwrap.go new file mode 100644 index 0000000000..dd46aa8f1c --- /dev/null +++ b/dcrec/secp256k1/fieldwrap.go @@ -0,0 +1,584 @@ +// Copyright (c) 2015-2026 The Decred developers +// Copyright (c) 2013-2026 Dave Collins +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package secp256k1 + +import ( + "github.com/decred/dcrd/dcrec/secp256k1/v4/field10x26" +) + +// ---------------------------------------------------------------------------- +// NOTE: [FieldVal] is intentionally a wrapper as opposed to a direct type +// alias despite it creating some boilerplate. +// +// Some field implementations have more restrictive semantics, so the publicly +// available type must adhere to the most restrictive among all supported ones. +// +// Using a type alias would end up showing the documentation for the +// architecture-specific implementation that is ultimately selected which would +// very likely lead to misleading documentation and incorrect usage. +// ---------------------------------------------------------------------------- + +// fieldImpl defines the concrete finite field implementation. This will +// ultimately allow selection of different concrete implementations. +type fieldImpl = field10x26.Element + +// FieldVal implements optimized fixed-precision arithmetic over the +// secp256k1 finite field. This means all arithmetic is performed modulo +// +// 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f +// +// WARNING: Since it is so important for the field arithmetic to be extremely +// fast for high performance crypto, this type does not perform any validation +// of documented preconditions where it ordinarily would. As a result, it is +// IMPERATIVE for callers to understand some key concepts that are described +// below and ensure the methods are called with the necessary preconditions that +// each method is documented with. For example, some methods only give the +// correct result if the field element is normalized and others require the +// field elements involved to have a maximum magnitude and THERE ARE NO EXPLICIT +// CHECKS TO ENSURE THOSE PRECONDITIONS ARE SATISFIED. This does, +// unfortunately, make the type more difficult to use correctly and while it is +// typically preferable to ensure all state and input is valid for most code, +// this is a bit of an exception because those extra checks really add up in +// what ends up being critical hot paths. +// +// The first key concept when working with this type is normalization. In order +// to avoid the need to propagate a ton of carries, the internal representation +// provides additional overflow bits for each limb of the overall 256-bit value. +// This means that there are multiple internal representations for the same +// value and, as a result, any methods that rely on comparison of the value, +// such as equality and oddness determination, require the caller to provide a +// normalized field element. +// +// The second key concept when working with this type is magnitude. As +// previously mentioned, the internal representation provides additional +// overflow bits which means that the more math operations that are performed on +// the field element between normalizations, the more those overflow bits +// accumulate. The magnitude is effectively that maximum possible number of +// those overflow bits that could possibly be required as a result of a given +// operation. Since there are only a limited number of overflow bits available, +// this implies that the max possible magnitude MUST be tracked by the caller +// and the caller MUST normalize the field element if a given operation would +// cause the magnitude of the result to exceed the max allowed value. +// +// IMPORTANT: The max allowed magnitude of a field element is 32. +type FieldVal struct { + impl fieldImpl +} + +// String returns the field element as a normalized human-readable hex string. +// +// Preconditions: None +// Output Normalized: Field is not modified -- same as input element +// Output Max Magnitude: Field is not modified -- same as input element +func (f FieldVal) String() string { + return f.impl.String() +} + +// Zero sets the field element to zero in constant time. A newly created field +// element is already set to zero. This function can be useful to clear an +// existing field element for reuse. +// +// Preconditions: None +// Output Normalized: Yes +// Output Max Magnitude: 1 +func (f *FieldVal) Zero() { + f.impl.Zero() +} + +// Set sets the field element equal to the passed one in constant time. The +// resulting field element will have the same normalization state and magnitude +// as the passed field element. +// +// The field element 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. +// +// Preconditions: None +// Output Normalized: Same as input element +// Output Max Magnitude: Same as input element +func (f *FieldVal) Set(val *FieldVal) *FieldVal { + f.impl.Set(&val.impl) + return f +} + +// SetInt sets the field element to the passed integer in constant time. This +// is a convenience function since it is fairly common to perform arithmetic +// with small native integers. +// +// The field element is returned to support chaining. This enables syntax such +// as f := new(FieldVal).SetInt(2).Mul(f2) so that f = 2 * f2. +// +// Preconditions: None +// Output Normalized: Yes +// Output Max Magnitude: 1 +func (f *FieldVal) SetInt(ui uint16) *FieldVal { + f.impl.SetInt(ui) + return f +} + +// SetBytes packs the passed 32-byte big-endian value into the internal +// representation in constant time. It interprets the provided array as a +// 256-bit big-endian unsigned integer, packs it, 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. +// +// Preconditions: None +// Output Normalized: Yes when no overflow, No otherwise +// Output Max Magnitude: 1 +func (f *FieldVal) SetBytes(b *[32]byte) uint32 { + return f.impl.SetBytes(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 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. +// +// Preconditions: None +// Output Normalized: Yes when no overflow, No otherwise +// Output Max Magnitude: 1 +func (f *FieldVal) SetByteSlice(b []byte) bool { + return f.impl.SetByteSlice(b) +} + +// Normalize converts the internal representation into its canonical +// representation and performs modular reduction over the secp256k1 field prime +// in constant time. +// +// Preconditions: None +// Output Normalized: Yes +// Output Max Magnitude: 1 +func (f *FieldVal) Normalize() *FieldVal { + f.impl.Normalize() + return f +} + +// PutBytesUnchecked unpacks the field element 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, [FieldVal.PutBytes], which unpacks the field +// element 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. +// +// Preconditions: +// - The field element MUST be normalized +// - The target slice MUST have at least 32 bytes available +func (f *FieldVal) PutBytesUnchecked(b []byte) { + f.impl.PutBytesUnchecked(b) +} + +// PutBytes unpacks the field element to a 32-byte big-endian value using the +// passed byte array in constant time. +// +// There is a similar function, [FieldVal.PutBytesUnchecked], which unpacks the +// field element 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 [FieldVal.Bytes], which unpacks the field +// element into a new array and returns that which can sometimes be more +// ergonomic in applications that aren't concerned about an additional copy. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) PutBytes(b *[32]byte) { + f.impl.PutBytes(b) +} + +// Bytes unpacks the field element to a 32-byte big-endian value in constant +// time. +// +// See [FieldVal.PutBytes] and [FieldVal.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. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) Bytes() *[32]byte { + return f.impl.Bytes() +} + +// IsZeroBit returns 1 when the field element 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 [FieldVal.IsZero] for the version +// that returns a bool. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsZeroBit() uint32 { + return f.impl.IsZeroBit() +} + +// IsZero returns whether or not the field element is equal to zero in constant +// time. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsZero() bool { + return f.impl.IsZero() +} + +// IsOneBit returns 1 when the field element 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 [FieldVal.IsOne] for the version +// that returns a bool. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsOneBit() uint32 { + return f.impl.IsOneBit() +} + +// IsOne returns whether or not the field element is equal to one in constant +// time. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsOne() bool { + return f.impl.IsOne() +} + +// IsOddBit returns 1 when the field element 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 [FieldVal.IsOdd] for the version +// that returns a bool. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsOddBit() uint32 { + return f.impl.IsOddBit() +} + +// IsOdd returns whether or not the field element is an odd number in constant +// time. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsOdd() bool { + return f.impl.IsOdd() +} + +// Equals returns whether or not the two field elements are the same in constant +// time. +// +// Preconditions: +// - Both field elements being compared MUST be normalized +func (f *FieldVal) Equals(val *FieldVal) bool { + return f.impl.Equals(&val.impl) +} + +// NegateVal negates the passed element and stores the result in f in constant +// time. The caller must provide the maximum magnitude of the passed field +// element for a correct result. +// +// The field element is returned to support chaining. This enables syntax like: +// f.NegateVal(f2).AddInt(1) so that f = -f2 + 1. +// +// Preconditions: +// - The max magnitude of the input field element MUST be 31 +// Output Normalized: No +// Output Max Magnitude: Input magnitude + 1 +func (f *FieldVal) NegateVal(val *FieldVal, magnitude uint32) *FieldVal { + f.impl.NegateVal(&val.impl, magnitude) + return f +} + +// Negate negates the field element in constant time. The existing field +// element is modified. The caller must provide the maximum magnitude of the +// field element for a correct result. +// +// The field element is returned to support chaining. This enables syntax like: +// f.Negate().AddInt(1) so that f = -f + 1. +// +// Preconditions: +// - The max magnitude MUST be 31 +// Output Normalized: No +// Output Max Magnitude: Input magnitude + 1 +func (f *FieldVal) Negate(magnitude uint32) *FieldVal { + f.impl.Negate(magnitude) + return f +} + +// AddInt adds the passed integer to the existing field element 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 element is returned to support chaining. This enables syntax like: +// f.AddInt(1).Add(f2) so that f = f + 1 + f2. +// +// Preconditions: +// - The field element MUST have a max magnitude of 31 +// - The integer MUST be at most 32767 +// Output Normalized: No +// Output Max Magnitude: Existing field magnitude + 1 +func (f *FieldVal) AddInt(ui uint16) *FieldVal { + f.impl.AddInt(ui) + return f +} + +// Add adds the passed element to the existing field element and stores the +// result in f in constant time. +// +// The field element is returned to support chaining. This enables syntax like: +// f.Add(f2).AddInt(1) so that f = f + f2 + 1. +// +// Preconditions: +// - The sum of the magnitudes of the two field elements MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Sum of the magnitude of the two individual field elements +func (f *FieldVal) Add(val *FieldVal) *FieldVal { + f.impl.Add(&val.impl) + return f +} + +// Add2 adds the passed two field elements together and stores the result in f +// in constant time. +// +// The field element is returned to support chaining. This enables syntax like: +// f3.Add2(f, f2).AddInt(1) so that f3 = f + f2 + 1. +// +// Preconditions: +// - The sum of the magnitudes of the two field elements MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Sum of the magnitude of the two field elements +func (f *FieldVal) Add2(val *FieldVal, val2 *FieldVal) *FieldVal { + f.impl.Add2(&val.impl, &val2.impl) + return f +} + +// MulBy2 multiplies the field element by 2 and stores the result in f in +// constant time. Note that this function can overflow if multiplying the +// element causes any individual limb to overflow uint32. Therefore it is +// important that the caller ensures no overflows will occur before using this +// function. +// +// The field element is returned to support chaining. This enables syntax like: +// f.MulBy2().Add(f2) so that f = 2 * f + f2. +// +// Preconditions: +// - The field element magnitude multiplied by 2 MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Existing field magnitude times 2 +func (f *FieldVal) MulBy2() *FieldVal { + f.impl.MulBy2() + return f +} + +// MulBy3 multiplies the field element by 3 and stores the result in f in +// constant time. Note that this function can overflow if multiplying the +// element causes any individual limb to overflow uint32. Therefore it is +// important that the caller ensures no overflows will occur before using this +// function. +// +// The field element is returned to support chaining. This enables syntax like: +// f.MulBy3().Add(f2) so that f = 3 * f + f2. +// +// Preconditions: +// - The field element magnitude multiplied by 3 MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Existing field element magnitude times 3 +func (f *FieldVal) MulBy3() *FieldVal { + f.impl.MulBy3() + return f +} + +// MulBy4 multiplies the field element by 4 and stores the result in f in +// constant time. Note that this function can overflow if multiplying the +// element causes any individual limb to overflow uint32. Therefore it is +// important that the caller ensures no overflows will occur before using this +// function. +// +// The field element is returned to support chaining. This enables syntax like: +// f.MulBy4().Add(f2) so that f = 4 * f + f2. +// +// Preconditions: +// - The field element magnitude multiplied by 4 MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Existing field element magnitude times 4 +func (f *FieldVal) MulBy4() *FieldVal { + f.impl.MulBy4() + return f +} + +// MulBy8 multiplies the field element by 8 and stores the result in f in +// constant time. Note that this function can overflow if multiplying the +// element causes any individual limb to overflow uint32. Therefore it is +// important that the caller ensures no overflows will occur before using this +// function. +// +// The field element is returned to support chaining. This enables syntax like: +// f.MulBy8().Add(f2) so that f = 8 * f + f2. +// +// Preconditions: +// - The field element magnitude multiplied by 8 MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Existing field element magnitude times 8 +func (f *FieldVal) MulBy8() *FieldVal { + f.impl.MulBy8() + return f +} + +// MulInt multiplies the field element by the passed int and stores the result +// in f in constant time. Note that this function can overflow if multiplying +// the element causes any individual limb to overflow uint32. Therefore it is +// important that the caller ensures no overflows will occur before using this +// function. +// +// Callers should prefer using the specialized methods for multiplying by 2, 3, +// 4, and 8, as they are commonly used in curve equations. +// +// See [FieldVal.MulBy2], [FieldVal.MulBy3], [FieldVal.MulBy4], and +// [FieldVal.MulBy8] for the aforementioned specialized methods. +// +// The field element is returned to support chaining. This enables syntax like: +// f.MulInt(2).Add(f2) so that f = 2 * f + f2. +// +// Preconditions: +// - The field element magnitude multiplied by given val MUST be at most 32 +// Output Normalized: No +// Output Max Magnitude: Existing field element magnitude times the provided integer +func (f *FieldVal) MulInt(val uint8) *FieldVal { + f.impl.MulInt(val) + return f +} + +// Mul multiplies the passed element to the existing field element and stores +// the result in f in constant time. Note that this function can overflow if +// multiplying causes any individual limb to overflow uint32. In practice, this +// means the magnitude of either element involved in the multiplication must be +// at most 8. +// +// The field element is returned to support chaining. This enables syntax like: +// f.Mul(f2).AddInt(1) so that f = (f * f2) + 1. +// +// Preconditions: +// - Both field elements MUST have a max magnitude of 8 +// Output Normalized: No +// Output Max Magnitude: 1 +func (f *FieldVal) Mul(val *FieldVal) *FieldVal { + f.impl.Mul(&val.impl) + return f +} + +// Mul2 multiplies the passed two field elements together and stores the result +// in f in constant time. Note that this function can overflow if multiplying +// any of the individual limbs exceeds a max uint32. In practice, this means +// the magnitude of either element involved in the multiplication must be a max +// of 8. +// +// The field element is returned to support chaining. This enables syntax like: +// f3.Mul2(f, f2).AddInt(1) so that f3 = (f * f2) + 1. +// +// Preconditions: +// - Both input field elements MUST have a max magnitude of 8 +// Output Normalized: No +// Output Max Magnitude: 1 +func (f *FieldVal) Mul2(val *FieldVal, val2 *FieldVal) *FieldVal { + f.impl.Mul2(&val.impl, &val2.impl) + return f +} + +// SquareRootVal either calculates the square root of the passed element when it +// exists or the square root of the negation of the element 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 element itself and false +// when it is for its negation. +// +// Note that this function can overflow if multiplying any of the individual +// limbs exceeds a max uint32. In practice, this means the magnitude of the +// field must be at most 8 to prevent overflow. The magnitude of the result +// will be 1. +// +// Preconditions: +// - The input field element MUST have a max magnitude of 8 +// Output Normalized: No +// Output Max Magnitude: 1 +func (f *FieldVal) SquareRootVal(val *FieldVal) bool { + return f.impl.SquareRootVal(&val.impl) +} + +// Square squares the field element in constant time. The existing field +// element is modified. Note that this function can overflow if multiplying any +// of the individual limbs exceeds a max uint32. In practice, this means the +// magnitude of the field element must be at most 8 to prevent overflow. +// +// The field element is returned to support chaining. This enables syntax like: +// f.Square().Mul(f2) so that f = f^2 * f2. +// +// Preconditions: +// - The field element MUST have a max magnitude of 8 +// Output Normalized: No +// Output Max Magnitude: 1 +func (f *FieldVal) Square() *FieldVal { + f.impl.Square() + return f +} + +// SquareVal squares the passed element and stores the result in f in constant +// time. Note that this function can overflow if multiplying any of the +// individual limbs exceeds a max uint32. In practice, this means the magnitude +// of the field element being squared must be at most 8 to prevent overflow. +// +// The field element is returned to support chaining. This enables syntax like: +// f3.SquareVal(f).Mul(f) so that f3 = f^2 * f = f^3. +// +// Preconditions: +// - The input field element MUST have a max magnitude of 8 +// Output Normalized: No +// Output Max Magnitude: 1 +func (f *FieldVal) SquareVal(val *FieldVal) *FieldVal { + f.impl.SquareVal(&val.impl) + return f +} + +// Inverse finds the modular multiplicative inverse of the field element in +// constant time. The existing field element is modified. +// +// The field element is returned to support chaining. This enables syntax like: +// f.Inverse().Mul(f2) so that f = f^-1 * f2. +// +// Preconditions: +// - The field element MUST have a max magnitude of 8 +// Output Normalized: No +// Output Max Magnitude: 1 +func (f *FieldVal) Inverse() *FieldVal { + f.impl.Inverse() + return f +} + +// IsGtOrEqPrimeMinusOrder returns whether or not the field element is greater +// than or equal to the field prime minus the secp256k1 group order in constant +// time. +// +// Preconditions: +// - The field element MUST be normalized +func (f *FieldVal) IsGtOrEqPrimeMinusOrder() bool { + return f.impl.IsGtOrEqPrimeMinusOrder() +} diff --git a/dcrec/secp256k1/fieldwrap_test.go b/dcrec/secp256k1/fieldwrap_test.go new file mode 100644 index 0000000000..a72f53edd5 --- /dev/null +++ b/dcrec/secp256k1/fieldwrap_test.go @@ -0,0 +1,302 @@ +// 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 ( + mrand "math/rand" + "testing" + "time" +) + +// assertSameReceiver ensures the wrapper preserves the chaining API. +func assertSameReceiver(t *testing.T, got, want *FieldVal) { + t.Helper() + + if got != want { + t.Fatalf("method returned %p, want %p", got, want) + } +} + +// assertFieldValMatchesImpl ensures the wrapper and underlying implementation +// produce identical normalized values. +func assertFieldValMatchesImpl(t *testing.T, got *FieldVal, want *fieldImpl) { + t.Helper() + + got.Normalize() + want.Normalize() + + if !got.impl.Equals(want) { + t.Fatalf("mismatched values:\n got: %v\nwant: %v", got.impl.String(), + want.String()) + } +} + +// randBytes32 returns a 32-byte array created from a random value generated by +// the passed rng. +func randBytes32(t *testing.T, rng *mrand.Rand) [32]byte { + t.Helper() + + var buf [32]byte + if _, err := rng.Read(buf[:]); err != nil { + t.Fatalf("failed to read random: %v", err) + } + + return buf +} + +// randFieldValAndImplElement returns a [FieldVal] and [fieldImpl] element +// created from a random value generated by the passed rng. +func randFieldValAndImplElement(t *testing.T, rng *mrand.Rand) (*FieldVal, *fieldImpl) { + t.Helper() + + buf := randBytes32(t, rng) + + // Create and return a field value. + var fv FieldVal + fv.SetBytes(&buf) + fv.Normalize() + + var elem fieldImpl + elem.SetBytes(&buf) + elem.Normalize() + + return &fv, &elem +} + +// TestFieldValWrapper ensures the wrapper around the architecture-specific +// field implementation works as intended. It intentionally does NOT perform +// exhaustive testing of each of the methods since that is handled by each +// implementation. Instead, it focuses on making sure the wrapper returns the +// same result as the underlying implementation and the methods that involve +// chaining return the same wrapped instance. +func TestFieldValWrapper(t *testing.T) { + // Use a unique random seed for each test instance and log it if the tests + // fail. + seed := time.Now().Unix() + rng := mrand.New(mrand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + assertEqBool := func(got, want bool) { + t.Helper() + + if got != want { + t.Fatalf("mismatched result -- got %v, want %v", got, want) + } + } + + assertEqUint32 := func(got, want uint32) { + t.Helper() + + if got != want { + t.Fatalf("mismatched result -- got %v, want %v", got, want) + } + } + + for i := 0; i < 25; i++ { + // Create a field val and underlying implementation element from the + // same random value and immediately ensure they match because the + // helper depends on [FieldVal.SetBytes]. This also indirectly tests + // [FieldVal.Normalize]. + fv, impl := randFieldValAndImplElement(t, rng) + assertFieldValMatchesImpl(t, fv, impl) + + // Ensure [FieldVal.String] forwards properly. This is done early to + // avoid misleading output if anything were to fail later. + if got := fv.String(); got != impl.String() { + t.Errorf("mismatched string -- got %v, want %v", got, impl.String()) + } + + // Ensure [FieldVal.Normalize] forwards and chains properly. + // + // The output of the helper is already normalized, so multiply to + // potentially create internal overflow (for implementations where it + // applies). + // + // Note that this relies on the correctness of [FieldVal.MulInt], but + // that is tested later, so it's safe. + // + // This could be done after [FieldVal.MulInt] is tested to avoid that, + // but it is done early to avoid what could potentially be very + // confusing output otherwise since most of the other tests rely on + // normalization. + fv.MulInt(20) + impl.MulInt(20) + assertSameReceiver(t, fv.Normalize(), fv) + assertFieldValMatchesImpl(t, fv, impl.Normalize()) + + // ---------------------- + // Construction wrappers. + // ---------------------- + + // Ensure [FieldVal.Set] forwards properly. + fvSet, implSet := new(FieldVal).Set(fv), new(fieldImpl).Set(impl) + assertFieldValMatchesImpl(t, fvSet, implSet) + + // Ensure [FieldVal.Zero] forwards properly. + fv.Zero() + impl.Zero() + assertFieldValMatchesImpl(t, fv, impl) + + // Ensure [FieldVal.SetBytes] and [FieldVal.SetByteSlice] forward + // properly with a random value. + b32 := randBytes32(t, rng) + assertEqUint32(fv.SetBytes(&b32), impl.SetBytes(&b32)) + assertFieldValMatchesImpl(t, fv, impl) + assertEqBool(fv.SetByteSlice(b32[:]), impl.SetByteSlice(b32[:])) + assertFieldValMatchesImpl(t, fv, impl) + + // Ensure [FieldVal.SetBytes] and [FieldVal.SetByteSlice] forward + // properly with the field prime to ensure overflow is forwarded + // properly. + curveParams.P.FillBytes(b32[:]) + assertEqUint32(fv.SetBytes(&b32), impl.SetBytes(&b32)) + assertFieldValMatchesImpl(t, fv, impl) + assertEqBool(fv.SetByteSlice(b32[:]), impl.SetByteSlice(b32[:])) + assertFieldValMatchesImpl(t, fv, impl) + + // Ensure [FieldVal.SetBytes] and [FieldVal.SetByteSlice] forward + // properly with the group order to ensure non-overflow is forwarded + // properly. + curveParams.N.FillBytes(b32[:]) + assertEqUint32(fv.SetBytes(&b32), impl.SetBytes(&b32)) + assertFieldValMatchesImpl(t, fv, impl) + assertEqBool(fv.SetByteSlice(b32[:]), impl.SetByteSlice(b32[:])) + assertFieldValMatchesImpl(t, fv, impl) + + // Ensure [FieldVal.SetInt] forwards and chains properly. + assertSameReceiver(t, fv.SetInt(1), fv) + assertFieldValMatchesImpl(t, fv, impl.SetInt(1)) + + // ---------------------- + // Output bytes wrappers. + // ---------------------- + + // Generate new random value since the instances were clobbered above. + fv, impl = randFieldValAndImplElement(t, rng) + + // Ensure [FieldVal.PutBytesUnchecked] forwards properly. + var fvBuf32, implBuf32 [32]byte + fv.PutBytesUnchecked(fvBuf32[:]) + impl.PutBytesUnchecked(implBuf32[:]) + if fvBuf32 != implBuf32 { + t.Fatalf("mismatched result -- got %x, want %x", fvBuf32, implBuf32) + } + + // Ensure [FieldVal.PutBytes] forwards properly. + fvBuf32, implBuf32 = [32]byte{}, [32]byte{} + fv.PutBytes(&fvBuf32) + impl.PutBytes(&implBuf32) + if fvBuf32 != implBuf32 { + t.Fatalf("mismatched result -- got %x, want %x", fvBuf32, implBuf32) + } + + // Ensure [FieldVal.Bytes] forwards properly. + fvBuf32, implBuf32 = *fv.Bytes(), *impl.Bytes() + if fvBuf32 != implBuf32 { + t.Fatalf("mismatched result -- got %x, want %x", fvBuf32, implBuf32) + } + + // -------------------- + // Comparison wrappers. + // -------------------- + + // Ensure [FieldVal.IsZeroBit] forwards properly. + assertEqUint32(fv.IsZeroBit(), impl.IsZeroBit()) + + // Ensure [FieldVal.IsZero] forwards properly. + assertEqBool(fv.IsZero(), impl.IsZero()) + + // Ensure [FieldVal.IsOneBit] forwards properly. + assertEqUint32(fv.IsOneBit(), impl.IsOneBit()) + + // Ensure [FieldVal.IsOne] forwards properly. + assertEqBool(fv.IsOne(), impl.IsOne()) + + // Ensure [FieldVal.IsOddBit] forwards properly. + assertEqUint32(fv.IsOddBit(), impl.IsOddBit()) + + // Ensure [FieldVal.IsOdd] forwards properly. + assertEqBool(fv.IsOdd(), impl.IsOdd()) + + // Ensure [FieldVal.Equals] forwards properly. + fv2, impl2 := randFieldValAndImplElement(t, rng) + assertEqBool(fv.Equals(fv2), impl.Equals(impl2)) + + // Ensure [FieldVal.IsGtOrEqPrimeMinusOrder] forwards properly. + assertEqBool(fv.IsGtOrEqPrimeMinusOrder(), impl.IsGtOrEqPrimeMinusOrder()) + + // -------------------- + // Arithmetic wrappers. + // -------------------- + + // Ensure [FieldVal.NegateVal] forwards and chains properly. + assertSameReceiver(t, fv.NegateVal(fv2, 1), fv) + assertFieldValMatchesImpl(t, fv, impl.NegateVal(impl2, 1)) + + // Ensure [FieldVal.Negate] forwards and chains properly. + assertSameReceiver(t, fv.Negate(1), fv) + assertFieldValMatchesImpl(t, fv, impl.Negate(1)) + + // Ensure [FieldVal.AddInt] forwards and chains properly. + assertSameReceiver(t, fv.AddInt(1), fv) + assertFieldValMatchesImpl(t, fv, impl.AddInt(1)) + + // Ensure [FieldVal.Add] forwards and chains properly. + assertSameReceiver(t, fv.Add(fv2), fv) + assertFieldValMatchesImpl(t, fv, impl.Add(impl2)) + + // Ensure [FieldVal.Add2] forwards and chains properly. + assertSameReceiver(t, fv.Add2(fv, fv2), fv) + assertFieldValMatchesImpl(t, fv, impl.Add2(impl, impl2)) + + // Ensure [FieldVal.MulBy2] forwards and chains properly. + assertSameReceiver(t, fv.MulBy2(), fv) + assertFieldValMatchesImpl(t, fv, impl.MulBy2()) + + // Ensure [FieldVal.MulBy3] forwards and chains properly. + assertSameReceiver(t, fv.MulBy3(), fv) + assertFieldValMatchesImpl(t, fv, impl.MulBy3()) + + // Ensure [FieldVal.MulBy4] forwards and chains properly. + assertSameReceiver(t, fv.MulBy4(), fv) + assertFieldValMatchesImpl(t, fv, impl.MulBy4()) + + // Ensure [FieldVal.MulBy8] forwards and chains properly. + assertSameReceiver(t, fv.MulBy8(), fv) + assertFieldValMatchesImpl(t, fv, impl.MulBy8()) + + // Ensure [FieldVal.MulInt] forwards and chains properly. + assertSameReceiver(t, fv.MulInt(16), fv) + assertFieldValMatchesImpl(t, fv, impl.MulInt(16)) + + // Ensure [FieldVal.Mul] forwards and chains properly. + assertSameReceiver(t, fv.Mul(fv2), fv) + assertFieldValMatchesImpl(t, fv, impl.Mul(impl2)) + + // Ensure [FieldVal.Mul2] forwards and chains properly. + assertSameReceiver(t, fv.Mul2(fv, fv2), fv) + assertFieldValMatchesImpl(t, fv, impl.Mul2(impl, impl2)) + + // Ensure [FieldVal.SquareRootVal] forwards properly. + assertEqBool(fv.SquareRootVal(fv2), impl.SquareRootVal(impl2)) + assertFieldValMatchesImpl(t, fv, impl) + + // Ensure [FieldVal.Square] forwards and chains properly. + assertSameReceiver(t, fv.Square(), fv) + assertFieldValMatchesImpl(t, fv, impl.Square()) + + // Ensure [FieldVal.SquareVal] forwards and chains properly. + assertSameReceiver(t, fv.SquareVal(fv2), fv) + assertFieldValMatchesImpl(t, fv, impl.SquareVal(impl2)) + + // Ensure [FieldVal.Inverse] forwards and chains properly. + assertSameReceiver(t, fv.Inverse(), fv) + assertFieldValMatchesImpl(t, fv, impl.Inverse()) + } +} diff --git a/dcrec/secp256k1/internal/arith/README.md b/dcrec/secp256k1/internal/arith/README.md new file mode 100644 index 0000000000..09660b20b7 --- /dev/null +++ b/dcrec/secp256k1/internal/arith/README.md @@ -0,0 +1,23 @@ +arith +===== + +[![Build Status](https://github.com/decred/dcrd/workflows/Build%20and%20Test/badge.svg)](https://github.com/decred/dcrd/actions) +[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4/internal/arith) + +## Overview + +This provides low-level constant-time primitives and modulus-agnostic arithmetic +shared by multiple implementations in this module. + +See [internal/proofs/README.md](../proofs/README.md) for formal verification of +the 512-bit multiplication and 512-bit squaring. + +## Installation and Updating + +This package is internal and therefore is neither directly installed nor needs +to be manually updated. + +## License + +Package arith is licensed under the [copyfree](http://copyfree.org) ISC License. diff --git a/dcrec/secp256k1/internal/arith/arith.go b/dcrec/secp256k1/internal/arith/arith.go new file mode 100644 index 0000000000..b6e75cd2f2 --- /dev/null +++ b/dcrec/secp256k1/internal/arith/arith.go @@ -0,0 +1,211 @@ +// 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 arith provides low-level constant-time primitives and +// modulus-agnostic arithmetic. +package arith + +import "math/bits" + +// Mul512 sets t = x * y as an unreduced 512-bit product. +func Mul512(t *[8]uint64, x, y *[4]uint64) { + // The intermediate bounds and carry assumptions used by this algorithm have + // been formally verified. The verification artifacts are available in + // internal/proofs. + + a0, a1, a2, a3 := x[0], x[1], x[2], x[3] + b0, b1, b2, b3 := y[0], y[1], y[2], y[3] + + var c uint64 + + // Row 0: p0..p4 = a * b0. + // + // Note that since h3 is the upper 64 bits of the product of two uint64s: + // h3 ≤ floor((2^64-1)^2 / 2^64) = 2^64 - 2 + // + // Without any other considerations, c ≤ 1, so a loose bound is: + // p4 ≤ h3 + 1 = 2^64 - 1 < 2^64 + // + // This already shows that the carryless add in p4 is safe, however, a tight + // upper bound is more useful to prove no overflow is possible in the upper + // words of the subsequent rows. + // + // Claim: p4 ≤ 2^64 - 2 + // + // Consider the row product A*b, where A ≤ 2^256 - 1, b ≤ 2^64 - 1, then: + // A*b ≤ (2^256 - 1)(2^64 - 1) = 2^320 - 2^256 - 2^64 + 1 + // + // Next, expressing the product in base 2^256 gives: + // A*b = p4*2^256 + qlow + // + // Where qlow is the low 256 bits of the product and p4 is the integer + // quotient: + // p4 = floor(A*b / 2^256) + // qlow = A*b (mod 2^256) + // + // Finally, bound the quotient: + // p4 = floor(A*b / 2^256) + // ≤ floor((2^320 - 2^256 - 2^64 + 1) / 2^256) + // = floor(2^64 - 1 - 2^(-192) + 2^(-256)) + // ≤ 2^64 - 2 + // + // So, p4 ≤ 2^64 - 2. + 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. + // + // Per row 0 above, the tight bound on q4 for this row is: + // q4 ≤ 2^64 - 2 + // + // Since c ≤ 1: + // p5 ≤ q4 + 1 = 2^64 - 1 < 2^64 + // + // So, the carryless add in p5 is safe. + 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. + // + // The same bounds calculation as row 1 applies. + 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. + // + // The same bounds calculation as row 1 applies. + 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 +} + +// Square512 sets t = a^2 as an unreduced 512-bit product. +func Square512(t *[8]uint64, a *[4]uint64) { + // The intermediate bounds and carry assumptions used by this algorithm have + // been formally verified. The verification artifacts are available in + // internal/proofs. + + a0, a1, a2, a3 := a[0], a[1], a[2], a[3] + + var c uint64 + + // Off-diagonal upper-triangle products (not yet doubled). + // + // Note that since h03 is the upper 64 bits of the product of two uint64s: + // h03 ≤ floor((2^64-1)^2 / 2^64) = 2^64 - 2 + // + // Then, because c ≤ 1, a loose bound is: + // p4 ≤ h03 + 1 = 2^64 - 1 < 2^64 + // + // Therefore, it is safe to discard the carry. + p2, p1 := bits.Mul64(a0, a1) + h02, l02 := bits.Mul64(a0, a2) + h03, l03 := bits.Mul64(a0, a3) + 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 + + // The p5 carry is safe to discard because p5 + h13 + c ≤ 2^64 - 1 (where c + // is the carry from p4 + l13). + // + // A full proof involves case analysis that is omitted here since the + // impossibility of the carry is formally proven in internal/proofs, but the + // key point is that the only way the final add could have a carry is if all + // 3 of the following conditions were simultaneously true: + // + // 1) p5_old = 1 (the carry from the earlier chain, so ≤ 1) + // 2) h13 = 2^64 - 2 (h13 ≤ 2^64 - 2 as proven previously) + // 3) c = 1 (implies p4 + l13 ≥ 2^64) + // + // However, that combination of conditions is impossible because in order + // for condition 2 to be true, a1 = a3 = 2^64 - 1, in which case l13 = 1 + // and so in order for condition 3 to also be true, p4 = 2^64 - 1. But then + // the combination of those conditions forces p5_old = 0. + h13, l13 := bits.Mul64(a1, a3) + p4, c = bits.Add64(p4, l13, 0) + p5, _ = bits.Add64(p5, h13, c) + + // Similarly, the p6 carry is safe to discard because, per above: + // h23 ≤ 2^64 - 2 + // + // Then, again c ≤ 1, so the same loose bound applies: + // p6 ≤ h23 + 1 = 2^64 - 1 < 2^64 + 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. + // + // The carry on the final add is safe to discard because a < p < 2^256, so: + // (2^256 - 1)^2 = 2^512 - 2^257 + 1 < 2^512 + 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 +} diff --git a/dcrec/secp256k1/internal/arith/arith_test.go b/dcrec/secp256k1/internal/arith/arith_test.go new file mode 100644 index 0000000000..31f9469409 --- /dev/null +++ b/dcrec/secp256k1/internal/arith/arith_test.go @@ -0,0 +1,395 @@ +// 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 arith + +import ( + "encoding/binary" + "fmt" + "math/big" + mrand "math/rand" + "testing" + "time" +) + +// mustBig converts the passed hex string into a big integer and will panic if +// there is an error. This is only provided for the hard-coded constants so +// errors in the source code can be detected. It will only (and must only) be +// called with hard-coded values. +func mustBig(s string) *big.Int { + val, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("failed to parse big integer from hex: " + s) + } + return val +} + +// mustBigToUint256 converts a [big.Int] to an array of 4 uint64s representing a +// 256-bit little-endian value. It will panic if the big integer is larger than +// the maximum 256-bit value. This is only provided for the hard-coded +// constants and randomly-generated values that are known to be in range, so +// errors in the source code can be detected. +func mustBigToUint256(v *big.Int) [4]uint64 { + if v.BitLen() > 256 { + panic(fmt.Sprintf("big integer %x is larger than max uint256", v)) + } + + var buf [32]byte + v.FillBytes(buf[:]) + + var result [4]uint64 + for i := 0; i < 4; i++ { + result[i] = binary.BigEndian.Uint64(buf[32-((i+1)*8):]) + } + return result +} + +// uint512ToBig converts an array of 8 uint64s representing a 512-bit +// little-endian value to a [big.Int]. +func uint512ToBig(v [8]uint64) *big.Int { + var buf [64]byte + for i := 0; i < 8; i++ { + binary.BigEndian.PutUint64(buf[i*8:], v[7-i]) + } + + return new(big.Int).SetBytes(buf[:]) +} + +// randBig returns a random 256-bit [big.Int] created from the passed rng. +func randBig(t *testing.T, rng *mrand.Rand) *big.Int { + t.Helper() + + var buf [32]byte + if _, err := rng.Read(buf[:]); err != nil { + t.Fatalf("failed to read random: %v", err) + } + + return new(big.Int).SetBytes(buf[:]) +} + +// TestMul512 ensures [Mul512] returns the expected result by comparing the +// product against the [big.Int] result. It also tests commutativity and full +// buffer replacement. +func TestMul512(t *testing.T) { + tests := []struct { + name string // test description + x, y string // hex encoded multiplicands + }{{ + name: "all zero", + x: "0", + y: "0", + }, { + name: "zero * identity", + x: "0", + y: "1", + }, { + name: "zero * max uint256", + x: "0", + y: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "identity", + x: "1", + y: "1", + }, { + name: "identity * max uint256", + x: "1", + y: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "small values", + x: "2", + y: "3", + }, { + name: "unbalanced small values", + x: "1", + y: "ffffffffffffffff", + }, { + name: "small * max uint256", + x: "2", + y: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "max uint64 * max uint64", + x: "ffffffffffffffff", + y: "ffffffffffffffff", + }, { + name: "max uint64 * 2*max uint64", + x: "ffffffffffffffff", + y: "1fffffffffffffffe", + }, { + name: "2^1 * 2^1", + x: "2", + y: "2", + }, { + name: "2^8 * 2^8", + x: "100", + y: "100", + }, { + name: "2^16 * 2^16", + x: "10000", + y: "10000", + }, { + name: "2^32 * 2^32", + x: "100000000", + y: "100000000", + }, { + name: "2^64 * 2^64", + x: "10000000000000000", + y: "10000000000000000", + }, { + name: "2^128 * 2^128", + x: "100000000000000000000000000000000", + y: "100000000000000000000000000000000", + }, { + name: "2^192 * 2^64", + x: "1000000000000000000000000000000000000000000000000", + y: "10000000000000000", + }, { + name: "2^255 * 2^255", + x: "8000000000000000000000000000000000000000000000000000000000000000", + y: "8000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "max uint256 minus one", + x: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + y: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + }, { + name: "max uint256 * max uint256", + x: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + y: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "alternating bits", + x: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + y: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + }, { + name: "alternating bits 2", + x: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + y: "5555555555555555555555555555555555555555555555555555555555555555", + }, { + name: "carry propagation through zero limbs", + x: "ffffffffffffffffffffffffffffffff00000000000000000000000000000000", + y: "100000000000000000000000000000001", + }} + + for _, test := range tests { + xBig, yBig := mustBig(test.x), mustBig(test.y) + x, y := mustBigToUint256(xBig), mustBigToUint256(yBig) + want := new(big.Int).Mul(xBig, yBig) + + // Fill the array that will be used to store the product with non-zero + // values to ensure the entire array is overwritten. + var product [8]uint64 + for i := 0; i < 8; i++ { + product[i] = 0xffffffffffffffff + } + + // Compute the product with [Mul512] and ensure it matches the same + // result produced by [big.Int]. + Mul512(&product, &x, &y) + if got := uint512ToBig(product); got.Cmp(want) != 0 { + t.Errorf("%s: incorrect product\n x: %064x\n y: %064x\n"+ + "got: %0128x\nwant: %0128x", test.name, xBig, yBig, got, want) + } + + // Ensure commutativity works properly with [Mul512] by computing the + // product again with the operands swapped. Poison the array used to + // store the product again to ensure the opposite order overwrites the + // entire array too. + for i := 0; i < 8; i++ { + product[i] = 0xffffffffffffffff + } + Mul512(&product, &y, &x) + if got := uint512ToBig(product); got.Cmp(want) != 0 { + t.Errorf("%s (swapped): incorrect product\n y: %064x\n x: %064x\n"+ + "got: %0128x\nwant: %0128x", test.name, yBig, xBig, got, want) + } + } +} + +// TestMul512Random ensures that multiplying randomly-generated values via +// [Mul512] returns the expected result by comparing the product against the +// [big.Int] result. It also tests commutativity. +func TestMul512Random(t *testing.T) { + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().UnixNano() + rng := mrand.New(mrand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + for i := 0; i < 1000; i++ { + // Generate random [big.Int] operands and the expected product. + xBig, yBig := randBig(t, rng), randBig(t, rng) + x, y := mustBigToUint256(xBig), mustBigToUint256(yBig) + want := new(big.Int).Mul(xBig, yBig) + + // Compute the product with [Mul512] and ensure it matches the same + // result produced by [big.Int]. + var product [8]uint64 + Mul512(&product, &x, &y) + if got := uint512ToBig(product); got.Cmp(want) != 0 { + t.Errorf("incorrect product\n x: %064x\n y: %064x\ngot: %0128x\n"+ + "want: %0128x", xBig, yBig, got, want) + } + + // Ensure commutativity works properly with [Mul512] by computing the + // product again with the operands swapped. + var product2 [8]uint64 + Mul512(&product2, &y, &x) + if got := uint512ToBig(product2); got.Cmp(want) != 0 { + t.Errorf("incorrect product\n y: %064x\n x: %064x\ngot: %0128x\n"+ + "want: %0128x", yBig, xBig, got, want) + } + } +} + +// TestSquare512 ensures [Square512] returns the expected result by comparing it +// against the [big.Int] result. It also tests full buffer replacement and that +// [Mul512] and [Square512] agree on the result. +func TestSquare512(t *testing.T) { + tests := []struct { + name string // test description + a string // hex encoded value to square + }{{ + name: "zero", + a: "0", + }, { + name: "identity", + a: "1", + }, { + name: "small value", + a: "2", + }, { + name: "2^4", + a: "10", + }, { + name: "2^8", + a: "100", + }, { + name: "2^16", + a: "10000", + }, { + name: "2^32", + a: "100000000", + }, { + name: "max uint64", + a: "ffffffffffffffff", + }, { + name: "2^64", + a: "10000000000000000", + }, { + name: "max uint128 minus one", + a: "fffffffffffffffffffffffffffffffe", + }, { + name: "max uint128", + a: "ffffffffffffffffffffffffffffffff", + }, { + name: "2^128", + a: "100000000000000000000000000000000", + }, { + name: "max uint192", + a: "ffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "2^192", + a: "1000000000000000000000000000000000000000000000000", + }, { + name: "2^255", + a: "8000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "near max uint256", + a: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0001", + }, { + name: "max uint256 minus one", + a: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + }, { + name: "max uint256", + a: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, { + name: "sparse bits", + a: "8000000000000000000000000000000100000000000000000000000000000001", + }, { + name: "high and low bits", + a: "8000000000000000000000000000000000000000000000000000000000000001", + }, { + name: "alternating bits", + a: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", + }, { + name: "alternating bits 2", + a: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, { + name: "alternating bits 3", + a: "5555555555555555555555555555555555555555555555555555555555555555", + }, { + name: "carry propagation through zero limbs", + a: "ffffffffffffffffffffffffffffffff00000000000000000000000000000001", + }} + + for _, test := range tests { + aBig := mustBig(test.a) + a := mustBigToUint256(aBig) + want := new(big.Int).Mul(aBig, aBig) + + // Fill the array that will be used to store the result with non-zero + // values to ensure the entire array is overwritten. + var result [8]uint64 + for i := 0; i < 8; i++ { + result[i] = 0xffffffffffffffff + } + + // Compute the result with [Square512] and ensure it matches the same + // result produced by [big.Int]. + Square512(&result, &a) + if got := uint512ToBig(result); got.Cmp(want) != 0 { + t.Errorf("%s: incorrect square\n a: %064x\n got: %0128x\n"+ + "want: %0128x", test.name, aBig, got, want) + } + + // Ensure [Mul512] and [Square512] agree. + var mulResult [8]uint64 + Mul512(&mulResult, &a, &a) + if result != mulResult { + t.Errorf("%s: square and mul disagree\n a: %064x\n"+ + " square: %0128x\n mul: %0128x", test.name, aBig, + uint512ToBig(result), uint512ToBig(mulResult)) + } + } +} + +// TestSquare512Random ensures that squaring randomly-generated values via +// [Square512] returns the expected result by comparing it against the [big.Int] +// result. It also tests that [Mul512] and [Square512] agree on the result. +func TestSquare512Random(t *testing.T) { + // Use a unique random seed each test instance and log it if the tests fail. + seed := time.Now().UnixNano() + rng := mrand.New(mrand.NewSource(seed)) + defer func(t *testing.T, seed int64) { + if t.Failed() { + t.Logf("random seed: %d", seed) + } + }(t, seed) + + for i := 0; i < 1000; i++ { + // Generate a random [big.Int] operand and the expected result. + aBig := randBig(t, rng) + a := mustBigToUint256(aBig) + want := new(big.Int).Mul(aBig, aBig) + + // Compute the result with [Square512] and ensure it matches the same + // result produced by [big.Int]. + var result [8]uint64 + Square512(&result, &a) + if got := uint512ToBig(result); got.Cmp(want) != 0 { + t.Errorf("incorrect square\n a: %064x\n got: %0128x\n"+ + "want: %0128x", aBig, got, want) + } + + // Ensure [Mul512] and [Square512] agree. + var mulResult [8]uint64 + Mul512(&mulResult, &a, &a) + if result != mulResult { + t.Errorf("square and mul disagree\n a: %064x\n"+ + " square: %0128x\n mul: %0128x", aBig, + uint512ToBig(result), uint512ToBig(mulResult)) + } + } +} diff --git a/dcrec/secp256k1/internal/arith/consttime.go b/dcrec/secp256k1/internal/arith/consttime.go new file mode 100644 index 0000000000..b798e824d5 --- /dev/null +++ b/dcrec/secp256k1/internal/arith/consttime.go @@ -0,0 +1,56 @@ +// Copyright (c) 2020-2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package arith + +// ConstantTimeEq returns 1 if a == b or 0 otherwise in constant time. +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) +} + +// ConstantTimeNotEq64 returns 1 if a != b or 0 otherwise in constant time. +func ConstantTimeNotEq64(a, b uint64) uint32 { + t := a ^ b + return uint32((t | -t) >> 63) +} + +// ConstantTimeLess returns 1 if a < b or 0 otherwise in constant time. +func ConstantTimeLess(a, b uint32) uint32 { + return uint32((uint64(a) - uint64(b)) >> 63) +} + +// ConstantTimeLessOrEq returns 1 if a <= b or 0 otherwise in constant time. +func ConstantTimeLessOrEq(a, b uint32) uint32 { + return uint32((uint64(a) - uint64(b) - 1) >> 63) +} + +// ConstantTimeGreater returns 1 if a > b or 0 otherwise in constant time. +func ConstantTimeGreater(a, b uint32) uint32 { + return ConstantTimeLess(b, a) +} + +// ConstantTimeGreaterOrEq returns 1 if a >= b or 0 otherwise in constant time. +func ConstantTimeGreaterOrEq(a, b uint32) uint32 { + return ConstantTimeLessOrEq(b, a) +} + +// ConstantTimeMin returns min(a,b) in constant time. +func ConstantTimeMin(a, b uint32) uint32 { + return b ^ ((a ^ b) & -ConstantTimeLess(a, b)) +} + +// ConstantTimeSelect64 returns a when cond == 1 or b when cond == 0 in constant +// time. +// +// WARNING: The behavior is undefined if cond is anything other than 0 or 1. +func ConstantTimeSelect64(cond, a, b uint64) uint64 { + mask := -cond + return b ^ (a^b)&mask +} diff --git a/dcrec/secp256k1/internal/arith/consttime_test.go b/dcrec/secp256k1/internal/arith/consttime_test.go new file mode 100644 index 0000000000..35941ee8bc --- /dev/null +++ b/dcrec/secp256k1/internal/arith/consttime_test.go @@ -0,0 +1,291 @@ +// Copyright (c) 2026 The Decred developers +// Copyright (c) 2013-2026 Dave Collins +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package arith + +import "testing" + +// TestConstantTimeEq ensures [ConstantTimeEq] returns the expected results. +func TestConstantTimeEq(t *testing.T) { + tests := []struct { + a uint32 // first value + b uint32 // second value + want uint32 // expected result (0 or 1) + }{ + {0, 0, 1}, + {1, 1, 1}, + {1<<32 - 1, 1<<32 - 1, 1}, + {0x12345678, 0x12345678, 1}, + {0, 1, 0}, + {1, 0, 0}, + {1<<32 - 1, 0, 0}, + {0x12345678, 0x87654321, 0}, + {0, 1 << 31, 0}, + {1 << 31, 1 << 31, 1}, + {1, 2, 0}, + {1<<32 - 2, 1<<32 - 1, 0}, + } + + for _, test := range tests { + got := ConstantTimeEq(test.a, test.b) + if got != test.want { + t.Errorf("%08x == %08x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeEq64 ensures [ConstantTimeEq64] returns the expected results. +func TestConstantTimeEq64(t *testing.T) { + tests := []struct { + a uint64 // first value + b uint64 // second value + want uint32 // expected result (0 or 1) + }{ + {0, 0, 1}, + {1, 1, 1}, + {1<<64 - 1, 1<<64 - 1, 1}, + {0x123456789abcdef0, 0x123456789abcdef0, 1}, + {0, 1, 0}, + {1, 0, 0}, + {1<<64 - 1, 0, 0}, + {0x123456789abcdef0, 0xfedcba9876543210, 0}, + {0, 1 << 63, 0}, + {1 << 63, 1 << 63, 1}, + {1, 2, 0}, + {1<<64 - 2, 1<<64 - 1, 0}, + } + + for _, test := range tests { + got := ConstantTimeEq64(test.a, test.b) + if got != test.want { + t.Errorf("%016x == %016x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeNotEq64 ensures [ConstantTimeNotEq64] returns the expected +// results. +func TestConstantTimeNotEq64(t *testing.T) { + tests := []struct { + a uint64 // first value + b uint64 // second value + want uint32 // expected result + }{ + {0, 0, 0}, + {1, 1, 0}, + {1<<64 - 1, 1<<64 - 1, 0}, + {0x123456789abcdef0, 0x123456789abcdef0, 0}, + {0, 1, 1}, + {1, 0, 1}, + {1<<64 - 1, 0, 1}, + {0x123456789abcdef0, 0xfedcba9876543210, 1}, + {0, 1 << 63, 1}, + {1 << 63, 1 << 63, 0}, + {1, 2, 1}, + {1<<64 - 2, 1<<64 - 1, 1}, + } + + for _, test := range tests { + got := ConstantTimeNotEq64(test.a, test.b) + if got != test.want { + t.Errorf("%016x != %016x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeLess ensures [ConstantTimeLess] returns the expected results. +func TestConstantTimeLess(t *testing.T) { + tests := []struct { + a uint32 // first value + b uint32 // second value + want uint32 // expected result (0 or 1) + }{ + {0, 1, 1}, + {1, 2, 1}, + {0, 1<<32 - 1, 1}, + {1<<31 - 1, 1 << 31, 1}, + {1<<32 - 2, 1<<32 - 1, 1}, + {0x12345678, 0x87654321, 1}, + {1, 0, 0}, + {1, 1, 0}, + {1<<32 - 1, 0, 0}, + {1<<32 - 1, 1<<32 - 1, 0}, + {0x87654321, 0x12345678, 0}, + {0, 0, 0}, + {1 << 31, 1<<32 - 1, 1}, + } + + for _, test := range tests { + got := ConstantTimeLess(test.a, test.b) + if got != test.want { + t.Errorf("%08x < %08x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeLessOrEq ensures [ConstantTimeLessOrEq] returns the expected +// results. +func TestConstantTimeLessOrEq(t *testing.T) { + tests := []struct { + a uint32 // first value + b uint32 // second value + want uint32 // expected result (0 or 1) + }{ + {0, 0, 1}, + {0, 1, 1}, + {1, 2, 1}, + {1, 1, 1}, + {0, 1<<32 - 1, 1}, + {0x12345678, 0x87654321, 1}, + {1<<32 - 1, 1<<32 - 1, 1}, + {1<<32 - 2, 1<<32 - 1, 1}, + {1, 0, 0}, + {2, 1, 0}, + {1<<32 - 1, 0, 0}, + {0x87654321, 0x12345678, 0}, + {1 << 31, 1<<32 - 1, 1}, + {1 << 31, 1 << 31, 1}, + } + + for _, test := range tests { + got := ConstantTimeLessOrEq(test.a, test.b) + if got != test.want { + t.Errorf("%08x <= %08x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeGreater ensures [ConstantTimeGreater] returns the expected +// results. +func TestConstantTimeGreater(t *testing.T) { + tests := []struct { + a uint32 // first value + b uint32 // second value + want uint32 // expected result (0 or 1) + }{ + {1, 0, 1}, + {2, 1, 1}, + {1<<32 - 1, 0, 1}, + {1<<32 - 1, 1<<32 - 2, 1}, + {0x87654321, 0x12345678, 1}, + {0, 1, 0}, + {1, 1, 0}, + {0, 1<<32 - 1, 0}, + {1<<32 - 1, 1<<32 - 1, 0}, + {0x12345678, 0x87654321, 0}, + {0, 0, 0}, + {1<<32 - 1, 1 << 31, 1}, + } + + for _, test := range tests { + got := ConstantTimeGreater(test.a, test.b) + if got != test.want { + t.Errorf("%08x > %08x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeGreaterOrEq ensures [ConstantTimeGreaterOrEq] returns the +// expected results. +func TestConstantTimeGreaterOrEq(t *testing.T) { + tests := []struct { + a uint32 // first value + b uint32 // second value + want uint32 // expected result (0 or 1) + }{ + {0, 0, 1}, + {1, 0, 1}, + {1, 1, 1}, + {2, 1, 1}, + {1<<32 - 1, 0, 1}, + {1<<32 - 1, 1<<32 - 1, 1}, + {1<<32 - 1, 1<<32 - 2, 1}, + {0x87654321, 0x12345678, 1}, + {0, 1, 0}, + {1, 2, 0}, + {0, 1<<32 - 1, 0}, + {0x12345678, 0x87654321, 0}, + {1 << 31, 1 << 31, 1}, + {1 << 31, 1<<32 - 1, 0}, + } + + for _, test := range tests { + got := ConstantTimeGreaterOrEq(test.a, test.b) + if got != test.want { + t.Errorf("%08x >= %08x: got %d, want %d", test.a, test.b, got, + test.want) + } + } +} + +// TestConstantTimeMin ensures [ConstantTimeMin] returns the expected results. +func TestConstantTimeMin(t *testing.T) { + tests := []struct { + a uint32 // first value + b uint32 // second value + want uint32 // expected minimum value + }{ + {0, 1, 0}, + {1, 2, 1}, + {0, 1<<32 - 1, 0}, + {1<<32 - 2, 1<<32 - 1, 1<<32 - 2}, + {0x12345678, 0x87654321, 0x12345678}, + {1, 0, 0}, + {2, 1, 1}, + {1<<32 - 1, 0, 0}, + {1<<32 - 1, 1<<32 - 2, 1<<32 - 2}, + {0x87654321, 0x12345678, 0x12345678}, + {0, 0, 0}, + {1, 1, 1}, + {1<<32 - 1, 1<<32 - 1, 1<<32 - 1}, + {0x12345678, 0x12345678, 0x12345678}, + {1 << 31, 1<<32 - 1, 1 << 31}, + {1<<32 - 1, 1 << 31, 1 << 31}, + } + + for _, test := range tests { + got := ConstantTimeMin(test.a, test.b) + if got != test.want { + t.Errorf("min(%08x, %08x): got %08x, want %08x", test.a, test.b, + got, test.want) + } + } +} + +// TestConstantTimeSelect64 ensures [ConstantTimeSelect64] returns the expected +// results. +func TestConstantTimeSelect64(t *testing.T) { + tests := []struct { + cond uint64 // condition value + a uint64 // first value + b uint64 // second value + want uint64 // expected selected value + }{ + {0, 1, 2, 2}, + {1, 1, 2, 1}, + {0, 1<<64 - 1, 1, 1}, + {1, 1<<64 - 1, 1, 1<<64 - 1}, + {0, 1, 1<<64 - 1, 1<<64 - 1}, + {1, 1, 1<<64 - 1, 1}, + {0, 1<<64 - 1, 1<<64 - 2, 1<<64 - 2}, + {1, 1<<64 - 1, 1<<64 - 2, 1<<64 - 1}, + {0, 0x123456789abcdef0, 0x123456789abcdef0, 0x123456789abcdef0}, + {1, 0x123456789abcdef0, 0x123456789abcdef0, 0x123456789abcdef0}, + } + + for _, test := range tests { + got := ConstantTimeSelect64(test.cond, test.a, test.b) + if got != test.want { + t.Errorf("sel(%d, %016x, %016x): got %016x, want %016x", test.cond, + test.a, test.b, got, test.want) + } + } +} diff --git a/dcrec/secp256k1/internal/cpufeat/README.md b/dcrec/secp256k1/internal/cpufeat/README.md new file mode 100644 index 0000000000..e03f2d9e51 --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/README.md @@ -0,0 +1,52 @@ +cpufeat +======= + +[![Build Status](https://github.com/decred/dcrd/workflows/Build%20and%20Test/badge.svg)](https://github.com/decred/dcrd/actions) +[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4/internal/cpufeat) + +Package cpufeat detects support for CPU features that are relevant to +architecture-specific optimizations throughout the `secp256k1` package. + +## Design + +The package intentionally provides a minimal abstraction consisting only of the +feature detection required by the `secp256k1` implementation. Architecture- +specific detection logic is gated behind build constraints and unsupported +architectures simply report that no optional features are supported. + +This organization allows optimized implementations to select the most efficient +available code path without duplicating processor detection logic throughout the +codebase and ensures portable fallback implementations remain available on all +supported platforms. + +## Pure Go Builds + +The `purego` build tag may be used to disable all assembly code in this package. +Since feature detection relies on assembly, all optional CPU features will +report as unsupported. + +## Testing + +It is possible to test implementations that require otherwise unavailable CPU +features by using software such as the [Intel Software Development +Emulator](https://www.intel.com/content/www/us/en/developer/articles/tool/software-development-emulator.html). + +Some relevant flags for testing purposes with the Intel SDE are: + +* BMI2: `-hsw Set chip-check and CPUID for Intel(R) Haswell CPU` +* ADX: `-bdw Set chip-check and CPUID for Intel(R) Broadwell CPU` + +The package determines supported features during package initialization, so +tests should be run under the emulator rather than attempting to enable features +after startup. + +## Installation and Updating + +This package is internal and therefore is neither directly installed nor needs +to be manually updated. + +## License + +Package cpufeat is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/dcrec/secp256k1/internal/cpufeat/cpu.go b/dcrec/secp256k1/internal/cpufeat/cpu.go new file mode 100644 index 0000000000..3ad7cbad3c --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu.go @@ -0,0 +1,22 @@ +// 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 cpufeat provides detection of CPU support for relevant hardware +// features. +package cpufeat + +// features caches the result of querying the CPU for supported features. +var features = detect() + +// Supported returns the feature set detected during package initialization. +func Supported() Features { + return features +} + +// Features houses flags that specify whether or not various features +// are supported by the CPU. +type Features struct { + BMI2 bool + ADX bool +} diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_386.s b/dcrec/secp256k1/internal/cpufeat/cpu_386.s new file mode 100644 index 0000000000..f1e6b34d3a --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_386.s @@ -0,0 +1,49 @@ +// Copyright (c) 2024-2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. +// +// Feature detection originally written by Dave Collins Feb 2019. Modifications +// for secp256k1 made in Jul 2026. + +//go:build !purego + +#include "textflag.h" + +// func supportsCPUID() bool +TEXT ·supportsCPUID(SB), NOSPLIT, $0-1 + // Per the Intel 64 and IA-32 Architectures Software Developer's Manual, + // CPUID is supported if bit 21 of the EFLAGS register can be modified. + // + // To that end, this works as follows: + // + // 1. Get the current value of EFLAGS by pushing it and popping it into AX. + // 2. Make a copy into BX for later comparison. + // 3. Toggle bit 21 (the EFLAGS ID bit) of AX. + // 4. Put the modified value back into EFLAGS by pushing it and popping it + // into EFLAGS. The CPU will either update bit 21 of the EFLAGS with the + // modified value when it supports CPUID or leave it unmodified when it + // does not. + // 5. Get the potentially modified value of EFLAGS by pushing it and popping + // it into AX. + // 6. Put the original value back into EFLAGS to avoid any observable side + // effects by pushing it and popping it into EFLAGS. + // 7. Compare the original and potentially modified EFLAGS (aka AX vs BX) + // 8. CPUID is supported when they do not match since bit 21 was able to be + // modified. + PUSHFL + POPL AX + MOVL AX, BX + XORL $0x200000, AX + PUSHL AX + POPFL + PUSHFL + POPL AX + PUSHL BX + POPFL + CMPL AX, BX + JE nocpuid + MOVB $1, ret+0(FP) + RET +nocpuid: + MOVB $0, ret+0(FP) + RET diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_amd64.s b/dcrec/secp256k1/internal/cpufeat/cpu_amd64.s new file mode 100644 index 0000000000..678f442fc2 --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_amd64.s @@ -0,0 +1,49 @@ +// Copyright (c) 2024-2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. +// +// Feature detection originally written by Dave Collins Feb 2019. Modifications +// for secp256k1 made in Jul 2026. + +//go:build !purego + +#include "textflag.h" + +// func supportsCPUID() bool +TEXT ·supportsCPUID(SB), NOSPLIT, $0-1 + // Per the Intel 64 and IA-32 Architectures Software Developer's Manual, + // CPUID is supported if bit 21 of the EFLAGS register can be modified. + // + // To that end, this works as follows: + // + // 1. Get the current value of EFLAGS by pushing it and popping it into AX. + // 2. Make a copy into BX for later comparison. + // 3. Toggle bit 21 (the EFLAGS ID bit) of AX. + // 4. Put the modified value back into EFLAGS by pushing it and popping it + // into EFLAGS. The CPU will either update bit 21 of the EFLAGS with the + // modified value when it supports CPUID or leave it unmodified when it + // does not. + // 5. Get the potentially modified value of EFLAGS by pushing it and popping + // it into AX. + // 6. Put the original value back into EFLAGS to avoid any observable side + // effects by pushing it and popping it into EFLAGS. + // 7. Compare the original and potentially modified EFLAGS (aka AX vs BX) + // 8. CPUID is supported when they do not match since bit 21 was able to be + // modified. + PUSHFQ + POPQ AX + MOVQ AX, BX + XORQ $0x200000, AX + PUSHQ AX + POPFQ + PUSHFQ + POPQ AX + PUSHQ BX + POPFQ + CMPQ AX, BX + JE nocpuid + MOVB $1, ret+0(FP) + RET +nocpuid: + MOVB $0, ret+0(FP) + RET diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_generic.go b/dcrec/secp256k1/internal/cpufeat/cpu_generic.go new file mode 100644 index 0000000000..18852d31bf --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_generic.go @@ -0,0 +1,12 @@ +// 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. + +//go:build !(386 || amd64) || purego + +package cpufeat + +// detect returns false for all features by default. +func detect() Features { + return Features{} +} diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_generic_test.go b/dcrec/secp256k1/internal/cpufeat/cpu_generic_test.go new file mode 100644 index 0000000000..3519650823 --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_generic_test.go @@ -0,0 +1,20 @@ +// 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. + +//go:build !(386 || amd64) || purego + +package cpufeat + +import "testing" + +// TestDetectGeneric ensures that the generic feature detection fallback used +// for architectures without dedicated support (and the purego build tag) +// correctly reports that no optional features are supported. +func TestDetectGeneric(t *testing.T) { + want := Features{} + if got := detect(); got != want { + t.Fatalf("unexpected detected features -- got %+v, want %+v", got, + want) + } +} diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_test.go b/dcrec/secp256k1/internal/cpufeat/cpu_test.go new file mode 100644 index 0000000000..78c8d246c9 --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_test.go @@ -0,0 +1,28 @@ +// 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 cpufeat + +import "testing" + +// TestSupported ensures that querying the cached feature set is stable and +// idempotent across repeated calls since it is only ever computed once during +// package initialization. It also ensures that invoking detect directly +// produces the same result. +func TestSupported(t *testing.T) { + got1 := Supported() + got2 := Supported() + if got1 != got2 { + t.Fatalf("inconsistent results across calls -- got %+v and %+v", got1, + got2) + } + + // Ensure the cached features exposed via [Supported] match since they are + // derived at package initialization time. + want := detect() + if got := Supported(); got != want { + t.Fatalf("unexpected supported features -- got %+v, want %+v", got, + want) + } +} diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_x86.go b/dcrec/secp256k1/internal/cpufeat/cpu_x86.go new file mode 100644 index 0000000000..69633495ea --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_x86.go @@ -0,0 +1,86 @@ +// Copyright (c) 2024-2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. +// +// Feature detection originally written by Dave Collins Feb 2019 for blake256. +// Additional cleanup and comments added Jul 2024. Modifications for secp256k1 +// made in Jul 2026. + +//go:build (386 || amd64) && !purego + +package cpufeat + +// supportsCPUID returns true when the CPU supports the CPUID opcode. +// +//go:noescape +func supportsCPUID() bool + +// cpuid provides access to the CPUID opcode. +// +//go:noescape +func cpuid(eaxIn, ecxIn uint32) (eax, ebx, ecx, edx uint32) + +// hasBit returns whether or not the provided bit is set in the given test +// value. +func hasBit(testVal uint32, bit uint) bool { + return testVal>>bit&1 == 1 +} + +// detect returns the result of querying the CPU to determine supported +// features. +func detect() Features { + // Per CPUID—CPU Identification in Chapter 3 of the Intel 64 and IA-32 + // Architectures Software Developer's Manual, Volume 2A: + // + // "The ID flag (bit 21) in the EFLAGS register indicates support for the + // CPUID instruction. If a software procedure can set and clear this flag, + // the processor executing the procedure supports the CPUID instruction. + // This instruction operates the same in non-64-bit modes and 64-bit mode. + // + // CPUID returns processor identification and feature information in the + // EAX, EBX, ECX, and EDX registers. The output is dependent on the + // contents of the EAX register upon execution (in some cases, ECX as + // well)." + // + // The inputs and outputs for determining various levels of support that are + // relevant to secp256k1 are: + // + // Initial EAX Value | Output + // ------------------|------------------------------------------------ + // 0x00 | EAX = Maximum Input Value for Basic CPUID Info. + // ------------------------------------------------------------------- + // 0x07 | EBX = Feature Information + // | Bit 8 = Bit Manipulation Instruction Set 2 (BMI2) + // | Bit 19 = Multi-Precision Add-Carry Extensions (ADX) + const ( + eaxInputQueryMax = 0x00 + eaxInputQueryExtFeatFlags = 0x07 + + ebx7OutputBMI2Bit = 8 + ebx7OutputADXBit = 19 + ) + + // Nothing to do if the CPU somehow does not support CPUID. Go probably + // won't even run on such a CPU, but as the Intel manual states, it is + // technically required to check if CPUID is supported before querying it + // and it's best to be safe. + var f Features + if !supportsCPUID() { + return f + } + + // Querying the supported feature info for BMI2 and ADX is only valid if the + // CPU at least supports querying the Structured Extended Feature + // Enumeration sub-leaf. + maxEAXInput, _, _, _ := cpuid(eaxInputQueryMax, 0) + if maxEAXInput < eaxInputQueryExtFeatFlags { + return f + } + + // Query extended feature info to determine BMI2 and ADX support. + _, ebx, _, _ := cpuid(eaxInputQueryExtFeatFlags, 0) + f.BMI2 = hasBit(ebx, ebx7OutputBMI2Bit) + f.ADX = hasBit(ebx, ebx7OutputADXBit) + + return f +} diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_x86.s b/dcrec/secp256k1/internal/cpufeat/cpu_x86.s new file mode 100644 index 0000000000..0237b3cf73 --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_x86.s @@ -0,0 +1,21 @@ +// Copyright (c) 2024-2026 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. +// +// Feature detection originally written by Dave Collins Feb 2019. Modifications +// for secp256k1 made in Jul 2026. + +//go:build (386 || amd64) && !purego + +#include "textflag.h" + +// func cpuid(eaxIn, ecxIn uint32) (eax, ebx, ecx, edx uint32) +TEXT ·cpuid(SB), NOSPLIT, $0-24 + MOVL eaxIn+0(FP), AX + MOVL ecxIn+4(FP), CX + CPUID + MOVL AX, eax+8(FP) + MOVL BX, ebx+12(FP) + MOVL CX, ecx+16(FP) + MOVL DX, edx+20(FP) + RET diff --git a/dcrec/secp256k1/internal/cpufeat/cpu_x86_test.go b/dcrec/secp256k1/internal/cpufeat/cpu_x86_test.go new file mode 100644 index 0000000000..177ebe707f --- /dev/null +++ b/dcrec/secp256k1/internal/cpufeat/cpu_x86_test.go @@ -0,0 +1,111 @@ +// 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. + +//go:build (386 || amd64) && !purego + +package cpufeat + +import "testing" + +// TestHasBit ensures that determining whether a specific bit is set in a 32-bit +// test value works as expected for edge cases, including the exact bit +// positions used to detect BMI2 and ADX. +func TestHasBit(t *testing.T) { + tests := []struct { + name string // test description + testVal uint32 // value to test + bit uint // bit position to check + want bool // expected result + }{{ + name: "bit 0 set", + testVal: 0x00000001, + bit: 0, + want: true, + }, { + name: "bit 0 unset", + testVal: 0x00000000, + bit: 0, + want: false, + }, { + name: "bit 8 set (BMI2 position in CPUID leaf 7 EBX)", + testVal: 0x00000100, + bit: 8, + want: true, + }, { + name: "bit 8 unset with all other bits set", + testVal: 0xfffffeff, + bit: 8, + want: false, + }, { + name: "bit 19 set (ADX position in CPUID leaf 7 EBX)", + testVal: 0x00080000, + bit: 19, + want: true, + }, { + name: "bit 19 unset with all other bits set", + testVal: 0xfff7ffff, + bit: 19, + want: false, + }, { + name: "bit 31 set (highest bit)", + testVal: 0x80000000, + bit: 31, + want: true, + }, { + name: "bit 31 unset with all other bits set", + testVal: 0x7fffffff, + bit: 31, + want: false, + }, { + name: "all bits set, check an arbitrary middle bit", + testVal: 0xffffffff, + bit: 15, + want: true, + }, { + name: "no bits set, check an arbitrary middle bit", + testVal: 0x00000000, + bit: 15, + want: false, + }} + + for _, test := range tests { + got := hasBit(test.testVal, test.bit) + if got != test.want { + t.Errorf("%s: unexpected result -- got %v, want %v", test.name, + got, test.want) + } + } +} + +// TestSupportsCPUID ensures that querying whether the CPU supports the CPUID +// opcode returns an idempotent result across repeated calls. +// +// Note that this intentionally does not assert a specific value since the +// result is hardware dependent. In practice, every CPU capable of running +// this compiled test binary supports CPUID, but the point of this test is to +// catch a broken implementation that behaves inconsistently rather than to +// assert a particular machine's capabilities. +func TestSupportsCPUID(t *testing.T) { + got1 := supportsCPUID() + got2 := supportsCPUID() + if got1 != got2 { + t.Fatalf("returned inconsistent results across calls -- got %v vs %v", + got1, got2) + } +} + +// TestCPUID ensures that querying CPUID with the same inputs consistently +// returns the same outputs. +func TestCPUID(t *testing.T) { + // The maximum supported input value leaf (EAX=0) is queried since it is the + // first leaf queried by [detect] itself which makes it reasonable for + // exercising the primitive directly. + eax1, ebx1, ecx1, edx1 := cpuid(0, 0) + eax2, ebx2, ecx2, edx2 := cpuid(0, 0) + if eax1 != eax2 || ebx1 != ebx2 || ecx1 != ecx2 || edx1 != edx2 { + t.Fatalf("cpuid returned inconsistent results across calls -- got "+ + "(%x,%x,%x,%x) vs (%x,%x,%x,%x)", eax1, ebx1, ecx1, edx1, eax2, + ebx2, ecx2, edx2) + } +} diff --git a/dcrec/secp256k1/internal/proofs/README.md b/dcrec/secp256k1/internal/proofs/README.md index ba7e919f6e..079069a8f9 100644 --- a/dcrec/secp256k1/internal/proofs/README.md +++ b/dcrec/secp256k1/internal/proofs/README.md @@ -6,8 +6,9 @@ proofs This consists of formal verification artifacts used to help establish the correctness of the security-critical arithmetic implementations. -The current proofs formally verify properties of the optimized field arithmetic -implementations using [z3](https://github.com/Z3Prover/z3): +The current proofs formally verify properties of the optimized scalar and field +arithmetic implementations using the [Z3 Theorem +Prover](https://github.com/Z3Prover/z3): - Correctness of multi-precision multiplication - Bounds on intermediate values @@ -45,3 +46,7 @@ optimized implementations preserve the required arithmetic invariants. - [Field Modular Reduction](field_4x64_reduce_prover.py) Provides formal verification of the reduction of a 512-bit value represented using saturated 64-bit limbs modulo the secp256k1 prime. + +- [Scalar Modular Reduction](modnscalar_4x64_reduce_prover.py) + Provides formal verification of the reduction of a 512-bit value represented + using saturated 64-bit limbs modulo the secp256k1 group order. diff --git a/dcrec/secp256k1/internal/proofs/field_4x64_reduce_prover.py b/dcrec/secp256k1/internal/proofs/field_4x64_reduce_prover.py index 7b2b50b5ef..504b170f05 100644 --- a/dcrec/secp256k1/internal/proofs/field_4x64_reduce_prover.py +++ b/dcrec/secp256k1/internal/proofs/field_4x64_reduce_prover.py @@ -16,11 +16,11 @@ # by shorter mathematical variables for clarity. # Define P. -field64Prime0 = BitVecVal(0xfffffffefffffc2f, 64) -field64Prime1 = BitVecVal(0xffffffffffffffff, 64) -field64Prime2 = BitVecVal(0xffffffffffffffff, 64) -field64Prime3 = BitVecVal(0xffffffffffffffff, 64) -field64Prime = Concat(field64Prime3, field64Prime2, field64Prime1, field64Prime0) +fieldPrime0 = BitVecVal(0xfffffffefffffc2f, 64) +fieldPrime1 = BitVecVal(0xffffffffffffffff, 64) +fieldPrime2 = BitVecVal(0xffffffffffffffff, 64) +fieldPrime3 = BitVecVal(0xffffffffffffffff, 64) +fieldPrime = Concat(fieldPrime3, fieldPrime2, fieldPrime1, fieldPrime0) # Define 2P. twicePrime0 = BitVecVal(0xfffffffdfffff85e, 64) @@ -31,7 +31,7 @@ twicePrime = Concat(twicePrime4, twicePrime3, twicePrime2, twicePrime1, twicePrime0) # Define c = 2**256 - P. -field64PrimeComplement = BitVecVal(2**32+977, 64) +fieldPrimeComplement = BitVecVal(2**32+977, 64) # -------------------------------------------------------------------------- # Prove the constant definitions satisfy the required identities before @@ -39,8 +39,8 @@ # -------------------------------------------------------------------------- # Prove twicePrime = 2P. -field64Prime320 = ZeroExt(64, field64Prime) -prove(twicePrime == field64Prime320 << 1, "twicePrime != 2P") +fieldPrime320 = ZeroExt(64, fieldPrime) +prove(twicePrime == fieldPrime320 << 1, "twicePrime != 2P") # -------------------------------------------------------------------------- # Lemma R-identity: P + c == 2**256 @@ -51,8 +51,8 @@ # => 0 + c ≡ 2**256 (mod P) # => c ≡ 2**256 (mod P) # -------------------------------------------------------------------------- -field64PrimeComplement320 = ZeroExt(256, field64PrimeComplement) -prove(field64Prime320+field64PrimeComplement320 == BitVecVal(1, 320)<<256, +fieldPrimeComplement320 = ZeroExt(256, fieldPrimeComplement) +prove(fieldPrime320+fieldPrimeComplement320 == BitVecVal(1, 320)<<256, "Lemma R-identity: P+c != 2**256") # --------------- @@ -62,19 +62,19 @@ discards = [] # first reduction: 512 bits -> 289 bits. -h, t0 = mul64(x[4], field64PrimeComplement) +h, t0 = mul64(x[4], fieldPrimeComplement) -hi, lo = mul64(x[5], field64PrimeComplement) +hi, lo = mul64(x[5], fieldPrimeComplement) t1, carry = add64(lo, h, ZERO) h, discarded = add64(hi, ZERO, carry) discards.append(discarded) -hi, lo = mul64(x[6], field64PrimeComplement) +hi, lo = mul64(x[6], fieldPrimeComplement) t2, carry = add64(lo, h, ZERO) h, discarded = add64(hi, ZERO, carry) discards.append(discarded) -hi, lo = mul64(x[7], field64PrimeComplement) +hi, lo = mul64(x[7], fieldPrimeComplement) t3, carry = add64(lo, h, ZERO) t4, discarded = add64(hi, ZERO, carry) discards.append(discarded) @@ -90,7 +90,7 @@ t4_saved = t4 # second reduction: 289 bits -> t < 2P -h, t4 = mul64(t4, field64PrimeComplement) +h, t4 = mul64(t4, fieldPrimeComplement) t0, carry = add64(t0, t4, ZERO) t1, carry = add64(t1, h, carry) @@ -100,10 +100,10 @@ t4 = carry # final reduction: t < 2P -> t < P -s0, borrow = sub64(t0, field64Prime0, ZERO) -s1, borrow = sub64(t1, field64Prime1, borrow) -s2, borrow = sub64(t2, field64Prime2, borrow) -s3, borrow = sub64(t3, field64Prime3, borrow) +s0, borrow = sub64(t0, fieldPrime0, ZERO) +s1, borrow = sub64(t1, fieldPrime1, borrow) +s2, borrow = sub64(t2, fieldPrime2, borrow) +s3, borrow = sub64(t3, fieldPrime3, borrow) _, borrow = sub64(t4, ZERO, borrow) r0 = If(borrow == ONE, t0, s0) r1 = If(borrow == ONE, t1, s1) @@ -121,7 +121,7 @@ # # Since the complement is 33 bits, this proves the value does not exceed # 256 + 33 = 289 bits after the first reduction. -prove(ULE(t4_saved, field64PrimeComplement), "top limb after 1st reduction > complement") +prove(ULE(t4_saved, fieldPrimeComplement), "top limb after 1st reduction > complement") # Value after second reduction is less than twice the prime (t < 2P). t = Concat(t4, t3, t2, t1, t0) @@ -129,7 +129,7 @@ # Fully reduced result is less than the prime (r < P). r = Concat(r3, r2, r1, r0) -prove(ULT(r, field64Prime), "fully reduced result >= P") +prove(ULT(r, fieldPrime), "fully reduced result >= P") # ----------------------------------------------------------------------------- # Prove functional congruence: r ≡ x (mod P). @@ -155,7 +155,7 @@ # which contains no multipliers at all and reduces to adder-network equivalence # that the solver dispatches quickly. # -# The Go function, field64Reduce512, computes r in three stages: +# The Go function, field4x64.reduce512, computes r in three stages: # # 1) First reduction: Fold the high 256 bits of x (x4..x7) into the low 256 bits # (x0..x3). This produces t_1 = x_lo + x_hi*c which fits in 289 bits (5 @@ -169,7 +169,7 @@ # P which is exactly the definition of x ≡ r (mod P). def prove_functional_congruence_lemmas(): """ - Prove r == x (mod P) for field64Reduce512. + Prove r == x (mod P) for field4x64.reduce512. x is the 512-bit input (8 limbs x0..x7) and r is the fully-reduced 256-bit output (4 limbs). @@ -325,14 +325,14 @@ def pack_limbs(limbs): # and the 4-limb result wouldn't represent it. This fact is proven above # for the real total, so this does not smuggle in a new assumption. # ------------------------------------------------------------------------- - p = cwidth(field64Prime) + p = cwidth(fieldPrime) twop = cwidth(twicePrime) z = BitVecs("z0 z1 z2 z3 z4", 64) s = [None]*4 - s[0], borrow = sub64(z[0], field64Prime0, ZERO) - s[1], borrow = sub64(z[1], field64Prime1, borrow) - s[2], borrow = sub64(z[2], field64Prime2, borrow) - s[3], borrow = sub64(z[3], field64Prime3, borrow) + s[0], borrow = sub64(z[0], fieldPrime0, ZERO) + s[1], borrow = sub64(z[1], fieldPrime1, borrow) + s[2], borrow = sub64(z[2], fieldPrime2, borrow) + s[3], borrow = sub64(z[3], fieldPrime3, borrow) _, borrow = sub64(z[4], ZERO, borrow) t = pack_limbs(z) r = If(borrow == ONE, t, pack_limbs(s)) diff --git a/dcrec/secp256k1/internal/proofs/modnscalar_4x64_reduce_prover.py b/dcrec/secp256k1/internal/proofs/modnscalar_4x64_reduce_prover.py new file mode 100644 index 0000000000..1414738768 --- /dev/null +++ b/dcrec/secp256k1/internal/proofs/modnscalar_4x64_reduce_prover.py @@ -0,0 +1,571 @@ +# Copyright (c) 2026 The Decred developers +# Copyright (c) 2026 Dave Collins +# Use of this source code is governed by an ISC +# license that can be found in the LICENSE file. + +from z3_proof_helpers import * + +# ------- +# Inputs. +# ------- + +x = BitVecs('x0 x1 x2 x3 x4 x5 x6 x7', 64) + +# NOTE: The code in this section reuses the variable names from the Go code +# being proven for easy cross referencing. However, the comments refer to them +# by shorter mathematical variables for clarity. + +# Define N. +orderLimb0 = BitVecVal(0xbfd25e8cd0364141, 64) +orderLimb1 = BitVecVal(0xbaaedce6af48a03b, 64) +orderLimb2 = BitVecVal(0xfffffffffffffffe, 64) +orderLimb3 = BitVecVal(0xffffffffffffffff, 64) +order = Concat(orderLimb3, orderLimb2, orderLimb1, orderLimb0) + +# Define 2N. +twiceOrderLimb0 = BitVecVal(0x7fa4bd19a06c8282, 64) +twiceOrderLimb1 = BitVecVal(0x755db9cd5e914077, 64) +twiceOrderLimb2 = BitVecVal(0xfffffffffffffffd, 64) +twiceOrderLimb3 = BitVecVal(0xffffffffffffffff, 64) +twiceOrderLimb4 = ONE +twiceOrder = Concat( + twiceOrderLimb4, twiceOrderLimb3, twiceOrderLimb2, + twiceOrderLimb1, twiceOrderLimb0 +) + +# Define 2**256 - N. +orderComplementLimb0 = BitVecVal(0x402da1732fc9bebf, 64) +orderComplementLimb1 = BitVecVal(0x4551231950b75fc4, 64) +orderComplementLimb2 = ONE +orderComplement = Concat(orderComplementLimb2, orderComplementLimb1, orderComplementLimb0) + +# -------------------------------------------------------------------------- +# Prove the constant definitions satisfy the required identities before +# trusting anything that uses them. +# -------------------------------------------------------------------------- + +# Prove twiceOrder = 2N. +order320 = ZeroExt(64, order) +prove(twiceOrder == order320 << 1, "twiceOrder != 2N") + +# -------------------------------------------------------------------------- +# Lemma R-identity: N + c == 2**256 +# +# This lemma justifies replacing multiples of 2**256 with multiples of c when +# working modulo N: +# +# N + c ≡ 2**256 (mod N) +# => 0 + c ≡ 2**256 (mod N) +# => c ≡ 2**256 (mod N) +# -------------------------------------------------------------------------- +orderComplement320 = ZeroExt(128, orderComplement) +prove(order320+orderComplement320 == BitVecVal(1, 320)<<256, + "Lemma R-identity: N+c != 2**256") + +# --------------- +# Model the code. +# --------------- + +discards = [] + +# first reduction: 512 bits -> 385 bits. +h0, t0 = mul64(x[4], orderComplementLimb0) +h1, t1 = mul64(x[4], orderComplementLimb1) +t1, carry = add64(t1, h0, ZERO) +t2, carry = add64(x[4], h1, carry) +t3 = carry + +h0, l0 = mul64(x[5], orderComplementLimb0) +h1, l1 = mul64(x[5], orderComplementLimb1) +t1, carry = add64(t1, l0, ZERO) +t2, carry = add64(t2, h0, carry) +t3, discarded = add64(t3, h1, carry) +discards.append(discarded) +t2, carry = add64(t2, l1, ZERO) +t3, carry = add64(t3, x[5], carry) +t4 = carry + +h0, l0 = mul64(x[6], orderComplementLimb0) +h1, l1 = mul64(x[6], orderComplementLimb1) +t2, carry = add64(t2, l0, ZERO) +t3, carry = add64(t3, h0, carry) +t4, discarded = add64(t4, h1, carry) +discards.append(discarded) +t3, carry = add64(t3, l1, ZERO) +t4, carry = add64(t4, x[6], carry) +t5 = carry + +h0, l0 = mul64(x[7], orderComplementLimb0) +h1, l1 = mul64(x[7], orderComplementLimb1) +t3, carry = add64(t3, l0, ZERO) +t4, carry = add64(t4, h0, carry) +t5, discarded = add64(t5, h1, carry) +discards.append(discarded) +t4, carry = add64(t4, l1, ZERO) +t5, carry = add64(t5, x[7], carry) +t6 = carry + +t0, carry = add64(t0, x[0], ZERO) +t1, carry = add64(t1, x[1], carry) +t2, carry = add64(t2, x[2], carry) +t3, carry = add64(t3, x[3], carry) +t4, carry = add64(t4, ZERO, carry) +t5, carry = add64(t5, ZERO, carry) +t6, discarded = add64(t6, ZERO, carry) +discards.append(discarded) + +# Save for analysis. +t6_saved_1 = t6 +t_full_redux_1 = Concat(ZERO, t6, t5, t4, t3, t2, t1, t0) +first_redux_input_high = Concat(x[7], x[6], x[5], x[4]) + +# second reduction: 385 bits -> 258 bits. +h0, l0 = mul64(t4, orderComplementLimb0) +h1, l1 = mul64(t4, orderComplementLimb1) +l2 = t4 +t0, carry = add64(t0, l0, ZERO) +t1, carry = add64(t1, h0, carry) +t2, carry = add64(t2, h1, carry) +t3, carry = add64(t3, ZERO, carry) +t4 = carry +t1, carry = add64(t1, l1, ZERO) +t2, carry = add64(t2, l2, carry) +t3, carry = add64(t3, ZERO, carry) +t4, discarded = add64(t4, ZERO, carry) +discards.append(discarded) + +h0, l0 = mul64(t5, orderComplementLimb0) +h1, l1 = mul64(t5, orderComplementLimb1) +t1, carry = add64(t1, l0, ZERO) +t2, carry = add64(t2, h0, carry) +t3, carry = add64(t3, h1, carry) +t4, discarded = add64(t4, ZERO, carry) +discards.append(discarded) +t2, carry = add64(t2, l1, ZERO) +t3, carry = add64(t3, t5, carry) +t4, discarded = add64(t4, ZERO, carry) +discards.append(discarded) + +t2, carry = add64(t2, t6*orderComplementLimb0, ZERO) +t3, carry = add64(t3, t6*orderComplementLimb1, carry) +t4, discarded = add64(t4, t6, carry) +discards.append(discarded) + +# Save intermediate results for analysis. +t3_carry_2 = carry +t3_saved_2 = t3 +t4_saved_2 = t4 + +# third reduction: max 258 bits -> t < 2N. +t0, carry = add64(t0, t4*orderComplementLimb0, ZERO) +t1, carry = add64(t1, t4*orderComplementLimb1, carry) +t2, carry = add64(t2, t4, carry) +t3, carry = add64(t3, ZERO, carry) +t4 = carry + +# final reduction: t < 2N -> t < N +s0, borrow = sub64(t0, orderLimb0, ZERO) +s1, borrow = sub64(t1, orderLimb1, borrow) +s2, borrow = sub64(t2, orderLimb2, borrow) +s3, borrow = sub64(t3, orderLimb3, borrow) +_, borrow = sub64(t4, ZERO, borrow) +r0 = If(borrow == ONE, t0, s0) +r1 = If(borrow == ONE, t1, s1) +r2 = If(borrow == ONE, t2, s2) +r3 = If(borrow == ONE, t3, s3) + +# ------- +# Proofs. +# ------- + +# Discarded carries are never set. +prove_no_discarded_carries(discards) + +# Top limb after first reduction is at most 1 (t6 ≤ 1). +# +# This along with the proof there is no discarded carry on t6 after the first +# reduction proves the value does not exceed 385 bits (as expected since the +# complement is 129 bits and there were a max of 256 bits in the upper limbs +# before the first reduction, so 256 + 129 = 385). +prove(ULE(t6_saved_1, ONE), "top limb after 1st reduction > 1") + +# Top limb after second reduction is at most 3 (t4 ≤ 3). +# +# This along with the proof there is no discarded carry on t4 after the second +# reduction proves the value does not exceed 258 bits (as expected because the +# complement is 129 bits and there were 129 max bits left in the upper limbs +# after the first reduction, so 129 + 129 = 258). +prove(ULE(t4_saved_2, BitVecVal(3, 64)), "top limb after 2nd reduction > 3") + +# Top limb after second reduction times complement limb 0 never exceeds uint64. +# ((t4*c0)>>64 == 0) +# +# This proves the regular 64-bit product used in the third reduction does not +# overflow. +t4c0hi, _ = mul64(t4_saved_2, orderComplementLimb0) +prove( + t4c0hi == ZERO, + "high word of top limb from 2nd reduction times complement limb 0 != 0", +) + +# Top limb after second reduction times complement limb 1 never exceeds uint64. +# ((t4*c1)>>64 == 0) +# +# This proves the regular 64-bit product used in the third reduction does not +# overflow. +t4c1hi, _ = mul64(t4_saved_2, orderComplementLimb1) +prove( + t4c1hi == ZERO, + "high word of top limb from 2nd reduction times complement limb 1 != 0", +) + +# Top limb after third reduction is at most 1 (t4 ≤ 1). +# +# This proof is not strictly necessary since the bound is actually tigher per +# the next proof which implicitly proves this fact. However, it is fast to +# prove and including it provides nice symmetry with the previous proofs for the +# bounds of the top limb after each reduction. +prove(ULE(t4, ONE), "top limb after 3rd reduction > 1") + +# Value after third reduction is less than twice the group order (t < 2N). +t = Concat(t4, t3, t2, t1, t0) +prove(ULT(t, twiceOrder), "value after 3rd reduction >= 2N") + +# Fully reduced result is less than the group order (r < N). +r = Concat(r3, r2, r1, r0) +prove(ULT(r, order), "fully reduced result >= N") + +# ----------------------------------------------------------------------------- +# Prove functional congruence: r ≡ x (mod N). +# +# The checks above establish bounds and the safety of discarded carries, +# self-consistency of the constants, and the reduction identity (lemma +# R-identity). +# +# The following lemmas establish the arithmetic identity of each transformation +# stage. Together with the previously proven bounds and reduction identity, +# they imply the overall congruence r ≡ x (mod N). +# +# A direct assertion such as t == x_lo + (x >> 256)*P is intractable because +# it introduces a monolithic 256x256 multiplier that appears nowhere in the +# modeled code, so the SAT solver is forced into bit-level equivalence +# checking between two structurally unrelated multiplier circuits. +# +# Instead, because every accumulation block in the implementation is a fixed +# carry-propagation network over the *outputs* of bits.Mul64, whether or not +# those 128-bit inputs are actually products is irrelevant to the network. Each +# block satisfies its per-block identity for ALL 128-bit values. So each block +# identity is proven here as a universal lemma over unconstrained variables, +# which contains no multipliers at all and reduces to adder-network equivalence +# that the solver dispatches quickly. +# +# The Go function, scalar64Reduce512, computes r in four stages: +# +# 1) First reduction: Fold the high 256 bits of x (x4..x7) into the low 256 bits +# (x0..x3). This produces t_1 = x_lo + x_hi*c which fits in 385 bits (7 +# 64-bit limbs with the 7th limb small). +# 2) Second reduction: Fold t_1's 5th, 6th, and 7th limbs back in the same way. +# This produces t_2 = t_1_lo + t_1_hi*c which fits in 258 bits (5 64-bit +# limbs with the 7th limb small) +# 3) Third reduction: Fold t_2's 5th limb back in the same way. This produces +# t_3 = t_2_lo + t_1_hi*c, where t_3 < 2N. +# 4) Final reduction: One conditional subtraction of N which is valid because +# t_3 < 2N. +# +# The lemmas below chain together to prove r ≡ t3 ≡ t2 ≡ t1 ≡ x (mod N) and 0 ≤ +# r < N which is exactly the definition of x ≡ r (mod N). +def prove_functional_congruence_lemmas(): + """ + Prove r == x (mod N) for scalar64Reduce512. + + x is the 512-bit input (8 limbs x0..x7) and r is the fully-reduced 256-bit + output (4 limbs). + """ + + # Use a wide enough congruence width that nothing in a 512-bit input ever + # wraps around. This is comfortably wider than the largest quantity + # of x_hi*c < 2**385. + CONGRUENCE_WIDTH = 512 + + def cwidth(v): + """Zero-extend v to CONGRUENCE_WIDTH bits.""" + return ZeroExt(CONGRUENCE_WIDTH - v.size(), v) + + def pack(*limbs): + """ + Concatenate the provided big-endian 64-bit limbs into a single + CONGRUENCE_WIDTH-bit value: + + limb0*2**(64*num_limbs) + limb1*2**(64*(num_limbs-1)) + ... + """ + return cwidth(Concat(*limbs)) + + def pack_limbs(limbs): + """ + Concatenate the provided iterable little-endian 64-bit limbs into a + single CONGRUENCE_WIDTH-bit value: + + limbs[0] + limbs[1]*2**64 + limbs[2]*2**128 + ... + """ + return cwidth(Concat(*reversed(limbs))) + + def compose_mul_by_c(p0, p1, xj): + """ + Compose the multi-limb product xj*c from the two 128-bit partial + products produced by multiplying xj with the two complement limbs. + + Since the third complement limb is 1, the full product is: + + p0 + (p1 << 64) + (xj << 128) + + The result is equal to xj*c and is exactly how the implementation + accumulates it. + """ + return cwidth(p0) + (cwidth(p1)<<64) + (cwidth(xj)<<128) + + # Universal lemma variables shared by R-open, R-interior, and + # R-second-interior. + # + # x_j models an arbitrary 64-bit input limb. + # + # p0 and p1 model the 128-bit products returned by bits.Mul64(x[j], c0) and + # bits.Mul64(x[j], c1) (the lower two limbs of c) at some point in the real + # code. They are NOT tied to any specific x[j] or t[k]. + # + # h0 and l0 are the upper and lower halves of p0, respectively. Likewise h1 + # and l1 for p1. + # + # x_j_times_c is the composed value of x[j]*c. + x_j = BitVec("x_j", 64) + p0, p1 = BitVecs("p0 p1", 128) + h0, l0 = split128(p0) + h1, l1 = split128(p1) + x_j_times_c = compose_mul_by_c(p0, p1, x_j) + + # ------------------------------------------------------------------------- + # Lemma R-open: The opening row of the first reduction (the x4 line). + # + # h0, t0 = bits.Mul64(x[4], orderComplementLimb0) + # h1, t1 = bits.Mul64(x[4], orderComplementLimb1) + # t1, carry = bits.Add64(t1, h0, 0) + # t2, carry = bits.Add64(x[4], h1, carry) + # t3 = carry + # + # This only writes fresh limbs and has no discarded carries, so no + # assumptions are needed. + # ------------------------------------------------------------------------- + t0 = l0 + t1, carry = add64(l1, h0, ZERO) + t2, carry = add64(x_j, h1, carry) + t3 = carry + prove(pack(t3,t2,t1,t0) == x_j_times_c, "R-open: state != x4*c") + + # ------------------------------------------------------------------------- + # Lemma R-interior: The shape shared by the x5, x6, and x7 blocks in the + # first reduction. + # + # h0, l0 = bits.Mul64(x[j], orderComplementLimb0) + # h1, l1 = bits.Mul64(x[j], orderComplementLimb1) + # t[j], carry = bits.Add64(t[j], l0, 0) + # t[j+1], carry = bits.Add64(t[j+1], h0, carry) + # t[j+2], _ = bits.Add64(t[j+2], h1, carry) + # t[j+1], carry = bits.Add64(t[j+1], l1, 0) + # t[j+2], carry = bits.Add64(t[j+2], x[j], carry) + # t[j+3] = carry + # + # a0..a3 model the incoming running total in t0..t3. + # + # One carry is discarded and assumed zero. This fact is proven above by the + # discard checks for the real x5/x6/x7 blocks, so this does not smuggle in a + # new assumption. + # ------------------------------------------------------------------------- + a = BitVecs("a0 a1 a2", 64) + w = [None]*4 + w[0], carry = add64(a[0], l0, ZERO) + w[1], carry = add64(a[1], h0, carry) + w[2], discarded = add64(a[2], h1, carry) + w[1], carry = add64(w[1], l1, ZERO) + w[2], carry = add64(w[2], x_j, carry) + w[3] = carry + prove( + pack_limbs(w) == pack_limbs(a) + x_j_times_c, + "R-interior: block != state + xj*c", + assumptions=[discarded == ZERO], + ) + + # ------------------------------------------------------------------------- + # Lemma R-tail: Fold x0..x3 into running total after the blocks above. + # + # t0, carry = bits.Add64(t0, x[0], 0) + # t1, carry = bits.Add64(t1, x[1], carry) + # ... + # t5, carry = bits.Add64(t5, 0, carry) + # t6, _ = bits.Add64(t6, 0, carry) + # + # b0..b6 model the incoming running total in t0..t6. + # + # The final line is modeled here as add64 with its own carry-out assumed + # zero. This fact is proven above by the discard checks for the real total, + # so this does not smuggle in a new assumption. + # ------------------------------------------------------------------------- + b = BitVecs("b0 b1 b2 b3 b4 b5 b6", 64) + w = [None]*7 + w[0], carry = add64(b[0], x[0], ZERO) + w[1], carry = add64(b[1], x[1], carry) + w[2], carry = add64(b[2], x[2], carry) + w[3], carry = add64(b[3], x[3], carry) + w[4], carry = add64(b[4], ZERO, carry) + w[5], carry = add64(b[5], ZERO, carry) + w[6], discarded = add64(b[6], ZERO, carry) + prove( + pack_limbs(w) == pack_limbs(b) + pack_limbs(x[:4]), + "R-tail: post-fold total != state + x_lo", + assumptions=[discarded == ZERO], + ) + + # ------------------------------------------------------------------------- + # Lemma R-second-interior: The carry-propagation shape shared by the t4 and + # t5 blocks of the second reduction. + # + # Unlike R-interior, this block propagates carries through an existing fifth + # accumulator limb rather than terminating with a new carry limb. + # + # h0, l0 = bits.Mul64(t[k], orderComplementLimb0) + # h1, l1 = bits.Mul64(t[k], orderComplementLimb1) + # // h2, l2 = bits.Mul64(t[k], orderComplementLimb2) => h2=0, l2=t[k] + # t[j], carry = bits.Add64(t[j], l0, 0) + # t[j+1], carry = bits.Add64(t[j+1], h0, carry) + # ... + # t[j+4], _ = bits.Add64(t[j+4], 0, carry) + # + # d0..d4 model the incoming running total in t0..t4. + # + # d4 is zero when instantiated for the t4 block. + # + # The t4 additions are modeled as add64 with the carry-out assumed zero. + # This fact is proven above by the discard checks for the real totals, so + # this does not smuggle in a new assumption. + # ------------------------------------------------------------------------- + d = BitVecs("d0 d1 d2 d3 d4", 64) + w = [None]*5 + w[0], carry = add64(d[0], l0, ZERO) + w[1], carry = add64(d[1], h0, carry) + w[2], carry = add64(d[2], h1, carry) + w[3], carry = add64(d[3], ZERO, carry) + w[4], discarded1 = add64(d[4], ZERO, carry) + w[1], carry = add64(w[1], l1, ZERO) + w[2], carry = add64(w[2], x_j, carry) + w[3], carry = add64(w[3], ZERO, carry) + w[4], discarded2 = add64(w[4], ZERO, carry) + prove( + pack_limbs(w) == pack_limbs(d) + x_j_times_c, + "R-second-interior: block != state + xj*c", + assumptions=[discarded1 == ZERO, discarded2 == ZERO], + ) + + # ------------------------------------------------------------------------- + # Lemma R-second-tail: The t6 block of the second reduction. + # + # t2, carry = bits.Add64(t2, t6*orderComplementLimb0, 0) + # t3, carry = bits.Add64(t3, t6*orderComplementLimb1, carry) + # t4, _ = bits.Add64(t4, t6, carry) + # + # f0..f2 model the incoming running total in t2..t4. + # + # t6 models the incoming carry limb produced by the preceding block. + # + # The real implementation uses plain (truncating) 64-bit products here rather + # than bits.Mul64. Since t6 ≤ 1, these products are exact, so modeling them + # as ordinary 64-bit multiplications is equivalent to the implementation. + # + # Assumes t6 ≤ 1 and one discarded carry is 0. These facts are both proven + # above, so this is not smuggling in new assumptions. + # ------------------------------------------------------------------------- + f = BitVecs("f0 f1 f2", 64) + t6 = BitVec("t6", 64) + w = [None]*3 + w[0], carry = add64(f[0], t6*orderComplementLimb0, ZERO) + w[1], carry = add64(f[1], t6*orderComplementLimb1, carry) + w[2], discarded = add64(f[2], t6, carry) + t6_times_c = compose_mul_by_c( + ZeroExt(64, t6) * ZeroExt(64, orderComplementLimb0), + ZeroExt(64, t6) * ZeroExt(64, orderComplementLimb1), + t6, + ) + prove( + pack_limbs(w) == pack_limbs(f) + t6_times_c, + "R-second-tail: block != state + t6*c", + assumptions=[ULE(t6, 1), discarded == ZERO], + ) + + # ------------------------------------------------------------------------- + # Lemma R-third: The third reduction. + # + # t0, carry = bits.Add64(t0, t4*orderComplementLimb0, 0) + # t1, carry = bits.Add64(t1, t4*orderComplementLimb1, carry) + # t2, carry = bits.Add64(t2, t4, carry) + # t3, carry = bits.Add64(t3, 0, carry) + # t4 = carry + # + # g0..g3 model the incoming running total in t0..t3. Although the product + # is added only into t0..t2, the carry chain propagates through t3. + # + # t4 models the incoming carry limb produced by the preceding block. + # + # The real implementation uses plain (truncating) 64-bit products here rather + # than bits.Mul64. Since t4 ≤ 3, these products are exact, so modeling them + # as ordinary 64-bit multiplications is equivalent to the implementation. + # + # Assumes t4 ≤ 3. This fact is proven above, so this is not smuggling in + # a new assumption. + # ------------------------------------------------------------------------- + g = BitVecs("g0 g1 g2 g3", 64) + t4 = BitVec("t4", 64) + w = [None]*5 + w[0], carry = add64(g[0], t4*orderComplementLimb0, ZERO) + w[1], carry = add64(g[1], t4*orderComplementLimb1, carry) + w[2], carry = add64(g[2], t4, carry) + w[3], carry = add64(g[3], ZERO, carry) + w[4] = carry + t4_times_c = compose_mul_by_c( + ZeroExt(64, t4) * ZeroExt(64, orderComplementLimb0), + ZeroExt(64, t4) * ZeroExt(64, orderComplementLimb1), + t4, + ) + prove( + pack_limbs(w) == pack_limbs(g) + t4_times_c, + "R-third: 3rd reduction != state + t4*c", + assumptions=[ULE(t4, 3)], + ) + + # ------------------------------------------------------------------------- + # Lemma R-final: The conditional-subtraction final reduction. + # + # s0, borrow = bits.Sub64(t0, orderLimb0, 0) + # ... + # _, borrow = bits.Sub64(t4, 0, borrow) + # r[i] = constantTimeSelect64(borrow, t[i], s[i]) + # + # z0..z4 model t0..t4 going into this stage. + # + # This is only valid because t < 2N as otherwise t - N could still exceed N + # and the 4-limb result wouldn't represent it. This fact is proven above + # for the real total, so this does not smuggle in a new assumption. + # ------------------------------------------------------------------------- + n = cwidth(order) + z = BitVecs("z0 z1 z2 z3 z4", 64) + s = [None]*4 + s[0], borrow = sub64(z[0], orderLimb0, ZERO) + s[1], borrow = sub64(z[1], orderLimb1, borrow) + s[2], borrow = sub64(z[2], orderLimb2, borrow) + s[3], borrow = sub64(z[3], orderLimb3, borrow) + _, borrow = sub64(z[4], ZERO, borrow) + t = pack_limbs(z) + r = If(borrow == ONE, t, pack_limbs(s)) + prove( + r == If(ULT(t, n), t, t - n), + "R-final: conditional-subtract result != t mod N", + assumptions=[ULT(t, cwidth(twiceOrder))], + ) + +prove_functional_congruence_lemmas() \ No newline at end of file diff --git a/dcrec/secp256k1/modnscalar.go b/dcrec/secp256k1/modnscalar.go index 671260c3e0..1cb4d3e32a 100644 --- a/dcrec/secp256k1/modnscalar.go +++ b/dcrec/secp256k1/modnscalar.go @@ -5,9 +5,13 @@ package secp256k1 import ( + "encoding/binary" "encoding/hex" "math/big" + "math/bits" "sync" + + "github.com/decred/dcrd/dcrec/secp256k1/v4/internal/arith" ) // References: @@ -18,81 +22,76 @@ import ( // https://cacr.uwaterloo.ca/hac/ // Many elliptic curve operations require working with scalars in a finite field -// characterized by the order of the group underlying the secp256k1 curve. -// Given this precision is larger than the biggest available native type, -// obviously some form of bignum math is needed. This code implements -// specialized fixed-precision field arithmetic rather than relying on an -// arbitrary-precision arithmetic package such as math/big for dealing with the -// math modulo the group order since the size is known. As a result, rather -// large performance gains are achieved by taking advantage of many -// optimizations not available to arbitrary-precision arithmetic and generic -// modular arithmetic algorithms. +// defined by the order of the group underlying the secp256k1 curve. Since the +// group order exceeds the width of the largest available native type, some form +// of multi-precision arithmetic is required. This code implements specialized +// fixed-precision field arithmetic rather than relying on an +// arbitrary-precision arithmetic package such as math/big for performing +// arithmetic modulo the fixed group order. As a result, significant +// performance gains are achieved by taking advantage of many optimizations not +// available to arbitrary-precision arithmetic and generic modular arithmetic +// algorithms. // -// There are various ways to internally represent each element. For example, -// the most obvious representation would be to use an array of 4 uint64s (64 -// bits * 4 = 256 bits). However, that representation suffers from the fact -// that there is no native Go type large enough to handle the intermediate -// results while adding or multiplying two 64-bit numbers. +// There are various ways to internally represent each element with their own +// pros and cons. Some common representations are: // -// Given the above, this implementation represents the field elements as 8 -// uint32s with each word (array entry) treated as base 2^32. This was chosen -// because most systems at the current time are 64-bit (or at least have 64-bit -// registers available for specialized purposes such as MMX) so the intermediate -// results can typically be done using a native register (and using uint64s to -// avoid the need for additional half-word arithmetic) - +// - 8 uint32s with base 2^32 limbs (aka 8x32: 32 bits * 8 = 256 bits) +// - 10 uint32s with base 2^26 limbs (aka 10x26: 26 bits * 10 = 260 bits) +// - 4 uint64s with base 2^64 limbs (aka 4x64: 64 bits * 4 = 256 bits) +// - 5 uint64s with base 2^52 limbs (aka 5x52: 52 bits * 5 = 260 bits) +// +// The primary tradeoff is complexity versus performance and the optimal choice +// also largely depends on the available hardware capabilities. +// +// For example, 5x52 and 10x26 allow performing several operations in a row +// before carry propagation or modular reduction becomes necessary since there +// are additional bits available in each limb, but that comes at the cost of +// manual magnitude tracking, multiple non-canonical representations of each +// number, and periodic normalization to propagate the accumulated carries and +// perform the modular reduction all at once. +// +// Conversely, 8x32 and 4x64 involve less complexity, are simpler to use, have +// fewer limbs to manage, and only have a single canonical representation for +// each number, but that comes at the cost of requiring carry propagation and +// modular reduction for every operation and larger intermediate results. +// +// The requirement for larger intermediate results is particularly notable for +// uint64s because there is no native Go type large enough to handle the 128-bit +// intermediate results while adding and multiplying the 64-bit limbs. +// +// This implementation historically used 8x32 for that reason, coupled with the +// fact that Go did not reliably make use of hardware-supported wide arithmetic, +// to ensure all intermediate results fit cleanly into uint64s. +// +// However, most systems are now 64-bit and almost all have instructions capable +// of handling the 128-bit intermediate results without the need for additional +// half-word arithmetic. Further, modern versions of Go automatically use those +// instructions on hardware that supports it. +// +// Given the above, this implementation represents the field elements as 4 +// uint64s with each limb (array entry) treated as base 2^64 (aka 4x64) and +// keeps the value reduced at all times. const ( - // These fields provide convenient access to each of the words of the + // These fields provide convenient access to each of the limbs of the // secp256k1 curve group order N to improve code readability. // // The group order of the curve per [SECG] is: - // 0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141 - // - // nolint: dupword - orderWordZero uint32 = 0xd0364141 - orderWordOne uint32 = 0xbfd25e8c - orderWordTwo uint32 = 0xaf48a03b - orderWordThree uint32 = 0xbaaedce6 - orderWordFour uint32 = 0xfffffffe - orderWordFive uint32 = 0xffffffff - orderWordSix uint32 = 0xffffffff - orderWordSeven uint32 = 0xffffffff - - // These fields provide convenient access to each of the words of the two's + // 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + orderLimb0 uint64 = 0xbfd25e8cd0364141 + orderLimb1 uint64 = 0xbaaedce6af48a03b + orderLimb2 uint64 = 0xfffffffffffffffe + orderLimb3 uint64 = 0xffffffffffffffff + + // These fields provide convenient access to each of the limbs of the two's // complement of the secp256k1 curve group order N to improve code // readability. // // The two's complement of the group order is: // 0x00000000 00000000 00000000 00000001 45512319 50b75fc4 402da173 2fc9bebf - orderComplementWordZero uint32 = (^orderWordZero) + 1 - orderComplementWordOne uint32 = ^orderWordOne - orderComplementWordTwo uint32 = ^orderWordTwo - orderComplementWordThree uint32 = ^orderWordThree - // orderComplementWordFour uint32 = ^orderWordFour // unused - // orderComplementWordFive uint32 = ^orderWordFive // unused - // orderComplementWordSix uint32 = ^orderWordSix // unused - // orderComplementWordSeven uint32 = ^orderWordSeven // unused - - // These fields provide convenient access to each of the words of the - // secp256k1 curve group order N / 2 to improve code readability and avoid - // the need to recalculate them. - // - // The half order of the secp256k1 curve group is: - // 0x7fffffff ffffffff ffffffff ffffffff 5d576e73 57a4501d dfe92f46 681b20a0 - // - // nolint: dupword - halfOrderWordZero uint32 = 0x681b20a0 - halfOrderWordOne uint32 = 0xdfe92f46 - halfOrderWordTwo uint32 = 0x57a4501d - halfOrderWordThree uint32 = 0x5d576e73 - halfOrderWordFour uint32 = 0xffffffff - halfOrderWordFive uint32 = 0xffffffff - halfOrderWordSix uint32 = 0xffffffff - halfOrderWordSeven uint32 = 0x7fffffff - - // uint32Mask is simply a mask with all bits set for a uint32 and is used to - // improve the readability of the code. - uint32Mask = 0xffffffff + orderComplementLimb0 uint64 = (^orderLimb0) + 1 + orderComplementLimb1 uint64 = ^orderLimb1 + orderComplementLimb2 uint64 = ^orderLimb2 + // orderComplementLimb3 uint64 = ^orderLimb3 // unused ) // ModNScalar implements optimized 256-bit constant-time fixed-precision @@ -106,39 +105,37 @@ const ( // around if absolutely needed. For example, subtraction can be performed by // adding the negation. // -// Should it be absolutely necessary, conversion to the standard library -// math/big.Int can be accomplished by using the Bytes method, slicing the -// resulting fixed-size array, and feeding it to big.Int.SetBytes. However, -// that should typically be avoided when possible as conversion to big.Ints -// requires allocations, is not constant time, and is slower when working modulo -// the group order. +// Should it be absolutely necessary, conversion to a standard library [big.Int] +// can be accomplished by using [ModNScalar.Bytes], slicing the resulting +// fixed-size array, and feeding it to [big.Int.SetBytes]. However, that should +// typically be avoided when possible as conversion to a [big.Int] requires +// allocations, is not constant time, and is slower when working modulo the +// group order. type ModNScalar struct { - // The scalar is represented as 8 32-bit integers in base 2^32. + // The scalar is represented as 4 64-bit integers in base 2^64. // // The following depicts the internal representation: - // --------------------------------------------------------- - // | n[7] | n[6] | ... | n[0] | - // | 32 bits | 32 bits | ... | 32 bits | - // | Mult: 2^(32*7) | Mult: 2^(32*6) | ... | Mult: 2^(32*0) | - // --------------------------------------------------------- + // -------------------------------------------------------------------- + // | n[3] | n[2] | n[1] | n[0] | + // | 64 bits | 64 bits | 64 bits | 64 bits | + // | Mult: 2^(64*3) | Mult: 2^(64*2) | Mult: 2^(64*1) | Mult: 2^(64*0) | + // -------------------------------------------------------------------- // - // For example, consider the number 2^87 + 2^42 + 1. It would be + // For example, consider the number 2^135 + 2^87 + 1. It would be // represented as: // n[0] = 1 - // n[1] = 2^10 - // n[2] = 2^23 - // n[3..7] = 0 - // - // The full 256-bit value is then calculated by looping i from 7..0 and - // doing sum(n[i] * 2^(32i)) like so: - // n[7] * 2^(32*7) = 0 * 2^224 = 0 - // n[6] * 2^(32*6) = 0 * 2^192 = 0 - // ... - // n[2] * 2^(32*2) = 2^23 * 2^64 = 2^87 - // n[1] * 2^(32*1) = 2^10 * 2^32 = 2^42 - // n[0] * 2^(32*0) = 1 * 2^0 = 1 - // Sum: 0 + 0 + ... + 2^87 + 2^42 + 1 = 2^87 + 2^42 + 1 - n [8]uint32 + // n[1] = 2^23 + // n[2] = 2^7 + // n[3] = 0 + // + // The full 256-bit value is then calculated by looping i from 3..0 and + // doing sum(n[i] * 2^(64i)) like so: + // n[3] * 2^(64*3) = 0 * 2^192 = 0 + // n[2] * 2^(64*2) = 2^7 * 2^128 = 2^135 + // n[1] * 2^(64*1) = 2^23 * 2^64 = 2^87 + // n[0] * 2^(64*0) = 1 * 2^0 = 1 + // Sum: 0 + 2^135 + 2^87 + 1 = 2^135 + 2^87 + 1 + n [4]uint64 } // String returns the scalar as a human-readable hex string. @@ -163,7 +160,7 @@ func (s *ModNScalar) Set(val *ModNScalar) *ModNScalar { // already set to zero. This function can be useful to clear an existing scalar // for reuse. func (s *ModNScalar) Zero() { - s.n = [8]uint32{} + s.n = [4]uint64{} } // IsZeroBit returns 1 when the scalar is equal to zero or 0 otherwise in @@ -174,16 +171,13 @@ func (s *ModNScalar) Zero() { // operations require a numeric value. See [ModNScalar.IsZero] for the version // that returns a bool. func (s *ModNScalar) IsZeroBit() uint32 { - // The scalar can only be zero if no bits are set in any of the words. - bits := s.n[0] | s.n[1] | s.n[2] | s.n[3] | s.n[4] | s.n[5] | s.n[6] | s.n[7] - return constantTimeEq(bits, 0) + return arith.ConstantTimeEq64(s.n[0]|s.n[1]|s.n[2]|s.n[3], 0) } // IsZero returns whether or not the scalar is equal to zero in constant time. func (s *ModNScalar) IsZero() bool { // The scalar can only be zero if no bits are set in any of the words. - bits := s.n[0] | s.n[1] | s.n[2] | s.n[3] | s.n[4] | s.n[5] | s.n[6] | s.n[7] - return bits == 0 + return (s.n[0] | s.n[1] | s.n[2] | s.n[3]) == 0 } // SetInt sets the scalar to the passed integer in constant time. This is a @@ -193,79 +187,10 @@ func (s *ModNScalar) IsZero() bool { // The scalar is returned to support chaining. This enables syntax like: // s := new(ModNScalar).SetInt(2).Mul(s2) so that s = 2 * s2. func (s *ModNScalar) SetInt(ui uint32) *ModNScalar { - s.Zero() - s.n[0] = ui + s.n = [4]uint64{uint64(ui), 0, 0, 0} return s } -// overflows determines if the current scalar is greater than or equal to the -// group order in constant time and returns 1 if it is or 0 otherwise. -func (s *ModNScalar) overflows() uint32 { - // The intuition here is that the scalar is greater than the group order if - // one of the higher individual words is greater than corresponding word of - // the group order and all higher words in the scalar are equal to their - // corresponding word of the group order. Since this type is modulo the - // group order, being equal is also an overflow back to 0. - // - // Note that the words 5, 6, and 7 are all the max uint32 value, so there is - // no need to test if those individual words of the scalar exceeds them, - // hence, only equality is checked for them. - highWordsEqual := constantTimeEq(s.n[7], orderWordSeven) - highWordsEqual &= constantTimeEq(s.n[6], orderWordSix) - highWordsEqual &= constantTimeEq(s.n[5], orderWordFive) - overflow := highWordsEqual & constantTimeGreater(s.n[4], orderWordFour) - highWordsEqual &= constantTimeEq(s.n[4], orderWordFour) - overflow |= highWordsEqual & constantTimeGreater(s.n[3], orderWordThree) - highWordsEqual &= constantTimeEq(s.n[3], orderWordThree) - overflow |= highWordsEqual & constantTimeGreater(s.n[2], orderWordTwo) - highWordsEqual &= constantTimeEq(s.n[2], orderWordTwo) - overflow |= highWordsEqual & constantTimeGreater(s.n[1], orderWordOne) - highWordsEqual &= constantTimeEq(s.n[1], orderWordOne) - overflow |= highWordsEqual & constantTimeGreaterOrEq(s.n[0], orderWordZero) - - return overflow -} - -// reduce256 reduces the current scalar modulo the group order in accordance -// with the overflows parameter in constant time. The overflows parameter -// specifies whether or not the scalar is known to be greater than the group -// order and MUST either be 1 in the case it is or 0 in the case it is not for a -// correct result. -func (s *ModNScalar) reduce256(overflows uint32) { - // Notice that since s < 2^256 < 2N (where N is the group order), the max - // possible number of reductions required is one. Therefore, in the case a - // reduction is needed, it can be performed with a single subtraction of N. - // Also, recall that subtraction is equivalent to addition by the two's - // complement while ignoring the carry. - // - // When s >= N, the overflows parameter will be 1. Conversely, it will be 0 - // when s < N. Thus multiplying by the overflows parameter will either - // result in 0 or the multiplicand itself. - // - // Combining the above along with the fact that s + 0 = s, the following is - // a constant time implementation that works by either adding 0 or the two's - // complement of N as needed. - // - // The final result will be in the range 0 <= s < N as expected. - overflows64 := uint64(overflows) - c := uint64(s.n[0]) + overflows64*uint64(orderComplementWordZero) - s.n[0] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[1]) + overflows64*uint64(orderComplementWordOne) - s.n[1] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[2]) + overflows64*uint64(orderComplementWordTwo) - s.n[2] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[3]) + overflows64*uint64(orderComplementWordThree) - s.n[3] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[4]) + overflows64 // * 1 - s.n[4] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[5]) // + overflows64 * 0 - s.n[5] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[6]) // + overflows64 * 0 - s.n[6] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(s.n[7]) // + overflows64 * 0 - s.n[7] = uint32(c & uint32Mask) -} - // SetBytes interprets the provided array as a 256-bit big-endian unsigned // integer, reduces it modulo the group order, sets the scalar to the result, // and returns either 1 if it was reduced (aka it overflowed) or 0 otherwise in @@ -275,23 +200,41 @@ func (s *ModNScalar) reduce256(overflows uint32) { // from a bool to numeric value in constant time and many constant-time // operations require a numeric value. func (s *ModNScalar) SetBytes(b *[32]byte) uint32 { - // Pack the 256 total bits across the 8 uint32 words. This could be done + // Pack the 256 total bits across the 4 uint64 words. This could be done // with a for loop, but benchmarks show this unrolled version is about 2 // times faster than the variant that uses a loop. - s.n[0] = uint32(b[31]) | uint32(b[30])<<8 | uint32(b[29])<<16 | uint32(b[28])<<24 - s.n[1] = uint32(b[27]) | uint32(b[26])<<8 | uint32(b[25])<<16 | uint32(b[24])<<24 - s.n[2] = uint32(b[23]) | uint32(b[22])<<8 | uint32(b[21])<<16 | uint32(b[20])<<24 - s.n[3] = uint32(b[19]) | uint32(b[18])<<8 | uint32(b[17])<<16 | uint32(b[16])<<24 - s.n[4] = uint32(b[15]) | uint32(b[14])<<8 | uint32(b[13])<<16 | uint32(b[12])<<24 - s.n[5] = uint32(b[11]) | uint32(b[10])<<8 | uint32(b[9])<<16 | uint32(b[8])<<24 - s.n[6] = uint32(b[7]) | uint32(b[6])<<8 | uint32(b[5])<<16 | uint32(b[4])<<24 - s.n[7] = uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 - - // The value might be >= N, so reduce it as required and return whether or - // not it was reduced. - needsReduce := s.overflows() - s.reduce256(needsReduce) - return needsReduce + s.n[0] = binary.BigEndian.Uint64(b[24:32]) + s.n[1] = binary.BigEndian.Uint64(b[16:24]) + s.n[2] = binary.BigEndian.Uint64(b[8:16]) + s.n[3] = binary.BigEndian.Uint64(b[0:8]) + + // Since s < 2^256 < 2N (where N is the secp256k1 group order), the max + // possible number of reductions required is one. Therefore, in the case a + // reduction is needed, it can be performed with a single subtraction of N. + // + // Since N must only conditionally be subtracted when s ≥ N, the following + // handles it in constant time by always calculating t = s - N and selecting + // the correct case via a constant time select. + + // Subtract N with borrow propagation. borrow is set iff s < N. + // + // In other words, the input overflowed (≥ N) when s - N does NOT borrow. + // + // t = s - N + var t0, t1, t2, t3, borrow uint64 + t0, borrow = bits.Sub64(s.n[0], orderLimb0, 0) + t1, borrow = bits.Sub64(s.n[1], orderLimb1, borrow) + t2, borrow = bits.Sub64(s.n[2], orderLimb2, borrow) + t3, borrow = bits.Sub64(s.n[3], orderLimb3, borrow) + + // Constant-time select. + // + // Set s = s when s < N (aka borrow is set). Otherwise s = t = s - N. + s.n[0] = arith.ConstantTimeSelect64(borrow, s.n[0], t0) + s.n[1] = arith.ConstantTimeSelect64(borrow, s.n[1], t1) + s.n[2] = arith.ConstantTimeSelect64(borrow, s.n[2], t2) + s.n[3] = arith.ConstantTimeSelect64(borrow, s.n[3], t3) + return uint32(1 - borrow) } // zeroArray32 zeroes the provided 32-byte buffer. @@ -311,8 +254,10 @@ func zeroArray32(b *[32]byte) { // size or it is acceptable to use this function with the described truncation // and overflow behavior. func (s *ModNScalar) SetByteSlice(b []byte) bool { + // Always copy a total of 32 bytes regardless of the input length to avoid + // introducing data-dependent timing. var b32 [32]byte - b = b[:constantTimeMin(uint32(len(b)), 32)] + b = b[:arith.ConstantTimeMin(uint32(len(b)), 32)] copy(b32[:], b32[:32-len(b)]) copy(b32[32-len(b):], b) result := s.SetBytes(&b32) @@ -324,49 +269,21 @@ func (s *ModNScalar) SetByteSlice(b []byte) bool { // 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 scalar 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. +// There is a similar function, [ModNScalar.PutBytes], which unpacks the scalar +// 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. // // Preconditions: // - The target slice MUST have at least 32 bytes available func (s *ModNScalar) PutBytesUnchecked(b []byte) { - // Unpack the 256 total bits from the 8 uint32 words. This could be done - // with a for loop, but benchmarks show this unrolled version is about 2 + // Unpack the 256 total bits from the 4 uint64 words. This could be done + // with a for loop, but benchmarks show this unrolled version is about 3 // times faster than the variant which uses a loop. - b[31] = byte(s.n[0]) - b[30] = byte(s.n[0] >> 8) - b[29] = byte(s.n[0] >> 16) - b[28] = byte(s.n[0] >> 24) - b[27] = byte(s.n[1]) - b[26] = byte(s.n[1] >> 8) - b[25] = byte(s.n[1] >> 16) - b[24] = byte(s.n[1] >> 24) - b[23] = byte(s.n[2]) - b[22] = byte(s.n[2] >> 8) - b[21] = byte(s.n[2] >> 16) - b[20] = byte(s.n[2] >> 24) - b[19] = byte(s.n[3]) - b[18] = byte(s.n[3] >> 8) - b[17] = byte(s.n[3] >> 16) - b[16] = byte(s.n[3] >> 24) - b[15] = byte(s.n[4]) - b[14] = byte(s.n[4] >> 8) - b[13] = byte(s.n[4] >> 16) - b[12] = byte(s.n[4] >> 24) - b[11] = byte(s.n[5]) - b[10] = byte(s.n[5] >> 8) - b[9] = byte(s.n[5] >> 16) - b[8] = byte(s.n[5] >> 24) - b[7] = byte(s.n[6]) - b[6] = byte(s.n[6] >> 8) - b[5] = byte(s.n[6] >> 16) - b[4] = byte(s.n[6] >> 24) - b[3] = byte(s.n[7]) - b[2] = byte(s.n[7] >> 8) - b[1] = byte(s.n[7] >> 16) - b[0] = byte(s.n[7] >> 24) + binary.BigEndian.PutUint64(b[0:8], s.n[3]) + binary.BigEndian.PutUint64(b[8:16], s.n[2]) + binary.BigEndian.PutUint64(b[16:24], s.n[1]) + binary.BigEndian.PutUint64(b[24:32], s.n[0]) } // PutBytes unpacks the scalar to a 32-byte big-endian value using the passed @@ -406,11 +323,8 @@ func (s *ModNScalar) IsOdd() bool { func (s *ModNScalar) Equals(val *ModNScalar) bool { // Xor only sets bits when they are different, so the two scalars can only // be the same if no bits are set after xoring each word. - bits := (s.n[0] ^ val.n[0]) | (s.n[1] ^ val.n[1]) | (s.n[2] ^ val.n[2]) | - (s.n[3] ^ val.n[3]) | (s.n[4] ^ val.n[4]) | (s.n[5] ^ val.n[5]) | - (s.n[6] ^ val.n[6]) | (s.n[7] ^ val.n[7]) - - return bits == 0 + return ((s.n[0] ^ val.n[0]) | (s.n[1] ^ val.n[1]) | (s.n[2] ^ val.n[2]) | + (s.n[3] ^ val.n[3])) == 0 } // Add2 adds the passed two scalars together modulo the group order in constant @@ -418,27 +332,42 @@ func (s *ModNScalar) Equals(val *ModNScalar) bool { // // The scalar is returned to support chaining. This enables syntax like: // s3.Add2(s, s2).AddInt(1) so that s3 = s + s2 + 1. -func (s *ModNScalar) Add2(val1, val2 *ModNScalar) *ModNScalar { - c := uint64(val1.n[0]) + uint64(val2.n[0]) - s.n[0] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[1]) + uint64(val2.n[1]) - s.n[1] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[2]) + uint64(val2.n[2]) - s.n[2] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[3]) + uint64(val2.n[3]) - s.n[3] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[4]) + uint64(val2.n[4]) - s.n[4] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[5]) + uint64(val2.n[5]) - s.n[5] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[6]) + uint64(val2.n[6]) - s.n[6] = uint32(c & uint32Mask) - c = (c >> 32) + uint64(val1.n[7]) + uint64(val2.n[7]) - s.n[7] = uint32(c & uint32Mask) - - // The result is now 256 bits, but it might still be >= N, so use the - // existing normal reduce method for 256-bit values. - s.reduce256(uint32(c>>32) + s.overflows()) +func (s *ModNScalar) Add2(a, b *ModNScalar) *ModNScalar { + // Since both values are already in the range 0 ≤ val < N (where N is the + // secp256k1 group order), the maximum possible result is < 2N - 1. So a + // maximum of one subtraction of N is required in the worst case. + // + // Since N must only conditionally be subtracted when a+b ≥ N, the following + // handles it in constant time by calculating both t = a+b and u = a+b - N + // and selecting the correct case via a constant time select. + + // Add with carry propagation. overflow is set iff t = a+b ≥ 2^256. + // + // t = a + b + var t0, t1, t2, t3, overflow, carry uint64 + 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) + + // Subtract N with borrow propagation. borrow is set iff t = a+b < N. + // + // u = t - N = a+b - N + var u0, u1, u2, u3, borrow uint64 + u0, borrow = bits.Sub64(t0, orderLimb0, 0) + u1, borrow = bits.Sub64(t1, orderLimb1, borrow) + u2, borrow = bits.Sub64(t2, orderLimb2, borrow) + u3, borrow = bits.Sub64(t3, orderLimb3, borrow) + + // Constant-time select. + // + // Set s = t = a+b only when there was no overflow and t < N (borrow set). + // Otherwise s = u = a+b - N. + cond := (1 - overflow) & borrow + s.n[0] = arith.ConstantTimeSelect64(cond, t0, u0) + s.n[1] = arith.ConstantTimeSelect64(cond, t1, u1) + s.n[2] = arith.ConstantTimeSelect64(cond, t2, u2) + s.n[3] = arith.ConstantTimeSelect64(cond, t3, u3) return s } @@ -451,315 +380,363 @@ func (s *ModNScalar) Add(val *ModNScalar) *ModNScalar { return s.Add2(s, val) } -// accumulator96 provides a 96-bit accumulator for use in the intermediate -// calculations requiring more than 64-bits. -type accumulator96 struct { - n [3]uint32 -} +// scalar64Reduce512 reduces a 512-bit little-endian limb array modulo the group +// order in constant time and stores the result in r. +func scalar64Reduce512(r *[4]uint64, x *[8]uint64) { + // This algorithm has been formally verified, including its intermediate + // bounds, carry assumptions, and functional correctness. The verification + // artifacts are available in internal/proofs. -// Add adds the passed unsigned 64-bit value to the accumulator. -func (a *accumulator96) Add(v uint64) { - low := uint32(v & uint32Mask) - hi := uint32(v >> 32) - a.n[0] += low - hi += constantTimeLess(a.n[0], low) // Carry if overflow in n[0]. - a.n[1] += hi - a.n[2] += constantTimeLess(a.n[1], hi) // Carry if overflow in n[1]. -} + // The overall strategy employed here is: + // 1) Start with the full unreduced 512-bit product of the two scalars. + // 2) Reduce the result modulo the group order via Crandall reduction + // (described below). + // 3) Repeat step 2 noting that each iteration reduces the required number + // of bits by 127 because the two's complement of N has 127 leading zero + // bits. + // 4) Once reduced to less than a max of 2N, perform the final reduction + // with a constant-time conditional subtraction. + // + // Technically the max possible input value here will be (N-1)^2, since the + // only time it is called is with the product of two scalars that are always + // mod N. Nevertheless, it is safer to treat it as the product of two max + // size 256-bit values, (2^256-1)^2 = 2^512 - 2^257 + 1, to ensure + // correctness if that were to ever change. + // + // Per [HAC] section 14.3.4: Reduction method of moduli of special form, + // when the modulus is of the special form m = b^t - c, highly efficient + // reduction can be achieved. While [HAC] only presents the algorithm and + // does not call it out by name or provide the mathematical justification, + // the underlying technique is known as Crandall reduction and is often + // presented as 2^k - c. It is easy to see they are equivalent by setting + // b = 2 and t = k. + // + // The secp256k1 group order is 2^256 - 0x14551231950b75fc4402da1732fc9bebf, + // so it fits this criteria where: + // k = 256 + // c = 432420386565659656852420866394968145599 + // + // Crandall reduction works by taking advantage of the fact that if a prime + // is of the form 2^k - c, then 2^k - c ≡ 0 (mod p), so 2^k ≡ c (mod p). In + // other words, every multiple of 2^k is equivalent to adding c when working + // modulo p. + // + // Since the 512-bit value to reduce is tightly packed into uint64s, the + // upper 4 limbs are all multiples of 2^256. Therefore, reducing modulo the + // group order is equivalent to multiplying those upper limbs by c and + // adding the result to the corresponding lower 4 limbs while propagating + // the carries. + // + // For the specific case of the secp256k1 group order, a max of 4 reductions + // are required because c is 129 bits and so the first round will reduce + // from 512 bits to a max of 385 bits, the second round will reduce to a max + // of 258 bits, and the third round will reduce to within 2N. Then, a + // conditional subtraction of N handles the final reduction. + // + // The reduction is slightly complicated by the fact c needs 3 uint64 limbs + // to represent it, so multiplying the upper limbs by c requires multiplying + // each one by all 3 limbs of c and carefully managing the columns and + // carries. The uppermost limb of c = 1, so multiplication by that limb is + // avoided in the code below. -// Rsh32 right shifts the accumulator by 32 bits. -func (a *accumulator96) Rsh32() { - a.n[0] = a.n[1] - a.n[1] = a.n[2] - a.n[2] = 0 -} + var t0, t1, t2, t3, t4, t5, t6, l0, h0, l1, h1, l2, carry uint64 -// reduce385 reduces the 385-bit intermediate result in the passed terms modulo -// the group order in constant time and stores the result in s. -func (s *ModNScalar) reduce385(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 uint64) { - // At this point, the intermediate result in the passed terms has been - // reduced to fit within 385 bits, so reduce it again using the same method - // described in reduce512. As before, the intermediate result will end up - // being reduced by another 127 bits to 258 bits, thus 9 32-bit terms are - // needed for this iteration. The reduced terms are assigned back to t0 - // through t8. - // - // Note that several of the intermediate calculations require adding 64-bit - // products together which would overflow a uint64, so a 96-bit accumulator - // is used instead until the value is reduced enough to use native uint64s. - - // Terms for 2^(32*0). - var acc accumulator96 - acc.n[0] = uint32(t0) // == acc.Add(t0) because acc is guaranteed to be 0. - acc.Add(t8 * uint64(orderComplementWordZero)) - t0 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*1). - acc.Add(t1) - acc.Add(t8 * uint64(orderComplementWordOne)) - acc.Add(t9 * uint64(orderComplementWordZero)) - t1 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*2). - acc.Add(t2) - acc.Add(t8 * uint64(orderComplementWordTwo)) - acc.Add(t9 * uint64(orderComplementWordOne)) - acc.Add(t10 * uint64(orderComplementWordZero)) - t2 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*3). - acc.Add(t3) - acc.Add(t8 * uint64(orderComplementWordThree)) - acc.Add(t9 * uint64(orderComplementWordTwo)) - acc.Add(t10 * uint64(orderComplementWordOne)) - acc.Add(t11 * uint64(orderComplementWordZero)) - t3 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*4). - acc.Add(t4) - acc.Add(t8) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t9 * uint64(orderComplementWordThree)) - acc.Add(t10 * uint64(orderComplementWordTwo)) - acc.Add(t11 * uint64(orderComplementWordOne)) - acc.Add(t12 * uint64(orderComplementWordZero)) - t4 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*5). - acc.Add(t5) - // acc.Add(t8 * uint64(orderComplementWordFive)) // 0 - acc.Add(t9) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t10 * uint64(orderComplementWordThree)) - acc.Add(t11 * uint64(orderComplementWordTwo)) - acc.Add(t12 * uint64(orderComplementWordOne)) - t5 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*6). - acc.Add(t6) - // acc.Add(t8 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t9 * uint64(orderComplementWordFive)) // 0 - acc.Add(t10) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t11 * uint64(orderComplementWordThree)) - acc.Add(t12 * uint64(orderComplementWordTwo)) - t6 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*7). - acc.Add(t7) - // acc.Add(t8 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t9 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t10 * uint64(orderComplementWordFive)) // 0 - acc.Add(t11) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t12 * uint64(orderComplementWordThree)) - t7 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*8). - // acc.Add(t9 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t10 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t11 * uint64(orderComplementWordFive)) // 0 - acc.Add(t12) // * uint64(orderComplementWordFour) // * 1 - t8 = uint64(acc.n[0]) - // acc.Rsh32() // No need since not used after this. Guaranteed to be 0. - - // NOTE: All of the remaining multiplications for this iteration result in 0 - // as they all involve multiplying by combinations of the fifth, sixth, and - // seventh words of the two's complement of N, which are 0, so skip them. - - // At this point, the result is reduced to fit within 258 bits, so reduce it - // again using a slightly modified version of the same method. The maximum - // value in t8 is 2 at this point and therefore multiplying it by each word - // of the two's complement of N and adding it to a 32-bit term will result - // in a maximum requirement of 33 bits, so it is safe to use native uint64s - // here for the intermediate term carry propagation. - // - // Also, since the maximum value in t8 is 2, this ends up reducing by - // another 2 bits to 256 bits. - c := t0 + t8*uint64(orderComplementWordZero) - s.n[0] = uint32(c & uint32Mask) - c = (c >> 32) + t1 + t8*uint64(orderComplementWordOne) - s.n[1] = uint32(c & uint32Mask) - c = (c >> 32) + t2 + t8*uint64(orderComplementWordTwo) - s.n[2] = uint32(c & uint32Mask) - c = (c >> 32) + t3 + t8*uint64(orderComplementWordThree) - s.n[3] = uint32(c & uint32Mask) - c = (c >> 32) + t4 + t8 // * uint64(orderComplementWordFour) == * 1 - s.n[4] = uint32(c & uint32Mask) - c = (c >> 32) + t5 // + t8*uint64(orderComplementWordFive) == 0 - s.n[5] = uint32(c & uint32Mask) - c = (c >> 32) + t6 // + t8*uint64(orderComplementWordSix) == 0 - s.n[6] = uint32(c & uint32Mask) - c = (c >> 32) + t7 // + t8*uint64(orderComplementWordSeven) == 0 - s.n[7] = uint32(c & uint32Mask) - - // The result is now 256 bits, but it might still be >= N, so use the - // existing normal reduce method for 256-bit values. - s.reduce256(uint32(c>>32) + s.overflows()) -} + // ------------------------------------------------------------------------- + // First reduction. + // + // The code in this section is equivalent to the following if uint512 and + // larger numbers and shifts were supported directly: + // + // t0 = uint64(x%(1<<256) + (x>>256)*c) + // t1 = uint64((x%(1<<256) + (x>>256)*c) >> 64) + // t2 = uint64((x%(1<<256) + (x>>256)*c) >> 128) + // t3 = uint64((x%(1<<256) + (x>>256)*c) >> 192) + // t4 = uint64((x%(1<<256) + (x>>256)*c) >> 256) + // t5 = uint64((x%(1<<256) + (x>>256)*c) >> 320) + // t6 = uint64((x%(1<<256) + (x>>256)*c) >> 384) + // + // To visualize the logic, multiply the 3 limbs of c by the upper limbs via + // the standard pencil-and-paper method: + // + // 2^576 2^512 2^448 2^384 2^320 2^256 (place values) + // ----------------------------------- + // x7 x6 x5 x4 + // c2 c1 c0 * + // ----------------------------------- + // x4c2 x4c1 x4c0 + // x5c2 x5c1 x5c0 + + // x6c2 x6c1 x6c0 + + // x7c2 x7c1 x7c0 + + // ----------------------------------- + // x7c2 .... .... .... .... x4c0 + // + // Then add them to the associated lower limbs per the reduction identity: + // + // 2^384 2^320 2^256 2^192 2^128 2^64 2^0 (place values) + // ----------------------------------------- + // x3 x2 x1 x0 + // x4c2 x4c1 x4c0 + + // x5c2 x5c1 x5c0 + + // x6c2 x6c1 x6c0 + + // x7c2 x7c1 x7c0 + + // ----------------------------------------- + // t6 t5 t4 t3 t2 t1 t0 + // + // Where t6 ≤ 1 for the potential carry. + // + // Thus 0 ≤ t < 2^385 after the 1st reduction. + // ------------------------------------------------------------------------- + + // Compute x4*c with result in t0..t2 with carries propagated and the final + // carry in t3. + h0, t0 = bits.Mul64(x[4], orderComplementLimb0) + h1, t1 = bits.Mul64(x[4], orderComplementLimb1) + t1, carry = bits.Add64(t1, h0, 0) + t2, carry = bits.Add64(x[4], h1, carry) + t3 = carry + + // Compute x5*c with result added to t1..t3 with carries propagated and the + // final carry in t4. + // + // Note that since h1 is the upper 64 bits of the product of a uint64 with + // c1 (limb 1 of c) and c1 < 2^63 - 1: + // h1 ≤ floor((2^64-1)(2^63-1) / 2^64) = 2^63 - 2 + // + // Then, because current t3 ≤ 1 and carry ≤ 1, a loose bound for the first + // t3 below is: + // t3 ≤ h1 + 2 = 2^63 < 2^64 + // + // Therefore, it is safe to discard the carry. + h0, l0 = bits.Mul64(x[5], orderComplementLimb0) + h1, l1 = bits.Mul64(x[5], orderComplementLimb1) + t1, carry = bits.Add64(t1, l0, 0) + t2, carry = bits.Add64(t2, h0, carry) + t3, _ = bits.Add64(t3, h1, carry) + t2, carry = bits.Add64(t2, l1, 0) + t3, carry = bits.Add64(t3, x[5], carry) + t4 = carry + + // Compute x6*c with result added to t2..t4 with carries propagated and the + // final carry in t5. + // + // It is safe to discard the carry on the first t4 for the same reason as + // above. + h0, l0 = bits.Mul64(x[6], orderComplementLimb0) + h1, l1 = bits.Mul64(x[6], orderComplementLimb1) + t2, carry = bits.Add64(t2, l0, 0) + t3, carry = bits.Add64(t3, h0, carry) + t4, _ = bits.Add64(t4, h1, carry) + t3, carry = bits.Add64(t3, l1, 0) + t4, carry = bits.Add64(t4, x[6], carry) + t5 = carry + + // Compute x7*c with result added to t3..t5 with carries propagated and the + // final carry in t6. + // + // It is safe to discard the carry on the first t5 for the same reason as + // above. + h0, l0 = bits.Mul64(x[7], orderComplementLimb0) + h1, l1 = bits.Mul64(x[7], orderComplementLimb1) + t3, carry = bits.Add64(t3, l0, 0) + t4, carry = bits.Add64(t4, h0, carry) + t5, _ = bits.Add64(t5, h1, carry) + t4, carry = bits.Add64(t4, l1, 0) + t5, carry = bits.Add64(t5, x[7], carry) + t6 = carry + + // Add result to lower limbs and propagate carries. + // + // It is safe to discard the carry on t6 since the resulting t6 ≤ 1 as + // previously described. + 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 = bits.Add64(t4, 0, carry) + t5, carry = bits.Add64(t5, 0, carry) + t6, _ = bits.Add64(t6, 0, carry) + + // ------------------------------------------------------------------------- + // Second reduction. + // + // The value now fits in 385 bits, so reduce it again. Only t4, t5, and t6 + // need to be considered since the higher limb, t7, is ≥ 448 bits and thus + // guaranteed to be 0. Further, as previously noted, t6 ≤ 1. + // + // The code in this section is equivalent to following if uint512 and larger + // numbers and shifts were supported: + // + // t0 = uint64(t%2^256 + (t>>256)*c) + // t1 = uint64((t%2^256 + (t>>256)*c) >> 64) + // t2 = uint64((t%2^256 + (t>>256)*c) >> 128) + // t3 = uint64((t%2^256 + (t>>256)*c) >> 192) + // t4 = uint64((t%2^256 + (t>>256)*c) >> 256) + // + // To visualize the logic, multiply the 3 limbs of c by the new upper limbs + // via the standard pencil-and-paper method: + // + // 2^512 2^448 2^384 2^320 2^256 (place values) + // ----------------------------- + // t6 t5 t4 + // c2 c1 c0 * + // ----------------------------- + // t4c2 t4c1 t4c0 + // t5c2 t5c1 t5c0 + + // t6c2 t6c1 t6c0 + + // ---------------------------- + // t6c2 .... .... .... t4c0 + // + // Then add them to the associated lower limbs per the reduction identity + // noting that the code reuses t for the sums: + // + // 2^256 2^192 2^128 2^64 2^0 (place values) + // ----------------------------- + // t3 t2 t1 t0 + // t4c2 t4c1 t4c0 + + // t5c2 t5c1 t5c0 + + // t6c2 t6c1 t6c0 + + // ----------------------------- + // t4 t3 t2 t1 t0 + // + // With a loose bound for t4 ≤ 3. + // + // Proof: + // + // Known: c2=1, t6 ≤ 1, t3 ≤ 2^64-1, t5 ≤ 2^64-1, and c1 ≤ 2^63-1. + // + // Let s = t3 + t5c2 + t6c1. Then: + // s ≤ 2^64-1 + (2^64-1)*1 + 1*(2^63-1) = 2^65 + 2^63 - 3 + // + // So, the loose bound on the carry in to t4 is carryIn ≤ floor(s/2^64) = 2. + // + // Finally, t4 ≤ t6*c2 + carryIn ≤ 1*1 + 2 ≤ 3. + // + // Thus 0 ≤ t < 2^258 after the 2nd reduction. + // ------------------------------------------------------------------------- -// reduce512 reduces the 512-bit intermediate result in the passed terms modulo -// the group order down to 385 bits in constant time and stores the result in s. -func (s *ModNScalar) reduce512(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15 uint64) { - // At this point, the intermediate result in the passed terms is grouped - // into the respective bases. + // Compute t4*c with result added to t0..t2 with carries propagated and the + // final carry in t4. // - // Per [HAC] section 14.3.4: Reduction method of moduli of special form, - // when the modulus is of the special form m = b^t - c, where log_2(c) < t, - // highly efficient reduction can be achieved per the provided algorithm. - // - // The secp256k1 group order fits this criteria since it is: - // 2^256 - 432420386565659656852420866394968145599 - // - // Technically the max possible value here is (N-1)^2 since the two scalars - // being multiplied are always mod N. Nevertheless, it is safer to consider - // it to be (2^256-1)^2 = 2^512 - 2^257 + 1 since it is the product of two - // 256-bit values. - // - // The algorithm is to reduce the result modulo the prime by subtracting - // multiples of the group order N. However, in order simplify carry - // propagation, this adds with the two's complement of N to achieve the same - // result. - // - // Since the two's complement of N has 127 leading zero bits, this will end - // up reducing the intermediate result from 512 bits to 385 bits, resulting - // in 13 32-bit terms. The reduced terms are assigned back to t0 through - // t12. - // - // Note that several of the intermediate calculations require adding 64-bit - // products together which would overflow a uint64, so a 96-bit accumulator - // is used instead. - - // Terms for 2^(32*0). - var acc accumulator96 - acc.n[0] = uint32(t0) // == acc.Add(t0) because acc is guaranteed to be 0. - acc.Add(t8 * uint64(orderComplementWordZero)) - t0 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*1). - acc.Add(t1) - acc.Add(t8 * uint64(orderComplementWordOne)) - acc.Add(t9 * uint64(orderComplementWordZero)) - t1 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*2). - acc.Add(t2) - acc.Add(t8 * uint64(orderComplementWordTwo)) - acc.Add(t9 * uint64(orderComplementWordOne)) - acc.Add(t10 * uint64(orderComplementWordZero)) - t2 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*3). - acc.Add(t3) - acc.Add(t8 * uint64(orderComplementWordThree)) - acc.Add(t9 * uint64(orderComplementWordTwo)) - acc.Add(t10 * uint64(orderComplementWordOne)) - acc.Add(t11 * uint64(orderComplementWordZero)) - t3 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*4). - acc.Add(t4) - acc.Add(t8) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t9 * uint64(orderComplementWordThree)) - acc.Add(t10 * uint64(orderComplementWordTwo)) - acc.Add(t11 * uint64(orderComplementWordOne)) - acc.Add(t12 * uint64(orderComplementWordZero)) - t4 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*5). - acc.Add(t5) - // acc.Add(t8 * uint64(orderComplementWordFive)) // 0 - acc.Add(t9) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t10 * uint64(orderComplementWordThree)) - acc.Add(t11 * uint64(orderComplementWordTwo)) - acc.Add(t12 * uint64(orderComplementWordOne)) - acc.Add(t13 * uint64(orderComplementWordZero)) - t5 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*6). - acc.Add(t6) - // acc.Add(t8 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t9 * uint64(orderComplementWordFive)) // 0 - acc.Add(t10) // * uint64(orderComplementWordFour)) // * 1 - acc.Add(t11 * uint64(orderComplementWordThree)) - acc.Add(t12 * uint64(orderComplementWordTwo)) - acc.Add(t13 * uint64(orderComplementWordOne)) - acc.Add(t14 * uint64(orderComplementWordZero)) - t6 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*7). - acc.Add(t7) - // acc.Add(t8 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t9 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t10 * uint64(orderComplementWordFive)) // 0 - acc.Add(t11) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t12 * uint64(orderComplementWordThree)) - acc.Add(t13 * uint64(orderComplementWordTwo)) - acc.Add(t14 * uint64(orderComplementWordOne)) - acc.Add(t15 * uint64(orderComplementWordZero)) - t7 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*8). - // acc.Add(t9 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t10 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t11 * uint64(orderComplementWordFive)) // 0 - acc.Add(t12) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t13 * uint64(orderComplementWordThree)) - acc.Add(t14 * uint64(orderComplementWordTwo)) - acc.Add(t15 * uint64(orderComplementWordOne)) - t8 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*9). - // acc.Add(t10 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t11 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t12 * uint64(orderComplementWordFive)) // 0 - acc.Add(t13) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t14 * uint64(orderComplementWordThree)) - acc.Add(t15 * uint64(orderComplementWordTwo)) - t9 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*10). - // acc.Add(t11 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t12 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t13 * uint64(orderComplementWordFive)) // 0 - acc.Add(t14) // * uint64(orderComplementWordFour) // * 1 - acc.Add(t15 * uint64(orderComplementWordThree)) - t10 = uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*11). - // acc.Add(t12 * uint64(orderComplementWordSeven)) // 0 - // acc.Add(t13 * uint64(orderComplementWordSix)) // 0 - // acc.Add(t14 * uint64(orderComplementWordFive)) // 0 - acc.Add(t15) // * uint64(orderComplementWordFour) // * 1 - t11 = uint64(acc.n[0]) - acc.Rsh32() - - // NOTE: All of the remaining multiplications for this iteration result in 0 - // as they all involve multiplying by combinations of the fifth, sixth, and - // seventh words of the two's complement of N, which are 0, so skip them. - - // Terms for 2^(32*12). - t12 = uint64(acc.n[0]) - // acc.Rsh32() // No need since not used after this. Guaranteed to be 0. - - // At this point, the result is reduced to fit within 385 bits, so reduce it - // again using the same method accordingly. - s.reduce385(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) + // It is safe to discard the carry on the final t4 given the carries ≤ 1. + h0, l0 = bits.Mul64(t4, orderComplementLimb0) + h1, l1 = bits.Mul64(t4, orderComplementLimb1) + l2 = t4 // h2, l2 = bits.Mul64(t4, orderComplementLimb2) => h2=0, l2=t4 + t0, carry = bits.Add64(t0, l0, 0) + t1, carry = bits.Add64(t1, h0, carry) + t2, carry = bits.Add64(t2, h1, carry) + t3, carry = bits.Add64(t3, 0, carry) + t4 = carry + t1, carry = bits.Add64(t1, l1, 0) + t2, carry = bits.Add64(t2, l2, carry) + t3, carry = bits.Add64(t3, 0, carry) + t4, _ = bits.Add64(t4, 0, carry) + + // Compute t5*c with result added to t1..t3 with carries propagated. + // + // It is safe to discard the carry on the final t4 given the carries ≤ 1. + h0, l0 = bits.Mul64(t5, orderComplementLimb0) + h1, l1 = bits.Mul64(t5, orderComplementLimb1) + // h2, l2 = bits.Mul64(t5, orderComplementLimb2) => h2=0, l2=t5 + t1, carry = bits.Add64(t1, l0, 0) + t2, carry = bits.Add64(t2, h0, carry) + t3, carry = bits.Add64(t3, h1, carry) + t4, _ = bits.Add64(t4, 0, carry) + t2, carry = bits.Add64(t2, l1, 0) + t3, carry = bits.Add64(t3, t5, carry) + t4, _ = bits.Add64(t4, 0, carry) + + // Compute t6*c with result added to t2..t4 with carries propagated. + // + // Note that since t6 ≤ 1, the product can't overflow a uint64, so it is + // safe to use normal 64-bit multiplication. + // + // It is safe to discard the carry on t4 since the resulting t4 ≤ 3 as + // previously described. + t2, carry = bits.Add64(t2, t6*orderComplementLimb0, 0) + t3, carry = bits.Add64(t3, t6*orderComplementLimb1, carry) + t4, _ = bits.Add64(t4, t6, carry) + + // ------------------------------------------------------------------------- + // Third reduction. + // + // The value now fits in 258 bits, so reduce it again. Only t4 needs to be + // considered since the higher limbs are ≥ 320 bits and thus guaranteed to + // be 0. + // + // The code in this section is equivalent to following if uint512 and larger + // numbers and shifts were supported: + // + // t0 = uint64(t%2^256 + (t>>256)*c) + // t1 = uint64((t%2^256 + (t>>256)*c) >> 64) + // t2 = uint64((t%2^256 + (t>>256)*c) >> 128) + // t3 = uint64((t%2^256 + (t>>256)*c) >> 192) + // t4 = uint64((t%2^256 + (t>>256)*c) >> 256) + // + // To visualize the logic, multiply the 3 limbs of c by the new upper limb + // via the standard pencil-and-paper method: + // + // 2^512 2^448 2^384 2^320 2^256 (place values) + // ----------------------------- + // t4 + // c2 c1 c0 * + // ----------------------------- + // t4c2 t4c1 t4c0 + // + // Then add them to the associated lower limbs noting that the code reuses t + // for the sums: + // + // 2^256 2^192 2^128 2^64 2^0 (place values) + // ----------------------------- + // t3 t2 t1 t0 + // t4c2 t4c1 t4c0 + + // ----------------------------- + // t4 t3 t2 t1 t0 + // + // Where t4 ≤ 1 for the carry. + // ------------------------------------------------------------------------- + + // Compute t4*c with result added to t0..t2 with carries propagated and the + // final carry in t4. + // + // Note that since current t4 ≤ 3, these bounds for the product hold: + // t4*c0 < 2^64 + // t4*c1 < 2^64 + // + // So, uint64 overflow is impossible and therefore it is safe to use normal + // 64-bit multiplication. + t0, carry = bits.Add64(t0, t4*orderComplementLimb0, 0) + t1, carry = bits.Add64(t1, t4*orderComplementLimb1, carry) + t2, carry = bits.Add64(t2, t4, carry) + t3, carry = bits.Add64(t3, 0, carry) + t4 = carry + + // ------------------------------------------------------------------------- + // Final reduction. + // + // The value is now in the range 0 ≤ t < 2N, so one 5-limb conditional + // subtract of N guarantees it is fully reduced. + // ------------------------------------------------------------------------- + + // Subtract N with borrow propagation. borrow is set iff t < N. + // + // In other words, the value overflowed (≥ N) when t - N does NOT borrow. + // + // s = t - N + var s0, s1, s2, s3, borrow uint64 + s0, borrow = bits.Sub64(t0, orderLimb0, 0) + s1, borrow = bits.Sub64(t1, orderLimb1, borrow) + s2, borrow = bits.Sub64(t2, orderLimb2, borrow) + s3, borrow = bits.Sub64(t3, orderLimb3, borrow) + _, borrow = bits.Sub64(t4, 0, borrow) + + // Constant-time select. + // + // Set r = t only when t < N (borrow set). + // Otherwise r = s = t - N. + r[0] = arith.ConstantTimeSelect64(borrow, t0, s0) + r[1] = arith.ConstantTimeSelect64(borrow, t1, s1) + r[2] = arith.ConstantTimeSelect64(borrow, t2, s2) + r[3] = arith.ConstantTimeSelect64(borrow, t3, s3) } // Mul2 multiplies the passed two scalars together modulo the group order in @@ -767,159 +744,10 @@ func (s *ModNScalar) reduce512(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, // // The scalar is returned to support chaining. This enables syntax like: // s3.Mul2(s, s2).AddInt(1) so that s3 = (s * s2) + 1. -func (s *ModNScalar) Mul2(val, val2 *ModNScalar) *ModNScalar { - // This could be done with for loops and an array to store the intermediate - // terms, but this unrolled version is significantly faster. - - // The overall strategy employed here is: - // 1) Calculate the 512-bit product of the two scalars using the standard - // pencil-and-paper method. - // 2) Reduce the result modulo the prime by effectively subtracting - // multiples of the group order N (actually performed by adding multiples - // of the two's complement of N to avoid implementing subtraction). - // 3) Repeat step 2 noting that each iteration reduces the required number - // of bits by 127 because the two's complement of N has 127 leading zero - // bits. - // 4) Once reduced to 256 bits, call the existing reduce method to perform - // a final reduction as needed. - // - // Note that several of the intermediate calculations require adding 64-bit - // products together which would overflow a uint64, so a 96-bit accumulator - // is used instead. - - // Terms for 2^(32*0). - var acc accumulator96 - acc.Add(uint64(val.n[0]) * uint64(val2.n[0])) - t0 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*1). - acc.Add(uint64(val.n[0]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[0])) - t1 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*2). - acc.Add(uint64(val.n[0]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[0])) - t2 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*3). - acc.Add(uint64(val.n[0]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[0])) - t3 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*4). - acc.Add(uint64(val.n[0]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[0])) - t4 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*5). - acc.Add(uint64(val.n[0]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[0])) - t5 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*6). - acc.Add(uint64(val.n[0]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[0])) - t6 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*7). - acc.Add(uint64(val.n[0]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[1]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[1])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[0])) - t7 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*8). - acc.Add(uint64(val.n[1]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[2]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[2])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[1])) - t8 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*9). - acc.Add(uint64(val.n[2]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[3]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[3])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[2])) - t9 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*10). - acc.Add(uint64(val.n[3]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[4]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[4])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[3])) - t10 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*11). - acc.Add(uint64(val.n[4]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[5]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[5])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[4])) - t11 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*12). - acc.Add(uint64(val.n[5]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[6]) * uint64(val2.n[6])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[5])) - t12 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*13). - acc.Add(uint64(val.n[6]) * uint64(val2.n[7])) - acc.Add(uint64(val.n[7]) * uint64(val2.n[6])) - t13 := uint64(acc.n[0]) - acc.Rsh32() - - // Terms for 2^(32*14). - acc.Add(uint64(val.n[7]) * uint64(val2.n[7])) - t14 := uint64(acc.n[0]) - acc.Rsh32() - - // What's left is for 2^(32*15). - t15 := uint64(acc.n[0]) - // acc.Rsh32() // No need since not used after this. Guaranteed to be 0. - - // At this point, all of the terms are grouped into their respective base - // and occupy up to 512 bits. Reduce the result accordingly. - s.reduce512(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, - t15) +func (s *ModNScalar) Mul2(a, b *ModNScalar) *ModNScalar { + var product [8]uint64 + arith.Mul512(&product, &a.n, &b.n) + scalar64Reduce512(&s.n, &product) return s } @@ -938,15 +766,10 @@ func (s *ModNScalar) Mul(val *ModNScalar) *ModNScalar { // The scalar is returned to support chaining. This enables syntax like: // s3.SquareVal(s).Mul(s) so that s3 = s^2 * s = s^3. func (s *ModNScalar) SquareVal(val *ModNScalar) *ModNScalar { - // This could technically be optimized slightly to take advantage of the - // fact that many of the intermediate calculations in squaring are just - // doubling, however, benchmarking has shown that due to the need to use a - // 96-bit accumulator, any savings are essentially offset by that and - // consequently there is no real difference in performance over just - // multiplying the value by itself to justify the extra code for now. This - // can be revisited in the future if it becomes a bottleneck in practice. - - return s.Mul2(val, val) + var product [8]uint64 + arith.Square512(&product, &val.n) + scalar64Reduce512(&s.n, &product) + return s } // Square squares the scalar modulo the group order in constant time. The @@ -969,32 +792,39 @@ func (s *ModNScalar) NegateVal(val *ModNScalar) *ModNScalar { // minus the value. This implies that the result will always be in the // desired range with the sole exception of 0 because N - 0 = N itself. // - // Therefore, in order to avoid the need to reduce the result for every - // other case in order to achieve constant time, this creates a mask that is - // all 0s in the case of the scalar being negated is 0 and all 1s otherwise - // and bitwise ands that mask with each word. - // - // Finally, to simplify the carry propagation, this adds the two's - // complement of the scalar to N in order to achieve the same result. - bits := val.n[0] | val.n[1] | val.n[2] | val.n[3] | val.n[4] | val.n[5] | - val.n[6] | val.n[7] - mask := uint64(uint32Mask * constantTimeNotEq(bits, 0)) - c := uint64(orderWordZero) + (uint64(^val.n[0]) + 1) - s.n[0] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordOne) + uint64(^val.n[1]) - s.n[1] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordTwo) + uint64(^val.n[2]) - s.n[2] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordThree) + uint64(^val.n[3]) - s.n[3] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordFour) + uint64(^val.n[4]) - s.n[4] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordFive) + uint64(^val.n[5]) - s.n[5] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordSix) + uint64(^val.n[6]) - s.n[6] = uint32(c & mask) - c = (c >> 32) + uint64(orderWordSeven) + uint64(^val.n[7]) - s.n[7] = uint32(c & mask) + // The following handles that case in constant time by creating a mask that + // is all 0s in the case the scalar being negated is 0 and all 1s otherwise + // and then bitwise ands that mask with each limb. + // + // This approach was chosen over subtracting from 0 and then conditionally + // adding N because it's over twice as fast on 32-bit hardware while only + // being about 3-4% slower on 64-bit hardware. + // + // Determine mask first to allow aliasing. + combinedLimbs := val.n[0] | val.n[1] | val.n[2] | val.n[3] + mask := -uint64(arith.ConstantTimeNotEq64(combinedLimbs, 0)) + + // Unconditionally subtract the scalar from the group order. + // + // To simplify the carry propagation, this adds the two's complement of the + // scalar to N in order to achieve the same result. + // + // s = N - val + var c uint64 + s.n[0], c = bits.Add64(orderLimb0, ^val.n[0], 1) + s.n[1], c = bits.Add64(orderLimb1, ^val.n[1], c) + s.n[2], c = bits.Add64(orderLimb2, ^val.n[2], c) + s.n[3], _ = bits.Add64(orderLimb3, ^val.n[3], c) + + // Either keep the result when val != 0 or clear it when val == 0. The + // result is either: + // + // val == 0: s = 0 + // val != 0: s = N - val + s.n[0] &= mask + s.n[1] &= mask + s.n[2] &= mask + s.n[3] &= mask return s } @@ -1066,26 +896,42 @@ func (s *ModNScalar) InverseNonConst() *ModNScalar { // IsOverHalfOrder returns whether or not the scalar exceeds the group order // divided by 2 in constant time. func (s *ModNScalar) IsOverHalfOrder() bool { - // The intuition here is that the scalar is greater than half of the group - // order if one of the higher individual words is greater than the - // corresponding word of the half group order and all higher words in the - // scalar are equal to their corresponding word of the half group order. - // - // Note that the words 4, 5, and 6 are all the max uint32 value, so there is - // no need to test if those individual words of the scalar exceeds them, - // hence, only equality is checked for them. - result := constantTimeGreater(s.n[7], halfOrderWordSeven) - highWordsEqual := constantTimeEq(s.n[7], halfOrderWordSeven) - highWordsEqual &= constantTimeEq(s.n[6], halfOrderWordSix) - highWordsEqual &= constantTimeEq(s.n[5], halfOrderWordFive) - highWordsEqual &= constantTimeEq(s.n[4], halfOrderWordFour) - result |= highWordsEqual & constantTimeGreater(s.n[3], halfOrderWordThree) - highWordsEqual &= constantTimeEq(s.n[3], halfOrderWordThree) - result |= highWordsEqual & constantTimeGreater(s.n[2], halfOrderWordTwo) - highWordsEqual &= constantTimeEq(s.n[2], halfOrderWordTwo) - result |= highWordsEqual & constantTimeGreater(s.n[1], halfOrderWordOne) - highWordsEqual &= constantTimeEq(s.n[1], halfOrderWordOne) - result |= highWordsEqual & constantTimeGreater(s.n[0], halfOrderWordZero) - - return result != 0 + // These fields provide convenient access to each of the limbs of the + // secp256k1 curve group order N / 2 to improve code readability and avoid + // the need to recalculate them. + // + // The half order of the secp256k1 curve group is: + // 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0 + // + // Converting that to field representation (base 2^64) is: + // + // n[0] = 0xdfe92f46681b20a0 + // n[1] = 0x5d576e7357a4501d + // n[2] = 0xffffffffffffffff + // n[3] = 0x7fffffffffffffff + // + // This can be verified with the following test code: + // halfOrder := new(big.Int).Div(curveParams.N, big.NewInt(2)) + // var s ModNScalar + // s.SetByteSlice(halfOrder.Bytes()) + // t.Logf("%x", s.n) + const ( + halfOrderLimb0 uint64 = 0xdfe92f46681b20a0 + halfOrderLimb1 uint64 = 0x5d576e7357a4501d + halfOrderLimb2 uint64 = 0xffffffffffffffff + halfOrderLimb3 uint64 = 0x7fffffffffffffff + ) + + // The goal is to return true when the scalar is greater than half of the + // group order. That is, return true when s > N/2, which is trivially + // rearranged to s - (N/2 + 1) ≥ 0. + // + // In other words, the condition is met iff subtracting (N/2 + 1) from s is + // non-negative (aka there was no borrow). + var borrow uint64 + _, borrow = bits.Sub64(s.n[0], halfOrderLimb0+1, borrow) + _, borrow = bits.Sub64(s.n[1], halfOrderLimb1, borrow) + _, borrow = bits.Sub64(s.n[2], halfOrderLimb2, borrow) + _, borrow = bits.Sub64(s.n[3], halfOrderLimb3, borrow) + return borrow == 0 } diff --git a/dcrec/secp256k1/modnscalar_test.go b/dcrec/secp256k1/modnscalar_test.go index 251708a6ff..94e329f237 100644 --- a/dcrec/secp256k1/modnscalar_test.go +++ b/dcrec/secp256k1/modnscalar_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2023 The Decred developers +// Copyright (c) 2020-2026 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -116,23 +116,27 @@ func TestModNScalarSetInt(t *testing.T) { tests := []struct { name string // test description in uint32 // test value - expected [8]uint32 // expected raw ints + expected [4]uint64 // expected limbs }{{ + name: "zero", + in: 0, + expected: [4]uint64{0, 0, 0, 0}, + }, { name: "five", in: 5, - expected: [8]uint32{5, 0, 0, 0, 0, 0, 0, 0}, + expected: [4]uint64{5, 0, 0, 0}, }, { - name: "group order word zero", - in: orderWordZero, - expected: [8]uint32{orderWordZero, 0, 0, 0, 0, 0, 0, 0}, + name: "max uint8 (2^8 - 1)", + in: 255, + expected: [4]uint64{255, 0, 0, 0}, }, { - name: "group order word zero + 1", - in: orderWordZero + 1, - expected: [8]uint32{orderWordZero + 1, 0, 0, 0, 0, 0, 0, 0}, + name: "max uint16 (2^16 - 1)", + in: 65535, + expected: [4]uint64{65535, 0, 0, 0}, }, { - name: "2^32 - 1", + name: "max uint32 (2^32 - 1)", in: 4294967295, - expected: [8]uint32{4294967295, 0, 0, 0, 0, 0, 0, 0}, + expected: [4]uint64{4294967295, 0, 0, 0}, }} for _, test := range tests { @@ -152,88 +156,136 @@ func TestModNScalarSetBytes(t *testing.T) { tests := []struct { name string // test description in string // hex encoded test value - expected [8]uint32 // expected raw ints + expected [4]uint64 // expected raw ints overflow bool // expected overflow result }{{ name: "zero", in: "00", - expected: [8]uint32{0, 0, 0, 0, 0, 0, 0, 0}, + expected: [4]uint64{0, 0, 0, 0}, + overflow: false, + }, { + name: "one (only limb zero set)", + in: "01", + expected: [4]uint64{1, 0, 0, 0}, + overflow: false, + }, { + name: "2^64 - 1 (limb zero all ones)", + in: "ffffffffffffffff", + expected: [4]uint64{0xffffffffffffffff, 0, 0, 0}, + overflow: false, + }, { + name: "2^64 (limb one low bit)", + in: "010000000000000000", + expected: [4]uint64{0, 1, 0, 0}, + overflow: false, + }, { + name: "2^128 - 1 (limbs zero and one all ones)", + in: "ffffffffffffffffffffffffffffffff", + expected: [4]uint64{0xffffffffffffffff, 0xffffffffffffffff, 0, 0}, + overflow: false, + }, { + name: "2^128 (limb two low bit)", + in: "0100000000000000000000000000000000", + expected: [4]uint64{0, 0, 1, 0}, + overflow: false, + }, { + name: "2^192 - 1 (limbs zero, one, and two all ones)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffff", + expected: [4]uint64{ + 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0, + }, + overflow: false, + }, { + name: "2^192 (limb three low bit)", + in: "01000000000000000000000000000000000000000000000000", + expected: [4]uint64{0, 0, 0, 1}, + overflow: false, + }, { + name: "2^255 (limb three high bit)", + in: "8000000000000000000000000000000000000000000000000000000000000000", + expected: [4]uint64{0, 0, 0, 0x8000000000000000}, + overflow: false, + }, { + name: "distinct value in every limb (verifies limb ordering)", + in: "0123456789abcdeffedcba98765432100f1e2d3c4b5a69788796a5b4c3d2e1f0", + expected: [4]uint64{ + 0x08796a5b4c3d2e1f0, 0x0f1e2d3c4b5a6978, 0xfedcba9876543210, + 0x0123456789abcdef, + }, overflow: false, }, { name: "group order (aka 0)", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", - expected: [8]uint32{0, 0, 0, 0, 0, 0, 0, 0}, + expected: [4]uint64{0, 0, 0, 0}, overflow: true, }, { - name: "group order - 1", + name: "group order - 1 (limb zero one below prime, upper limbs maxed)", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", - expected: [8]uint32{ - 0xd0364140, 0xbfd25e8c, 0xaf48a03b, 0xbaaedce6, - 0xfffffffe, 0xffffffff, 0xffffffff, 0xffffffff, + expected: [4]uint64{ + 0xbfd25e8cd0364140, 0xbaaedce6af48a03b, 0xfffffffffffffffe, + 0xffffffffffffffff, }, overflow: false, }, { - name: "group order + 1 (aka 1, overflow in word zero)", + name: "group order + 1 (aka 1, overflow in limb zero)", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142", - expected: [8]uint32{1, 0, 0, 0, 0, 0, 0, 0}, + expected: [4]uint64{1, 0, 0, 0}, overflow: true, }, { - name: "group order word zero", - in: "d0364141", - expected: [8]uint32{0xd0364141, 0, 0, 0, 0, 0, 0, 0}, - overflow: false, - }, { - name: "group order word zero and one", + name: "group order limb zero", in: "bfd25e8cd0364141", - expected: [8]uint32{0xd0364141, 0xbfd25e8c, 0, 0, 0, 0, 0, 0}, + expected: [4]uint64{0xbfd25e8cd0364141, 0, 0, 0}, overflow: false, }, { - name: "group order words zero, one, and two", - in: "af48a03bbfd25e8cd0364141", - expected: [8]uint32{0xd0364141, 0xbfd25e8c, 0xaf48a03b, 0, 0, 0, 0, 0}, + name: "group order limbs zero and one", + in: "baaedce6af48a03bbfd25e8cd0364141", + expected: [4]uint64{0xbfd25e8cd0364141, 0xbaaedce6af48a03b, 0, 0}, overflow: false, }, { - name: "overflow in word one", - in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8dd0364141", - expected: [8]uint32{0, 1, 0, 0, 0, 0, 0, 0}, - overflow: true, + name: "group order limbs zero, one, and two", + in: "fffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + expected: [4]uint64{ + 0xbfd25e8cd0364141, 0xbaaedce6af48a03b, 0xfffffffffffffffe, 0, + }, + overflow: false, }, { - name: "overflow in word two", + name: "group order + 2^64 (overflow in limb one)", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03cbfd25e8cd0364141", - expected: [8]uint32{0, 0, 1, 0, 0, 0, 0, 0}, + expected: [4]uint64{0, 1, 0, 0}, overflow: true, }, { - name: "overflow in word three", - in: "fffffffffffffffffffffffffffffffebaaedce7af48a03bbfd25e8cd0364141", - expected: [8]uint32{0, 0, 0, 1, 0, 0, 0, 0}, + name: "group order + 2^128 (overflow in limb two)", + in: "ffffffffffffffffffffffffffffffffbaaedce6af48a03bbfd25e8cd0364141", + expected: [4]uint64{0, 0, 1, 0}, overflow: true, }, { - name: "overflow in word four", - in: "ffffffffffffffffffffffffffffffffbaaedce6af48a03bbfd25e8cd0364141", - expected: [8]uint32{0, 0, 0, 0, 1, 0, 0, 0}, + name: "2^256 - 1 (max input)", + in: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + expected: [4]uint64{0x402da1732fc9bebe, 0x4551231950b75fc4, 1, 0}, + overflow: true, }, { name: "(group order - 1) * 2 NOT mod N, truncated >32 bytes", in: "01fffffffffffffffffffffffffffffffd755db9cd5e9140777fa4bd19a06c8284", - expected: [8]uint32{ - 0x19a06c82, 0x777fa4bd, 0xcd5e9140, 0xfd755db9, - 0xffffffff, 0xffffffff, 0xffffffff, 0x01ffffff, + expected: [4]uint64{ + 0x777fa4bd19a06c82, 0xfd755db9cd5e9140, 0xffffffffffffffff, + 0x01ffffffffffffff, }, overflow: false, }, { - name: "alternating bits", + name: "alternating bits (spans all limbs)", in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", - expected: [8]uint32{ - 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, - 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, + expected: [4]uint64{ + 0xa5a5a5a5a5a5a5a5, 0xa5a5a5a5a5a5a5a5, 0xa5a5a5a5a5a5a5a5, + 0xa5a5a5a5a5a5a5a5, }, overflow: false, }, { - name: "alternating bits 2", + name: "alternating bits 2 (spans all limbs)", in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", - expected: [8]uint32{ - 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, - 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, 0x5a5a5a5a, + expected: [4]uint64{ + 0x5a5a5a5a5a5a5a5a, 0x5a5a5a5a5a5a5a5a, 0x5a5a5a5a5a5a5a5a, + 0x5a5a5a5a5a5a5a5a, }, overflow: false, }} @@ -305,35 +357,35 @@ func TestModNScalarBytes(t *testing.T) { in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", }, { - name: "group order + 1 (aka 1, overflow in word zero)", + name: "group order + 1 (aka 1, overflow in limb zero)", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142", expected: "0000000000000000000000000000000000000000000000000000000000000001", }, { - name: "group order word zero", + name: "group order limb zero", in: "d0364141", expected: "00000000000000000000000000000000000000000000000000000000d0364141", }, { - name: "group order word zero and one", + name: "group order limb zero and one", in: "bfd25e8cd0364141", expected: "000000000000000000000000000000000000000000000000bfd25e8cd0364141", }, { - name: "group order words zero, one, and two", + name: "group order limbs zero, one, and two", in: "af48a03bbfd25e8cd0364141", expected: "0000000000000000000000000000000000000000af48a03bbfd25e8cd0364141", }, { - name: "overflow in word one", + name: "overflow in limb one", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8dd0364141", expected: "0000000000000000000000000000000000000000000000000000000100000000", }, { - name: "overflow in word two", + name: "overflow in limb two", in: "fffffffffffffffffffffffffffffffebaaedce6af48a03cbfd25e8cd0364141", expected: "0000000000000000000000000000000000000000000000010000000000000000", }, { - name: "overflow in word three", + name: "overflow in limb three", in: "fffffffffffffffffffffffffffffffebaaedce7af48a03bbfd25e8cd0364141", expected: "0000000000000000000000000000000000000001000000000000000000000000", }, { - name: "overflow in word four", + name: "overflow in limb four", in: "ffffffffffffffffffffffffffffffffbaaedce6af48a03bbfd25e8cd0364141", expected: "0000000000000000000000000000000100000000000000000000000000000000", }, { @@ -501,11 +553,11 @@ func TestModNScalarEqualsRandom(t *testing.T) { t.Fatalf("failed equality check\nscalar in: %v", s) } - // Flip a random bit in a random word and ensure it's no longer equal. - randomWord := rng.Int31n(int32(len(s.n))) - randomBit := uint32(1 << uint32(rng.Int31n(32))) + // Flip a random bit in a random limb and ensure it's no longer equal. + randomLimb := rng.Int31n(int32(len(s.n))) + randomBit := uint64(1 << uint64(rng.Int63n(64))) s2 := new(ModNScalar).Set(s) - s2.n[randomWord] ^= randomBit + s2.n[randomLimb] ^= randomBit if s2.Equals(s) { t.Fatalf("failed inequality check\nscalar in: %v", s2) } @@ -531,55 +583,35 @@ func TestModNScalarAdd(t *testing.T) { in2: "0", expected: "1", }, { - name: "group order (aka 0) + 1 (gets reduced, no overflow)", - in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", - in2: "1", - expected: "1", - }, { - name: "group order - 1 + 1 (aka 0, overflow to prime)", + name: "group order-1 + 1 (aka 0, overflow to order)", in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", in2: "1", expected: "0", }, { - name: "group order - 1 + 2 (aka 1, overflow in word zero)", - in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", - in2: "2", + name: "group order (aka 0) + 1 (aka 1, gets reduced, no overflow)", + in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + in2: "1", expected: "1", }, { - name: "overflow in word one", - in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8bd0364141", - in2: "100000001", + name: "group order-1 + 2 (aka 1, overflow in limb zero)", + in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + in2: "2", expected: "1", }, { - name: "overflow in word two", + name: "overflow in limb one", in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03abfd25e8cd0364141", in2: "10000000000000001", expected: "1", }, { - name: "overflow in word three", - in1: "fffffffffffffffffffffffffffffffebaaedce5af48a03bbfd25e8cd0364141", - in2: "1000000000000000000000001", - expected: "1", - }, { - name: "overflow in word four", + name: "overflow in limb two", in1: "fffffffffffffffffffffffffffffffdbaaedce6af48a03bbfd25e8cd0364141", in2: "100000000000000000000000000000001", expected: "1", }, { - name: "overflow in word five", - in1: "fffffffffffffffffffffffefffffffebaaedce6af48a03bbfd25e8cd0364141", - in2: "10000000000000000000000000000000000000001", - expected: "1", - }, { - name: "overflow in word six", + name: "overflow in limb three", in1: "fffffffffffffffefffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", in2: "1000000000000000000000000000000000000000000000001", expected: "1", - }, { - name: "overflow in word seven", - in1: "fffffffefffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", - in2: "100000000000000000000000000000000000000000000000000000001", - expected: "1", }, { name: "alternating bits", in1: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", @@ -601,7 +633,7 @@ func TestModNScalarAdd(t *testing.T) { // Ensure the result has the expected value. s1.Add(s2) if !s1.Equals(expected) { - t.Errorf("%s: unexpected result\ngot: %x\nwant: %x", test.name, + t.Errorf("%s: unexpected result\ngot: %v\nwant: %v", test.name, s1, expected) continue } @@ -645,53 +677,6 @@ func TestModNScalarAddRandom(t *testing.T) { } } -// TestAccumulator96Add ensures that the internal 96-bit accumulator used by -// multiplication works as expected for overflow edge cases including overflow. -func TestAccumulator96Add(t *testing.T) { - tests := []struct { - name string // test description - start accumulator96 // starting value of accumulator - in uint64 // value to add to accumulator - expected accumulator96 // expected value of accumulator after addition - }{{ - name: "0 + 0 = 0", - start: accumulator96{[3]uint32{0, 0, 0}}, - in: 0, - expected: accumulator96{[3]uint32{0, 0, 0}}, - }, { - name: "overflow in word zero", - start: accumulator96{[3]uint32{0xffffffff, 0, 0}}, - in: 1, - expected: accumulator96{[3]uint32{0, 1, 0}}, - }, { - name: "overflow in word one", - start: accumulator96{[3]uint32{0, 0xffffffff, 0}}, - in: 0x100000000, - expected: accumulator96{[3]uint32{0, 0, 1}}, - }, { - name: "overflow in words one and two", - start: accumulator96{[3]uint32{0xffffffff, 0xffffffff, 0}}, - in: 1, - expected: accumulator96{[3]uint32{0, 0, 1}}, - }, { - // Start accumulator at 129127208455837319175 which is the result of - // 4294967295 * 4294967295 accumulated seven times. - name: "max result from eight adds of max uint32 multiplications", - start: accumulator96{[3]uint32{7, 4294967282, 6}}, - in: 18446744065119617025, - expected: accumulator96{[3]uint32{8, 4294967280, 7}}, - }} - - for _, test := range tests { - acc := test.start - acc.Add(test.in) - if acc.n != test.expected.n { - t.Errorf("%s: wrong result\ngot: %v\nwant: %v", test.name, acc.n, - test.expected.n) - } - } -} - // TestModNScalarMul ensures that multiplying two scalars together works as // expected for edge cases. func TestModNScalarMul(t *testing.T) { @@ -720,6 +705,16 @@ func TestModNScalarMul(t *testing.T) { in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", in2: "2", expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", + }, { + name: "(group order-1) * 3", + in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + in2: "3", + expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e", + }, { + name: "(group order-1) * 4", + in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + in2: "4", + expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413d", }, { name: "(group order-1) * (group order-1)", in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", @@ -730,56 +725,76 @@ func TestModNScalarMul(t *testing.T) { in1: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", in2: "2", expected: "1", + }, { + name: "highest bit in limb zero squared", + in1: "8000000000000000", + in2: "8000000000000000", + expected: "40000000000000000000000000000000", + }, { + name: "highest bit in limb one squared", + in1: "80000000000000000000000000000000", + in2: "80000000000000000000000000000000", + expected: "4000000000000000000000000000000000000000000000000000000000000000", + }, { + name: "highest bit in limb two squared", + in1: "800000000000000000000000000000000000000000000000", + in2: "800000000000000000000000000000000000000000000000", + expected: "515448c6542dd7f1100b685ccbf26fafc0000000000000000000000000000000", + }, { + name: "highest bit in limb three squared", + in1: "8000000000000000000000000000000000000000000000000000000000000000", + in2: "8000000000000000000000000000000000000000000000000000000000000000", + expected: "2759c7356071a6f179a5fd7916f341f19d0525b0839f3e1e225b3c8519f5f450", + }, { + name: "cross limb product 64x128", + in1: "10000000000000000", + in2: "100000000000000000000000000000000", + expected: "1000000000000000000000000000000000000000000000000", + }, { + name: "cross limb product 64x192", + in1: "10000000000000000", + in2: "1000000000000000000000000000000000000000000000000", + expected: "14551231950b75fc4402da1732fc9bebf", + }, { + name: "cross limb product 128x192", + in1: "100000000000000000000000000000000", + in2: "1000000000000000000000000000000000000000000000000", + expected: "14551231950b75fc4402da1732fc9bebf0000000000000000", }, { name: "group order (aka 0) * 3", in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", in2: "3", expected: "0", }, { - name: "overflow in word eight", + name: "overflow in limb four", in1: "100000000000000000000000000000000", in2: "100000000000000000000000000000000", expected: "14551231950b75fc4402da1732fc9bebf", }, { - name: "overflow in word nine", - in1: "1000000000000000000000000000000000000", - in2: "1000000000000000000000000000000000000", - expected: "14551231950b75fc4402da1732fc9bebf00000000", - }, { - name: "overflow in word ten", + name: "overflow in limb five", in1: "10000000000000000000000000000000000000000", in2: "10000000000000000000000000000000000000000", expected: "14551231950b75fc4402da1732fc9bebf0000000000000000", }, { - name: "overflow in word eleven", - in1: "100000000000000000000000000000000000000000000", - in2: "100000000000000000000000000000000000000000000", - expected: "14551231950b75fc4402da1732fc9bebf000000000000000000000000", - }, { - name: "overflow in word twelve", + name: "overflow in limb six", in1: "1000000000000000000000000000000000000000000000000", in2: "1000000000000000000000000000000000000000000000000", expected: "4551231950b75fc4402da1732fc9bec04551231950b75fc4402da1732fc9bebf", }, { - name: "overflow in word thirteen", - in1: "10000000000000000000000000000000000000000000000000000", - in2: "10000000000000000000000000000000000000000000000000000", - expected: "50b75fc4402da1732fc9bec09d671cd51b343a1b66926b57d2a4c1c61536bda7", - }, { - name: "overflow in word fourteen", + name: "overflow in limb seven", in1: "100000000000000000000000000000000000000000000000000000000", in2: "100000000000000000000000000000000000000000000000000000000", expected: "402da1732fc9bec09d671cd581c69bc59509b0b074ec0aea8f564d667ec7eb3c", }, { - name: "overflow in word fifteen", - in1: "1000000000000000000000000000000000000000000000000000000000000", - in2: "1000000000000000000000000000000000000000000000000000000000000", - expected: "2fc9bec09d671cd581c69bc5e697f5e41f12c33a0a7b6f4e3302b92ea029cecd", + name: "max limb * one", + in1: "ffffffffffffffff", + in2: "1", + expected: "ffffffffffffffff", }, { - name: "double overflow in internal accumulator", - in1: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", - in2: "55555555555555555555555555555554e8e4f44ce51835693ff0ca2ef01215c2", - expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b7f", + name: "max limb * max limb", + in1: "ffffffffffffffff", + in2: "ffffffffffffffff", + expected: "fffffffffffffffe0000000000000001", }, { name: "alternating bits", in1: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", @@ -881,35 +896,35 @@ func TestModNScalarSquare(t *testing.T) { in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142", expected: "1", }, { - name: "overflow in word eight", + name: "overflow in limb eight", in: "100000000000000000000000000000000", expected: "14551231950b75fc4402da1732fc9bebf", }, { - name: "overflow in word nine", + name: "overflow in limb nine", in: "1000000000000000000000000000000000000", expected: "14551231950b75fc4402da1732fc9bebf00000000", }, { - name: "overflow in word ten", + name: "overflow in limb ten", in: "10000000000000000000000000000000000000000", expected: "14551231950b75fc4402da1732fc9bebf0000000000000000", }, { - name: "overflow in word eleven", + name: "overflow in limb eleven", in: "100000000000000000000000000000000000000000000", expected: "14551231950b75fc4402da1732fc9bebf000000000000000000000000", }, { - name: "overflow in word twelve", + name: "overflow in limb twelve", in: "1000000000000000000000000000000000000000000000000", expected: "4551231950b75fc4402da1732fc9bec04551231950b75fc4402da1732fc9bebf", }, { - name: "overflow in word thirteen", + name: "overflow in limb thirteen", in: "10000000000000000000000000000000000000000000000000000", expected: "50b75fc4402da1732fc9bec09d671cd51b343a1b66926b57d2a4c1c61536bda7", }, { - name: "overflow in word fourteen", + name: "overflow in limb fourteen", in: "100000000000000000000000000000000000000000000000000000000", expected: "402da1732fc9bec09d671cd581c69bc59509b0b074ec0aea8f564d667ec7eb3c", }, { - name: "overflow in word fifteen", + name: "overflow in limb fifteen", in: "1000000000000000000000000000000000000000000000000000000000000", expected: "2fc9bec09d671cd581c69bc5e697f5e41f12c33a0a7b6f4e3302b92ea029cecd", }, { @@ -979,6 +994,26 @@ func TestModNScalarSquareRandom(t *testing.T) { } } +// checkModNScalarNegateProps checks algebraic properties of scalar negation. +func checkModNScalarNegateProps(t *testing.T, val *ModNScalar) { + t.Helper() + + negation := new(ModNScalar).NegateVal(val) + + // Ensure involution produces the original value. That is -(-x) == x. + involution := new(ModNScalar).Set(negation).Negate() + if !involution.Equals(val) { + t.Errorf("mismatched involution -- got: %v, want: %v", involution, + val) + } + + // Ensure x + (-x) == 0. + sum := new(ModNScalar).Add2(val, negation) + if !sum.IsZero() { + t.Errorf("x + (-x) != 0 -- got: %v, want: 0", sum) + } +} + // TestModNScalarNegate ensures that negating scalars works as expected for edge // cases. func TestModNScalarNegate(t *testing.T) { @@ -995,33 +1030,41 @@ func TestModNScalarNegate(t *testing.T) { in: "1", expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", }, { - name: "negation in word one", - in: "0000000000000000000000000000000000000000000000000000000100000000", - expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8bd0364141", + name: "two", + in: "2", + expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", + }, { + name: "three", + in: "3", + expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413e", + }, { + name: "group order - 2", + in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", + expected: "2", + }, { + name: "group order - 1", + in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + expected: "1", }, { - name: "negation in word two", + name: "negation in limb one", in: "0000000000000000000000000000000000000000000000010000000000000000", expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03abfd25e8cd0364141", }, { - name: "negation in word three", - in: "0000000000000000000000000000000000000001000000000000000000000000", - expected: "fffffffffffffffffffffffffffffffebaaedce5af48a03bbfd25e8cd0364141", - }, { - name: "negation in word four", + name: "negation in limb two", in: "0000000000000000000000000000000100000000000000000000000000000000", expected: "fffffffffffffffffffffffffffffffdbaaedce6af48a03bbfd25e8cd0364141", }, { - name: "negation in word five", - in: "0000000000000000000000010000000000000000000000000000000000000000", - expected: "fffffffffffffffffffffffefffffffebaaedce6af48a03bbfd25e8cd0364141", - }, { - name: "negation in word six", + name: "negation in limb three", in: "0000000000000001000000000000000000000000000000000000000000000000", expected: "fffffffffffffffefffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", }, { - name: "negation in word seven", - in: "0000000100000000000000000000000000000000000000000000000000000000", - expected: "fffffffefffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + name: "half group order (floor)", + in: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", + expected: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", + }, { + name: "half group order (ceil)", + in: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", + expected: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", }, { name: "alternating bits", in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", @@ -1051,6 +1094,9 @@ func TestModNScalarNegate(t *testing.T) { result2, expected) continue } + + // Ensure algebraic properties with negation hold. + checkModNScalarNegateProps(t, s) } } @@ -1086,6 +1132,35 @@ func TestModNScalarNegateRandom(t *testing.T) { "big int result: %x\nscalar result %v", bigIntVal, modNVal, bigIntResult, modNValResult) } + + // Ensure algebraic properties with negation hold. + checkModNScalarNegateProps(t, modNVal) + } +} + +// checkModNScalarInverseProps checks algebraic properties of the +// multiplicative modular inverse of scalars. +func checkModNScalarInverseProps(t *testing.T, val *ModNScalar) { + t.Helper() + + // Exception for 0 which does not have a modular multiplicative inverse. + if val.IsZero() { + return + } + + inverse := new(ModNScalar).InverseValNonConst(val) + + // Ensure x * x^-1 ≡ 1 (mod N). + product := new(ModNScalar).Mul2(val, inverse) + one := new(ModNScalar).SetInt(1) + if !product.Equals(one) { + t.Errorf("x * x^-1 != 1 (mod N) -- got: %v, want: %v", product, one) + } + + // Ensure (x^-1)^-1 == x. + doubleInv := new(ModNScalar).InverseValNonConst(inverse) + if !doubleInv.Equals(val) { + t.Errorf("(x^-1)^-1 != x (mod N) -- got: %v, want: %v", doubleInv, val) } } @@ -1105,33 +1180,29 @@ func TestModNScalarInverseNonConst(t *testing.T) { in: "1", expected: "1", }, { - name: "inverse carry in word one", - in: "0000000000000000000000000000000000000000000000000000000100000000", - expected: "5588b13effffffffffffffffffffffff934e5b00ca8417bf50177f7ba415411a", + name: "two", + in: "2", + expected: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", + }, { + name: "group order - 1", + in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + expected: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + }, { + name: "group order - 2", + in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", + expected: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", }, { - name: "inverse carry in word two", + name: "inverse carry in limb one", in: "0000000000000000000000000000000000000000000000010000000000000000", expected: "4b0dff665588b13effffffffffffffffa09f710af01555259d4ad302583de6dc", }, { - name: "inverse carry in word three", - in: "0000000000000000000000000000000000000001000000000000000000000000", - expected: "34b9ec244b0dff665588b13effffffffbcff4127932a971a78274c9d74176b38", - }, { - name: "inverse carry in word four", + name: "inverse carry in limb two", in: "0000000000000000000000000000000100000000000000000000000000000000", expected: "50a51ac834b9ec244b0dff665588b13e9984d5b3cf80ef0fd6a23766a3ee9f22", }, { - name: "inverse carry in word five", - in: "0000000000000000000000010000000000000000000000000000000000000000", - expected: "27cfab5e50a51ac834b9ec244b0dff6622f16e85b683d5a059bcd5a3b29d9dff", - }, { - name: "inverse carry in word six", + name: "inverse carry in limb three", in: "0000000000000001000000000000000000000000000000000000000000000000", expected: "897f30c127cfab5e50a51ac834b9ec239c53f268b4700c14f19b9499ac58d8ad", - }, { - name: "inverse carry in word seven", - in: "0000000100000000000000000000000000000000000000000000000000000000", - expected: "6494ef93897f30c127cfab5e50a51ac7b4e8f713e0cddd182234e907286ae6b3", }, { name: "alternating bits", in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", @@ -1163,6 +1234,9 @@ func TestModNScalarInverseNonConst(t *testing.T) { result2, expected) continue } + + // Ensure algebraic properties with inverses hold. + checkModNScalarInverseProps(t, s) } } @@ -1198,6 +1272,30 @@ func TestModNScalarInverseNonConstRandom(t *testing.T) { "big int result: %x\nscalar result %v", bigIntVal, modNVal, bigIntResult, modNValResult) } + + // Ensure algebraic properties with inverses hold. + checkModNScalarInverseProps(t, modNVal) + } +} + +// checkModNScalarHalfOrderProps checks algebraic properties of the half order +// comparison. +func checkModNScalarHalfOrderProps(t *testing.T, val *ModNScalar) { + t.Helper() + + // Exception for 0 which is the only scalar where negation does not produce + // a distinct value. + if val.IsZero() { + return + } + + // Ensure negating a scalar flips whether it is over half order. + // + // x > N/2 implies N-x < N/2 and vice versa. + negation := new(ModNScalar).NegateVal(val) + if val.IsOverHalfOrder() == negation.IsOverHalfOrder() { + t.Errorf("negation did not flip half order -- val: %v, negation: %v", + val, negation) } } @@ -1229,30 +1327,46 @@ func TestModNScalarIsOverHalfOrder(t *testing.T) { in: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1", expected: true, }, { - name: "over half order word one", - in: "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f47681b20a0", + name: "over half order limb one", + in: "7fffffffffffffffffffffffffffffff5d576e7357a4501edfe92f46681b20a0", expected: true, }, { - name: "over half order word two", - in: "7fffffffffffffffffffffffffffffff5d576e7357a4501edfe92f46681b20a0", + name: "group order - 2", + in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd036413f", expected: true, }, { - name: "over half order word three", - in: "7fffffffffffffffffffffffffffffff5d576e7457a4501ddfe92f46681b20a0", + name: "group order - 1", + in: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + expected: true, + }, { + name: "highest limb below half order", + in: "7fffffffffffffffffffffffffffffff00000000000000000000000000000000", + expected: false, + }, { + name: "highest bit set", + in: "8000000000000000000000000000000000000000000000000000000000000000", expected: true, }, { - name: "over half order word seven", - in: "8fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0", + name: "alternating bits", + in: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", expected: true, + }, { + name: "alternating bits 2", + in: "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", + expected: false, }} for _, test := range tests { - result := mustModNScalarWithOverflow(test.in).IsOverHalfOrder() + s := mustModNScalarWithOverflow(test.in) + result := s.IsOverHalfOrder() if result != test.expected { t.Errorf("%s: unexpected result -- got: %v, want: %v", test.name, result, test.expected) continue } + + // Ensure algebraic properties with the half order hold. + checkModNScalarHalfOrderProps(t, s) } } @@ -1286,5 +1400,8 @@ func TestModNScalarIsOverHalfOrderRandom(t *testing.T) { "in: %v\nbig int result: %v\nscalar result %v", bigIntVal, modNVal, bigIntResult, modNValResult) } + + // Ensure algebraic properties with the half order hold. + checkModNScalarHalfOrderProps(t, modNVal) } }