From 47160c1cfe774efdaa6c43ef8a802f71b3ba96c4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 2 Apr 2016 04:12:50 -0700 Subject: [PATCH] parsing and serialization. --- lib/bcoin/address.js | 153 ++++++++++++++++--------------------- lib/bcoin/keypair.js | 50 ++++++------ lib/bcoin/script.js | 106 +++++++++++++++++--------- lib/bcoin/utils.js | 176 ++----------------------------------------- 4 files changed, 167 insertions(+), 318 deletions(-) diff --git a/lib/bcoin/address.js b/lib/bcoin/address.js index 0f1246d1..bd7ad20f 100644 --- a/lib/bcoin/address.js +++ b/lib/bcoin/address.js @@ -8,6 +8,8 @@ var bcoin = require('../bcoin'); var utils = bcoin.utils; var assert = utils.assert; var network = bcoin.protocol.network; +var BufferWriter = require('./writer'); +var BufferReader = require('./reader'); /** * Address @@ -65,8 +67,6 @@ Address.prototype.getID = function getID() { }; Address.prototype.addKey = function addKey(key) { - key = utils.ensureBuffer(key); - if (utils.indexOf(this.keys, key) !== -1) return; @@ -76,11 +76,7 @@ Address.prototype.addKey = function addKey(key) { }; Address.prototype.removeKey = function removeKey(key) { - var index; - - key = utils.ensureBuffer(key); - - index = utils.indexOf(this.keys, key); + var index = utils.indexOf(this.keys, key); if (index === -1) return; @@ -210,7 +206,7 @@ Address.prototype.getScriptAddress = function getScriptAddress() { if (this.witness) { this._scriptAddress = - Address.compileHash(this.getScriptHash256(), 'witnessscripthash'); + Address.compileHash(this.getScriptHash256(), 'witnessscripthash', 0); } else { this._scriptAddress = Address.compileHash(this.getScriptHash160(), 'scripthash'); @@ -233,7 +229,7 @@ Address.prototype.getKeyAddress = function getKeyAddress() { return this._address; if (this.witness) - this._address = Address.compileHash(this.getKeyHash(), 'witnesspubkeyhash'); + this._address = Address.compileHash(this.getKeyHash(), 'witnesspubkeyhash', 0); else this._address = Address.compileHash(this.getKeyHash(), 'pubkeyhash'); @@ -421,125 +417,108 @@ Address.prototype.__defineGetter__('address', function() { }); Address.hash160 = function hash160(key) { - key = utils.ensureBuffer(key); return utils.ripesha(key); }; Address.sha256 = function sha256(key) { - key = utils.ensureBuffer(key); return utils.sha256(key); }; -Address.compileHash = function compileHash(hash, prefixType) { - var prefix, version, size, off, addr; +Address.compileHash = function compileHash(hash, type, version) { + var p, prefix; if (!Buffer.isBuffer(hash)) hash = new Buffer(hash, 'hex'); - if (!prefixType) - prefixType = 'pubkeyhash'; + if (!type) + type = 'pubkeyhash'; - prefix = network.address.prefixes[prefixType]; - version = network.address.versions[prefixType]; + prefix = network.address.prefixes[type]; - assert(prefix != null, 'Not a valid address prefix.'); - assert(hash.length === 20 || hash.length === 32, - 'Hash is the wrong length.'); - - size = 1 + hash.length + 4; - - if (version != null) - size += 2; - - addr = new Buffer(size); - - off = 0; - - off += utils.writeU8(addr, prefix, off); - if (version != null) { - off += utils.writeU8(addr, version, off); - off += utils.writeU8(addr, 0, off); - } - off += utils.copy(hash, addr, off); - off += utils.copy(utils.checksum(addr.slice(0, off)), addr, off); - - return utils.toBase58(addr); -}; - -Address.compileData = function compileData(key, prefix) { - if (prefix === 'witnessscripthash') - key = Address.sha256(key); - else - key = Address.hash160(key); - - return Address.compileHash(key, prefix); -}; - -Address.parse = function parse(addr, prefixType) { - var chk, prefix, version, size, hash; - - if (!Buffer.isBuffer(addr)) - addr = utils.fromBase58(addr); - - if (prefixType == null) - prefixType = network.address.prefixesByVal[addr[0]]; - - if (!prefixType) - prefixType = 'pubkeyhash'; - - prefix = network.address.prefixes[prefixType]; - version = network.address.versions[prefixType]; + if (version == null) + version = network.address.versions[type]; assert(prefix != null, 'Not a valid address prefix.'); - // prefix - size = 1; + if (version == null) + assert(hash.length === 20, 'Hash is the wrong size.'); + else if (version === 0 && type === 'witnesspubkeyhash') + assert(hash.length === 20, 'Hash is the wrong size.'); + else if (version === 0 && type === 'witnessscripthash') + assert(hash.length === 32, 'Hash is the wrong size.'); - // version + nul byte - if (version != null) - size += 2; + p = new BufferWriter(); - hash = addr.slice(size, -4); + p.writeU8(prefix); + if (version != null) { + p.writeU8(version); + p.writeU8(0) + } + p.writeBytes(hash); + p.writeChecksum(); - // hash - if (prefixType === 'witnessscripthash') - size += 32; + return utils.toBase58(p.render()); +}; + +Address.compileData = function compileData(data, type, version) { + if (type === 'witnessscripthash') + data = Address.sha256(data); else - size += 20; + data = Address.hash160(data); - assert(addr.length === size + 4, 'Address is not the right length.'); + return Address.compileHash(data, type, version); +}; - assert(addr[0] === prefix, 'Address is not the right prefix.'); +Address.parse = function parse(address) { + var prefix, type, version, hash; + + if (!Buffer.isBuffer(address)) + address = utils.fromBase58(address); + + p = new BufferReader(address, true); + prefix = p.readU8(); + + type = network.address.prefixesByVal[prefix]; + version = network.address.versions[type]; + + assert(type != null, 'Not a valid address prefix.'); if (version != null) { - assert(addr[1] === version, - 'Address is not the right program version.'); - assert(addr[2] === 0, - 'Address version padding is zero.'); + version = p.readU8(); + assert(version >= 0 && version <= 16, 'Bad program version.'); + assert(p.readU8() === 0, 'Address version padding is zero.'); } - chk = utils.checksum(addr.slice(0, -4)); + if (type === 'witnessscripthash') + hash = p.readBytes(32); + else + hash = p.readBytes(20); - assert(utils.readU32(chk, 0) === utils.readU32(addr, size), - 'Address checksum failed.'); + p.verifyChecksum(); return { - type: prefixType, + type: type, hash: hash, version: version == null ? -1 : version }; }; -Address.validate = function validate(addr, prefix) { - if (!addr) +Address.validate = function validate(address, type) { + if (!address) + return false; + + if (!Buffer.isBuffer(address) && typeof address !== 'string') return false; try { - Address.parse(addr, prefix); + address = Address.parse(address); } catch (e) { return false; } + if (type && address.type !== type) + return false; + return true; }; diff --git a/lib/bcoin/keypair.js b/lib/bcoin/keypair.js index 96f66425..03ec97e8 100644 --- a/lib/bcoin/keypair.js +++ b/lib/bcoin/keypair.js @@ -8,6 +8,8 @@ var bcoin = require('../bcoin'); var utils = require('./utils'); var assert = utils.assert; var network = bcoin.protocol.network; +var BufferWriter = require('./writer'); +var BufferReader = require('./reader'); /** * KeyPair @@ -87,45 +89,44 @@ KeyPair.prototype.toSecret = function toSecret() { }; KeyPair.toSecret = function toSecret(privateKey, compressed) { - var buf = new Buffer(1 + privateKey.length + (compressed ? 1 : 0) + 4); - var off = 0; + var p = new BufferWriter(); - off += utils.writeU8(buf, network.prefixes.privkey, 0); - off += utils.copy(privateKey, buf, off); + p.writeU8(network.prefixes.privkey); + p.writeBytes(privateKey); if (compressed !== false) - off += utils.writeU8(buf, 1, off); + p.writeU8(1); - utils.copy(utils.checksum(buf.slice(0, off)), buf, off); + p.writeChecksum(); - return utils.toBase58(buf); + return utils.toBase58(p.render()); }; -KeyPair._fromSecret = function _fromSecret(privateKey) { - var key, compressed; +KeyPair._fromSecret = function _fromSecret(secret) { + var data = utils.fromBase58(secret); + var p = new BufferReader(data, true); + var compressed = false; + var privateKey; - key = utils.fromBase58(privateKey); - assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4)))); - assert.equal(key[0], network.prefixes.privkey); + assert(p.readU8() === network.prefixes.privkey, 'Bad network.'); - key = key.slice(0, -4); - if (key.length === 34) { - assert.equal(key[33], 1); - privateKey = key.slice(1, -1); + privateKey = p.readBytes(32); + + if (p.left() > 1) { + assert(p.readU8() === 1); compressed = true; - } else { - privateKey = key.slice(1); - compressed = false; } + p.verifyChecksum(); + return { privateKey: privateKey, compressed: compressed }; }; -KeyPair.fromSecret = function fromSecret(privateKey) { - return new KeyPair(KeyPair._fromSecret(privateKey)); +KeyPair.fromSecret = function fromSecret(secret) { + return new KeyPair(KeyPair._fromSecret(secret)); }; KeyPair.prototype.toJSON = function toJSON(passphrase) { @@ -148,7 +149,7 @@ KeyPair.prototype.toJSON = function toJSON(passphrase) { }; KeyPair._fromJSON = function _fromJSON(json, passphrase) { - var privateKey; + var privateKey, publicKey; assert.equal(json.v, 1); assert.equal(json.name, 'keypair'); @@ -164,13 +165,14 @@ KeyPair._fromJSON = function _fromJSON(json, passphrase) { } if (json.publicKey) { + publicKey = utils.fromBase58(json.publicKey); return { - publicKey: utils.fromBase58(json.publicKey), + publicKey: publicKey, compressed: publicKey[0] !== 0x04 }; } - assert(false); + assert(false, 'Could not parse KeyPair JSON.'); }; KeyPair.fromJSON = function fromJSON(json, passphrase) { diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index de25f8e3..6017cf01 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -1489,16 +1489,26 @@ Script.getInputAddress = function getInputAddress(code, prev, isWitness) { return; if (Script.isPubkeyhashInput(code)) { - return bcoin.address.compileData(code[1], - isWitness ? 'witnesspubkeyhash' : 'pubkeyhash'); + if (isWitness) { + return bcoin.address.compileData( + code[1], + 'witnesspubkeyhash', + 0); + } + return bcoin.address.compileData(code[1], 'pubkeyhash'); } if (Script.isMultisigInput(code, null, isWitness)) return; if (Script.isScripthashInput(code, null, isWitness)) { - return bcoin.address.compileData(code[code.length - 1], - isWitness ? 'witnessscripthash' : 'scripthash'); + if (isWitness) { + return bcoin.address.compileData( + code[code.length - 1], + 'witnessscripthash', + 0); + } + return bcoin.address.compileData(code[code.length - 1], 'scripthash'); } }; @@ -1509,7 +1519,10 @@ Script.prototype.getAddress = function getAddress() { program = this.getWitnessProgram(); if (!program.type || program.type === 'unknown') return; - return bcoin.address.compileHash(program.data, program.type); + return bcoin.address.compileHash( + program.data, + program.type, + program.version); } // Convert p2pk to p2pkh addresses @@ -1822,26 +1835,14 @@ Script.isNonstandardInput = function isNonstandardInput(code, prev, isWitness) { }; Script.createOutputScript = function(options) { - var script, keys, m, n, hash, flags, address, redeem; + var script, m, n, hash, flags, address, redeem; if (!options) options = {}; - if (options.keys) { - keys = options.keys.map(utils.ensureBuffer); - - m = options.m; - n = options.n || keys.length; - - assert(m >= 1 && m <= n, 'm must be between 1 and n'); - - assert( - n >= 1 && n <= (options.scriptHash ? 15 : 3), - 'n must be between 1 and 15'); - - script = Script.createMultisig(keys, m, n); - } else if (options.address) { + if (options.address) { address = bcoin.address.parse(options.address); + if (address.type === 'pubkeyhash') script = Script.createPubkeyhash(address.hash); else if (address.type === 'scripthash') @@ -1849,29 +1850,62 @@ Script.createOutputScript = function(options) { else if (address.version !== -1) script = Script.createWitnessProgram(address.version, address.hash); else - assert(false); - } else if (options.key) { - script = Script.createPubkey(utils.ensureBuffer(options.key)); - } else if (options.flags) { + assert(false, 'Unknown address type.'); + + return script; + } + + if (options.flags) { flags = options.flags; if (typeof flags === 'string') - flags = new Buffer(flags, 'ascii'); + flags = new Buffer(flags, 'utf8'); assert(Buffer.isBuffer(flags)); - assert(flags.length <= constants.script.maxOpReturn); - script = Script.createNulldata(flags); + assert(flags.length <= constants.script.maxOpReturn, 'Nulldata too large.'); + return Script.createNulldata(flags); + } + + if (options.key) { + script = Script.createPubkey(options.key); + } else if (options.keyHash) { + assert(options.keyHash.length === 20); + if (options.version != null) + script = Script.createWitnessProgram(options.version, options.keyHash); + else + script = Script.createPubkeyhash(options.keyHash); + } else if (options.keys) { + m = options.m; + n = options.n || options.keys.length; + assert(m >= 1 && m <= n, 'm must be between 1 and n'); + assert(n >= 1 && n <= (options.scriptHash ? 15 : 3), + 'n must be between 1 and 15'); + script = Script.createMultisig(options.keys, m, n); + } else if (Buffer.isBuffer(options.scriptHash)) { + if (options.version != null) { + assert(options.scriptHash.length === 32); + return Script.createWitnessProgram(options.version, options.scriptHash); + } + assert(options.scriptHash.length === 20); + return Script.createScripthash(options.scriptHash); + } + + if (!script) + return new Script([]); + + if (options.locktime != null) { + script.code.unshift(opcodes.OP_DROP); + script.code.unshift(opcodes.OP_CHECKLOCKTIMEVERIFY); + script.code.unshift(Script.array(options.locktime)); } if (options.scriptHash) { - if (options.locktime != null) { - script = new Script([ - Script.array(options.locktime), - opcodes.OP_CHECKLOCKTIMEVERIFY, - opcodes.OP_DROP - ].concat(script.code)); - } redeem = script; - hash = utils.ripesha(script.encode()); - script = Script.createScripthash(hash); + if (options.version != null) { + hash = utils.sha256(redeem.encode()); + script = Script.createWitnessProgram(options.version, hash); + } else { + hash = utils.ripesha(redeem.encode()); + script = Script.createScripthash(hash); + } script.redeem = redeem; } diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index f39bb093..bb942361 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -66,56 +66,6 @@ utils.toBuffer = function toBuffer(msg, enc) { assert(false); }; -utils.toArray = function toArray(msg, enc) { - var res = []; - var i, c, hi, lo, slice, num; - - if (!msg) - return res; - - if (Buffer.isBuffer(msg)) - return Array.prototype.slice.call(msg); - - if (Array.isArray(msg)) - return msg.slice(); - - if (typeof msg === 'string') { - if (!enc) { - for (i = 0; i < msg.length; i++) { - c = msg.charCodeAt(i); - hi = c >> 8; - lo = c & 0xff; - if (hi) - res.push(hi, lo); - else - res.push(lo); - } - } else if (enc === 'hex') { - msg = msg.replace(/[^a-z0-9]+/ig, ''); - if (msg.length % 2 !== 0) - msg = '0' + msg; - - for (i = 0; i < msg.length; i += 8) { - slice = msg.slice(i, i + 8); - num = parseInt(slice, 16); - - if (slice.length === 8) - res.push((num >>> 24) & 0xff); - if (slice.length >= 6) - res.push((num >>> 16) & 0xff); - if (slice.length >= 4) - res.push((num >>> 8) & 0xff); - res.push(num & 0xff); - } - } - } else { - for (i = 0; i < msg.length; i++) - res[i] = msg[i] | 0; - } - - return res; -}; - var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' + 'abcdefghijkmnopqrstuvwxyz'; @@ -399,34 +349,6 @@ utils.isHex = function isHex(msg) { return typeof msg === 'string' && /^[0-9a-f]+$/i.test(msg); }; -function binaryInsert(list, item, compare, search) { - var start = 0; - var end = list.length; - var pos, cmp; - - while (start < end) { - pos = (start + end) >> 1; - cmp = compare(item, list[pos]); - - if (cmp === 0) { - start = pos; - end = pos; - break; - } else if (cmp < 0) { - end = pos; - } else { - start = pos + 1; - } - } - - if (!search) - list.splice(start, 0, item); - - return start; -} - -utils.binaryInsert = binaryInsert; - utils.isEqual = function isEqual(a, b) { var i; @@ -457,51 +379,13 @@ if (typeof setImmediate === 'function') { }; } -function RequestCache() { - this.map = {}; - this.count = 0; -} - -RequestCache.prototype.add = function add(id, cb) { - id = utils.toHex(id); - - if (this.map[id]) { - this.map[id].push(cb); - return false; - } else { - this.map[id] = [ cb ]; - this.count++; - return true; - } -}; - -RequestCache.prototype.fulfill = function fulfill(id, err, data) { - var cbs; - - id = utils.toHex(id); - - cbs = this.map[id]; - - if (!this.map[id]) - return; - - delete this.map[id]; - this.count--; - - cbs.forEach(function(cb) { - cb(err, data); - }); -}; - -utils.RequestCache = RequestCache; - utils.asyncify = function asyncify(callback) { if (callback && callback._asyncified) return callback; function asyncifyFn(err, result1, result2) { if (!callback) - return err || result1; + return; utils.nextTick(function() { callback(err, result1, result2); }); @@ -811,37 +695,6 @@ utils.array2ip = function array2ip(ip, version) { } }; -utils.isArrayLike = function isArrayLike(msg) { - return msg - && !Array.isArray(msg) - && typeof msg === 'object' - && typeof msg.length === 'number'; -}; - -utils.isArray = function isArray(msg) { - return Array.isArray(msg); -}; - -utils.isBuffer = function isBuffer(msg) { - return Buffer.isBuffer(msg); -}; - -utils.ensureBuffer = function ensureBuffer(msg) { - if (Buffer.isBuffer(msg)) - return msg; - - if (Array.isArray(msg)) - return new Buffer(msg); - - if (utils.isHex(msg)) - return new Buffer(msg, 'hex'); - - if (utils.isBase58(msg)) - return utils.fromBase58(msg); - - throw new Error('Cannot ensure buffer'); -}; - utils._inspect = function _inspect(obj, color) { return typeof obj !== 'string' ? util.inspect(obj, null, 20, color !== false) @@ -986,10 +839,6 @@ utils.now = function now() { return +new Date() / 1000 | 0; }; -utils.host = function host(addr) { - return addr.split(':')[0]; -}; - utils.U32 = new bn(0xffffffff); utils.U64 = new bn('ffffffffffffffff', 'hex'); @@ -1421,9 +1270,6 @@ utils.write32BE = function write32BE(dst, num, off) { utils.write64 = function write64(dst, num, off) { var i; - // if (!bn.isBN(num)) - // num = new bn(+num); - if (!bn.isBN(num)) return utils.write64N(dst, num, off); @@ -1441,9 +1287,7 @@ utils.write64 = function write64(dst, num, off) { if (num.bitLength() > 64) num = num.uand(utils.U64); - num = num.toArray('le', 8); - - assert.equal(num.length, 8); + num = num.toBuffer('le', 8); for (i = 0; i < num.length; i++) dst[off++] = num[i] & 0xff; @@ -1454,9 +1298,6 @@ utils.write64 = function write64(dst, num, off) { utils.write64BE = function write64BE(dst, num, off) { var i; - // if (!bn.isBN(num)) - // num = new bn(+num); - if (!bn.isBN(num)) return utils.write64NBE(dst, num, off); @@ -1474,9 +1315,7 @@ utils.write64BE = function write64BE(dst, num, off) { if (num.bitLength() > 64) num = num.uand(utils.U64); - num = num.toArray('be', 8); - - assert.equal(num.length, 8); + num = num.toBuffer('be', 8); for (i = 0; i < num.length; i++) dst[off++] = num[i] & 0xff; @@ -2009,12 +1848,6 @@ utils.revMap = function revMap(map) { return reversed; }; -if (utils.isBrowser) { - bn.prototype.toBuffer = function toBuffer(order, size) { - return this.toArrayLike(Buffer, order, size); - }; -} - /** * VerifyError */ @@ -2036,7 +1869,8 @@ function VerifyError(object, code, reason, score) { this.code = code; this.reason = score === -1 ? null : reason; this.score = score; - this.message = reason + this.message = 'Verification failure: ' + + reason + ' (code=' + code + ', score=' + score + ', height=' + this.height