From 5852cf4e4c1743c38fbccd8e978e16f71e4e8842 Mon Sep 17 00:00:00 2001 From: Steven Lee Date: Fri, 22 Mar 2024 02:10:33 -0700 Subject: [PATCH] Use OP_PUSHNUM_13 as rune protocol identifier --- src/constants.ts | 2 ++ src/runestone.ts | 14 +++++---- src/utils.ts | 70 +++++++++++++++++------------------------- test/runestone.test.ts | 64 +++++++++++++++++--------------------- 4 files changed, 67 insertions(+), 83 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 0065232..84fdda8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,4 @@ +import * as bitcoin from 'bitcoinjs-lib'; import { u128 } from './u128'; export const MAX_DIVISIBILITY = 38; @@ -5,3 +6,4 @@ export const MAX_LIMIT = u128(1n << 64n); export const RESERVED = u128(6402364363415443603228541259936211926n); export const SUBSIDY_HALVING_INTERVAL = 210_000; export const MAX_SCRIPT_ELEMENT_SIZE = 520; +export const MAGIC_NUMBER = bitcoin.opcodes.OP_13; diff --git a/src/runestone.ts b/src/runestone.ts index ff5068c..e233612 100644 --- a/src/runestone.ts +++ b/src/runestone.ts @@ -1,4 +1,5 @@ import { + MAGIC_NUMBER, MAX_DIVISIBILITY, MAX_LIMIT, MAX_SCRIPT_ELEMENT_SIZE, @@ -13,7 +14,7 @@ import _ from 'lodash'; import { Option, Some, None } from '@sniptt/monads'; import { Rune } from './rune'; import { Flag } from './flag'; -import { Instruction, decompileScriptAllBuffer } from './utils'; +import { Instruction, tryConvertInstructionToBuffer } from './utils'; import { RuneId } from './runeid'; export const MAX_SPACERS = 0b00000111_11111111_11111111_11111111; @@ -214,7 +215,7 @@ export class Runestone { const stack: bitcoin.Stack = []; stack.push(bitcoin.opcodes.OP_RETURN); - stack.push(Buffer.from('RUNE_TEST')); + stack.push(MAGIC_NUMBER); const payload = Buffer.concat(payloads); let i = 0; @@ -227,7 +228,7 @@ export class Runestone { static payload(transaction: bitcoin.Transaction): Option { for (const output of transaction.outs) { - const instructions = decompileScriptAllBuffer(output.script); + const instructions = bitcoin.script.decompile(output.script); if (instructions === null) { throw new Error('unable to decompile'); } @@ -242,15 +243,16 @@ export class Runestone { nextInstruction = instructions.shift(); if ( !nextInstruction || - Instruction.isNumber(nextInstruction) || - Buffer.compare(nextInstruction, Buffer.from('RUNE_TEST')) !== 0 + Instruction.isBuffer(nextInstruction) || + nextInstruction !== MAGIC_NUMBER ) { continue; } let payloads: Buffer[] = []; - for (const result of instructions) { + for (const instruction of instructions) { + const result = tryConvertInstructionToBuffer(instruction); if (Instruction.isBuffer(result)) { payloads.push(result); } diff --git a/src/utils.ts b/src/utils.ts index 555ba7a..aa1eb7e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,48 +12,34 @@ export namespace Instruction { } } -export function decompileScriptAllBuffer(script: Buffer) { - const instructions = bitcoin.script.decompile(script); - if (instructions === null) { - return null; - } - - const result: Instruction[] = []; - for (const instruction of instructions) { - if (Instruction.isNumber(instruction)) { - switch (instruction) { - case bitcoin.opcodes.OP_0: - result.push(Buffer.alloc(0)); - break; - case bitcoin.opcodes.OP_1: - case bitcoin.opcodes.OP_2: - case bitcoin.opcodes.OP_3: - case bitcoin.opcodes.OP_4: - case bitcoin.opcodes.OP_5: - case bitcoin.opcodes.OP_6: - case bitcoin.opcodes.OP_7: - case bitcoin.opcodes.OP_8: - case bitcoin.opcodes.OP_9: - case bitcoin.opcodes.OP_10: - case bitcoin.opcodes.OP_11: - case bitcoin.opcodes.OP_12: - case bitcoin.opcodes.OP_13: - case bitcoin.opcodes.OP_14: - case bitcoin.opcodes.OP_15: - case bitcoin.opcodes.OP_16: - result.push(Buffer.from([instruction - bitcoin.opcodes.OP_1 + 1])); - break; - case bitcoin.opcodes.OP_1NEGATE: - result.push(Buffer.from([0x80])); - break; - default: - result.push(instruction); - break; - } - } else { - result.push(instruction); +export function tryConvertInstructionToBuffer(instruction: Instruction) { + if (Instruction.isNumber(instruction)) { + switch (instruction) { + case bitcoin.opcodes.OP_0: + return Buffer.alloc(0); + case bitcoin.opcodes.OP_1: + case bitcoin.opcodes.OP_2: + case bitcoin.opcodes.OP_3: + case bitcoin.opcodes.OP_4: + case bitcoin.opcodes.OP_5: + case bitcoin.opcodes.OP_6: + case bitcoin.opcodes.OP_7: + case bitcoin.opcodes.OP_8: + case bitcoin.opcodes.OP_9: + case bitcoin.opcodes.OP_10: + case bitcoin.opcodes.OP_11: + case bitcoin.opcodes.OP_12: + case bitcoin.opcodes.OP_13: + case bitcoin.opcodes.OP_14: + case bitcoin.opcodes.OP_15: + case bitcoin.opcodes.OP_16: + return Buffer.from([instruction - bitcoin.opcodes.OP_1 + 1]); + case bitcoin.opcodes.OP_1NEGATE: + return Buffer.from([0x80]); + default: + return instruction; } + } else { + return instruction; } - - return result; } diff --git a/test/runestone.test.ts b/test/runestone.test.ts index 3c0fd14..5c0628b 100644 --- a/test/runestone.test.ts +++ b/test/runestone.test.ts @@ -5,10 +5,9 @@ import { u128 } from '../src/u128'; import { None, Option, Some } from '@sniptt/monads'; import { Tag } from '../src/tag'; import { Flag } from '../src/flag'; -import { MAX_DIVISIBILITY } from '../src/constants'; +import { MAGIC_NUMBER, MAX_DIVISIBILITY } from '../src/constants'; import { Rune } from '../src/rune'; import { SpacedRune } from '../src/spacedrune'; -import { decompileScriptAllBuffer } from '../src/utils'; import { Edict } from '../src/edict'; import { Etching } from '../src/etching'; import { RuneId } from '../src/runeid'; @@ -22,7 +21,7 @@ describe('runestone', () => { return Runestone.decipher( getSimpleTransaction([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, getPayload(integers), ]) ).unwrap(); @@ -89,7 +88,7 @@ describe('runestone', () => { test('deciphering_valid_runestone_with_invalid_script_postfix_returns_script_error', () => { const transaction = getSimpleTransaction([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, ]); transaction.outs[0].script = Buffer.concat([ @@ -105,7 +104,7 @@ describe('runestone', () => { Runestone.decipher( getSimpleTransaction([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, Buffer.from([128]), ]) ).isSome() @@ -117,7 +116,7 @@ describe('runestone', () => { Runestone.decipher( getSimpleTransaction([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, Buffer.concat([ Buffer.from([0]), u128.encodeVarInt(createRuneId(1).toU128()), @@ -140,10 +139,7 @@ describe('runestone', () => { test('deciphering_empty_runestone_is_successful', () => { expect( Runestone.decipher( - getSimpleTransaction([ - bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), - ]) + getSimpleTransaction([bitcoin.opcodes.OP_RETURN, MAGIC_NUMBER]) ).isSome() ).toBe(true); }); @@ -154,7 +150,7 @@ describe('runestone', () => { const transaction = new bitcoin.Transaction(); let scriptPubKey = bitcoin.script.compile([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, 4, ]); scriptPubKey = Buffer.concat([scriptPubKey, Buffer.from([4])]); @@ -162,7 +158,7 @@ describe('runestone', () => { transaction.addOutput( bitcoin.script.compile([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, payload, ]), 0 @@ -685,7 +681,7 @@ describe('runestone', () => { const runestone = Runestone.decipher( getSimpleTransaction([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, u128.encodeVarInt(u128(Tag.FLAGS)), u128.encodeVarInt(Flag.mask(Flag.ETCH)), u128.encodeVarInt(u128(Tag.DIVISIBILITY)), @@ -717,7 +713,7 @@ describe('runestone', () => { transaction.addOutput( bitcoin.script.compile([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, payload, ]), 0 @@ -742,7 +738,7 @@ describe('runestone', () => { transaction.addOutput( bitcoin.script.compile([ bitcoin.opcodes.OP_RETURN, - Buffer.from('RUNE_TEST'), + MAGIC_NUMBER, payload, ]), 0 @@ -758,18 +754,16 @@ describe('runestone', () => { test('runestone_size', () => { function testcase(edicts: Edict[], etching: Option, size: number) { expect( - new Runestone(false, None, None, edicts, etching).encipher().length - - 1 - - 'RUNE_TEST'.length + new Runestone(false, None, None, edicts, etching).encipher().length ).toBe(size); } - testcase([], None, 1); + testcase([], None, 2); testcase( [], Some(new Etching(0, Some(new Rune(u128(0))), 0, None, None)), - 6 + 7 ); testcase( @@ -777,7 +771,7 @@ describe('runestone', () => { Some( new Etching(MAX_DIVISIBILITY, Some(new Rune(u128(0))), 0, None, None) ), - 8 + 9 ); testcase( @@ -795,13 +789,13 @@ describe('runestone', () => { }) ) ), - 19 + 20 ); testcase( [], Some(new Etching(0, Some(new Rune(u128.MAX)), 0, None, None)), - 24 + 25 ); testcase( @@ -815,7 +809,7 @@ describe('runestone', () => { Some( new Etching(MAX_DIVISIBILITY, Some(new Rune(u128.MAX)), 0, None, None) ), - 30 + 31 ); testcase( @@ -829,7 +823,7 @@ describe('runestone', () => { Some( new Etching(MAX_DIVISIBILITY, Some(new Rune(u128.MAX)), 0, None, None) ), - 48 + 49 ); testcase( @@ -841,7 +835,7 @@ describe('runestone', () => { }, ], None, - 11 + 12 ); testcase( @@ -853,7 +847,7 @@ describe('runestone', () => { }, ], None, - 29 + 30 ); testcase( @@ -870,7 +864,7 @@ describe('runestone', () => { }, ], None, - 50 + 51 ); testcase( @@ -892,7 +886,7 @@ describe('runestone', () => { }, ], None, - 71 + 72 ); testcase( @@ -902,7 +896,7 @@ describe('runestone', () => { output: u128(0), })), None, - 56 + 57 ); testcase( @@ -912,7 +906,7 @@ describe('runestone', () => { output: u128(0), })), None, - 68 + 69 ); testcase( @@ -922,7 +916,7 @@ describe('runestone', () => { output: u128(0), })), None, - 65 + 66 ); testcase( @@ -932,7 +926,7 @@ describe('runestone', () => { output: u128(0), })), None, - 63 + 64 ); }); @@ -1143,7 +1137,7 @@ describe('runestone', () => { None ).encipher(); - const instructions = decompileScriptAllBuffer(script); + const instructions = bitcoin.script.decompile(script); expect(instructions?.length).toBe(3); } @@ -1160,7 +1154,7 @@ describe('runestone', () => { None ).encipher(); - const instructions = decompileScriptAllBuffer(script); + const instructions = bitcoin.script.decompile(script); expect(instructions?.length).toBe(4); } });