Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions dcrec/secp256k1/common.go
Original file line number Diff line number Diff line change
@@ -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
}
37 changes: 37 additions & 0 deletions dcrec/secp256k1/common_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
30 changes: 14 additions & 16 deletions dcrec/secp256k1/field64.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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).
Expand Down
41 changes: 0 additions & 41 deletions dcrec/secp256k1/modnscalar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down