diff --git a/dcrec/secp256k1/common.go b/dcrec/secp256k1/common.go new file mode 100644 index 0000000000..b45e013bd5 --- /dev/null +++ b/dcrec/secp256k1/common.go @@ -0,0 +1,55 @@ +// 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 new file mode 100644 index 0000000000..cde06019ff --- /dev/null +++ b/dcrec/secp256k1/common_test.go @@ -0,0 +1,37 @@ +// 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/field64.go b/dcrec/secp256k1/field64.go index c751f2b07a..99e7ea7133 100644 --- a/dcrec/secp256k1/field64.go +++ b/dcrec/secp256k1/field64.go @@ -84,11 +84,10 @@ func (f *FieldVal64) SetBytes(b *[32]byte) uint32 { s2, borrow = bits.Sub64(f.n[2], field64Prime2, borrow) s3, borrow = bits.Sub64(f.n[3], field64Prime3, borrow) - mask := -(1 - borrow) - f.n[0] ^= (s0 ^ f.n[0]) & mask - f.n[1] ^= (s1 ^ f.n[1]) & mask - f.n[2] ^= (s2 ^ f.n[2]) & mask - f.n[3] ^= (s3 ^ f.n[3]) & mask + 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) } @@ -301,11 +300,11 @@ func (f *FieldVal64) Add2(a, b *FieldVal64) *FieldVal64 { // Pass 3: constant-time select. Keep t only when there was no overflow and // t < p (borrow set); otherwise use s - mask := -((1 - overflow) & borrow) - f.n[0] = s0 ^ ((t0 ^ s0) & mask) - f.n[1] = s1 ^ ((t1 ^ s1) & mask) - f.n[2] = s2 ^ ((t2 ^ s2) & mask) - f.n[3] = s3 ^ ((t3 ^ s3) & mask) + 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 } @@ -553,17 +552,16 @@ func field64Reduce512(r *[4]uint64, x *[8]uint64) { // subtract of p fully reduces it. t4 = carry - var s0, s1, s2, s3, mask, borrow uint64 + 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) - mask = -borrow - r[0] = s0 ^ ((t0 ^ s0) & mask) - r[1] = s1 ^ ((t1 ^ s1) & mask) - r[2] = s2 ^ ((t2 ^ s2) & mask) - r[3] = s3 ^ ((t3 ^ s3) & mask) + r[0] = constantTimeSelect64(borrow, t0, s0) + r[1] = constantTimeSelect64(borrow, t1, s1) + r[2] = constantTimeSelect64(borrow, t2, s2) + r[3] = constantTimeSelect64(borrow, t3, s3) } // field64Mul sets r = a * b (mod p). diff --git a/dcrec/secp256k1/modnscalar.go b/dcrec/secp256k1/modnscalar.go index ec6443f543..30c11f9d7c 100644 --- a/dcrec/secp256k1/modnscalar.go +++ b/dcrec/secp256k1/modnscalar.go @@ -210,47 +210,6 @@ func (s *ModNScalar) SetInt(ui uint32) *ModNScalar { return s } -// 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)) -} - // 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 {