|
| 1 | +let Buffer = require('safe-buffer').Buffer |
| 2 | +let bs58check = require('bs58check') |
| 3 | +let crypto = require('./crypto') |
| 4 | +let ecc = require('tiny-secp256k1') |
| 5 | +let typeforce = require('typeforce') |
| 6 | +let wif = require('wif') |
| 7 | + |
| 8 | +let NETWORK_TYPE = typeforce.compile({ |
| 9 | + wif: typeforce.UInt8, |
| 10 | + bip32: { |
| 11 | + public: typeforce.UInt32, |
| 12 | + private: typeforce.UInt32 |
| 13 | + } |
| 14 | +}) |
| 15 | + |
| 16 | +let BITCOIN = { |
| 17 | + bip32: { |
| 18 | + public: 0x0488b21e, |
| 19 | + private: 0x0488ade4 |
| 20 | + }, |
| 21 | + wif: 0x80 |
| 22 | +} |
| 23 | + |
| 24 | +function BIP32 (d, Q, chainCode, network) { |
| 25 | + this.d = d || null |
| 26 | + this.Q = Q || ecc.pointFromScalar(d, true) |
| 27 | + if (this.Q === null) throw new TypeError('Bad public key') |
| 28 | + |
| 29 | + this.chainCode = chainCode |
| 30 | + this.depth = 0 |
| 31 | + this.index = 0 |
| 32 | + this.network = network |
| 33 | + this.parentFingerprint = 0x00000000 |
| 34 | +} |
| 35 | + |
| 36 | +function fromSeed (seed, network) { |
| 37 | + typeforce(typeforce.Buffer, seed) |
| 38 | + if (seed.length < 16) throw new TypeError('Seed should be at least 128 bits') |
| 39 | + if (seed.length > 64) throw new TypeError('Seed should be at most 512 bits') |
| 40 | + if (network) typeforce(NETWORK_TYPE, network) |
| 41 | + network = network || BITCOIN |
| 42 | + |
| 43 | + let I = crypto.hmacSHA512('Bitcoin seed', seed) |
| 44 | + let IL = I.slice(0, 32) |
| 45 | + let IR = I.slice(32) |
| 46 | + |
| 47 | + // if IL is 0 or >= n, the master key is invalid |
| 48 | + if (!ecc.isPrivate(IL)) throw new TypeError('Private key not in range [1, n)') |
| 49 | + |
| 50 | + return new BIP32(IL, null, IR, network) |
| 51 | +} |
| 52 | + |
| 53 | +function fromBase58 (string, network) { |
| 54 | + let buffer = bs58check.decode(string) |
| 55 | + if (buffer.length !== 78) throw new TypeError('Invalid buffer length') |
| 56 | + if (network) typeforce(NETWORK_TYPE, network) |
| 57 | + network = network || BITCOIN |
| 58 | + |
| 59 | + // 4 bytes: version bytes |
| 60 | + let version = buffer.readUInt32BE(0) |
| 61 | + if (version !== network.bip32.private && |
| 62 | + version !== network.bip32.public) throw new TypeError('Invalid network version') |
| 63 | + |
| 64 | + // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... |
| 65 | + let depth = buffer[4] |
| 66 | + |
| 67 | + // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) |
| 68 | + let parentFingerprint = buffer.readUInt32BE(5) |
| 69 | + if (depth === 0) { |
| 70 | + if (parentFingerprint !== 0x00000000) throw new TypeError('Invalid parent fingerprint') |
| 71 | + } |
| 72 | + |
| 73 | + // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. |
| 74 | + // This is encoded in MSB order. (0x00000000 if master key) |
| 75 | + let index = buffer.readUInt32BE(9) |
| 76 | + if (depth === 0 && index !== 0) throw new TypeError('Invalid index') |
| 77 | + |
| 78 | + // 32 bytes: the chain code |
| 79 | + let chainCode = buffer.slice(13, 45) |
| 80 | + let hd |
| 81 | + |
| 82 | + // 33 bytes: private key data (0x00 + k) |
| 83 | + if (version === network.bip32.private) { |
| 84 | + if (buffer.readUInt8(45) !== 0x00) throw new TypeError('Invalid private key') |
| 85 | + let k = buffer.slice(46, 78) |
| 86 | + |
| 87 | + // if IL is 0 or >= n, the master key is invalid |
| 88 | + if (!ecc.isPrivate(k)) throw new TypeError('Private key not in range [1, n)') |
| 89 | + hd = new BIP32(k, null, chainCode, network) |
| 90 | + |
| 91 | + // 33 bytes: public key data (0x02 + X or 0x03 + X) |
| 92 | + } else { |
| 93 | + let X = buffer.slice(45, 78) |
| 94 | + |
| 95 | + // verify the X coordinate is a point on the curve |
| 96 | + if (!ecc.isPoint(X)) throw new TypeError('Point is not on the curve') |
| 97 | + hd = new BIP32(null, X, chainCode, network) |
| 98 | + } |
| 99 | + |
| 100 | + hd.depth = depth |
| 101 | + hd.index = index |
| 102 | + hd.parentFingerprint = parentFingerprint |
| 103 | + return hd |
| 104 | +} |
| 105 | + |
| 106 | +BIP32.prototype.getIdentifier = function () { |
| 107 | + return crypto.hash160(this.Q) |
| 108 | +} |
| 109 | + |
| 110 | +BIP32.prototype.getFingerprint = function () { |
| 111 | + return this.getIdentifier().slice(0, 4) |
| 112 | +} |
| 113 | + |
| 114 | +BIP32.prototype.getNetwork = function () { |
| 115 | + return this.network |
| 116 | +} |
| 117 | + |
| 118 | +BIP32.prototype.getPublicKey = function () { |
| 119 | + return this.Q |
| 120 | +} |
| 121 | + |
| 122 | +BIP32.prototype.neutered = function () { |
| 123 | + let neutered = new BIP32(null, this.Q, this.chainCode, this.network) |
| 124 | + neutered.depth = this.depth |
| 125 | + neutered.index = this.index |
| 126 | + neutered.parentFingerprint = this.parentFingerprint |
| 127 | + return neutered |
| 128 | +} |
| 129 | + |
| 130 | +BIP32.prototype.toBase58 = function () { |
| 131 | + let network = this.network |
| 132 | + let version = (!this.isNeutered()) ? network.bip32.private : network.bip32.public |
| 133 | + let buffer = Buffer.allocUnsafe(78) |
| 134 | + |
| 135 | + // 4 bytes: version bytes |
| 136 | + buffer.writeUInt32BE(version, 0) |
| 137 | + |
| 138 | + // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, .... |
| 139 | + buffer.writeUInt8(this.depth, 4) |
| 140 | + |
| 141 | + // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) |
| 142 | + buffer.writeUInt32BE(this.parentFingerprint, 5) |
| 143 | + |
| 144 | + // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. |
| 145 | + // This is encoded in big endian. (0x00000000 if master key) |
| 146 | + buffer.writeUInt32BE(this.index, 9) |
| 147 | + |
| 148 | + // 32 bytes: the chain code |
| 149 | + this.chainCode.copy(buffer, 13) |
| 150 | + |
| 151 | + // 33 bytes: the public key or private key data |
| 152 | + if (!this.isNeutered()) { |
| 153 | + // 0x00 + k for private keys |
| 154 | + buffer.writeUInt8(0, 45) |
| 155 | + this.d.copy(buffer, 46) |
| 156 | + |
| 157 | + // 33 bytes: the public key |
| 158 | + } else { |
| 159 | + // X9.62 encoding for public keys |
| 160 | + this.Q.copy(buffer, 45) |
| 161 | + } |
| 162 | + |
| 163 | + return bs58check.encode(buffer) |
| 164 | +} |
| 165 | + |
| 166 | +BIP32.prototype.toWIF = function () { |
| 167 | + if (!this.d) throw new TypeError('Missing private key') |
| 168 | + return wif.encode(this.network.wif, this.d, true) |
| 169 | +} |
| 170 | + |
| 171 | +let HIGHEST_BIT = 0x80000000 |
| 172 | + |
| 173 | +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions |
| 174 | +BIP32.prototype.derive = function (index) { |
| 175 | + typeforce(typeforce.UInt32, index) |
| 176 | + |
| 177 | + let isHardened = index >= HIGHEST_BIT |
| 178 | + let data = Buffer.allocUnsafe(37) |
| 179 | + |
| 180 | + // Hardened child |
| 181 | + if (isHardened) { |
| 182 | + if (this.isNeutered()) throw new TypeError('Missing private key for hardened child key') |
| 183 | + |
| 184 | + // data = 0x00 || ser256(kpar) || ser32(index) |
| 185 | + data[0] = 0x00 |
| 186 | + this.d.copy(data, 1) |
| 187 | + data.writeUInt32BE(index, 33) |
| 188 | + |
| 189 | + // Normal child |
| 190 | + } else { |
| 191 | + // data = serP(point(kpar)) || ser32(index) |
| 192 | + // = serP(Kpar) || ser32(index) |
| 193 | + this.Q.copy(data, 0) |
| 194 | + data.writeUInt32BE(index, 33) |
| 195 | + } |
| 196 | + |
| 197 | + let I = crypto.hmacSHA512(this.chainCode, data) |
| 198 | + let IL = I.slice(0, 32) |
| 199 | + let IR = I.slice(32) |
| 200 | + |
| 201 | + // if parse256(IL) >= n, proceed with the next value for i |
| 202 | + if (!ecc.isPrivate(IL)) return this.derive(index + 1) |
| 203 | + |
| 204 | + // Private parent key -> private child key |
| 205 | + let hd |
| 206 | + if (!this.isNeutered()) { |
| 207 | + // ki = parse256(IL) + kpar (mod n) |
| 208 | + let ki = ecc.privateAdd(this.d, IL) |
| 209 | + |
| 210 | + // In case ki == 0, proceed with the next value for i |
| 211 | + if (ki == null) return this.derive(index + 1) |
| 212 | + |
| 213 | + hd = new BIP32(ki, null, IR, this.network) |
| 214 | + |
| 215 | + // Public parent key -> public child key |
| 216 | + } else { |
| 217 | + // Ki = point(parse256(IL)) + Kpar |
| 218 | + // = G*IL + Kpar |
| 219 | + let Ki = ecc.pointAddScalar(this.Q, IL, true) |
| 220 | + |
| 221 | + // In case Ki is the point at infinity, proceed with the next value for i |
| 222 | + if (Ki === null) return this.derive(index + 1) |
| 223 | + |
| 224 | + hd = new BIP32(null, Ki, IR, this.network) |
| 225 | + } |
| 226 | + |
| 227 | + hd.depth = this.depth + 1 |
| 228 | + hd.index = index |
| 229 | + hd.parentFingerprint = this.getFingerprint().readUInt32BE(0) |
| 230 | + return hd |
| 231 | +} |
| 232 | + |
| 233 | +let UINT31_MAX = Math.pow(2, 31) - 1 |
| 234 | +function UInt31 (value) { |
| 235 | + return typeforce.UInt32(value) && value <= UINT31_MAX |
| 236 | +} |
| 237 | + |
| 238 | +BIP32.prototype.deriveHardened = function (index) { |
| 239 | + typeforce(UInt31, index) |
| 240 | + |
| 241 | + // Only derives hardened private keys by default |
| 242 | + return this.derive(index + HIGHEST_BIT) |
| 243 | +} |
| 244 | + |
| 245 | +// Private === not neutered |
| 246 | +// Public === neutered |
| 247 | +BIP32.prototype.isNeutered = function () { |
| 248 | + return this.d === null |
| 249 | +} |
| 250 | + |
| 251 | +function isBIP32Path (value) { |
| 252 | + return typeforce.String(value) && value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) |
| 253 | +} |
| 254 | + |
| 255 | +BIP32.prototype.derivePath = function (path) { |
| 256 | + typeforce(isBIP32Path, path) |
| 257 | + |
| 258 | + let splitPath = path.split('/') |
| 259 | + if (splitPath[0] === 'm') { |
| 260 | + if (this.parentFingerprint) throw new TypeError('Expected master, got child') |
| 261 | + |
| 262 | + splitPath = splitPath.slice(1) |
| 263 | + } |
| 264 | + |
| 265 | + return splitPath.reduce(function (prevHd, indexStr) { |
| 266 | + let index |
| 267 | + if (indexStr.slice(-1) === "'") { |
| 268 | + index = parseInt(indexStr.slice(0, -1), 10) |
| 269 | + return prevHd.deriveHardened(index) |
| 270 | + } else { |
| 271 | + index = parseInt(indexStr, 10) |
| 272 | + return prevHd.derive(index) |
| 273 | + } |
| 274 | + }, this) |
| 275 | +} |
| 276 | + |
| 277 | +BIP32.prototype.sign = function (hash) { |
| 278 | + return ecc.sign(hash, this.d) |
| 279 | +} |
| 280 | + |
| 281 | +BIP32.prototype.verify = function (hash, signature) { |
| 282 | + return ecc.verify(hash, signature, this.Q) |
| 283 | +} |
| 284 | + |
| 285 | +module.exports = { |
| 286 | + fromBase58, |
| 287 | + fromSeed |
| 288 | +} |
0 commit comments