Skip to content

Commit 60e7454

Browse files
author
sammyne
committed
feature(ExtendedKey): enforces the Zero() method
1 parent 4864ca4 commit 60e7454

7 files changed

+134
-6
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# bip32
22

3-
==========
4-
53
[![CircleCI](https://circleci.com/gh/sammyne/bip32.svg?style=svg)](https://circleci.com/gh/sammyne/bip32)
64
[![codecov](https://codecov.io/gh/sammyne/bip32/branch/master/graph/badge.svg)](https://codecov.io/gh/sammyne/bip32)
75
[![Go Report Card](https://goreportcard.com/badge/github.com/sammyne/bip32)](https://goreportcard.com/report/github.com/sammyne/bip32)

extended_key.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ import "github.com/btcsuite/btcd/btcec"
77
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
88

99
// ExtendedKey specifies the basic api for a extended private or public key.
10-
// TODO: add the Zero method manually clears all fields and bytes in the
11-
// extended key. This can be used to explicitly clear key material from memory
12-
// for enhanced security against memory scraping. This function only clears
13-
// this particular key and not any children that have already been derived.
1410
type ExtendedKey interface {
1511
// AddressPubKeyHash returns the public key hash (20 bytes) derived from the
1612
// key, which would be itself for public keys and the extended public key
@@ -73,4 +69,10 @@ type ExtendedKey interface {
7369
SetNet(keyID Magic)
7470
// String returns the extended key as a human-readable base58-encoded string.
7571
String() string
72+
73+
// Zero manually clears all fields and bytes in the
74+
// extended key. This can be used to explicitly clear key material from memory
75+
// for enhanced security against memory scraping. This function only clears
76+
// this particular key and not any children that have already been derived.
77+
Zero()
7678
}

private_key.go

+11
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,17 @@ func (priv *PrivateKey) ToECPrivate() *btcec.PrivateKey {
156156
return privKey
157157
}
158158

159+
// Zero implements ExtendedKey
160+
func (priv *PrivateKey) Zero() {
161+
if nil == priv {
162+
return
163+
}
164+
165+
priv.PublicKey.Zero()
166+
Zero(priv.Data)
167+
Zero(priv.Version)
168+
}
169+
159170
// publicKeyData load the corresponding public key data in compressed form
160171
// bound to this private key. It will check whether the data part of the public
161172
// key is initialised, and initialise it if necessary.

private_key_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,61 @@ func TestParsePrivateKey_Error(t *testing.T) {
306306
}
307307
}
308308
}
309+
310+
func TestPrivateKey_Zero(t *testing.T) {
311+
var testCases []bip32.Goldie
312+
ReadGoldenJSON(t, bip32.GoldenName, &testCases)
313+
314+
isZero := func(data []byte) bool {
315+
for _, v := range data {
316+
if 0 != v {
317+
return false
318+
}
319+
}
320+
321+
return true
322+
}
323+
324+
isZeroKey := func(xpriv *bip32.PrivateKey) bool {
325+
if nil == xpriv {
326+
return true
327+
}
328+
329+
xpub := xpriv.PublicKey
330+
return isZero(xpriv.Data) && isZero(xpriv.Version) &&
331+
isZero(xpub.ChainCode) && 0 == xpub.ChildIndex && isZero(xpub.Data) &&
332+
0 == xpub.Level && isZero(xpub.ParentFP) && isZero(xpub.Version)
333+
}
334+
335+
for _, c := range testCases {
336+
chains := c.Chains
337+
338+
t.Run("", func(st *testing.T) {
339+
for _, chain := range chains {
340+
xpriv, err := bip32.ParsePrivateKey(chain.ExtendedPrivateKey)
341+
if nil != err {
342+
st.Fatalf("unexpected error: %v", err)
343+
}
344+
345+
if isZeroKey(xpriv) {
346+
st.Fatalf("private key %s shouldn't be zeroed",
347+
chain.ExtendedPrivateKey)
348+
}
349+
350+
xpriv.Zero()
351+
352+
if !isZeroKey(xpriv) {
353+
st.Fatalf("private key %s should have been zeroed",
354+
chain.ExtendedPrivateKey)
355+
}
356+
}
357+
})
358+
}
359+
360+
// edge case as nil private key
361+
var xpriv *bip32.PrivateKey
362+
xpriv.Zero()
363+
if !isZeroKey(xpriv) {
364+
t.Fatal("nil private key should be treated as zeroed already")
365+
}
366+
}

public_key.go

+14
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ func (pub *PublicKey) String() string {
160160
return base58.CheckEncodeX(str, pub.Version...)
161161
}
162162

163+
// Zero implements ExtendedKey
164+
func (pub *PublicKey) Zero() {
165+
if nil == pub {
166+
return
167+
}
168+
169+
Zero(pub.ChainCode)
170+
pub.ChildIndex = 0
171+
Zero(pub.Data)
172+
pub.Level = 0
173+
Zero(pub.ParentFP)
174+
Zero(pub.Version)
175+
}
176+
163177
// NewPublicKey returns a new instance of an extended public key with the
164178
// given fields. No error checking is performed here as it's only intended to
165179
// be a convenience method used to create a populated struct. This function

public_key_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,41 @@ func TestPublicKey_String_Zero(t *testing.T) {
261261
t.Fatalf("invalid string: got %s, expect %s", got, expect)
262262
}
263263
}
264+
265+
func TestPublicKey_Zero(t *testing.T) {
266+
var testCases []bip32.Goldie
267+
ReadGoldenJSON(t, bip32.GoldenName, &testCases)
268+
269+
isZero := func(data []byte) bool {
270+
for _, v := range data {
271+
if 0 != v {
272+
return false
273+
}
274+
}
275+
276+
return true
277+
}
278+
279+
isZeroPK := func(xpub *bip32.PublicKey) bool {
280+
return nil == xpub || (isZero(xpub.ChainCode) && 0 == xpub.ChildIndex &&
281+
isZero(xpub.Data) && 0 == xpub.Level && isZero(xpub.ParentFP) &&
282+
isZero(xpub.Version))
283+
}
284+
285+
for i, c := range testCases {
286+
for j, chain := range c.Chains {
287+
xpub, _ := bip32.ParsePublicKey(chain.ExtendedPublicKey)
288+
xpub.Zero()
289+
if !isZeroPK(xpub) {
290+
t.Fatalf("#(%d,%d): public key %s isn't zeroed fully", i, j,
291+
chain.ExtendedPublicKey)
292+
}
293+
}
294+
}
295+
296+
var xpub *bip32.PublicKey
297+
xpub.Zero()
298+
if !isZeroPK(xpub) {
299+
t.Fatal("nil public key should remains zero")
300+
}
301+
}

utils.go

+7
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,10 @@ func HardenIndex(i uint32) uint32 {
77
/* if i > math.MaxUint32-HardenedKeyStart { } */
88
return i + HardenedKeyStart
99
}
10+
11+
// Zero clears all data items to 0
12+
func Zero(data []byte) {
13+
for i := range data {
14+
data[i] = 0
15+
}
16+
}

0 commit comments

Comments
 (0)