From 5e0d38ba54a778c26962452f3e711c786011424c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 5 May 2014 15:31:40 +1000 Subject: [PATCH] Address: add Address.*ScriptPubKey and tests The introduction of these two functions allow for the all the network related code to be eventually removed from Transaction and Script. Previously the result for non-standard transactions was undefined behaviour. This change mandates that an exception is thrown if a non-standard transaction is input. --- src/address.js | 55 ++++++++++++++++++++++++++++++++++++++++ src/script.js | 8 +----- test/address.js | 49 ++++++++++++++++++++++++++++++----- test/fixtures/address.js | 47 ++++++++++++++++++++++------------ 4 files changed, 129 insertions(+), 30 deletions(-) diff --git a/src/address.js b/src/address.js index 5457e52..bfe2f8f 100644 --- a/src/address.js +++ b/src/address.js @@ -1,5 +1,22 @@ var assert = require('assert') var base58check = require('./base58check') +var networks = require('./networks') + +function findScriptTypeByVersion(queryVersion) { + for (var networkName in networks) { + var network = networks[networkName] + + for (var versionName in network) { + var version = network[versionName] + + if (version === queryVersion) { + return versionName + } + } + } + + throw new Error('Version has no corresponding network') +} function Address(hash, version) { assert(Buffer.isBuffer(hash), 'First argument must be a Buffer') @@ -17,10 +34,48 @@ Address.fromBase58Check = function(string) { return new Address(decode.payload, decode.version) } +Address.fromScriptPubKey = function(script, network) { + network = network || networks.bitcoin + + var type = script.getOutType() + + // Pay-to-pubKeyHash + if (type === 'pubkeyhash') { + return new Address(new Buffer(script.chunks[2]), network.pubKeyHash) + } + + // Pay-to-scriptHash + else if (type === 'scripthash') { + return new Address(new Buffer(script.chunks[1]), network.scriptHash) + } + + throw new Error('Could not derive address from script') +} + // Export functions Address.prototype.toBase58Check = function () { return base58check.encode(this.hash, this.version) } + +Address.prototype.toScriptPubKey = function() { + var scriptType = findScriptTypeByVersion(this.version) + + // Pay-to-pubKeyHash + if (scriptType === 'pubKeyHash') { + return Script.createPubKeyHashScriptPubKey(this.hash) + } + + // Pay-to-scriptHash + else if (scriptType === 'scriptHash') { + return Script.createP2SHScriptPubKey(this.hash) + } + + throw new Error('Address has no matching script') +} + Address.prototype.toString = Address.prototype.toBase58Check module.exports = Address + +// http://stackoverflow.com/a/14098262 +var Script = require('./script') diff --git a/src/script.js b/src/script.js index 97aa070..f12ac6f 100644 --- a/src/script.js +++ b/src/script.js @@ -205,13 +205,7 @@ Script.prototype.toScriptHash = function() { Script.prototype.getToAddress = function(network) { network = network || networks.bitcoin - if(isPubkeyhash.call(this)) { - return new Address(new Buffer(this.chunks[2]), network.pubKeyHash) - } - - assert(isScripthash.call(this)) - - return new Address(new Buffer(this.chunks[1]), network.scriptHash) + return Address.fromScriptPubKey(this, network) } Script.prototype.getFromAddress = function(version) { diff --git a/test/address.js b/test/address.js index 25a541f..c276e87 100644 --- a/test/address.js +++ b/test/address.js @@ -1,15 +1,17 @@ var assert = require('assert') var Address = require('..').Address +var networks = require('..').networks +var Script = require('..').Script var b58fixtures = require('./fixtures/base58') var fixtures = require('./fixtures/address') -describe('Address', function() { - var bothVectors = fixtures.pubKeyHash.concat(fixtures.scriptHash) +function h2b(h) { return new Buffer(h, 'hex') } +describe('Address', function() { describe('Constructor', function() { it('does not mutate the input', function() { - bothVectors.forEach(function(f) { + fixtures.valid.forEach(function(f) { var hash = new Buffer(f.hex, 'hex') var addr = new Address(hash, f.version) @@ -28,8 +30,8 @@ describe('Address', function() { }) }) - bothVectors.forEach(function(f) { - it('imports ' + f.description + ' correctly', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.description + '(' + f.network + ') correctly', function() { var addr = Address.fromBase58Check(f.base58check) assert.equal(addr.version, f.version) @@ -38,9 +40,21 @@ describe('Address', function() { }) }) + describe('fromScriptPubKey', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.description + '(' + f.network + ') correctly', function() { + var script = Script.fromHex(f.script) + var addr = Address.fromScriptPubKey(script, networks[f.network]) + + assert.equal(addr.version, f.version) + assert.equal(addr.hash.toString('hex'), f.hex) + }) + }) + }) + describe('toBase58Check', function() { - bothVectors.forEach(function(f) { - it('exports ' + f.description + ' correctly', function() { + fixtures.valid.forEach(function(f) { + it('exports ' + f.description + '(' + f.network + ') correctly', function() { var addr = Address.fromBase58Check(f.base58check) var result = addr.toBase58Check() @@ -48,4 +62,25 @@ describe('Address', function() { }) }) }) + + describe('toScriptPubKey', function() { + fixtures.valid.forEach(function(f) { + it('imports ' + f.description + '(' + f.network + ') correctly', function() { + var addr = Address.fromBase58Check(f.base58check) + var script = addr.toScriptPubKey() + + assert.equal(script.toHex(), f.script) + }) + }) + + fixtures.invalid.toScriptPubKey.forEach(function(f) { + it('throws on ' + f.description, function() { + var addr = new Address(h2b(f.hex), f.version) + + assert.throws(function() { + addr.toScriptPubKey() + }) + }) + }) + }) }) diff --git a/test/fixtures/address.js b/test/fixtures/address.js index dd4fa28..7a6becc 100644 --- a/test/fixtures/address.js +++ b/test/fixtures/address.js @@ -1,30 +1,45 @@ module.exports = { - pubKeyHash: [ + valid: [ { - description: 'pubKeyHash (bitcoin)', + description: 'pubKeyHash', + network: 'bitcoin', version: 0, hex: '751e76e8199196d454941c45d1b3a323f1433bd6', - base58check: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH' + base58check: '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', + script: '76a914751e76e8199196d454941c45d1b3a323f1433bd688ac' }, { - description: 'pubKeyHash (testnet)', + description: 'pubKeyHash', + network: 'testnet', version: 111, hex: '751e76e8199196d454941c45d1b3a323f1433bd6', - base58check: 'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r' - } - ], - scriptHash: [ - { - description: 'scriptHash (bitcoin)', - version: 5, - hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6', - base58check: '3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr' + base58check: 'mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r', + script: '76a914751e76e8199196d454941c45d1b3a323f1433bd688ac' }, { - description: 'scriptHash (testnet)', + description: 'scriptHash', + network: 'bitcoin', + version: 5, + hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6', + base58check: '3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr', + script: 'a914cd7b44d0b03f2d026d1e586d7ae18903b0d385f687' + }, + { + description: 'scriptHash', + network: 'testnet', version: 196, hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6', - base58check: '2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w' + base58check: '2NByiBUaEXrhmqAsg7BbLpcQSAQs1EDwt5w', + script: 'a914cd7b44d0b03f2d026d1e586d7ae18903b0d385f687' } - ] + ], + invalid: { + toScriptPubKey: [ + { + description: 'Unknown Address version', + version: 0x99, + hex: 'cd7b44d0b03f2d026d1e586d7ae18903b0d385f6' + } + ] + } }