9 changed files with 331 additions and 366 deletions
@ -0,0 +1,180 @@ |
|||
var assert = require('assert') |
|||
var opcodes = require('./opcodes') |
|||
var Script = require('./script') |
|||
|
|||
function classifyScriptPubKey(script) { |
|||
if (isPubkeyhash.call(script)) { |
|||
return 'pubkeyhash' |
|||
} else if (isPubkey.call(script)) { |
|||
return 'pubkey' |
|||
} else if (isScripthash.call(script)) { |
|||
return 'scripthash' |
|||
} else if (isMultisig.call(script)) { |
|||
return 'multisig' |
|||
} else if (isNulldata.call(script)) { |
|||
return 'nulldata' |
|||
} else { |
|||
return 'nonstandard' |
|||
} |
|||
} |
|||
|
|||
function classifyScriptSig(script) { |
|||
if (script.chunks.length == 1 && Buffer.isBuffer(script.chunks[0])) { |
|||
return 'pubkey' |
|||
} else if (script.chunks.length == 2 && Buffer.isBuffer(script.chunks[0]) && Buffer.isBuffer(script.chunks[1])) { |
|||
return 'pubkeyhash' |
|||
} else if (script.chunks[0] == opcodes.OP_0 && script.chunks.slice(1).reduce(function(t, chunk, i) { |
|||
return t && Buffer.isBuffer(chunk) && (chunk[0] == 48 || i == script.chunks.length - 1) |
|||
}, true)) { |
|||
return 'multisig' |
|||
} else { |
|||
return 'nonstandard' |
|||
} |
|||
} |
|||
|
|||
function isPubkeyhash() { |
|||
return this.chunks.length == 5 && |
|||
this.chunks[0] == opcodes.OP_DUP && |
|||
this.chunks[1] == opcodes.OP_HASH160 && |
|||
Buffer.isBuffer(this.chunks[2]) && |
|||
this.chunks[2].length === 20 && |
|||
this.chunks[3] == opcodes.OP_EQUALVERIFY && |
|||
this.chunks[4] == opcodes.OP_CHECKSIG |
|||
} |
|||
|
|||
function isPubkey() { |
|||
return this.chunks.length === 2 && |
|||
Buffer.isBuffer(this.chunks[0]) && |
|||
this.chunks[1] === opcodes.OP_CHECKSIG |
|||
} |
|||
|
|||
function isScripthash() { |
|||
return this.chunks[this.chunks.length - 1] == opcodes.OP_EQUAL && |
|||
this.chunks[0] == opcodes.OP_HASH160 && |
|||
Buffer.isBuffer(this.chunks[1]) && |
|||
this.chunks[1].length === 20 && |
|||
this.chunks.length == 3 |
|||
} |
|||
|
|||
function isMultisig() { |
|||
return this.chunks.length > 3 && |
|||
// m is a smallint
|
|||
isSmallIntOp(this.chunks[0]) && |
|||
// n is a smallint
|
|||
isSmallIntOp(this.chunks[this.chunks.length - 2]) && |
|||
// n greater or equal to m
|
|||
this.chunks[0] <= this.chunks[this.chunks.length - 2] && |
|||
// n cannot be 0
|
|||
this.chunks[this.chunks.length - 2] !== opcodes.OP_0 && |
|||
// n is the size of chunk length minus 3 (m, n, OP_CHECKMULTISIG)
|
|||
this.chunks.length - 3 === this.chunks[this.chunks.length - 2] - opcodes.OP_RESERVED && |
|||
// last chunk is OP_CHECKMULTISIG
|
|||
this.chunks[this.chunks.length - 1] == opcodes.OP_CHECKMULTISIG |
|||
} |
|||
|
|||
function isNulldata() { |
|||
return this.chunks[0] === opcodes.OP_RETURN |
|||
} |
|||
|
|||
function isSmallIntOp(opcode) { |
|||
return ((opcode == opcodes.OP_0) || ((opcode >= opcodes.OP_1) && (opcode <= opcodes.OP_16))) |
|||
} |
|||
|
|||
// Standard Script Templates
|
|||
// {pubKey} OP_CHECKSIG
|
|||
function createPubKeyScriptPubKey(pubKey) { |
|||
return Script.fromChunks([ |
|||
pubKey.toBuffer(), |
|||
opcodes.OP_CHECKSIG |
|||
]) |
|||
} |
|||
|
|||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
|||
function createPubKeyHashScriptPubKey(hash) { |
|||
assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) |
|||
|
|||
return Script.fromChunks([ |
|||
opcodes.OP_DUP, |
|||
opcodes.OP_HASH160, |
|||
hash, |
|||
opcodes.OP_EQUALVERIFY, |
|||
opcodes.OP_CHECKSIG |
|||
]) |
|||
} |
|||
|
|||
// OP_HASH160 {scriptHash} OP_EQUAL
|
|||
function createP2SHScriptPubKey(hash) { |
|||
assert(Buffer.isBuffer(hash), 'Expected Buffer, got ' + hash) |
|||
|
|||
return Script.fromChunks([ |
|||
opcodes.OP_HASH160, |
|||
hash, |
|||
opcodes.OP_EQUAL |
|||
]) |
|||
} |
|||
|
|||
// m [pubKeys ...] n OP_CHECKMULTISIG
|
|||
function createMultisigScriptPubKey(m, pubKeys) { |
|||
assert(Array.isArray(pubKeys), 'Expected Array, got ' + pubKeys) |
|||
assert(pubKeys.length >= m, 'Not enough pubKeys provided') |
|||
|
|||
var pubKeyBuffers = pubKeys.map(function(pubKey) { |
|||
return pubKey.toBuffer() |
|||
}) |
|||
var n = pubKeys.length |
|||
|
|||
return Script.fromChunks([].concat( |
|||
(opcodes.OP_1 - 1) + m, |
|||
pubKeyBuffers, |
|||
(opcodes.OP_1 - 1) + n, |
|||
opcodes.OP_CHECKMULTISIG |
|||
)) |
|||
} |
|||
|
|||
// {signature}
|
|||
function createPubKeyScriptSig(signature) { |
|||
assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature) |
|||
|
|||
return Script.fromChunks(signature) |
|||
} |
|||
|
|||
// {signature} {pubKey}
|
|||
function createPubKeyHashScriptSig(signature, pubKey) { |
|||
assert(Buffer.isBuffer(signature), 'Expected Buffer, got ' + signature) |
|||
|
|||
return Script.fromChunks([signature, pubKey.toBuffer()]) |
|||
} |
|||
|
|||
// <scriptSig> {serialized scriptPubKey script}
|
|||
function createP2SHScriptSig(scriptSig, scriptPubKey) { |
|||
return Script.fromChunks([].concat( |
|||
scriptSig.chunks, |
|||
scriptPubKey.toBuffer() |
|||
)) |
|||
} |
|||
|
|||
// OP_0 [signatures ...]
|
|||
function createMultisigScriptSig(signatures, scriptPubKey) { |
|||
if (scriptPubKey) { |
|||
assert(isMultisig.call(scriptPubKey)) |
|||
|
|||
var m = scriptPubKey.chunks[0] |
|||
var k = m - (opcodes.OP_1 - 1) |
|||
assert(k <= signatures.length, 'Not enough signatures provided') |
|||
} |
|||
|
|||
return Script.fromChunks([].concat(opcodes.OP_0, signatures)) |
|||
} |
|||
|
|||
module.exports = { |
|||
classifyScriptPubKey: classifyScriptPubKey, |
|||
classifyScriptSig: classifyScriptSig, |
|||
createMultisigScriptPubKey: createMultisigScriptPubKey, |
|||
createMultisigScriptSig: createMultisigScriptSig, |
|||
createP2SHScriptPubKey: createP2SHScriptPubKey, |
|||
createP2SHScriptSig: createP2SHScriptSig, |
|||
createPubKeyHashScriptPubKey: createPubKeyHashScriptPubKey, |
|||
createPubKeyHashScriptSig: createPubKeyHashScriptSig, |
|||
createPubKeyScriptPubKey: createPubKeyScriptPubKey, |
|||
createPubKeyScriptSig: createPubKeyScriptSig |
|||
} |
@ -0,0 +1,132 @@ |
|||
var assert = require('assert') |
|||
var crypto = require('../src/crypto') |
|||
var networks = require('../src/networks') |
|||
var templates = require('../src/templates') |
|||
|
|||
var Address = require('../src/address') |
|||
var ECPubKey = require('../src/ecpubkey') |
|||
var Script = require('../src/script') |
|||
|
|||
var fixtures = require('./fixtures/script.json') |
|||
|
|||
function b2h(b) { return new Buffer(b).toString('hex') } |
|||
function h2b(h) { return new Buffer(h, 'hex') } |
|||
|
|||
describe('Templates', function() { |
|||
describe('classifyScriptSig', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
if (f.scriptPubKey) return |
|||
|
|||
it('supports ' + f.type, function() { |
|||
var script = Script.fromHex(f.hex) |
|||
var type = templates.classifyScriptSig(script) |
|||
|
|||
assert.equal(type, f.type) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
describe('classifyScriptPubKey', function() { |
|||
fixtures.valid.forEach(function(f) { |
|||
if (!f.scriptPubKey) return |
|||
|
|||
it('supports ' + f.type, function() { |
|||
var script = Script.fromHex(f.hex) |
|||
var type = templates.classifyScriptPubKey(script) |
|||
|
|||
assert.equal(type, f.type) |
|||
}) |
|||
}) |
|||
}) |
|||
|
|||
// FIXME: bad
|
|||
describe('pay-to-pubKeyHash', function() { |
|||
it('matches the test data', function() { |
|||
var f = fixtures.valid[2] |
|||
var address = Address.fromBase58Check('19E6FV3m3kEPoJD5Jz6dGKdKwTVvjsWUvu') |
|||
var script = templates.createPubKeyHashScriptPubKey(address.hash) |
|||
|
|||
assert.equal(script.toHex(), f.hex) |
|||
}) |
|||
}) |
|||
|
|||
// FIXME: bad
|
|||
describe('pay-to-pubkey', function() { |
|||
it('matches the test data', function() { |
|||
var f = fixtures.valid[0] |
|||
var pubKey = ECPubKey.fromHex(f.pubKey) |
|||
var script = templates.createPubKeyScriptPubKey(pubKey) |
|||
|
|||
assert.equal(script.toHex(), f.hex) |
|||
}) |
|||
}) |
|||
|
|||
// FIXME: bad
|
|||
describe('pay-to-scriptHash', function() { |
|||
it('matches the test data', function() { |
|||
var f = fixtures.valid[1] |
|||
var address = Address.fromBase58Check('3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8') |
|||
var script = templates.createP2SHScriptPubKey(address.hash) |
|||
|
|||
assert.equal(script.toHex(), f.hex) |
|||
}) |
|||
}) |
|||
|
|||
// FIXME: bad
|
|||
describe('2-of-3 Multi-Signature scriptPubKey', function() { |
|||
var pubKeys |
|||
|
|||
beforeEach(function() { |
|||
pubKeys = [ |
|||
'02ea1297665dd733d444f31ec2581020004892cdaaf3dd6c0107c615afb839785f', |
|||
'02fab2dea1458990793f56f42e4a47dbf35a12a351f26fa5d7e0cc7447eaafa21f', |
|||
'036c6802ce7e8113723dd92cdb852e492ebb157a871ca532c3cb9ed08248ff0e19' |
|||
].map(ECPubKey.fromHex) |
|||
}) |
|||
|
|||
it('should create valid redeemScript', function() { |
|||
var redeemScript = templates.createMultisigScriptPubKey(2, pubKeys) |
|||
|
|||
var hash160 = crypto.hash160(new Buffer(redeemScript.buffer)) |
|||
var multisigAddress = new Address(hash160, networks.bitcoin.scriptHash) |
|||
|
|||
assert.equal(multisigAddress.toString(), '32vYjxBb7pHJJyXgNk8UoK3BdRDxBzny2v') |
|||
}) |
|||
|
|||
it('should throw on not enough pubKeys provided', function() { |
|||
assert.throws(function() { |
|||
templates.createMultisigScriptPubKey(4, pubKeys) |
|||
}, /Not enough pubKeys provided/) |
|||
}) |
|||
}) |
|||
|
|||
// FIXME: bad
|
|||
describe('2-of-2 Multisig scriptSig', function() { |
|||
var pubKeys = [ |
|||
'02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1', |
|||
'0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a' |
|||
].map(ECPubKey.fromHex) |
|||
var signatures = [ |
|||
'304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801', |
|||
'3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501' |
|||
].map(h2b) |
|||
var expected = '0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae' |
|||
|
|||
it('should create a valid P2SH multisig scriptSig', function() { |
|||
var redeemScript = templates.createMultisigScriptPubKey(2, pubKeys) |
|||
var redeemScriptSig = templates.createMultisigScriptSig(signatures) |
|||
|
|||
var scriptSig = templates.createP2SHScriptSig(redeemScriptSig, redeemScript) |
|||
|
|||
assert.equal(b2h(scriptSig.buffer), expected) |
|||
}) |
|||
|
|||
it('should throw on not enough signatures', function() { |
|||
var redeemScript = templates.createMultisigScriptPubKey(2, pubKeys) |
|||
|
|||
assert.throws(function() { |
|||
templates.createMultisigScriptSig(signatures.slice(1), redeemScript) |
|||
}, /Not enough signatures provided/) |
|||
}) |
|||
}) |
|||
}) |
Loading…
Reference in new issue