From aa43e98ac0e2be85890cbde08c1b629db8018951 Mon Sep 17 00:00:00 2001 From: sidazhang Date: Fri, 11 Apr 2014 15:20:42 -0700 Subject: [PATCH 1/2] Aligning type naming with bitcoind and fixed pubkeyhash bug --- src/script.js | 123 +++++++++++++++++++++++++++++++++++++------------ test/script.js | 8 ++-- 2 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/script.js b/src/script.js index b8fba80..019178c 100644 --- a/src/script.js +++ b/src/script.js @@ -96,6 +96,7 @@ Script.prototype.parse = function() { } } + /** * Compare the script to known templates of scriptPubKey. * @@ -103,35 +104,97 @@ Script.prototype.parse = function() { * templates and return a string naming the detected type. * * Currently supported are: - * Address: + * Pubkeyhash * Paying to a Bitcoin address which is the hash of a pubkey. * OP_DUP OP_HASH160 [pubKeyHash] OP_EQUALVERIFY OP_CHECKSIG + * Example: * * Pubkey: * Paying to a public key directly. * [pubKey] OP_CHECKSIG + * Example: txid 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098 + * + * Scripthash (P2SH) + * Paying to an address which is the hash of a script + * OP_HASH160 [Scripthash] OP_EQUAL + * Example: + * + * Multisig + * Paying to multiple pubkeys and require a number of the signatures + * m [pubkey] [pubkey] [pubkey] n OP_CHECKMULTISIG + * Example: * * Strange: * Any other script (no template matched). */ + +// Below is the current standard set of out types +/* const char* GetTxnOutputType(txnouttype t) +{ + switch (t) + { + case TX_NONSTANDARD: return "nonstandard"; + case TX_PUBKEY: return "pubkey"; + case TX_PUBKEYHASH: return "pubkeyhash"; + case TX_SCRIPTHASH: return "scripthash"; + case TX_MULTISIG: return "multisig"; + case TX_NULL_DATA: return "nulldata"; + } + return NULL; +}*/ + +// https://github.com/bitcoin/bitcoin/blob/19e5b9d2dfcac4efadba636745485d9660fb1abe/src/script.cpp#L75 + +// supporting tx_null_data https://github.com/bitcoin/bitcoin/pull/3128 +// https://helloblock.io/mainnet/transactions/ebc9fa1196a59e192352d76c0f6e73167046b9d37b8302b6bb6968dfd279b767 Script.prototype.getOutType = function() { - if (this.chunks[this.chunks.length - 1] == Opcode.map.OP_EQUAL && - this.chunks[0] == Opcode.map.OP_HASH160 && - this.chunks.length == 3) { - // Transfer to M-OF-N - return 'P2SH' - } else if (this.chunks.length == 5 && - this.chunks[0] == Opcode.map.OP_DUP && - this.chunks[1] == Opcode.map.OP_HASH160 && - this.chunks[3] == Opcode.map.OP_EQUALVERIFY && - this.chunks[4] == Opcode.map.OP_CHECKSIG) { + if (this.chunks.length == 5 && + this.chunks[0] == Opcode.map.OP_DUP && + this.chunks[1] == Opcode.map.OP_HASH160 && + Array.isArray(this.chunks[2]) && + this.chunks[2].length === 20 && + this.chunks[3] == Opcode.map.OP_EQUALVERIFY && + this.chunks[4] == Opcode.map.OP_CHECKSIG) { // Transfer to Bitcoin address - return 'Pubkey' + return 'pubkeyhash'; + } else if (this.chunks.length === 2 && + Array.isArray(this.chunks[0]) && + this.chunks[1] === Opcode.map.OP_CHECKSIG) { + // [pubkey] OP_CHECKSIG + return 'pubkey'; + } else if (this.chunks[this.chunks.length - 1] == Opcode.map.OP_EQUAL && + this.chunks[0] == Opcode.map.OP_HASH160 && + Array.isArray(this.chunks[1]) && + this.chunks[1].length === 20 && + this.chunks.length == 3) { + // Transfer to M-OF-N + return 'scripthash'; + } else if (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] !== Opcode.map.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] - Opcode.map.OP_RESERVED && + // last chunk is OP_CHECKMULTISIG + this.chunks[this.chunks.length - 1] == Opcode.map.OP_CHECKMULTISIG) { + return 'multisig' + } else if (this.chunks[0] === Opcode.map.OP_RETURN) { + return 'nulldata' } else { - return 'Strange' + return 'nonstandard'; } } +function isSmallIntOp(opcode) { + return ((opcode == Opcode.map.OP_0) || + ((opcode >= Opcode.map.OP_1) && (opcode <= Opcode.map.OP_16))); +}; + /** * Returns the address corresponding to this output in hash160 form. * Assumes strange scripts are P2SH @@ -139,11 +202,11 @@ Script.prototype.getOutType = function() { Script.prototype.toScriptHash = function() { var outType = this.getOutType() - if (outType == 'Pubkey') { + if (outType == 'pubkeyhash') { return this.chunks[2] } - if (outType == 'P2SH') { + if (outType == 'scripthash') { return crypto.hash160(this.buffer) } @@ -154,11 +217,11 @@ Script.prototype.toScriptHash = function() { Script.prototype.getToAddress = function() { var outType = this.getOutType() - if (outType == 'Pubkey') { + if (outType == 'pubkeyhash') { return new Address(this.chunks[2]) } - if (outType == 'P2SH') { + if (outType == 'scripthash') { return new Address(this.chunks[1], 5) } @@ -197,23 +260,23 @@ Script.prototype.getFromAddress = function(){ */ Script.prototype.getInType = function() { if (this.chunks.length == 1 && - Array.isArray(this.chunks[0])) { + Array.isArray(this.chunks[0])) { // Direct IP to IP transactions only have the signature in their scriptSig. // TODO: We could also check that the length of the data is correct. - return 'Pubkey' + return 'pubkey'; } else if (this.chunks.length == 2 && - Array.isArray(this.chunks[0]) && - Array.isArray(this.chunks[1])) { - return 'Address' + Array.isArray(this.chunks[0]) && + Array.isArray(this.chunks[1])) { + return 'pubkeyhash'; } else if (this.chunks[0] == Opcode.map.OP_0 && - this.chunks.slice(1).reduce(function(t, chunk, i) { - return t && Array.isArray(chunk) && (chunk[0] == 48 || i == this.chunks.length - 1) - }, true)) { - return 'Multisig' + this.chunks.slice(1).reduce(function(t, chunk, i) { + return t && Array.isArray(chunk) && (chunk[0] == 48 || i == this.chunks.length - 1); + }, true)) { + return 'multisig'; } else { - return 'Strange' + return 'nonstandard'; } -} +}; /** * Returns the affected public key for this input. @@ -230,9 +293,9 @@ Script.prototype.getInType = function() { */ Script.prototype.simpleInPubKey = function() { switch (this.getInType()) { - case 'Address': + case 'pubkeyhash': return this.chunks[1] - case 'Pubkey': + case 'pubkey': // TODO: Theoretically, we could recover the pubkey from the sig here. // See https://bitcointalk.org/?topic=6430.0 throw new Error('Script does not contain pubkey') diff --git a/test/script.js b/test/script.js index 6a611ac..de2aec6 100644 --- a/test/script.js +++ b/test/script.js @@ -33,19 +33,19 @@ describe('Script', function() { describe('getOutType', function() { it('works for p2sh', function() { var script = Script.fromHex(p2shScriptPubKey) - assert.equal(script.getOutType(), 'P2SH') + assert.equal(script.getOutType(), 'scripthash') }) - it('works for pubkey', function() { + it('works for pubkeyhash', function() { var script = Script.fromHex(pubkeyScriptPubKey) - assert.equal(script.getOutType(), 'Pubkey') + assert.equal(script.getOutType(), 'pubkeyhash') }) }) describe('getInType', function() { it('works for address', function() { var script = Script.fromHex(addressScriptSig) - assert.equal(script.getInType(), 'Address') + assert.equal(script.getInType(), 'pubkeyhash') }) }) From 92cff2fb43b590b952ed67c3062258a1a45ecd1b Mon Sep 17 00:00:00 2001 From: sidazhang Date: Sun, 13 Apr 2014 14:32:22 -0700 Subject: [PATCH 2/2] Test vectors for nulldata (OP_RETURN) and multisig --- test/script.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/script.js b/test/script.js index de2aec6..b06018c 100644 --- a/test/script.js +++ b/test/script.js @@ -14,6 +14,17 @@ describe('Script', function() { p2shScriptPubKey = "a914e8c300c87986efa84c37c0519929019ef86eb5b487" pubkeyScriptPubKey = "76a9145a3acbc7bbcc97c5ff16f5909c9d7d3fadb293a888ac" addressScriptSig = "48304502206becda98cecf7a545d1a640221438ff8912d9b505ede67e0138485111099f696022100ccd616072501310acba10feb97cecc918e21c8e92760cd35144efec7622938f30141040cd2d2ce17a1e9b2b3b2cb294d40eecf305a25b7e7bfdafae6bb2639f4ee399b3637706c3d377ec4ab781355add443ae864b134c5e523001c442186ea60f0eb8" + + // https://helloblock.io/transactions/09dd94f2c85262173da87a745a459007bb1eed6eeb6bfa238a0cd91a16cf7790 + validMultisigScript = '5121032487c2a32f7c8d57d2a93906a6457afd00697925b0e6e145d89af6d3bca330162102308673d16987eaa010e540901cc6fe3695e758c19f46ce604e174dac315e685a52ae' + + // https://helloblock.io/transactions/dfa8ff97f33cb83dbaa22ed3a99883218d6afd681d486b374496d145b39a63b7 + // asm: "0 0 0 OP_CHECKMULTISIG" + invalidMultisigScript = '000000ae' + + // https://helloblock.io/transactions/5e9be7fb36ee49ce84bee4c8ef38ad0efc0608b78dae1c2c99075297ef527890 + // op_return + opreturnScript = '6a2606deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474' }) describe('constructor', function() { @@ -40,6 +51,21 @@ describe('Script', function() { var script = Script.fromHex(pubkeyScriptPubKey) assert.equal(script.getOutType(), 'pubkeyhash') }) + + it(' > Supports Multisig', function() { + var script = Script.fromHex(validMultisigScript) + assert.equal(script.getOutType(), 'multisig') + }) + + it(' > Supports invalid Multisig', function() { + var script = Script.fromHex(invalidMultisigScript) + assert.equal(script.getOutType(), 'nonstandard') + }) + + it(' > Supports null_data (OP_RETURN)', function() { + var script = Script.fromHex(opreturnScript) + assert.equal(script.getOutType(), 'nulldata') + }) }) describe('getInType', function() {