From 0a075573ed88aa6f64e87d0f50fdad39a3709a19 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Thu, 28 Nov 2013 21:01:55 +0200 Subject: [PATCH 1/6] Fix signing with compressed keys calcPubkeyRecoveryParam always assumed a non-compressed key, and was comparing the address generated from a non-compressed public key against the original address generated from the compressed public key. This commit fixes it by passing the entire pubkey object, and configuring the generated address to use the same compressed setting as the original one. --- src/ecdsa.js | 4 +++- src/message.js | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ecdsa.js b/src/ecdsa.js index 1aa15ed..e65b8e1 100644 --- a/src/ecdsa.js +++ b/src/ecdsa.js @@ -275,10 +275,12 @@ var ECDSA = { * This function simply tries all four cases and returns the value * that resulted in a successful pubkey recovery. */ - calcPubkeyRecoveryParam: function (address, r, s, hash) + calcPubkeyRecoveryParam: function (origPubkey, r, s, hash) { + var address = origPubkey.getBitcoinAddress().toString(); for (var i = 0; i < 4; i++) { var pubkey = ECDSA.recoverPubKey(r, s, hash, i); + pubkey.compressed = origPubkey.compressed; if (pubkey.getBitcoinAddress().toString() == address) { return i; } diff --git a/src/message.js b/src/message.js index abc2da5..ef29117 100644 --- a/src/message.js +++ b/src/message.js @@ -34,8 +34,7 @@ Message.signMessage = function (key, message, compressed) { var obj = ecdsa.parseSig(sig); - var address = key.getBitcoinAddress().toString(); - var i = ecdsa.calcPubkeyRecoveryParam(address, obj.r, obj.s, hash); + var i = ecdsa.calcPubkeyRecoveryParam(key, obj.r, obj.s, hash); i += 27; if (compressed) i += 4; From 9fd46c22fee6540321752ccee18a595abb7ea79d Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 12 Dec 2013 11:13:35 +0100 Subject: [PATCH 2/6] missing modulo operation. --- src/bip32.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bip32.js b/src/bip32.js index 36a0d2f..55818b1 100644 --- a/src/bip32.js +++ b/src/bip32.js @@ -83,7 +83,7 @@ BIP32key.prototype.ckd = function(i) { if (this.type == 'priv') { Ikey = Bitcoin.BigInteger.fromByteArrayUnsigned(I.slice(0,32)) - newkey = new key(this.key.priv.add(Ikey)) + newkey = new key(this.key.priv.add(Ikey).mod(ecparams.getN())) newkey.compressed = true fingerprint = util.sha256ripe160(this.key.getPub()).slice(0,4) } From 740a0fcb7d5dd0e592a531f24394ef3e38c63354 Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 12 Dec 2013 11:14:27 +0100 Subject: [PATCH 3/6] test vectors for bip32. --- test/bip32.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/bip32.js diff --git a/test/bip32.js b/test/bip32.js new file mode 100644 index 0000000..0c0f82a --- /dev/null +++ b/test/bip32.js @@ -0,0 +1,96 @@ +// Tests from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors + +var assert = require('assert'); +var BIP32key = require('..').BIP32key; + +var hexToBytes = require('../').convert.hexToBytes; +var bytesToString = require('../').convert.bytesToString; + +function checkKey(key, extPriv, extPub) { + assert.equal(key.serialize(), extPriv); + assert.equal(key.getPub().serialize(), extPub); +} + +test("BIP32 Test vector 1", function () { + var seed_str = '000102030405060708090a0b0c0d0e0f'; + var seed = bytesToString(hexToBytes(seed_str)); + + var key = new BIP32key(seed); + + checkKey(key, + "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", + "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"); + + var branch = key.ckd(0+BIP32_PRIME); + + checkKey(branch, + "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", + "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"); + + var branch2 = branch.ckd(1); + + checkKey(branch2, + "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", + "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"); + + var branch3 = branch2.ckd(2+BIP32_PRIME); + + checkKey(branch3, + "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", + "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"); + + var branch4 = branch3.ckd(2); + + checkKey(branch4, + "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", + "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"); + + var branch5 = branch4.ckd(1000000000); + + checkKey(branch5, + "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", + "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"); + +}); + +test("BIP32 Test vector 2", function () { + var seed_str = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542'; + var seed = bytesToString(hexToBytes(seed_str)); + + var key = new BIP32key(seed); + + checkKey(key, + "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", + "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"); + + var branch = key.ckd(0); + + checkKey(branch, + "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", + "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"); + + var branch2 = branch.ckd(2147483647+BIP32_PRIME); + + checkKey(branch2, + "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", + "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"); + + var branch3 = branch2.ckd(1); + + checkKey(branch3, + "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", + "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"); + + + var branch4 = branch3.ckd(2147483646+BIP32_PRIME); + + checkKey(branch4, + "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", + "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"); + + var branch5 = branch4.ckd(2); + + checkKey(branch5, + "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", + "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"); +}); From 26e5914be26e0f1a3f70e8188db95706f31a6a2f Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 12 Dec 2013 17:19:31 +0100 Subject: [PATCH 4/6] fix in pubkey deserialization code. --- src/bip32.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bip32.js b/src/bip32.js index 55818b1..9ce7b7e 100644 --- a/src/bip32.js +++ b/src/bip32.js @@ -48,7 +48,7 @@ BIP32key.prototype.deserialize = function(str) { fingerprint: bytes.slice(5,9), i: util.bytesToNum(bytes.slice(9,13).reverse()), chaincode: bytes.slice(13,45), - key: new key(type == 'priv' ? bytes.slice(46,78).concat([1]) : bytes.slice(45,78)) + key: type == 'priv' ? new key(bytes.slice(46,78).concat([1])) : bytes.slice(45,78) }) } From 14803d053858bd153f5b5c21f6e8c17f95d1bf7e Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 12 Dec 2013 17:25:29 +0100 Subject: [PATCH 5/6] fix check for public key when making a private derivation. --- src/bip32.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bip32.js b/src/bip32.js index 9ce7b7e..8a4dc1c 100644 --- a/src/bip32.js +++ b/src/bip32.js @@ -74,7 +74,7 @@ BIP32key.prototype.ckd = function(i) { else pub = this.key if (i >= 2147483648) { - if (this.priv) throw new Error("Can't do private derivation on public key!") + if (!priv) throw new Error("Can't do private derivation on public key!") blob = [0].concat(priv.slice(0,32),util.numToBytes(i,4).reverse()) } else blob = pub.concat(util.numToBytes(i,4).reverse()) From f9cbcbeb2bb9c84abfbdf41817355ce640d678de Mon Sep 17 00:00:00 2001 From: Pablo Martin Date: Thu, 12 Dec 2013 17:46:39 +0100 Subject: [PATCH 6/6] missing BIP32_PRIME definition in bip32 tests. --- test/bip32.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/bip32.js b/test/bip32.js index 0c0f82a..2ccee37 100644 --- a/test/bip32.js +++ b/test/bip32.js @@ -6,6 +6,8 @@ var BIP32key = require('..').BIP32key; var hexToBytes = require('../').convert.hexToBytes; var bytesToString = require('../').convert.bytesToString; +var BIP32_PRIME = 0x80000000; + function checkKey(key, extPriv, extPub) { assert.equal(key.serialize(), extPriv); assert.equal(key.getPub().serialize(), extPub);