Skip to content

Protocol Messages

clothic edited this page Sep 22, 2024 · 26 revisions

Design Goals

To preserve the feature where we can execute atomic swaps and can allow for the use of PSBTs, to construct expressive market mechanics that transact between sibling subprotocols, we would like to make use of the Runestone structure and extend its use in a way where we can embed protocol messages for protorunes within the Runestone structure without causing a cenotaph.

We do this by adding a new ODD field of the Runestone structure and claim one protocol field which does not conflict with future versions of the runes metaprotocol. The runes specification indicates that a tag within the range of protocol field values will never exceed 1 byte in its leb128 encoding, thus why the author of the runes specification terminates the range of Tag values at 127. We have the remaining space of a u128 to claim territory safely. Thus, we choose 2**14 - 1 (16383) which is the largest possible value in the u128 byte space which occupies only 2 bytes in leb128 encoding. This, we are confident, will never disturb the evolution of the runes metaprotocol.

We end up with the final structure:

enum Tag {
  Body = 0,
  Flags = 2,
  Rune = 4,
  Premine = 6,
  Cap = 8,
  Amount = 10,
  HeightStart = 12,
  HeightEnd = 14,
  OffsetStart = 16,
  OffsetEnd = 18,
  Mint = 20,
  Pointer = 22,
  Cenotaph = 126,
  Divisibility = 1,
  Spacers = 3,
  Symbol = 5,
  Nop = 127,
  Protocol = 16383,
}

Parsing

The Protocol field in a Runestone, consistent with the rest of the structure, is encoded as u128[]. These values are concatenated to form a single bytearray and parsed, intuitively, as an embedded list of leb128 encoded values. Encoding the array this way makes more efficient use of the Runestone structure by avoiding repeating the Tag for Protocol in most cases. To ensure the range of bytearrays does not exclude any bitfields within its terminal bytes, we choose a maximum length for a u128 value within a u128[] intended for interpretation as a u8[] to 15 bytes. This allows us to safely model an arbitrary bytearray within the Runestone paradigm.

The u128[] produced by decoding the list of leb128 values is interpreted one Protostone at a time. First, a subprotocol tag is parsed which should declare the protocol that the message targets. For protoburns this is 13, the protocol tag for the runes protocol. The next leb128 decodes to the amount of leb128 encoded values which should follow that encode the field/value pairs in the Protostone. Finally, the leb128 values are parsed in the declared quantity and interpreted as pairs, similar to the parsing of Runestone. In the same way that a Runestone is parsed, when a 0 tag is encountered, the remaining leb128 values are interpreted in sets of 4 as Edict structures, but which operate on the subprotocol.

In Runestone and Protostone, respectively, we have Protocol and Message potentially present, which are each a u128[] array which must be interpreted as a u8[]. Again, to ensure that we can make use of the full bitvector we pack into each u128, it is required that no u128 packed in these fields exceed 15 bytes in size.

ProtoTag

Below are the different fields possible in a Protostone structure:

enum ProtoTag {
  Body = 0,
  Message = 81,
  Burn = 83,
  Pointer = 91,
  Refund = 93,
  From = 95,
  Cenotaph = 126,
  Nop = 127
}

Protostone

When constructing the full Protostone, the final data structure should be organized as follows:

struct Protostone {
  edicts: Vec<Edict>,
  pointer: Option<u32>,
  refund_pointer: Option<u32>,
  burn: Option<u128>,
  message: Option<Vec<u8>>,
  from: Option<Vec<u32>>
}

Message field (calldata)

Also known as the calldata to the MessageContext.

This is an buffer of data that can be arbitrarily large. The sdk will automatically break down the calldata into tags that the indexer can pick up and piece back together. A magic number (0x01) is added to the start of the calldata to indicate the start of arbitrary data. If the first byte of calldata is not the magic number, then the entire calldata is zeroed out.

Any bytes that are 0x00 starting from the end will be dropped, until we read the first non zero byte. If the first non zero byte is not the magic end number (0x01), then the calldata will be zeroed out.

Delta-encoding

Edicts in a Protostone are delta-encoded, the same as they are in a top-level Runestone. They also, naturally, must be sorted prior to their encoding.

LEB128 Context

LEB128 (Little Endian Base 128) is a variable-length encoding used to represent arbitrarily large integers in a compact form. The primary advantage of LEB128 is that it can represent small numbers in fewer bytes, which helps save space.

More information can be found on the Rune Specification