2015-08-14 03:16:17 +02:00
var bufferutils = require ( './bufferutils' )
2015-08-11 09:01:47 +02:00
var typeforce = require ( 'typeforce' )
var types = require ( './types' )
2014-06-24 09:32:23 +02:00
2015-08-14 03:16:17 +02:00
var ECSignature = require ( './ecsignature' )
2014-06-24 09:32:23 +02:00
var ecurve = require ( 'ecurve' )
var curve = ecurve . getCurveByName ( 'secp256k1' )
2015-08-14 03:16:17 +02:00
var OPS = require ( './opcodes' )
var REVERSE _OPS = [ ]
for ( var op in OPS ) {
var code = OPS [ op ]
REVERSE _OPS [ code ] = op
}
function toASM ( chunks ) {
if ( types . Buffer ( chunks ) ) {
chunks = decompile ( chunks )
}
return chunks . map ( function ( chunk ) {
2015-08-18 01:12:02 +02:00
// data?
if ( Buffer . isBuffer ( chunk ) ) return chunk . toString ( 'hex' )
2015-08-14 03:16:17 +02:00
2015-08-18 01:12:02 +02:00
// opcode!
return REVERSE _OPS [ chunk ]
2015-08-14 03:16:17 +02:00
} ) . join ( ' ' )
}
function fromASM ( asm ) {
typeforce ( types . String , asm )
2015-08-18 01:12:02 +02:00
return compile ( asm . split ( ' ' ) . map ( function ( chunkStr ) {
// opcode?
if ( OPS [ chunkStr ] !== undefined ) return OPS [ chunkStr ]
2015-08-14 03:16:17 +02:00
2015-08-18 01:12:02 +02:00
// data!
return new Buffer ( chunkStr , 'hex' )
} ) )
2015-08-14 03:16:17 +02:00
}
function compile ( chunks ) {
// TODO: remove me
if ( types . Buffer ( chunks ) ) return chunks
typeforce ( types . Array , chunks )
var bufferSize = chunks . reduce ( function ( accum , chunk ) {
// data chunk
if ( Buffer . isBuffer ( chunk ) ) {
return accum + bufferutils . pushDataSize ( chunk . length ) + chunk . length
}
// opcode
return accum + 1
} , 0.0 )
var buffer = new Buffer ( bufferSize )
var offset = 0
chunks . forEach ( function ( chunk ) {
// data chunk
if ( Buffer . isBuffer ( chunk ) ) {
offset += bufferutils . writePushDataInt ( buffer , chunk . length , offset )
chunk . copy ( buffer , offset )
offset += chunk . length
// opcode
} else {
buffer . writeUInt8 ( chunk , offset )
offset += 1
}
} )
if ( offset !== buffer . length ) throw new Error ( 'Could not decode chunks' )
return buffer
}
function decompile ( buffer ) {
// TODO: remove me
if ( types . Array ( buffer ) ) return buffer
typeforce ( types . Buffer , buffer )
var chunks = [ ]
var i = 0
while ( i < buffer . length ) {
var opcode = buffer . readUInt8 ( i )
// data chunk
if ( ( opcode > OPS . OP _0 ) && ( opcode <= OPS . OP _PUSHDATA4 ) ) {
var d = bufferutils . readPushDataInt ( buffer , i )
// did reading a pushDataInt fail? empty script
if ( d === null ) return [ ]
i += d . size
// attempt to read too much data? empty script
if ( i + d . number > buffer . length ) return [ ]
var data = buffer . slice ( i , i + d . number )
i += d . number
chunks . push ( data )
// opcode
} else {
chunks . push ( opcode )
i += 1
}
}
return chunks
}
2014-06-12 13:14:22 +02:00
2015-02-23 00:36:57 +01:00
function isCanonicalPubKey ( buffer ) {
2014-06-24 09:32:23 +02:00
if ( ! Buffer . isBuffer ( buffer ) ) return false
try {
ecurve . Point . decodeFrom ( curve , buffer )
} catch ( e ) {
2015-05-20 16:46:36 +02:00
if ( ! ( e . message . match ( /Invalid sequence (length|tag)/ ) ) ) {
2015-02-23 00:36:57 +01:00
throw e
2015-05-20 16:46:36 +02:00
}
2014-06-24 09:32:23 +02:00
return false
}
return true
}
2015-02-23 00:36:57 +01:00
function isCanonicalSignature ( buffer ) {
2014-06-24 09:32:23 +02:00
if ( ! Buffer . isBuffer ( buffer ) ) return false
try {
ECSignature . parseScriptSignature ( buffer )
2015-02-23 00:36:57 +01:00
} catch ( e ) {
2015-03-02 03:31:03 +01:00
if ( ! ( e . message . match ( /Not a DER sequence|Invalid sequence length|Expected a DER integer|R length is zero|S length is zero|R value excessively padded|S value excessively padded|R value is negative|S value is negative|Invalid hashType/ ) ) ) {
2015-02-23 00:36:57 +01:00
throw e
2015-03-02 03:31:03 +01:00
}
2014-06-24 09:32:23 +02:00
return false
}
return true
}
2015-08-14 02:31:48 +02:00
function isPubKeyHashInput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-18 02:17:04 +02:00
return chunks . length === 2 &&
isCanonicalSignature ( chunks [ 0 ] ) &&
isCanonicalPubKey ( chunks [ 1 ] )
2014-06-12 13:14:22 +02:00
}
2015-08-14 02:31:48 +02:00
function isPubKeyHashOutput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-07 08:30:24 +02:00
2015-08-18 02:17:04 +02:00
return chunks . length === 5 &&
2015-08-14 03:16:17 +02:00
chunks [ 0 ] === OPS . OP _DUP &&
chunks [ 1 ] === OPS . OP _HASH160 &&
2015-08-18 02:17:04 +02:00
Buffer . isBuffer ( chunks [ 2 ] ) &&
chunks [ 2 ] . length === 20 &&
2015-08-14 03:16:17 +02:00
chunks [ 3 ] === OPS . OP _EQUALVERIFY &&
chunks [ 4 ] === OPS . OP _CHECKSIG
2014-06-24 09:32:23 +02:00
}
2015-08-14 02:31:48 +02:00
function isPubKeyInput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-07 08:30:24 +02:00
2015-08-18 02:17:04 +02:00
return chunks . length === 1 &&
isCanonicalSignature ( chunks [ 0 ] )
2014-06-12 13:14:22 +02:00
}
2015-08-14 02:31:48 +02:00
function isPubKeyOutput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-18 02:17:04 +02:00
return chunks . length === 2 &&
isCanonicalPubKey ( chunks [ 0 ] ) &&
2015-08-14 03:16:17 +02:00
chunks [ 1 ] === OPS . OP _CHECKSIG
2015-08-18 02:17:04 +02:00
}
2015-08-07 08:30:24 +02:00
2015-08-14 02:31:48 +02:00
function isScriptHashInput ( script , allowIncomplete ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-18 02:17:04 +02:00
if ( chunks . length < 2 ) return false
2014-06-24 09:32:23 +02:00
2015-08-18 02:17:04 +02:00
var lastChunk = chunks [ chunks . length - 1 ]
2014-06-24 09:32:23 +02:00
if ( ! Buffer . isBuffer ( lastChunk ) ) return false
2015-08-18 02:17:04 +02:00
var scriptSigChunks = chunks . slice ( 0 , - 1 )
2015-08-14 03:16:17 +02:00
var redeemScriptChunks = decompile ( lastChunk )
2015-02-15 16:39:57 +01:00
2015-03-04 11:00:07 +01:00
// is redeemScript a valid script?
2015-08-18 02:17:04 +02:00
if ( redeemScriptChunks . length === 0 ) return false
2014-06-24 09:32:23 +02:00
2015-08-18 02:17:04 +02:00
return classifyInput ( scriptSigChunks , allowIncomplete ) === classifyOutput ( redeemScriptChunks )
2014-06-24 09:32:23 +02:00
}
2015-08-14 02:31:48 +02:00
function isScriptHashOutput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-07 08:30:24 +02:00
2015-08-18 02:17:04 +02:00
return chunks . length === 3 &&
2015-08-14 03:16:17 +02:00
chunks [ 0 ] === OPS . OP _HASH160 &&
2015-08-18 02:17:04 +02:00
Buffer . isBuffer ( chunks [ 1 ] ) &&
chunks [ 1 ] . length === 20 &&
2015-08-14 03:16:17 +02:00
chunks [ 2 ] === OPS . OP _EQUAL
2014-06-24 09:32:23 +02:00
}
2015-02-11 04:01:20 +01:00
// allowIncomplete is to account for combining signatures
// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197
2015-08-14 02:31:48 +02:00
function isMultisigInput ( script , allowIncomplete ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-18 02:17:04 +02:00
if ( chunks . length < 2 ) return false
2015-08-14 03:16:17 +02:00
if ( chunks [ 0 ] !== OPS . OP _0 ) return false
2015-02-11 04:01:20 +01:00
if ( allowIncomplete ) {
2015-08-18 02:17:04 +02:00
return chunks . slice ( 1 ) . every ( function ( chunk ) {
2015-08-14 03:16:17 +02:00
return chunk === OPS . OP _0 || isCanonicalSignature ( chunk )
2015-02-11 04:01:20 +01:00
} )
}
2015-08-18 02:17:04 +02:00
return chunks . slice ( 1 ) . every ( isCanonicalSignature )
2014-06-24 09:32:23 +02:00
}
2015-08-14 02:31:48 +02:00
function isMultisigOutput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-18 02:17:04 +02:00
if ( chunks . length < 4 ) return false
2015-08-14 03:16:17 +02:00
if ( chunks [ chunks . length - 1 ] !== OPS . OP _CHECKMULTISIG ) return false
2014-06-24 09:32:23 +02:00
2015-08-18 02:17:04 +02:00
var mOp = chunks [ 0 ]
2015-08-14 03:16:17 +02:00
if ( mOp === OPS . OP _0 ) return false
if ( mOp < OPS . OP _1 ) return false
if ( mOp > OPS . OP _16 ) return false
2014-06-26 08:22:29 +02:00
2015-08-18 02:17:04 +02:00
var nOp = chunks [ chunks . length - 2 ]
2015-08-14 03:16:17 +02:00
if ( nOp === OPS . OP _0 ) return false
if ( nOp < OPS . OP _1 ) return false
if ( nOp > OPS . OP _16 ) return false
2014-06-26 08:22:29 +02:00
2015-08-14 03:16:17 +02:00
var m = mOp - ( OPS . OP _1 - 1 )
var n = nOp - ( OPS . OP _1 - 1 )
2014-06-24 09:32:23 +02:00
if ( n < m ) return false
2015-08-18 02:17:04 +02:00
var pubKeys = chunks . slice ( 1 , - 2 )
2014-06-26 08:22:29 +02:00
if ( n < pubKeys . length ) return false
return pubKeys . every ( isCanonicalPubKey )
2014-06-24 09:32:23 +02:00
}
2015-08-14 02:31:48 +02:00
function isNullDataOutput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
return chunks [ 0 ] === OPS . OP _RETURN
2014-06-12 13:14:22 +02:00
}
2015-08-14 02:31:48 +02:00
function classifyOutput ( script ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-14 02:31:48 +02:00
2015-08-18 02:17:04 +02:00
if ( isPubKeyHashOutput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'pubkeyhash'
2015-08-18 02:17:04 +02:00
} else if ( isScriptHashOutput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'scripthash'
2015-08-18 02:17:04 +02:00
} else if ( isMultisigOutput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'multisig'
2015-08-18 02:17:04 +02:00
} else if ( isPubKeyOutput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'pubkey'
2015-08-18 02:17:04 +02:00
} else if ( isNullDataOutput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'nulldata'
}
2014-10-13 09:53:07 +02:00
return 'nonstandard'
2014-10-13 09:38:54 +02:00
}
2015-08-14 02:31:48 +02:00
function classifyInput ( script , allowIncomplete ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( script )
2015-08-14 02:31:48 +02:00
2015-08-18 02:17:04 +02:00
if ( isPubKeyHashInput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'pubkeyhash'
2015-08-18 02:17:04 +02:00
} else if ( isMultisigInput ( chunks , allowIncomplete ) ) {
2014-10-13 09:38:54 +02:00
return 'multisig'
2015-08-18 02:17:04 +02:00
} else if ( isScriptHashInput ( chunks , allowIncomplete ) ) {
2015-02-11 04:01:20 +01:00
return 'scripthash'
2015-08-18 02:17:04 +02:00
} else if ( isPubKeyInput ( chunks ) ) {
2014-10-13 09:38:54 +02:00
return 'pubkey'
}
2014-10-13 09:53:07 +02:00
return 'nonstandard'
2014-10-13 09:38:54 +02:00
}
2014-06-12 13:14:22 +02:00
// Standard Script Templates
// {pubKey} OP_CHECKSIG
2015-02-23 00:36:57 +01:00
function pubKeyOutput ( pubKey ) {
2015-08-14 03:16:17 +02:00
return compile ( [ pubKey , OPS . OP _CHECKSIG ] )
2014-06-12 13:14:22 +02:00
}
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
2015-08-18 02:17:04 +02:00
function pubKeyHashOutput ( pubKeyHash ) {
2015-08-18 01:05:23 +02:00
typeforce ( types . Hash160bit , pubKeyHash )
2015-08-18 02:17:04 +02:00
2015-08-14 03:16:17 +02:00
return compile ( [ OPS . OP _DUP , OPS . OP _HASH160 , pubKeyHash , OPS . OP _EQUALVERIFY , OPS . OP _CHECKSIG ] )
2014-06-12 13:14:22 +02:00
}
// OP_HASH160 {scriptHash} OP_EQUAL
2015-08-18 02:17:04 +02:00
function scriptHashOutput ( scriptHash ) {
2015-08-18 01:05:23 +02:00
typeforce ( types . Hash160bit , scriptHash )
2015-08-18 02:17:04 +02:00
2015-08-14 03:16:17 +02:00
return compile ( [ OPS . OP _HASH160 , scriptHash , OPS . OP _EQUAL ] )
2014-06-12 13:14:22 +02:00
}
// m [pubKeys ...] n OP_CHECKMULTISIG
2015-02-23 00:36:57 +01:00
function multisigOutput ( m , pubKeys ) {
2015-08-11 09:01:47 +02:00
typeforce ( types . tuple ( types . Number , [ types . Buffer ] ) , arguments )
2014-09-15 06:21:01 +02:00
2014-06-12 13:14:22 +02:00
var n = pubKeys . length
2015-08-11 09:07:13 +02:00
if ( n < m ) throw new Error ( 'Not enough pubKeys provided' )
2014-06-12 13:14:22 +02:00
2015-08-14 03:16:17 +02:00
return compile ( [ ] . concat (
( OPS . OP _1 - 1 ) + m ,
2015-03-02 06:48:36 +01:00
pubKeys ,
2015-08-14 03:16:17 +02:00
( OPS . OP _1 - 1 ) + n ,
OPS . OP _CHECKMULTISIG
2015-08-18 02:17:04 +02:00
) )
2014-06-12 13:14:22 +02:00
}
// {signature}
2015-02-23 00:36:57 +01:00
function pubKeyInput ( signature ) {
2015-08-11 09:01:47 +02:00
typeforce ( types . Buffer , signature )
2014-06-12 13:14:22 +02:00
2015-08-14 03:16:17 +02:00
return compile ( [ signature ] )
2014-06-12 13:14:22 +02:00
}
// {signature} {pubKey}
2015-02-23 00:36:57 +01:00
function pubKeyHashInput ( signature , pubKey ) {
2015-08-11 09:01:47 +02:00
typeforce ( types . tuple ( types . Buffer , types . Buffer ) , arguments )
2014-06-12 13:14:22 +02:00
2015-08-14 03:16:17 +02:00
return compile ( [ signature , pubKey ] )
2014-06-12 13:14:22 +02:00
}
// <scriptSig> {serialized scriptPubKey script}
2015-08-14 02:31:48 +02:00
function scriptHashInput ( scriptSig , scriptPubKey ) {
2015-08-14 03:16:17 +02:00
var scriptSigChunks = decompile ( scriptSig )
var serializedScriptPubKey = compile ( scriptPubKey )
2015-08-18 02:17:04 +02:00
2015-08-14 03:16:17 +02:00
return compile ( [ ] . concat (
2015-08-14 02:31:48 +02:00
scriptSigChunks ,
serializedScriptPubKey
2015-08-18 02:17:04 +02:00
) )
2014-06-12 13:14:22 +02:00
}
// OP_0 [signatures ...]
2015-02-23 00:36:57 +01:00
function multisigInput ( signatures , scriptPubKey ) {
2014-06-12 13:14:22 +02:00
if ( scriptPubKey ) {
2015-08-14 03:16:17 +02:00
var chunks = decompile ( scriptPubKey )
2015-08-14 02:31:48 +02:00
if ( ! isMultisigOutput ( chunks ) ) throw new Error ( 'Expected multisig scriptPubKey' )
2014-06-12 13:14:22 +02:00
2015-08-14 02:31:48 +02:00
var mOp = chunks [ 0 ]
var nOp = chunks [ chunks . length - 2 ]
2015-08-14 03:16:17 +02:00
var m = mOp - ( OPS . OP _1 - 1 )
var n = nOp - ( OPS . OP _1 - 1 )
2014-08-30 05:02:02 +02:00
2015-08-11 09:07:13 +02:00
if ( signatures . length < m ) throw new Error ( 'Not enough signatures provided' )
if ( signatures . length > n ) throw new Error ( 'Too many signatures provided' )
2014-06-12 13:14:22 +02:00
}
2015-08-14 03:16:17 +02:00
return compile ( [ ] . concat ( OPS . OP _0 , signatures ) )
2014-06-12 13:14:22 +02:00
}
2015-02-23 00:36:57 +01:00
function nullDataOutput ( data ) {
2015-08-14 03:16:17 +02:00
return compile ( [ OPS . OP _RETURN , data ] )
2014-10-19 03:29:40 +02:00
}
2014-06-12 13:14:22 +02:00
module . exports = {
2015-08-14 03:16:17 +02:00
compile : compile ,
decompile : decompile ,
fromASM : fromASM ,
toASM : toASM ,
2014-11-28 02:50:37 +01:00
isCanonicalPubKey : isCanonicalPubKey ,
isCanonicalSignature : isCanonicalSignature ,
isPubKeyHashInput : isPubKeyHashInput ,
isPubKeyHashOutput : isPubKeyHashOutput ,
isPubKeyInput : isPubKeyInput ,
isPubKeyOutput : isPubKeyOutput ,
isScriptHashInput : isScriptHashInput ,
isScriptHashOutput : isScriptHashOutput ,
isMultisigInput : isMultisigInput ,
isMultisigOutput : isMultisigOutput ,
isNullDataOutput : isNullDataOutput ,
2014-06-13 01:58:52 +02:00
classifyOutput : classifyOutput ,
2014-11-28 02:50:37 +01:00
classifyInput : classifyInput ,
pubKeyOutput : pubKeyOutput ,
2014-06-13 01:58:52 +02:00
pubKeyHashOutput : pubKeyHashOutput ,
2014-11-28 02:50:37 +01:00
scriptHashOutput : scriptHashOutput ,
multisigOutput : multisigOutput ,
2014-06-13 01:58:52 +02:00
pubKeyInput : pubKeyInput ,
2014-11-28 02:50:37 +01:00
pubKeyHashInput : pubKeyHashInput ,
2014-06-13 01:58:52 +02:00
scriptHashInput : scriptHashInput ,
2014-11-28 02:50:37 +01:00
multisigInput : multisigInput ,
nullDataOutput : nullDataOutput
2014-06-12 13:14:22 +02:00
}