From d5cfa73a2f99fcf331eac1896e9118b4987c3d95 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 23 Jul 2022 00:54:23 -0500 Subject: [PATCH] secp256k1: Expose IsZeroBit on mod n scalar type. This exposes a new function on the ModNScalar type named IsZeroBit which operates similarly to IsZero except it returns a 0 or 1. This is being provided 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. Since the type is now exported for external use, consumers would otherwise have no way to perform the conversion. --- dcrec/secp256k1/modnscalar.go | 17 +++++++++++++++-- dcrec/secp256k1/modnscalar_test.go | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/dcrec/secp256k1/modnscalar.go b/dcrec/secp256k1/modnscalar.go index 8125d9a9..f66496ed 100644 --- a/dcrec/secp256k1/modnscalar.go +++ b/dcrec/secp256k1/modnscalar.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020 The Decred developers +// Copyright (c) 2020-2022 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -100,7 +100,7 @@ var ( // arithmetic over the secp256k1 group order. This means all arithmetic is // performed modulo: // -// 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 +// 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 // // It only implements the arithmetic needed for elliptic curve operations, // however, the operations that are not implemented can typically be worked @@ -174,6 +174,19 @@ func (s *ModNScalar) Zero() { s.n[7] = 0 } +// IsZeroBit returns 1 when the scalar 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 (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) +} + // 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. diff --git a/dcrec/secp256k1/modnscalar_test.go b/dcrec/secp256k1/modnscalar_test.go index 26a72739..6d2aec8d 100644 --- a/dcrec/secp256k1/modnscalar_test.go +++ b/dcrec/secp256k1/modnscalar_test.go @@ -80,28 +80,40 @@ func TestModNScalarZero(t *testing.T) { } } -// TestModNScalarIsZero ensures that checking if a scalar is zero works as -// expected. +// TestModNScalarIsZero ensures that checking if a scalar is zero via IsZero and +// IsZeroBit works as expected. func TestModNScalarIsZero(t *testing.T) { var s ModNScalar if !s.IsZero() { t.Errorf("new scalar is not zero - got %v (rawints %x)", s, s.n) } + if s.IsZeroBit() != 1 { + t.Errorf("new scalar is not zero - got %v (rawints %x)", s, s.n) + } s.SetInt(1) if s.IsZero() { t.Errorf("claims zero for nonzero scalar - got %v (rawints %x)", s, s.n) } + if s.IsZeroBit() == 1 { + t.Errorf("claims zero for nonzero scalar - got %v (rawints %x)", s, s.n) + } s.SetInt(0) if !s.IsZero() { t.Errorf("claims nonzero for zero scalar - got %v (rawints %x)", s, s.n) } + if s.IsZeroBit() != 1 { + t.Errorf("claims nonzero for zero scalar - got %v (rawints %x)", s, s.n) + } s.SetInt(1) s.Zero() if !s.IsZero() { - t.Errorf("claims zero for nonzero scalar - got %v (rawints %x)", s, s.n) + t.Errorf("claims nonzero for zero scalar - got %v (rawints %x)", s, s.n) + } + if s.IsZeroBit() != 1 { + t.Errorf("claims nonzero for zero scalar - got %v (rawints %x)", s, s.n) } }