@ -1,87 +1,165 @@
var assert = require ( 'assert' )
var opcodes = require ( './opcodes' )
// FIXME: use ECPubKey, currently the circular dependency breaks everything.
//
// Solutions:
// * Remove ECPubKey.getAddress
// - Minimal change, but likely unpopular
// * Move all script related functionality out of Address
// - Means a lot of changes to Transaction/Wallet
// * Ignore it (existing solution)
// * Some form of hackery with commonjs
//
var ecurve = require ( 'ecurve' )
var curve = ecurve . getCurveByName ( 'secp256k1' )
var ECSignature = require ( './ecsignature' )
var Script = require ( './script' )
function classifyOutput ( script ) {
assert ( script instanceof Script , 'Expected Script, got ' , script )
if ( isPubkeyhash . call ( script ) ) {
if ( isPubKeyHashOutput . call ( script ) ) {
return 'pubkeyhash'
} else if ( isPubkey . call ( script ) ) {
return 'pubkey'
} else if ( isScripthash . call ( script ) ) {
} else if ( isScriptHashOutput . call ( script ) ) {
return 'scripthash'
} else if ( isMultisig . call ( script ) ) {
} else if ( isMultisigOutput . call ( script ) ) {
return 'multisig'
} else if ( isNulldata . call ( script ) ) {
} else if ( isPubKeyOutput . call ( script ) ) {
return 'pubkey'
} else if ( isNulldataOutput . call ( script ) ) {
return 'nulldata'
} else {
return 'nonstandard'
}
}
function classifyInput ( script ) {
function classifyInput ( script , checkScriptHash ) {
assert ( script instanceof Script , 'Expected Script, got ' , script )
if ( checkScriptHash === undefined ) checkScriptHash = true
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 ] ) ) {
if ( isPubKeyHashInput . call ( script ) ) {
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 ) ) {
} else if ( checkScriptHash && isScriptHashInput . call ( script ) ) {
return 'scripthash'
} else if ( isMultisigInput . call ( script ) ) {
return 'multisig'
} else if ( isPubKeyInput . call ( script ) ) {
return 'pubkey'
} else {
return 'nonstandard'
}
}
function isPubkeyhash ( ) {
return this . chunks . length == 5 &&
this . chunks [ 0 ] == opcodes . OP_DUP &&
this . chunks [ 1 ] == opcodes . OP_HASH160 &&
function isCanonicalPubKey ( buffer ) {
if ( ! Buffer . isBuffer ( buffer ) ) return false
try {
// FIXME: boo
ecurve . Point . decodeFrom ( curve , buffer )
} catch ( e ) {
if ( ! ( e . message . match ( /Invalid sequence (length|tag)/ ) ) ) throw e
return false
}
return true
}
function isCanonicalSignature ( buffer ) {
if ( ! Buffer . isBuffer ( buffer ) ) return false
try {
ECSignature . parseScriptSignature ( buffer )
} catch ( e ) {
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/ ) ) ) throw e
return false
}
return true
}
function isPubKeyHashInput ( ) {
return this . chunks . length === 2 &&
isCanonicalSignature ( this . chunks [ 0 ] ) &&
isCanonicalPubKey ( this . chunks [ 1 ] )
}
function isPubKeyHashOutput ( ) {
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
this . chunks [ 3 ] === opcodes . OP_EQUALVERIFY &&
this . chunks [ 4 ] === opcodes . OP_CHECKSIG
}
function isPubkey ( ) {
function isPubKeyInput ( ) {
return this . chunks . length === 1 &&
isCanonicalSignature ( this . chunks [ 0 ] )
}
function isPubKeyOutput ( ) {
return this . chunks . length === 2 &&
Buffer . isBuffer ( this . chunks [ 0 ] ) &&
isCanonicalPubKey ( 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 &&
function isScriptHashInput ( ) {
if ( this . chunks . length < 2 ) return false
var lastChunk = this . chunks [ this . chunks . length - 1 ]
if ( ! Buffer . isBuffer ( lastChunk ) ) return false
var scriptSig = Script . fromChunks ( this . chunks . slice ( 0 , - 1 ) )
var scriptPubKey = Script . fromBuffer ( lastChunk )
return classifyInput ( scriptSig , false ) === classifyOutput ( scriptPubKey )
}
function isScriptHashOutput ( ) {
return this . chunks . length === 3 &&
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 ( ) {
this . chunks [ 2 ] === opcodes . OP_EQUAL
}
function isMultisigInput ( ) {
return this . chunks [ 0 ] === opcodes . OP_0 &&
this . chunks . slice ( 1 ) . every ( isCanonicalSignature )
}
function isMultisigOutput ( ) {
if ( this . chunks < 4 ) return false
if ( this . chunks [ this . chunks . length - 1 ] !== opcodes . OP_CHECKMULTISIG ) return false
var mS = this . chunks [ 0 ]
if ( ! isSmallIntOp ( mS ) ) return false
var nS = this . chunks [ this . chunks . length - 2 ]
if ( ! isSmallIntOp ( nS ) ) return false
var m = mS - ( opcodes . OP_1 - 1 )
var n = nS - ( opcodes . OP_1 - 1 )
if ( n < m ) return false
if ( n === 0 ) return false
if ( m > ( this . chunks . length - 3 ) ) return false
return this . chunks . slice ( 1 , - 2 ) . every ( isCanonicalPubKey )
}
function isNulldataOutput ( ) {
return this . chunks [ 0 ] === opcodes . OP_RETURN
}
function isSmallIntOp ( opcode ) {
return ( ( opcode == opcodes . OP_0 ) || ( ( opcode >= opcodes . OP_1 ) && ( opcode <= opcodes . OP_16 ) ) )
if ( Buffer . isBuffer ( opcode ) ) return false
return ( ( opcode === opcodes . OP_0 ) || ( ( opcode >= opcodes . OP_1 ) && ( opcode <= opcodes . OP_16 ) ) )
}
// Standard Script Templates
@ -160,7 +238,7 @@ function scriptHashInput(scriptSig, scriptPubKey) {
// OP_0 [signatures ...]
function multisigInput ( signatures , scriptPubKey ) {
if ( scriptPubKey ) {
assert ( isMultisig . call ( scriptPubKey ) )
assert ( isMultisigOutput . call ( scriptPubKey ) )
var m = scriptPubKey . chunks [ 0 ]
var k = m - ( opcodes . OP_1 - 1 )