diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index cdb82dc..8b488fe 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [10.x, 12.x, 14.x, 16.x, 18.x, latest] + node-version: [14.x, 16.x, 18.x, latest] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} diff --git a/.gitignore b/.gitignore index 642271f..cddcd66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ -coverage/ \ No newline at end of file +coverage/ + +yarn.lock \ No newline at end of file diff --git a/lib/hdkey.js b/lib/hdkey.js index 9a472b6..57f8c43 100644 --- a/lib/hdkey.js +++ b/lib/hdkey.js @@ -3,7 +3,7 @@ var Buffer = require('safe-buffer').Buffer var crypto = require('crypto') var bs58check = require('bs58check') var RIPEMD160 = require('ripemd160') -var secp256k1 = require('@exodus/secp256k1') +var secp256k1 = require('@exodus/bitcoinerlab-secp256k1') var MASTER_SECRET = Buffer.from('Bitcoin seed', 'utf8') var HARDENED_OFFSET = 0x80000000 @@ -33,10 +33,10 @@ Object.defineProperty(HDKey.prototype, 'privateKey', { }, set: function (value) { assert.equal(value.length, 32, 'Private key must be 32 bytes.') - assert(secp256k1.privateKeyVerify(value) === true, 'Invalid private key') + assert(secp256k1.isPrivate(value) === true, 'Invalid private key') this._privateKey = value - this._publicKey = Buffer.from(secp256k1.publicKeyCreate(value, true)) + this._publicKey = Buffer.from(secp256k1.pointFromScalar(value, true)) this._identifier = hash160(this.publicKey) this._fingerprint = this._identifier.slice(0, 4).readUInt32BE(0) } @@ -55,9 +55,9 @@ Object.defineProperty(HDKey.prototype, 'publicKey', { }, set: function (value) { assert(value.length === 33 || value.length === 65, 'Public key must be 33 or 65 bytes.') - assert(secp256k1.publicKeyVerify(value) === true, 'Invalid public key') + assert(secp256k1.isPoint(value) === true, 'Invalid public key') // force compressed point (performs public key verification) - const publicKey = (value.length === 65) ? secp256k1.publicKeyConvert(value, true) : value + const publicKey = (value.length === 65) ? secp256k1.pointCompress(value, true) : value setPublicKey(this, publicKey) } }) @@ -131,7 +131,7 @@ HDKey.prototype.deriveChild = function (index) { if (this.privateKey) { // ki = parse256(IL) + kpar (mod n) try { - hd.privateKey = Buffer.from(secp256k1.privateKeyTweakAdd(Buffer.from(this.privateKey), IL)) + hd.privateKey = Buffer.from(secp256k1.privateAdd(Buffer.from(this.privateKey), IL)) // throw if IL >= n || (privateKey + IL) === 0 } catch (err) { // In case parse256(IL) >= n or ki == 0, one should proceed with the next value for i @@ -142,7 +142,7 @@ HDKey.prototype.deriveChild = function (index) { // Ki = point(parse256(IL)) + Kpar // = G*IL + Kpar try { - hd.publicKey = Buffer.from(secp256k1.publicKeyTweakAdd(Buffer.from(this.publicKey), IL, true)) + hd.publicKey = Buffer.from(secp256k1.pointAddScalar(Buffer.from(this.publicKey), IL, true)) // throw if IL >= n || (g**IL + publicKey) is infinity } catch (err) { // In case parse256(IL) >= n or Ki is the point at infinity, one should proceed with the next value for i @@ -159,14 +159,14 @@ HDKey.prototype.deriveChild = function (index) { } HDKey.prototype.sign = function (hash) { - return Buffer.from(secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(this.privateKey)).signature) + return Buffer.from(secp256k1.sign(Uint8Array.from(hash), Uint8Array.from(this.privateKey))) } HDKey.prototype.verify = function (hash, signature) { - return secp256k1.ecdsaVerify( - Uint8Array.from(signature), + return secp256k1.verify( Uint8Array.from(hash), - Uint8Array.from(this.publicKey) + Uint8Array.from(this.publicKey), + Uint8Array.from(signature) ) } diff --git a/package.json b/package.json index 4219835..4a5044d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "lib/hdkey.js", "repository": { "type": "git", - "url": "git://github.com/cryptocoinjs/hdkey" + "url": "git://github.com/ExodusMovement/hdkey" }, "license": "MIT", "keywords": [ @@ -18,9 +18,9 @@ "crypto" ], "bugs": { - "url": "https://github.com/cryptocoinjs/hdkey/issues" + "url": "https://github.com/ExodusMovement/hdkey/issues" }, - "homepage": "https://github.com/cryptocoinjs/hdkey", + "homepage": "https://github.com/ExodusMovement/hdkey", "files": [], "devDependencies": { "bigi": "^1.1.0", @@ -34,10 +34,10 @@ "standard": "^7.1.1" }, "dependencies": { + "@exodus/bitcoinerlab-secp256k1": "^1.0.5-exodus.1", "bs58check": "^2.1.2", "ripemd160": "^2.0.2", - "safe-buffer": "^5.1.1", - "@exodus/secp256k1": "^4.0.2-exodus.0" + "safe-buffer": "^5.1.1" }, "scripts": { "lint": "standard", diff --git a/test/fixtures/hdkey.json b/test/fixtures/hdkey.json index cded41b..8411170 100644 --- a/test/fixtures/hdkey.json +++ b/test/fixtures/hdkey.json @@ -71,9 +71,99 @@ "path": "m/0/2147483647'/1/2147483646'/2", "public": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", "private": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/0'/0'", + "public": "xpub6Bj78LtBYv9W6wjeub9Uvivo7dko9XYHXpgaDXgbFDY3dTSkn8HsZr1ps5HRgFixDuqrED1m7VWSoyipD3YMFHaFaFAmrqKG36uQTSZU9rg", + "private": "xprv9xjkiqMHiYbCtTfBoZcUZaz4ZbvJk4pSAbkyR9Gygt14kf7cEayd23hM1oLtRwL8Kdcffs9rnVX9qdMnWTtCCGMDsLY2xvmuNNCv9BbtqyN" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/283'/0'", + "public": "xpub6D5jSGbfwpcKdc9TSdP8EwbpReEFUiZRBoCEZqqR71q9WWZPLtNXz75BNxFa5NcWsPpSXGbZCU7q9v5GcW29MNAH2ghE9F7FHsXzCXXw8co", + "private": "xprv9z6P2m4n7T42R84zLbr7sof5scPm5FqZpaGdmTRoYgJAdiEEoM4HSJkhXhe6Awn4X8f8XXfLs6Wv2NTX7jvmoiiLAU2yHBVYBY3e1iRweYW" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/84'/0'/0'", + "public": "xpub6CxednjUJboDP6B7UfFogysznCuw14LWkcrijYGACCw1jbou4GpZyPGmNHCsCRS3Dp9Upvr1mMtFVmgbQDXkn9jWNGJaqXqdcLD1ZoDpT3R", + "private": "xprv9yyJEHCaUEEvAc6eNdioKqwGEB5SbbcfPPw7w9rYdsQ2roUkWjWKRaxHX1rEzQy4TaWzVDoKoWt4AmJQ9fHiwfZuKMVipGtVCwt644QvR2G" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/1815'/0'", + "public": "xpub6CxN8G7WoHM6rswed4DREuEydKybriCUwWwaQoUaf9LUQPG8FKKtj32TWVjQ7VL37kL6uKimbdf4Wn8z8PXHrupjZkQ8chSRHXDebEJtLR2", + "private": "xprv9yy1ikacxunoePsBX2gQsmJF5J97TFUdaJ1ycR4y6ooVXavyhn1eBEhyfEUpKENkNy4aWr6rEcMECHLRbLsCwZS95nccsaLxmxD2p23JWJt" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/118'/0'", + "public": "xpub6CHiu9KznSSh9uVwbmyMdZ5Z5TUurD9jdxppNu54fca5YufXXrsTGqJjp5UPsxSVyedWMBKAzRowJxxmYLnpyAD1AZD3MVzK2fdfmA1e2zv", + "private": "xprv9yJNVdo6x4tPwRRUVkSMGR8pXReRSkRtGjuDaWfT7H36g7LNzKZCj2zFxnejhdFqSt64V2aCHDhkEPJKsn7jDFXb8FGcypB3bttwoWJixEt" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/60'/0'", + "public": "xpub6Bw1W7bAwU2VzMqocoW1vr8RBFiYfYMaHhwp76mujJhJNdQEGckwzZv9D2PMBFytXwJy8DaGQjuneSifZU2PH9yyCXEf4PjRgSNf9viiGvr", + "private": "xprv9xwf6c4H76UCmsmLWmy1ZiBgdDt4G5divV2DJiNJAyAKVq55j5ShSmbfMj3WRcT3QWVJcdbDSKmHgVaAVFaxCvMrEBQTjWgNJXL1iqF4A89" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/144'/0'", + "public": "xpub6CxYodsM6fZZFbB2gFo2pJgo692oYdnppCfxgQGY1jGULSCc15uPzKsSkf1xqHXsaYFHk2W9oucvnfR58Kij89Tx2F2tgJ91fJqVZ46FEuh", + "private": "xprv9yyCQ8LTGJ1G376ZaEG2TAk4Y7CK9B4ySykMt1rvTPjVTdsTTYb9SXYxuP7J4Wo6tTo1ByUDwueQWxMDfBqHiWjweMmCLbgaAuSjiqJWXgL" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/501'/0'", + "public": "xpub6CAAppkfPWX3Zb27ptHSQ6tvgPRXJeHQCrKdgvPnGPBhctUUAhaFnJev9QJ2EE6qugzHuMFdjYTs4wdcUX7KCiiNZWSa4kNrcJ5tzWgBadr", + "private": "xprv9yApRKDmZ8xkM6weirkS2xxC8Mb2uBZYqdQ2tXzAi3eik69KdAG1EWLSJAkPRbenDcGTfMDUdNDwEtBP1T7W1Vh72KKjoKpadiBhBjW4VWe" + }, + { + "seed": "69afbf0608755b3480ca7314c145695c64f973c988790eabb165d19809c7991acecfe4518b1456f274e171d496ddd044942278577b6efcdb69a6374d5341ea0a", + "path": "m/44'/1729'/0'", + "public": "xpub6DHkhd3Q3HCFUp5ZcrPprp76nCeZSvebWbro7SWYPFhnVrYXj555kE7tfkRgf8cgYyUDLvbyMHmrrnVjLrTH5P8vvCRJRjgGBnqpytBC4Gw", + "private": "xprv9zJQJ7WWCudxGL16WprpVgANEAp53Tvk9NwCK46vpvAod4DPBXkqCRoQpSLthvPpE5f1r2a3Tna9HRGcdJTgExtZtS52vdMRutCx5qDEfrS" + } + ], + "rawHex": [ + { + "private": "3e798fb45904e429cf0eba4868453b4103eeb681783f10d4a9380e33fed750d7", + "public": "02b64d1f42c9462d9063078de5e0bbea741af8999951809f5c7dab319fa78552d0" + }, + { + "private": "bfe85cee8a0ad77d9debafe2180816d90606d4f577a0684c0546993e2f6a6112", + "public": "0288c3e9da9327cf50c7b4fb3c6372e71b70b06a68951aad7051990dce25d8ce3c" + }, + { + "private": "71fc2b9bd6a02bd9b50f3e8ebbb29cc4953f9b589d3d97f93ca60020180aa534", + "public": "029ccdf213a7d41f1753fff3097db57df179f17c24f175737ac09c3708dd41b4bf" + }, + { + "private": "dde956d3257a0043628b862c44348b5621be7f0d7ea7f5a0660070017b8836f4", + "public": "02fb034a5f00248ed1563b732fb2d172bd5d896e015d67762f8689a5d6c8521fcf" + }, + { + "private": "df7627ee2889e5b16e0a879e6f4ee0d28d83c688bcc8ca7702443cc829659c19", + "public": "03cb5c6d70e0ba5e4ca4fb8fb05ad5dc3c207cacad347e932d6db7679642f1e2f6" + }, + { + "private": "abbe14f4b21ddac490c35996d47a62df429f6e6609e2f3db3fd0c658d2dad179", + "public": "03dc2cb5d8f479f4ceebf0b46f96b2306a06b63f086f5bea95778a9b9d9ca964f2" + }, + { + "private": "4cc8f8f92a26fc31312b056af3931326586251bd55f23868698d568eb6934c49", + "public": "02c05d64323cb88e9b93564a14218e294002de39f7b946763c556dba7b86ebfda7" + }, + { + "private": "da5711e9c9cdac2f626115d1c53e2123cfc92a486f476212cfcccd01d3675744", + "public": "0216c47fc709286f054a420f1a087858d9b17cc769bdd5938b235f09b2670c2845" + }, + { + "private": "5e09e65124c80d2f5cab61d120af5552454373789119e0030ed7c07665f2aff2", + "public": "03efff78835d6a81bbce6cce42b1bbe6b0bec429aa22500f7f649efa4eba984795" } ], - "invalid": [ - - ] -} + "invalid": [] +} \ No newline at end of file diff --git a/test/hdkey.test.js b/test/hdkey.test.js index 6c87505..0bd815d 100644 --- a/test/hdkey.test.js +++ b/test/hdkey.test.js @@ -66,6 +66,14 @@ describe('hdkey', function () { hdkey.publicKey = pub }) + it('should not throw if key is 33 bytes (compressed)', function () { + var priv = Buffer.from(fixtures.rawHex[0].private, 'hex') + var pub = curve.G.multiply(BigInteger.fromBuffer(priv)).getEncoded(true) + assert.equal(pub.length, 33) + var hdkey = new HDKey() + hdkey.publicKey = pub + }) + it('should not throw if key is 65 bytes (not compressed)', function () { var priv = secureRandom.randomBuffer(32) var pub = curve.G.multiply(BigInteger.fromBuffer(priv)).getEncoded(false) @@ -73,6 +81,22 @@ describe('hdkey', function () { var hdkey = new HDKey() hdkey.publicKey = pub }) + + it('should not throw if key is 65 bytes (not compressed)', function () { + var priv = Buffer.from(fixtures.rawHex[1].private, 'hex') + var pub = curve.G.multiply(BigInteger.fromBuffer(priv)).getEncoded(false) + assert.equal(pub.length, 65) + var hdkey = new HDKey() + hdkey.publicKey = pub + }) + + fixtures.rawHex.forEach(function (f) { + it('should convert to correct public key', function () { + var hdkey = new HDKey() + hdkey.privateKey = Buffer.from(f.private, 'hex') + assert.equal(hdkey.publicKey.toString('hex'), f.public) + }) + }) }) describe('+ fromExtendedKey()', function () { @@ -145,10 +169,10 @@ describe('hdkey', function () { assert.throws(function () { hdkey.verify(Buffer.alloc(99), a) - }, /message.*length/) + }, /Expected Scalar/) assert.throws(function () { hdkey.verify(ma, Buffer.alloc(99)) - }, /signature.*length/) + }, /Expected Signature/) }) })