1 | var bip66 = require('bip66')
|
2 | var bufferutils = require('./bufferutils')
|
3 | var typeforce = require('typeforce')
|
4 | var types = require('./types')
|
5 |
|
6 | var OPS = require('./opcodes.json')
|
7 | var REVERSE_OPS = (function () {
|
8 | var result = {}
|
9 | for (var op in OPS) {
|
10 | var code = OPS[op]
|
11 | result[code] = op
|
12 | }
|
13 | return result
|
14 | })()
|
15 |
|
16 | var OP_INT_BASE = OPS.OP_RESERVED
|
17 |
|
18 | function compile (chunks) {
|
19 |
|
20 | if (Buffer.isBuffer(chunks)) return chunks
|
21 |
|
22 | typeforce(types.Array, chunks)
|
23 |
|
24 | var bufferSize = chunks.reduce(function (accum, chunk) {
|
25 |
|
26 | if (Buffer.isBuffer(chunk)) {
|
27 |
|
28 | if (chunk.length === 1 && chunk[0] >= 1 && chunk[0] <= 16) {
|
29 | return accum + 1
|
30 | }
|
31 |
|
32 | return accum + bufferutils.pushDataSize(chunk.length) + chunk.length
|
33 | }
|
34 |
|
35 |
|
36 | return accum + 1
|
37 | }, 0.0)
|
38 |
|
39 | var buffer = new Buffer(bufferSize)
|
40 | var offset = 0
|
41 |
|
42 | chunks.forEach(function (chunk) {
|
43 |
|
44 | if (Buffer.isBuffer(chunk)) {
|
45 |
|
46 | if (chunk.length === 1 && chunk[0] >= 1 && chunk[0] <= 16) {
|
47 | var opcode = OP_INT_BASE + chunk[0]
|
48 | buffer.writeUInt8(opcode, offset)
|
49 | offset += 1
|
50 | return
|
51 | }
|
52 |
|
53 | offset += bufferutils.writePushDataInt(buffer, chunk.length, offset)
|
54 |
|
55 | chunk.copy(buffer, offset)
|
56 | offset += chunk.length
|
57 |
|
58 |
|
59 | } else {
|
60 | buffer.writeUInt8(chunk, offset)
|
61 | offset += 1
|
62 | }
|
63 | })
|
64 |
|
65 | if (offset !== buffer.length) throw new Error('Could not decode chunks')
|
66 | return buffer
|
67 | }
|
68 |
|
69 | function decompile (buffer) {
|
70 |
|
71 | if (types.Array(buffer)) return buffer
|
72 |
|
73 | typeforce(types.Buffer, buffer)
|
74 |
|
75 | var chunks = []
|
76 | var i = 0
|
77 |
|
78 | while (i < buffer.length) {
|
79 | var opcode = buffer[i]
|
80 |
|
81 |
|
82 | if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) {
|
83 | var d = bufferutils.readPushDataInt(buffer, i)
|
84 |
|
85 |
|
86 | if (d === null) return []
|
87 | i += d.size
|
88 |
|
89 |
|
90 | if (i + d.number > buffer.length) return []
|
91 |
|
92 | var data = buffer.slice(i, i + d.number)
|
93 | i += d.number
|
94 |
|
95 | chunks.push(data)
|
96 |
|
97 |
|
98 | } else {
|
99 | chunks.push(opcode)
|
100 |
|
101 | i += 1
|
102 | }
|
103 | }
|
104 |
|
105 | return chunks
|
106 | }
|
107 |
|
108 | function toASM (chunks) {
|
109 | if (Buffer.isBuffer(chunks)) {
|
110 | chunks = decompile(chunks)
|
111 | }
|
112 |
|
113 | return chunks.map(function (chunk) {
|
114 |
|
115 | if (Buffer.isBuffer(chunk)) return chunk.toString('hex')
|
116 |
|
117 |
|
118 | return REVERSE_OPS[chunk]
|
119 | }).join(' ')
|
120 | }
|
121 |
|
122 | function fromASM (asm) {
|
123 | typeforce(types.String, asm)
|
124 |
|
125 | return compile(asm.split(' ').map(function (chunkStr) {
|
126 |
|
127 | if (OPS[chunkStr] !== undefined) return OPS[chunkStr]
|
128 |
|
129 |
|
130 | return new Buffer(chunkStr, 'hex')
|
131 | }))
|
132 | }
|
133 |
|
134 | function isCanonicalPubKey (buffer) {
|
135 | if (!Buffer.isBuffer(buffer)) return false
|
136 | if (buffer.length < 33) return false
|
137 |
|
138 | switch (buffer[0]) {
|
139 | case 0x02:
|
140 | case 0x03:
|
141 | return buffer.length === 33
|
142 | case 0x04:
|
143 | return buffer.length === 65
|
144 | }
|
145 |
|
146 | return false
|
147 | }
|
148 |
|
149 | function isDefinedHashType (hashType) {
|
150 | var hashTypeMod = hashType & ~0x80
|
151 |
|
152 |
|
153 | return hashTypeMod > 0x00 && hashTypeMod < 0x04
|
154 | }
|
155 |
|
156 | function isCanonicalSignature (buffer) {
|
157 | if (!Buffer.isBuffer(buffer)) return false
|
158 | if (!isDefinedHashType(buffer[buffer.length - 1])) return false
|
159 |
|
160 | return bip66.check(buffer.slice(0, -1))
|
161 | }
|
162 |
|
163 | function isPubKeyHashInput (script) {
|
164 | var chunks = decompile(script)
|
165 |
|
166 | return chunks.length === 2 &&
|
167 | isCanonicalSignature(chunks[0]) &&
|
168 | isCanonicalPubKey(chunks[1])
|
169 | }
|
170 |
|
171 | function isPubKeyHashOutput (script) {
|
172 | var buffer = compile(script)
|
173 |
|
174 | return buffer.length === 25 &&
|
175 | buffer[0] === OPS.OP_DUP &&
|
176 | buffer[1] === OPS.OP_HASH160 &&
|
177 | buffer[2] === 0x14 &&
|
178 | buffer[23] === OPS.OP_EQUALVERIFY &&
|
179 | buffer[24] === OPS.OP_CHECKSIG
|
180 | }
|
181 |
|
182 | function isPubKeyInput (script) {
|
183 | var chunks = decompile(script)
|
184 |
|
185 | return chunks.length === 1 &&
|
186 | isCanonicalSignature(chunks[0])
|
187 | }
|
188 |
|
189 | function isPubKeyOutput (script) {
|
190 | var chunks = decompile(script)
|
191 |
|
192 | return chunks.length === 2 &&
|
193 | isCanonicalPubKey(chunks[0]) &&
|
194 | chunks[1] === OPS.OP_CHECKSIG
|
195 | }
|
196 |
|
197 | function isScriptHashInput (script, allowIncomplete) {
|
198 | var chunks = decompile(script)
|
199 | if (chunks.length < 2) return false
|
200 |
|
201 | var lastChunk = chunks[chunks.length - 1]
|
202 | if (!Buffer.isBuffer(lastChunk)) return false
|
203 |
|
204 | var scriptSigChunks = chunks.slice(0, -1)
|
205 | var redeemScriptChunks = decompile(lastChunk)
|
206 |
|
207 |
|
208 | if (redeemScriptChunks.length === 0) return false
|
209 |
|
210 | return classifyInput(scriptSigChunks, allowIncomplete) === classifyOutput(redeemScriptChunks)
|
211 | }
|
212 |
|
213 | function isScriptHashOutput (script) {
|
214 | var buffer = compile(script)
|
215 |
|
216 | return buffer.length === 23 &&
|
217 | buffer[0] === OPS.OP_HASH160 &&
|
218 | buffer[1] === 0x14 &&
|
219 | buffer[22] === OPS.OP_EQUAL
|
220 | }
|
221 |
|
222 | function isWitnessPubKeyHashOutput (script) {
|
223 | var buffer = compile(script)
|
224 |
|
225 | return buffer.length === 22 &&
|
226 | buffer[0] === OPS.OP_0 &&
|
227 | buffer[1] === 0x14
|
228 | }
|
229 |
|
230 | function isWitnessScriptHashOutput (script) {
|
231 | var buffer = compile(script)
|
232 |
|
233 | return buffer.length === 34 &&
|
234 | buffer[0] === OPS.OP_0 &&
|
235 | buffer[1] === 0x20
|
236 | }
|
237 |
|
238 |
|
239 |
|
240 | function isMultisigInput (script, allowIncomplete) {
|
241 | var chunks = decompile(script)
|
242 | if (chunks.length < 2) return false
|
243 | if (chunks[0] !== OPS.OP_0) return false
|
244 |
|
245 | if (allowIncomplete) {
|
246 | return chunks.slice(1).every(function (chunk) {
|
247 | return chunk === OPS.OP_0 || isCanonicalSignature(chunk)
|
248 | })
|
249 | }
|
250 |
|
251 | return chunks.slice(1).every(isCanonicalSignature)
|
252 | }
|
253 |
|
254 | function isMultisigOutput (script) {
|
255 | var chunks = decompile(script)
|
256 | if (chunks.length < 4) return false
|
257 | if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) return false
|
258 |
|
259 | var mOp = chunks[0]
|
260 | var nOp = chunks[chunks.length - 2]
|
261 |
|
262 | if (!types.Number(mOp)) return false
|
263 | if (!types.Number(nOp)) return false
|
264 |
|
265 | var m = mOp - OP_INT_BASE
|
266 | var n = nOp - OP_INT_BASE
|
267 |
|
268 |
|
269 | if (m <= 0) return false
|
270 | if (m > n) return false
|
271 | if (n > 16) return false
|
272 | if (n !== chunks.length - 3) return false
|
273 |
|
274 | return chunks.slice(1, -2).every(isCanonicalPubKey)
|
275 | }
|
276 |
|
277 | function isNullDataOutput (script) {
|
278 | var chunks = decompile(script)
|
279 | return chunks[0] === OPS.OP_RETURN
|
280 | }
|
281 |
|
282 | function classifyOutput (script) {
|
283 | var chunks = decompile(script)
|
284 |
|
285 | if (isWitnessPubKeyHashOutput(chunks)) {
|
286 | return 'witnesspubkeyhash'
|
287 | } else if (isWitnessScriptHashOutput(chunks)) {
|
288 | return 'witnessscripthash'
|
289 | } else if (isPubKeyHashOutput(chunks)) {
|
290 | return 'pubkeyhash'
|
291 | } else if (isScriptHashOutput(chunks)) {
|
292 | return 'scripthash'
|
293 | } else if (isMultisigOutput(chunks)) {
|
294 | return 'multisig'
|
295 | } else if (isPubKeyOutput(chunks)) {
|
296 | return 'pubkey'
|
297 | } else if (isNullDataOutput(chunks)) {
|
298 | return 'nulldata'
|
299 | }
|
300 |
|
301 | return 'nonstandard'
|
302 | }
|
303 |
|
304 | function classifyInput (script, allowIncomplete) {
|
305 | var chunks = decompile(script)
|
306 |
|
307 | if (isPubKeyHashInput(chunks)) {
|
308 | return 'pubkeyhash'
|
309 | } else if (isMultisigInput(chunks, allowIncomplete)) {
|
310 | return 'multisig'
|
311 | } else if (isScriptHashInput(chunks, allowIncomplete)) {
|
312 | return 'scripthash'
|
313 | } else if (isPubKeyInput(chunks)) {
|
314 | return 'pubkey'
|
315 | }
|
316 |
|
317 | return 'nonstandard'
|
318 | }
|
319 |
|
320 |
|
321 |
|
322 | function pubKeyOutput (pubKey) {
|
323 | return compile([pubKey, OPS.OP_CHECKSIG])
|
324 | }
|
325 |
|
326 |
|
327 | function pubKeyHashOutput (pubKeyHash) {
|
328 | typeforce(types.Hash160bit, pubKeyHash)
|
329 |
|
330 | return compile([OPS.OP_DUP, OPS.OP_HASH160, pubKeyHash, OPS.OP_EQUALVERIFY, OPS.OP_CHECKSIG])
|
331 | }
|
332 |
|
333 |
|
334 | function scriptHashOutput (scriptHash) {
|
335 | typeforce(types.Hash160bit, scriptHash)
|
336 |
|
337 | return compile([OPS.OP_HASH160, scriptHash, OPS.OP_EQUAL])
|
338 | }
|
339 |
|
340 |
|
341 | function multisigOutput (m, pubKeys) {
|
342 | typeforce(types.tuple(types.Number, [types.Buffer]), arguments)
|
343 |
|
344 | var n = pubKeys.length
|
345 | if (n < m) throw new Error('Not enough pubKeys provided')
|
346 |
|
347 | return compile([].concat(
|
348 | OP_INT_BASE + m,
|
349 | pubKeys,
|
350 | OP_INT_BASE + n,
|
351 | OPS.OP_CHECKMULTISIG
|
352 | ))
|
353 | }
|
354 |
|
355 |
|
356 | function witnessPubKeyHashOutput (pubKeyHash) {
|
357 | typeforce(types.Hash160bit, pubKeyHash)
|
358 |
|
359 | return compile([OPS.OP_0, pubKeyHash])
|
360 | }
|
361 |
|
362 |
|
363 | function witnessScriptHashOutput (scriptHash) {
|
364 | typeforce(types.Hash256bit, scriptHash)
|
365 |
|
366 | return compile([OPS.OP_0, scriptHash])
|
367 | }
|
368 |
|
369 |
|
370 | function pubKeyInput (signature) {
|
371 | typeforce(types.Buffer, signature)
|
372 |
|
373 | return compile([signature])
|
374 | }
|
375 |
|
376 |
|
377 | function pubKeyHashInput (signature, pubKey) {
|
378 | typeforce(types.tuple(types.Buffer, types.Buffer), arguments)
|
379 |
|
380 | return compile([signature, pubKey])
|
381 | }
|
382 |
|
383 |
|
384 | function scriptHashInput (scriptSig, scriptPubKey) {
|
385 | var scriptSigChunks = decompile(scriptSig)
|
386 | var serializedScriptPubKey = compile(scriptPubKey)
|
387 |
|
388 | return compile([].concat(
|
389 | scriptSigChunks,
|
390 | serializedScriptPubKey
|
391 | ))
|
392 | }
|
393 |
|
394 |
|
395 | function witnessScriptHashInput (scriptSig, scriptPubKey) {
|
396 | return scriptHashInput(scriptSig, scriptPubKey)
|
397 | }
|
398 |
|
399 |
|
400 | function multisigInput (signatures, scriptPubKey) {
|
401 | if (scriptPubKey) {
|
402 | var chunks = decompile(scriptPubKey)
|
403 | if (!isMultisigOutput(chunks)) throw new Error('Expected multisig scriptPubKey')
|
404 |
|
405 | var mOp = chunks[0]
|
406 | var nOp = chunks[chunks.length - 2]
|
407 | var m = mOp - OP_INT_BASE
|
408 | var n = nOp - OP_INT_BASE
|
409 |
|
410 | if (signatures.length < m) throw new Error('Not enough signatures provided')
|
411 | if (signatures.length > n) throw new Error('Too many signatures provided')
|
412 | }
|
413 |
|
414 | return compile([].concat(OPS.OP_0, signatures))
|
415 | }
|
416 |
|
417 | function nullDataOutput (data) {
|
418 | return compile([OPS.OP_RETURN, data])
|
419 | }
|
420 |
|
421 | module.exports = {
|
422 | compile: compile,
|
423 | decompile: decompile,
|
424 | fromASM: fromASM,
|
425 | toASM: toASM,
|
426 |
|
427 | number: require('./script_number'),
|
428 |
|
429 | isCanonicalPubKey: isCanonicalPubKey,
|
430 | isCanonicalSignature: isCanonicalSignature,
|
431 | isDefinedHashType: isDefinedHashType,
|
432 | isPubKeyHashInput: isPubKeyHashInput,
|
433 | isPubKeyHashOutput: isPubKeyHashOutput,
|
434 | isPubKeyInput: isPubKeyInput,
|
435 | isPubKeyOutput: isPubKeyOutput,
|
436 | isScriptHashInput: isScriptHashInput,
|
437 | isScriptHashOutput: isScriptHashOutput,
|
438 | isWitnessPubKeyHashOutput: isWitnessPubKeyHashOutput,
|
439 | isWitnessScriptHashOutput: isWitnessScriptHashOutput,
|
440 | isMultisigInput: isMultisigInput,
|
441 | isMultisigOutput: isMultisigOutput,
|
442 | isNullDataOutput: isNullDataOutput,
|
443 |
|
444 | classifyOutput: classifyOutput,
|
445 | classifyInput: classifyInput,
|
446 | pubKeyOutput: pubKeyOutput,
|
447 | pubKeyHashOutput: pubKeyHashOutput,
|
448 | scriptHashOutput: scriptHashOutput,
|
449 | witnessPubKeyHashOutput: witnessPubKeyHashOutput,
|
450 | witnessScriptHashInput: witnessScriptHashInput,
|
451 | witnessScriptHashOutput: witnessScriptHashOutput,
|
452 |
|
453 | multisigOutput: multisigOutput,
|
454 | pubKeyInput: pubKeyInput,
|
455 | pubKeyHashInput: pubKeyHashInput,
|
456 | scriptHashInput: scriptHashInput,
|
457 | multisigInput: multisigInput,
|
458 | nullDataOutput: nullDataOutput
|
459 | }
|