Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,23 +199,32 @@ function toBuffer (input) {
return bsv.Script.fromASM(text.replace(/\s+/g, ' ')).toBuffer()
}

// Whether a push anywhere in buf, including after OP_RETURN, runs past its end.
function truncated (buf) {
// What in buf, including the data after OP_RETURN, ASM cannot hold: a push
// that runs past the end, a push not written with the shortest opcode for its
// length (ASM records only the data), or an opcode with no name.
function asmObstacles (buf) {
const found = { truncated: false, nonMinimal: false, unnamed: false }
let i = 0
while (i < buf.length) {
const code = buf[i++]
if (code === 0 || code > OP.OP_PUSHDATA4) continue
if (code > OP.OP_PUSHDATA4) {
if (!NAMES[code]) found.unnamed = true
continue
}
if (code === 0) continue
let len = code
if (code >= OP.OP_PUSHDATA1) {
const w = code === OP.OP_PUSHDATA1 ? 1 : code === OP.OP_PUSHDATA2 ? 2 : 4
if (i + w > buf.length) return true
if (i + w > buf.length) { found.truncated = true; break }
len = w === 1 ? buf[i] : w === 2 ? buf.readUInt16LE(i) : buf.readUInt32LE(i)
i += w
const shortest = len === 0 ? 0 : len < OP.OP_PUSHDATA1 ? len : len < 0x100 ? OP.OP_PUSHDATA1 : len < 0x10000 ? OP.OP_PUSHDATA2 : OP.OP_PUSHDATA4
if (shortest !== code) found.nonMinimal = true
}
if (i + len > buf.length) return true
if (i + len > buf.length) { found.truncated = true; break }
i += len
}
return false
return found
}

// ASM that reads back as exactly `buf`, or an error. ASM cannot say how data
Expand All @@ -227,12 +236,12 @@ function exactAsm (buf) {
let back = null
try { back = toBuffer(asm) } catch (e) {}
if (!back || !back.equals(buf)) {
const ops = parse(buf, { deadBlocks: false })
const why = truncated(buf)
const found = asmObstacles(buf)
const why = found.truncated
? 'it contains a truncated push'
: ops.some(o => isPush(o) && o.code !== TAIL && opSize(pushOp(pushValue(o))) !== opSize(o))
: found.nonMinimal
? 'a push is not minimally encoded, and ASM cannot say how data was pushed'
: ops.some(o => o.code >= 0 && !isPush(o) && !NAMES[o.code])
: found.unnamed
? 'it uses an opcode that has no name, which @smartledger/bsv reads back from ASM only from 9.11.1'
: !/\bOP_/.test(asm)
? 'it is only data pushes, and ASM with no opcodes reads back as hex'
Expand Down
3 changes: 2 additions & 1 deletion test/unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ test('ASM output reads back byte for byte, or is refused', () => {
// An empty push in OP_RETURN data, as buildDataOut('') writes it (read back from bsv 9.11.2).
assert.strictEqual(exactAsm(Buffer.from('516a00020aff', 'hex')), 'OP_1 OP_RETURN 0 0aff')
assert.throws(() => exactAsm(Buffer.from('516a05aabb', 'hex')), /truncated push/)
assert.throws(() => exactAsm(Buffer.from('516a4c00', 'hex')), /not minimally encoded/)
assert.throws(() => exactAsm(Buffer.from('4c50' + 'ab'.repeat(80), 'hex')), /only data pushes/)

const bin = path.join(__dirname, '..', 'bin', 'scriptmin.js')
Expand All @@ -215,7 +216,7 @@ test('ASM output reads back byte for byte, or is refused', () => {
const written = require('fs').readFileSync(path.join(dir, 'out.asm'), 'utf8')
assert.ok(written.trim().endsWith('OP_RETURN ' + 'ab'.repeat(40)), written)
require('fs').writeFileSync(path.join(dir, 'odd.hex'), '76756a4c03aabbcc')
assert.throws(() => execFileSync('node', [bin, '--asm', '--tests', '0', '-o', path.join(dir, 'odd.asm'), path.join(dir, 'odd.hex')], { stdio: 'pipe' }), e => e.status === 2 && /write it as hex/.test(e.stderr))
assert.throws(() => execFileSync('node', [bin, '--asm', '--tests', '0', '-o', path.join(dir, 'odd.asm'), path.join(dir, 'odd.hex')], { stdio: 'pipe' }), e => e.status === 2 && /not minimally encoded/.test(e.stderr))
} finally {
require('fs').rmSync(dir, { recursive: true, force: true })
}
Expand Down
Loading