From 1dbf389ceb35f2eb6f11535c159a172c2a120b16 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Mon, 19 May 2014 21:24:48 -0500 Subject: [PATCH] Optimize public key serialization. This change removes the internal pad function in favor a more opimized paddedAppend function. Unlike pad, which would always alloate a new slice of the desired size and copy the bytes into it, paddedAppend only appends the leading padding when necesary, and uses the builtin append to copy the remaining source bytes. pad was also used in combination with another call to the builtin copy func to copy into a zeroed byte slice. As the slice is now created using make with an initial length of zero, this copy can also be removed. As confirmed by poking the bytes with the unsafe package, gc does not zero array elements between the len and cap when allocating slices with make(). In combination with the paddedAppend func, this results in only a single copy of each byte, with no unnecssary zeroing, when creating the serialized pubkeys. This has not been tested with other Go compilers (namely, gccgo and llgo), but the new behavior is still functionally correct regardless of compiler optimizations. The TestPad function has been removed as the pad func it tested has likewise been removed. ok @davecgh --- internal_test.go | 5 ---- pubkey.go | 51 ++++++++++++++++++----------------- pubkey_test.go | 68 ----------------------------------------------- test_coverage.txt | 45 ++++++++++++++++--------------- 4 files changed, 50 insertions(+), 119 deletions(-) diff --git a/internal_test.go b/internal_test.go index 9e45f732..4f71bbb2 100644 --- a/internal_test.go +++ b/internal_test.go @@ -30,11 +30,6 @@ func (f *fieldVal) TstSetRawInts(raw [10]uint32) *fieldVal { return f } -// TstPad makes the internal pad function available to the test package. -func TstPad(size int, b []byte) []byte { - return pad(size, b) -} - // TstFieldJacobianToBigAffine makes the internal fieldJacobianToBigAffine // function available to the test package. func (curve *KoblitzCurve) TstFieldJacobianToBigAffine(x, y, z *fieldVal) (*big.Int, *big.Int) { diff --git a/pubkey.go b/pubkey.go index bf575c3f..cf0da666 100644 --- a/pubkey.go +++ b/pubkey.go @@ -11,6 +11,13 @@ import ( "math/big" ) +// These constants define the lengths of serialized public keys. +const ( + PubKeyBytesLenCompressed = 33 + PubKeyBytesLenUncompressed = 65 + PubKeyBytesLenHybrid = 65 +) + func isOdd(a *big.Int) bool { return a.Bit(0) == 1 } @@ -63,7 +70,7 @@ func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err err format &= ^byte(0x1) switch len(pubKeyStr) { - case 65: // normal public key + case PubKeyBytesLenUncompressed: if format != pubkeyUncompressed && format != pubkeyHybrid { return nil, fmt.Errorf("invalid magic in pubkey str: "+ "%d", pubKeyStr[0]) @@ -75,7 +82,7 @@ func ParsePubKey(pubKeyStr []byte, curve *KoblitzCurve) (key *PublicKey, err err if format == pubkeyHybrid && ybit != isOdd(pubkey.Y) { return nil, fmt.Errorf("ybit doesn't match oddness") } - case 33: // compressed public key + case PubKeyBytesLenCompressed: // format is 0x2 | solution, // solution determines which solution of the curve we use. /// y^2 = x^3 + Curve.B @@ -117,45 +124,41 @@ func (p *PublicKey) ToECDSA() *ecdsa.PublicKey { // SerializeUncompressed serializes a public key in a 65-byte uncompressed // format. func (p *PublicKey) SerializeUncompressed() []byte { - b := make([]byte, 65) - b[0] = pubkeyUncompressed - copy(b[1:33], pad(32, p.X.Bytes())) - copy(b[33:], pad(32, p.Y.Bytes())) - return b + b := make([]byte, 0, PubKeyBytesLenUncompressed) + b = append(b, pubkeyUncompressed) + b = paddedAppend(32, b, p.X.Bytes()) + return paddedAppend(32, b, p.Y.Bytes()) } // SerializeCompressed serializes a public key in a 33-byte compressed format. func (p *PublicKey) SerializeCompressed() []byte { - b := make([]byte, 33) + b := make([]byte, 0, PubKeyBytesLenCompressed) format := pubkeyCompressed if isOdd(p.Y) { format |= 0x1 } - b[0] = format - copy(b[1:33], pad(32, p.X.Bytes())) - return b + b = append(b, format) + return paddedAppend(32, b, p.X.Bytes()) } // SerializeHybrid serializes a public key in a 65-byte hybrid format. func (p *PublicKey) SerializeHybrid() []byte { - b := make([]byte, 65) + b := make([]byte, 0, PubKeyBytesLenHybrid) format := pubkeyHybrid if isOdd(p.Y) { format |= 0x1 } - b[0] = format - copy(b[1:33], pad(32, p.X.Bytes())) - copy(b[33:], pad(32, p.Y.Bytes())) - return b + b = append(b, format) + b = paddedAppend(32, b, p.X.Bytes()) + return paddedAppend(32, b, p.Y.Bytes()) } -func pad(size int, b []byte) []byte { - // Prevent a possible panic if the input exceeds the expected size. - if len(b) > size { - size = len(b) +// paddedAppend appends the src byte slice to dst, returning the new slice. +// If the length of the source is smaller than the passed size, leading zero +// bytes are appended to the dst slice before appending src. +func paddedAppend(size uint, dst, src []byte) []byte { + for i := 0; i < int(size)-len(src); i++ { + dst = append(dst, 0) } - - p := make([]byte, size) - copy(p[size-len(b):], b) - return p + return append(dst, src...) } diff --git a/pubkey_test.go b/pubkey_test.go index 5ea36eb6..44ed016c 100644 --- a/pubkey_test.go +++ b/pubkey_test.go @@ -262,71 +262,3 @@ func TestPubKeys(t *testing.T) { } } } - -func TestPad(t *testing.T) { - tests := []struct { - name string // Short name to describe the test. - inSize int // The size to pad to. - inBytes []byte // The input bytes to pad. - expected []byte // The expected padded result. - }{ - { - name: "same size - no padding needed", - inSize: 32, - inBytes: []byte{ - 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, - 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, - 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, - 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, - }, - expected: []byte{ - 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, - 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, - 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, - 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, - }, - }, - { - name: "short 1 byte", - inSize: 32, - inBytes: []byte{ - 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, - 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, - 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, - 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, - }, - expected: []byte{ - 0x00, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, - 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, - 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, - 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, - }, - }, - { - name: "more bytes than pad size", - inSize: 31, - inBytes: []byte{ - 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, - 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, - 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, - 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, - }, - expected: []byte{ - 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, 0xa5, - 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, - 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, - 0x21, 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, - }, - }, - } - - t.Logf("Running %d tests.", len(tests)) - for i, test := range tests { - result := btcec.TstPad(test.inSize, test.inBytes) - if !bytes.Equal(result, test.expected) { - t.Errorf("pad #%d (%s) unexpected result:\n"+ - "got: %x\nwant: %x", i, test.name, result, - test.expected) - } - } -} diff --git a/test_coverage.txt b/test_coverage.txt index ed5c99f3..6b492e72 100644 --- a/test_coverage.txt +++ b/test_coverage.txt @@ -24,42 +24,43 @@ github.com/conformal/btcec/btcec.go KoblitzCurve.Add 100.00% (10/10) github.com/conformal/btcec/field.go fieldVal.Zero 100.00% (10/10) github.com/conformal/btcec/btcec.go KoblitzCurve.doubleJacobian 100.00% (9/9) github.com/conformal/btcec/btcec.go initS256 100.00% (9/9) -github.com/conformal/btcec/pubkey.go PublicKey.SerializeHybrid 100.00% (8/8) github.com/conformal/btcec/signature.go canonicalizeInt 100.00% (8/8) +github.com/conformal/btcec/pubkey.go PublicKey.SerializeHybrid 100.00% (7/7) github.com/conformal/btcec/btcec.go KoblitzCurve.Double 100.00% (7/7) -github.com/conformal/btcec/pubkey.go PublicKey.SerializeCompressed 100.00% (7/7) -github.com/conformal/btcec/privkey.go PrivKeyFromBytes 100.00% (6/6) -github.com/conformal/btcec/pubkey.go pad 100.00% (5/5) +github.com/conformal/btcec/pubkey.go PublicKey.SerializeCompressed 100.00% (6/6) github.com/conformal/btcec/field.go fieldVal.SetByteSlice 100.00% (5/5) -github.com/conformal/btcec/pubkey.go PublicKey.SerializeUncompressed 100.00% (5/5) -github.com/conformal/btcec/signature.go canonicalPadding 100.00% (4/4) github.com/conformal/btcec/btcec.go KoblitzCurve.bigAffineToField 100.00% (4/4) -github.com/conformal/btcec/field.go fieldVal.SetHex 100.00% (4/4) github.com/conformal/btcec/btcec.go KoblitzCurve.IsOnCurve 100.00% (4/4) -github.com/conformal/btcec/field.go fieldVal.SetInt 100.00% (3/3) +github.com/conformal/btcec/pubkey.go PublicKey.SerializeUncompressed 100.00% (4/4) +github.com/conformal/btcec/field.go fieldVal.SetHex 100.00% (4/4) +github.com/conformal/btcec/signature.go canonicalPadding 100.00% (4/4) +github.com/conformal/btcec/privkey.go PrivKeyFromBytes 100.00% (3/3) github.com/conformal/btcec/field.go fieldVal.Bytes 100.00% (3/3) -github.com/conformal/btcec/field.go fieldVal.String 100.00% (2/2) -github.com/conformal/btcec/field.go fieldVal.Equals 100.00% (2/2) -github.com/conformal/btcec/field.go fieldVal.IsZero 100.00% (2/2) -github.com/conformal/btcec/btcec.go S256 100.00% (2/2) -github.com/conformal/btcec/field.go fieldVal.AddInt 100.00% (2/2) +github.com/conformal/btcec/field.go fieldVal.SetInt 100.00% (3/3) +github.com/conformal/btcec/pubkey.go paddedAppend 100.00% (3/3) github.com/conformal/btcec/field.go fieldVal.Set 100.00% (2/2) +github.com/conformal/btcec/field.go fieldVal.String 100.00% (2/2) +github.com/conformal/btcec/btcec.go S256 100.00% (2/2) +github.com/conformal/btcec/field.go fieldVal.IsZero 100.00% (2/2) +github.com/conformal/btcec/field.go fieldVal.Equals 100.00% (2/2) +github.com/conformal/btcec/field.go fieldVal.AddInt 100.00% (2/2) github.com/conformal/btcec/pubkey.go isOdd 100.00% (1/1) -github.com/conformal/btcec/field.go fieldVal.Negate 100.00% (1/1) -github.com/conformal/btcec/btcec.go initAll 100.00% (1/1) -github.com/conformal/btcec/signature.go ParseSignature 100.00% (1/1) -github.com/conformal/btcec/signature.go ParseDERSignature 100.00% (1/1) -github.com/conformal/btcec/field.go fieldVal.Mul 100.00% (1/1) -github.com/conformal/btcec/btcec.go KoblitzCurve.Params 100.00% (1/1) -github.com/conformal/btcec/field.go fieldVal.Square 100.00% (1/1) github.com/conformal/btcec/field.go fieldVal.IsOdd 100.00% (1/1) github.com/conformal/btcec/btcec.go KoblitzCurve.ScalarBaseMult 100.00% (1/1) github.com/conformal/btcec/btcec.go KoblitzCurve.QPlus1Div4 100.00% (1/1) +github.com/conformal/btcec/signature.go ParseSignature 100.00% (1/1) +github.com/conformal/btcec/signature.go ParseDERSignature 100.00% (1/1) +github.com/conformal/btcec/field.go fieldVal.Negate 100.00% (1/1) +github.com/conformal/btcec/btcec.go initAll 100.00% (1/1) +github.com/conformal/btcec/field.go fieldVal.Square 100.00% (1/1) +github.com/conformal/btcec/btcec.go KoblitzCurve.Params 100.00% (1/1) +github.com/conformal/btcec/field.go fieldVal.Mul 100.00% (1/1) github.com/conformal/btcec/pubkey.go ParsePubKey 92.86% (26/28) +github.com/conformal/btcec/signature.go SignCompact 90.91% (20/22) github.com/conformal/btcec/pubkey.go decompressPoint 88.89% (8/9) github.com/conformal/btcec/signature.go recoverKeyFromSignature 86.96% (20/23) -github.com/conformal/btcec/signature.go SignCompact 86.36% (19/22) github.com/conformal/btcec/signature.go RecoverCompact 77.78% (7/9) github.com/conformal/btcec/signature.go hashToInt 77.78% (7/9) -github.com/conformal/btcec ------------------------------------- 98.59% (909/922) +github.com/conformal/btcec/pubkey.go PublicKey.ToECDSA 0.00% (0/1) +github.com/conformal/btcec ------------------------------------- 98.58% (902/915)