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