From 085eb08c6b1e3aec6a201fd5ac45c64a2daf9144 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 25 Jul 2026 23:53:41 -0500 Subject: [PATCH] secp256k1: Direct byte clears. The current implementation either clears fixed-size buffers by copying a zeroed array over them, looping over each entry and setting it zero, or manually setting each entry to 0. All of these approaches work as intended, but a cleaner and more efficent approach is to simply set it equal to the zero value of an array of the appropriate size. This package requires a minimum of Go 1.17 and so all supported versions of the Go compiler recognize this pattern and compile it to efficient memory-clearing instructions. While Go does not guarantee constant time operations, inspection of the generated code for supported architectures shows this produces a fixed sequence of stores as desired. Moreover, this approach avoids relying on runtime.memmove, which makes the generated code simpler and easier to reason about with respect to timing behavior. --- dcrec/secp256k1/ecdsa/signature.go | 6 +----- dcrec/secp256k1/field.go | 11 +---------- dcrec/secp256k1/modnscalar.go | 17 ++--------------- dcrec/secp256k1/schnorr/signature.go | 4 +--- 4 files changed, 5 insertions(+), 33 deletions(-) diff --git a/dcrec/secp256k1/ecdsa/signature.go b/dcrec/secp256k1/ecdsa/signature.go index 84d61c956e..43ebc4f9b4 100644 --- a/dcrec/secp256k1/ecdsa/signature.go +++ b/dcrec/secp256k1/ecdsa/signature.go @@ -22,10 +22,6 @@ import ( // https://www.secg.org/sec1-v2.pdf var ( - // zero32 is an array of 32 bytes used for the purposes of zeroing and is - // defined here to avoid extra allocations. - zero32 = [32]byte{} - // orderAsFieldVal is the order of the secp256k1 curve group stored as a // field value. It is provided here to avoid the need to create it multiple // times. @@ -140,7 +136,7 @@ func (sig *Signature) Serialize() []byte { // zeroArray32 zeroes the provided 32-byte buffer. func zeroArray32(b *[32]byte) { - copy(b[:], zero32[:]) + *b = [32]byte{} } // fieldToModNScalar converts a field value to scalar modulo the group order and diff --git a/dcrec/secp256k1/field.go b/dcrec/secp256k1/field.go index f633b46dfc..9c7c9367f0 100644 --- a/dcrec/secp256k1/field.go +++ b/dcrec/secp256k1/field.go @@ -198,16 +198,7 @@ func (f FieldVal) String() string { // Output Normalized: Yes // Output Max Magnitude: 1 func (f *FieldVal) Zero() { - f.n[0] = 0 - f.n[1] = 0 - f.n[2] = 0 - f.n[3] = 0 - f.n[4] = 0 - f.n[5] = 0 - f.n[6] = 0 - f.n[7] = 0 - f.n[8] = 0 - f.n[9] = 0 + f.n = [10]uint32{} } // Set sets the field value equal to the passed value in constant time. The diff --git a/dcrec/secp256k1/modnscalar.go b/dcrec/secp256k1/modnscalar.go index 9405a987f0..671260c3e0 100644 --- a/dcrec/secp256k1/modnscalar.go +++ b/dcrec/secp256k1/modnscalar.go @@ -95,12 +95,6 @@ const ( uint32Mask = 0xffffffff ) -var ( - // zero32 is an array of 32 bytes used for the purposes of zeroing and is - // defined here to avoid extra allocations. - zero32 = [32]byte{} -) - // ModNScalar implements optimized 256-bit constant-time fixed-precision // arithmetic over the secp256k1 group order. This means all arithmetic is // performed modulo: @@ -169,14 +163,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[0] = 0 - s.n[1] = 0 - s.n[2] = 0 - s.n[3] = 0 - s.n[4] = 0 - s.n[5] = 0 - s.n[6] = 0 - s.n[7] = 0 + s.n = [8]uint32{} } // IsZeroBit returns 1 when the scalar is equal to zero or 0 otherwise in @@ -309,7 +296,7 @@ func (s *ModNScalar) SetBytes(b *[32]byte) uint32 { // zeroArray32 zeroes the provided 32-byte buffer. func zeroArray32(b *[32]byte) { - copy(b[:], zero32[:]) + *b = [32]byte{} } // SetByteSlice interprets the provided slice as a 256-bit big-endian unsigned diff --git a/dcrec/secp256k1/schnorr/signature.go b/dcrec/secp256k1/schnorr/signature.go index 21846e48fa..46773fa8c8 100644 --- a/dcrec/secp256k1/schnorr/signature.go +++ b/dcrec/secp256k1/schnorr/signature.go @@ -237,9 +237,7 @@ func (sig *Signature) Verify(hash []byte, pubKey *secp256k1.PublicKey) bool { // zeroArray zeroes the memory of a scalar array. func zeroArray(a *[scalarSize]byte) { - for i := 0; i < scalarSize; i++ { - a[i] = 0x00 - } + *a = [scalarSize]byte{} } // schnorrSign generates an EC-Schnorr-DCRv0 signature over the secp256k1 curve