Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use OP_PUSHNUM_13 as rune protocol identifier #11

Merged
merged 1 commit into from
Mar 22, 2024
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
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as bitcoin from 'bitcoinjs-lib';
import { u128 } from './u128';

export const MAX_DIVISIBILITY = 38;
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;
14 changes: 8 additions & 6 deletions src/runestone.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
MAGIC_NUMBER,
MAX_DIVISIBILITY,
MAX_LIMIT,
MAX_SCRIPT_ELEMENT_SIZE,
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -227,7 +228,7 @@ export class Runestone {

static payload(transaction: bitcoin.Transaction): Option<Buffer> {
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');
}
Expand All @@ -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);
}
Expand Down
70 changes: 28 additions & 42 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
64 changes: 29 additions & 35 deletions test/runestone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -22,7 +21,7 @@ describe('runestone', () => {
return Runestone.decipher(
getSimpleTransaction([
bitcoin.opcodes.OP_RETURN,
Buffer.from('RUNE_TEST'),
MAGIC_NUMBER,
getPayload(integers),
])
).unwrap();
Expand Down Expand Up @@ -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([
Expand All @@ -105,7 +104,7 @@ describe('runestone', () => {
Runestone.decipher(
getSimpleTransaction([
bitcoin.opcodes.OP_RETURN,
Buffer.from('RUNE_TEST'),
MAGIC_NUMBER,
Buffer.from([128]),
])
).isSome()
Expand All @@ -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()),
Expand All @@ -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);
});
Expand All @@ -154,15 +150,15 @@ 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])]);
transaction.addOutput(bitcoin.script.compile(scriptPubKey), 0);
transaction.addOutput(
bitcoin.script.compile([
bitcoin.opcodes.OP_RETURN,
Buffer.from('RUNE_TEST'),
MAGIC_NUMBER,
payload,
]),
0
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -717,7 +713,7 @@ describe('runestone', () => {
transaction.addOutput(
bitcoin.script.compile([
bitcoin.opcodes.OP_RETURN,
Buffer.from('RUNE_TEST'),
MAGIC_NUMBER,
payload,
]),
0
Expand All @@ -742,7 +738,7 @@ describe('runestone', () => {
transaction.addOutput(
bitcoin.script.compile([
bitcoin.opcodes.OP_RETURN,
Buffer.from('RUNE_TEST'),
MAGIC_NUMBER,
payload,
]),
0
Expand All @@ -758,26 +754,24 @@ describe('runestone', () => {
test('runestone_size', () => {
function testcase(edicts: Edict[], etching: Option<Etching>, 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(
[],
Some(
new Etching(MAX_DIVISIBILITY, Some(new Rune(u128(0))), 0, None, None)
),
8
9
);

testcase(
Expand All @@ -795,13 +789,13 @@ describe('runestone', () => {
})
)
),
19
20
);

testcase(
[],
Some(new Etching(0, Some(new Rune(u128.MAX)), 0, None, None)),
24
25
);

testcase(
Expand All @@ -815,7 +809,7 @@ describe('runestone', () => {
Some(
new Etching(MAX_DIVISIBILITY, Some(new Rune(u128.MAX)), 0, None, None)
),
30
31
);

testcase(
Expand All @@ -829,7 +823,7 @@ describe('runestone', () => {
Some(
new Etching(MAX_DIVISIBILITY, Some(new Rune(u128.MAX)), 0, None, None)
),
48
49
);

testcase(
Expand All @@ -841,7 +835,7 @@ describe('runestone', () => {
},
],
None,
11
12
);

testcase(
Expand All @@ -853,7 +847,7 @@ describe('runestone', () => {
},
],
None,
29
30
);

testcase(
Expand All @@ -870,7 +864,7 @@ describe('runestone', () => {
},
],
None,
50
51
);

testcase(
Expand All @@ -892,7 +886,7 @@ describe('runestone', () => {
},
],
None,
71
72
);

testcase(
Expand All @@ -902,7 +896,7 @@ describe('runestone', () => {
output: u128(0),
})),
None,
56
57
);

testcase(
Expand All @@ -912,7 +906,7 @@ describe('runestone', () => {
output: u128(0),
})),
None,
68
69
);

testcase(
Expand All @@ -922,7 +916,7 @@ describe('runestone', () => {
output: u128(0),
})),
None,
65
66
);

testcase(
Expand All @@ -932,7 +926,7 @@ describe('runestone', () => {
output: u128(0),
})),
None,
63
64
);
});

Expand Down Expand Up @@ -1143,7 +1137,7 @@ describe('runestone', () => {
None
).encipher();

const instructions = decompileScriptAllBuffer(script);
const instructions = bitcoin.script.decompile(script);
expect(instructions?.length).toBe(3);
}

Expand All @@ -1160,7 +1154,7 @@ describe('runestone', () => {
None
).encipher();

const instructions = decompileScriptAllBuffer(script);
const instructions = bitcoin.script.decompile(script);
expect(instructions?.length).toBe(4);
}
});
Expand Down