bitcoinjs-lib/src/script.js

401 lines
9.7 KiB
JavaScript
Raw Normal View History

var bip66 = require('bip66')
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 OPS = require('./opcodes')
var REVERSE_OPS = (function () {
var result = {}
for (var op in OPS) {
var code = OPS[op]
result[code] = op
}
return result
})()
2015-08-14 03:16:17 +02:00
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) {
2015-08-25 10:08:32 +02:00
var opcode = buffer[i]
2015-08-14 03:16:17 +02:00
// 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
}
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
if (buffer.length < 33) return false
switch (buffer[0]) {
case 0x02:
case 0x03:
return buffer.length === 33
case 0x04:
return buffer.length === 65
2014-06-24 09:32:23 +02:00
}
return false
2014-06-24 09:32:23 +02:00
}
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
if (!isDefinedHashType(buffer[buffer.length - 1])) return false
2014-06-24 09:32:23 +02:00
return bip66.check(buffer.slice(0, -1))
2014-06-24 09:32:23 +02:00
}
function isDefinedHashType (hashType) {
var hashTypeMod = hashType & ~0x80
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
return hashTypeMod > 0x00 && hashTypeMod < 0x04
}
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])
}
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
}
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])
}
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
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)
// 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
}
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
}
// allowIncomplete is to account for combining signatures
// See https://github.com/bitcoin/bitcoin/blob/f425050546644a36b0b8e0eb2f6934a3e0f6f80f/src/script/sign.cpp#L195-L197
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
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-08-18 02:17:04 +02:00
return chunks.slice(1).every(isCanonicalSignature)
2014-06-24 09:32:23 +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-10-02 04:51:34 +02:00
if (!types.Number(mOp)) return false
2015-08-14 03:16:17 +02:00
if (mOp < OPS.OP_1) return false
if (mOp > OPS.OP_16) return false
2015-08-18 02:17:04 +02:00
var nOp = chunks[chunks.length - 2]
2015-10-02 04:51:34 +02:00
if (!types.Number(nOp)) return false
2015-08-14 03:16:17 +02:00
if (nOp < OPS.OP_1) return false
if (nOp > OPS.OP_16) return false
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)
2015-10-01 22:12:38 +02:00
if (n !== pubKeys.length) return false
return pubKeys.every(isCanonicalPubKey)
2014-06-24 09:32:23 +02:00
}
function isNullDataOutput (script) {
2015-08-14 03:16:17 +02:00
var chunks = decompile(script)
return chunks[0] === OPS.OP_RETURN
}
function classifyOutput (script) {
2015-08-14 03:16:17 +02:00
var chunks = decompile(script)
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
}
function classifyInput (script, allowIncomplete) {
2015-08-14 03:16:17 +02:00
var chunks = decompile(script)
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)) {
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
}
// 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])
}
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
2015-08-18 02:17:04 +02:00
function pubKeyHashOutput (pubKeyHash) {
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])
}
// OP_HASH160 {scriptHash} OP_EQUAL
2015-08-18 02:17:04 +02:00
function scriptHashOutput (scriptHash) {
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])
}
// 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)
var n = pubKeys.length
2015-08-11 09:07:13 +02:00
if (n < m) throw new Error('Not enough pubKeys provided')
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
))
}
// {signature}
2015-02-23 00:36:57 +01:00
function pubKeyInput (signature) {
2015-08-11 09:01:47 +02:00
typeforce(types.Buffer, signature)
2015-08-14 03:16:17 +02:00
return compile([signature])
}
// {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)
2015-08-14 03:16:17 +02:00
return compile([signature, pubKey])
}
// <scriptSig> {serialized scriptPubKey script}
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(
scriptSigChunks,
serializedScriptPubKey
2015-08-18 02:17:04 +02:00
))
}
// OP_0 [signatures ...]
2015-02-23 00:36:57 +01:00
function multisigInput (signatures, scriptPubKey) {
if (scriptPubKey) {
2015-08-14 03:16:17 +02:00
var chunks = decompile(scriptPubKey)
if (!isMultisigOutput(chunks)) throw new Error('Expected multisig scriptPubKey')
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)
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')
}
2015-08-14 03:16:17 +02:00
return compile([].concat(OPS.OP_0, signatures))
}
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])
}
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,
isDefinedHashType: isDefinedHashType,
2014-11-28 02:50:37 +01:00
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
}