bitpads-tools · x86-64 NASM Assembly
CLI
Tools
Hand-assembled x86-64. No C runtime. Direct kernel syscalls. Provable protocol conformance at the instruction level.
The bitpads-tools CLI is a command-line encoder written in hand-assembled x86-64 NASM assembly. It produces .bp binary frame files that conform to the BitPads Protocol v2.0 specification — Pure Signal, Wave, Record, and BitLedger frames across all four protocol layers.
The design philosophy is aggressive minimalism. There is no C runtime, no standard library, no heap allocation, no external dependencies. The tool issues write, open, close, and exit syscalls directly through the kernel ABI. Every byte in the output buffer was put there by explicit instruction. This makes the binary's protocol conformance provable at the instruction level rather than inferred through a stack of library calls.
The 256-byte context block is stack-allocated at startup and freed on exit. No memory management is required. No garbage collector runs. The encoder populates fields in the context block from parsed CLI flags, routes to the appropriate frame builder, and the builder writes bytes sequentially into a stack-allocated output buffer. The resulting bytes go to a file, to stdout, or are discarded in dry-run mode. There is no post-processing step.
Why NASM: Writing the encoder at the instruction level means every byte written to the output buffer is a deliberate, auditable act. There is no intermediate representation that could silently misalign a field, pad a struct, or reorder bytes. The bit positions in the output match the specification because the code that writes them was written to match the specification, and there is no compiler between the specification and the output.
Current platform: The macOS CLI (assemblycli/) is functional and produces conformant frames for all four frame types. A Linux port (linux_assembly_cli/) has been assembled to ELF64 but has not yet been linked or run on a Linux machine.
Internal Design
Architecture
The 256-Byte Context Block
All state for a single frame assembly operation lives in a 256-byte context block allocated on the stack at entry. The block is a flat structure — no linked lists, no dynamic allocation, no pointers to the heap. CLI flag parsing writes field values into named offsets within the block. The dispatcher reads those offsets to decide which frame builder to call. Each component builder reads from the context block and writes bytes directly to the output buffer.
The context block contains: frame type selector, Meta byte 1 and 2 field values, Layer 1 sender identity words, domain and permission bits, Layer 2 batch header fields (currency, scaling, precision, rounding mode), Layer 3 value mantissa and shift, account pair, direction and status flags, optional time block fields (T1S and T2 formats), task code and action nibble, note byte count, signal slot presence byte, and output routing selector (file / stdout / dry-run).
Frame Assembly Pipeline
cli_parse.asm — reads argv, populates context block fieldsdispatch.asm — reads frame type selector, routes to builderbuild_signal / wave / record / ledger.asmmeta1.asm + meta2.asm — construct Meta bytes 1 and 2layer1 / layer2 / layer3.asm — L1 identity, L2 batch, L3 recordvalue / time_comp / task / note / setup.asmcrc15.asm — polynomial x^15 + x + 1 over session payloadc0gram / signals / cmd1100 / ctx1101 / tel1110.asmfileio.asm + hexdump.asm — write syscall, dry-run hexDirectory Structure — assemblycli/
Output Specification
Frame Types
The CLI produces four frame types. Every frame begins with Meta byte 1. Mode, content type, and enhancement state are declared in that byte before the receiver processes anything else. A Pure Signal closes immediately; a BitLedger frame carries five independent protocol layers.
Pure Signal — 1 Byte
Meta byte 1 alone. The byte is the entire message. No session, no preamble, no continuation. Used for heartbeats, ACK requests, status pulses, and priority alerts. Bit 1 = 0 selects Wave mode; the frame closes after the single byte when the continuation bits declare none.
Mode
Req
None
Treat
Flags
Wave — 2–4 Bytes
Meta byte 1 plus a wave category byte (Wave byte) and, for categories 0100–0111, a Layer 1 value byte. Sixteen category codes spanning plain values, messages, status signals, commands, and stream opens. No identity overhead. No session Layer 1 required for basic categories. The CLI builds the wave category byte from the 4-bit category nibble and the wave content nibble.
Mode
Treat
Flags
Nibble
Nibble
Record — 13–29 Bytes
Both Meta bytes plus Layer 1 (8 bytes of session identity with embedded CRC-15) plus optional components attached on demand: a value block, a time block (T1S at 3 bytes or T2 at 4 bytes), a task block (2 bytes), and a note block (variable). Each optional component is declared by a presence bit in Meta byte 2. The minimal fully-identified record is 13 bytes; the full form with all four optional components is 29 bytes.
Mode
Flags
Context
+ CRC-15
(opt)
(opt)
(opt)
(opt)
BitLedger — 22+ Bytes
The complete double-entry frame. Meta bytes 1 and 2, Layer 1 (identity + CRC-15), Layer 2 batch header (6 bytes declaring currency, scaling factor, precision defaults, separator convention, rounding balance direction), an optional Session Config Extension (1–5 bytes for compound mode, nesting level, and opposing convention), and the 5-byte Layer 3 BitLedger record. Conservation is enforced at encoding: the batch balance check runs before the first byte of Layer 3 is written.
CRC-15
Header
Config
Flag
25 bits
Pair
L3
Bits
Invocation
Commands & Flags
All flags set fields in the 256-byte context block before dispatch. Flags are grouped by protocol layer. Absent flags leave context block fields at their default values (zero, which produces minimal valid output for that field).
| Flag | Values | Effect |
|---|---|---|
--type ▾ |
signal · wave · record · ledger |
Selects frame builder. Default: signal |
signalCalls waveCalls recordCalls ledgerCalls |
||
--output |
<filename.bp> |
Write frame bytes to named file. Creates or overwrites. |
--stdout |
flag | Write frame bytes to stdout (fd 1). Binary output. |
--dry-run |
flag | Route to hexdump.asm — print hex annotation to stdout, write nothing. |
| Flag | Values | Bit Position |
|---|---|---|
--ack |
flag | Sets bit 2 of Meta byte 1. Requests acknowledgement from receiver. |
--priority |
flag | Sets bit 5 (Role A). Receiver enters elevated handling mode. |
--cipher |
flag | Sets bit 6 (Role A). Declares rolling codebook cipher active. |
--ext-flags |
flag | Sets bit 7 (Role A). Declares extension byte follows. |
--category |
0x0–0xF |
Sets Wave byte category nibble (bits 1–4 of wave byte). Used with --type wave. |
--wave-content |
0x0–0xF |
Sets Wave byte content nibble (bits 5–8). Meaning is category-dependent. |
| Flag | Values | Effect |
|---|---|---|
--sender-id |
0x00000001–0xFFFFFFFF |
32-bit flat sender identity. Written to Layer 1 bytes 1–4. |
--domain |
0x0–0xF |
4-bit domain selector in Layer 1. Declares financial, engineering, or custom semantic layer. |
--permissions |
0x0–0xF |
4-bit permission field in Layer 1. Read/write/admin/stream capability mask. |
| Flag | Values | Effect |
|---|---|---|
--currency |
0x00–0xFF |
8-bit currency/unit code in Layer 2. Declares denomination for all records in the session. |
--scale |
0–15 |
4-bit scaling factor D (decimal shift). Sets the decimal position for the batch. |
--precision |
0–15 |
4-bit precision P. Minimum significant digits declared for the batch. |
--round-balance |
up · down · even |
Rounding balance direction. Sets 2-bit rounding mode field in Layer 2. |
| Flag | Values | Effect |
|---|---|---|
--mantissa ▾ |
0–16383 |
Value mantissa A in the encoding formula N = A × 2^S + r |
|
The CLI packs mantissa A and shift S into the 25-bit value field of Layer 3. A is the significant integer. S is the power-of-two scale. The encoded value N = A × 2^S satisfies the conservation invariant when summed across all records in a batch. Use |
||
--shift |
0–10 |
Value shift S. Sets the power-of-two multiplier. S=0 encodes N = A exactly. |
--account-pair |
0x0–0xE |
4-bit account pair / flow archetype in Layer 3 bits 28–31. 0xF is reserved for compound mode continuation. |
--debit |
flag | Sets direction flag in Layer 3 bit 1 to debit. Mutually exclusive with --credit. |
--credit |
flag | Sets direction flag in Layer 3 bit 1 to credit. |
--final |
flag | Sets completeness flag in Layer 3 bit 2. Marks record as final, not provisional. |
| Flag | Values | Effect |
|---|---|---|
--time-t1s |
<unix-seconds> |
Adds T1S time block (3 bytes). Compressed Unix timestamp to ~2-second resolution. |
--time-t2 |
<unix-seconds> |
Adds T2 time block (4 bytes). Full Unix timestamp with sub-second precision nibble. |
--task |
0x00–0xFF |
Adds 2-byte task component. 8-bit task code declares the class of work. |
--task-action |
0x0–0xF |
4-bit action nibble within the task component. Verb on the task code. |
--note |
<bytes> |
Appends note block. Length-prefixed variable bytes of annotation. |
--signal-slots |
0x00–0xFF |
Sets signal slot presence byte (P1–P8 active flags). Enhancement C0 bytes at declared positions. |
Example Invocations
# Pure Signal — 1 byte, ACK request
./bitpads --type signal --ack --output heartbeat.bp
# Wave — category 0x0 (plain value), content 0x5
./bitpads --type wave --category 0x0 --wave-content 0x5 --stdout
# Wave — category 0xC (compact command)
./bitpads --type wave --category 0xC --wave-content 0x1 --output cmd.bp
# Minimal Record — identity + value only
./bitpads --type record \
--sender-id 0x00010001 \
--domain 0x1 \
--mantissa 500 --shift 0 \
--output record-min.bp
# Record with time and task components
./bitpads --type record \
--sender-id 0x00010001 \
--domain 0x1 \
--mantissa 1250 --shift 2 \
--time-t2 1746748800 \
--task 0x07 --task-action 0x1 \
--output record-full.bp
# BitLedger frame — full double-entry
./bitpads --type ledger \
--sender-id 0x00010001 \
--domain 0x0 \
--currency 0x01 --scale 2 --precision 4 --round-balance even \
--mantissa 9999 --shift 0 \
--account-pair 0x1 --debit --final \
--output ledger.bp
# Dry-run — show hex annotation, write nothing
./bitpads --type ledger \
--sender-id 0x00010001 \
--mantissa 100 --shift 0 \
--account-pair 0x1 --credit \
--dry-run
Implementation Status
What Works
Taken directly from the project readme. The macOS CLI (assemblycli/) is functional and produces conformant output for all of the following:
- Frame assembly for Pure Signal, Wave, Record, and BitLedger types
- Layer 1 with CRC-15 embed — polynomial x^15 + x + 1 computed over session payload
- Layer 2 batch headers — currency, scaling, precision, separator convention, rounding balance
- Meta Byte 1 construction — mode, ACK, continuation, treatment, Role A/B flags
- Meta Byte 2 construction — domain, permissions, component presence bits, enhancement flags
- Value encoding across four tiers — mantissa and shift packing into 25-bit field
- T1S time fields — 3-byte compressed Unix timestamp to ~2-second resolution
- T2 time fields — 4-byte full timestamp with sub-second precision nibble
- Task and note blocks — 2-byte task component and length-prefixed note block
- C0 Enhancement Grammar signal slot presence byte — P1–P8 position flags
- Telegraph, compact command, and context declaration Wave categories (1110, 1100, 1101)
- File output —
open/write/closesyscalls to named.bpfile - Stdout output —
writesyscall to fd 1, binary frame bytes - Dry-run output mode — hex-annotated dump to stdout via
hexdump.asm
Implementation Status
Known Gaps
Taken directly from the project readme. These are the current open items in the implementation:
- Signal slot content bytes are declared in the presence byte but are not yet written to the output buffer — the byte is set but the slot's content follows only as a stub
- The 28-byte vs 22-byte footprint discrepancy in the spec needs resolution — the path from the 22-byte irreducible minimum to the spec's cited 28-byte figure depends on which Session Config Extension sub-fields are treated as mandatory
- No formal byte-level test vectors exist — conformance is verified by manual inspection of hex dumps, not automated comparison against known-good outputs
- No decoder — the CLI is encode-only; there is no tool to parse a
.bpfile back into human-readable fields --helpoutput is absent — there is no usage message; the flag list is documented here and in the project readme only
Binary Output
Output Formats
File Output — .bp
The default output mode. The CLI opens a file at the path given by --output, writes the frame bytes sequentially, and closes the file. The file contains raw binary — no header, no container, no framing. One frame per file in the current implementation. The receiver opens the file and reads from byte 0; the first byte is always Meta byte 1.
Stdout
With --stdout the CLI issues a write(1, outbuf, n) syscall and exits. The binary frame bytes stream to stdout. This allows the CLI to be piped into another tool or redirected to a file: ./bitpads --type signal --ack --stdout > pulse.bp.
Dry-Run Mode
With --dry-run the output buffer is routed to hexdump.asm instead of fileio.asm. Each byte is formatted as a two-digit hex value with a field annotation showing which protocol field it belongs to. Nothing is written to disk.
Hex Dump — Pure Signal (1 byte)
Hex Dump — Minimal BitLedger Frame (22 bytes)
Encoding Formula
Value Encoding
Every value in the protocol encodes as a scaled integer. No floating point anywhere.
The encoding formula is N = A × 2S + r, where A is the integer mantissa, S is the power-of-two shift, and r is the rounding remainder. The 25-bit field in Layer 3 packs A and S; r is declared separately by the rounding balance flag. Every integer from 0 to 33,554,431 (225 − 1) is exactly reachable with S = 0. For larger values, S increases the effective range at the cost of resolution.
A = integer mantissa · S = power-of-two shift · r = rounding remainder (0 or 1, declared by flag)
Encoding Tiers
Exact integers. A directly encodes the value. Maximum A = 33,554,431. Use for values up to ~33.5M in the session's base denomination.
--mantissa 100 --shift 0→ N = 100
Small multiplier. Doubles, quadruples, or octuples A. Useful for even values where resolution in the low bit is not needed.
--mantissa 500 --shift 2→ N = 2000
Medium multiplier. Effective range extends to ~500M–4B. Resolution coarsens proportionally. Rounding flag becomes significant.
--mantissa 8191 --shift 5→ N = 262,112
Large multiplier. Maximum S = 10 gives A × 1024. At A = 32,767 and S = 10, N ≈ 33.5B. Protocol maximum for a single record.
--mantissa 32767 --shift 10→ N = 33,553,408
Integrity Mechanism
CRC-15 Implementation
The CRC-15 is computed by src/crypto/crc15.asm over the session payload in Layer 1. The polynomial is x15 + x + 1 — a degree-15 polynomial with Hamming distance 4 for frame lengths up to 32,767 bits. This polynomial detects all burst errors of 15 bits or fewer, all single-bit errors, all double-bit errors, and all errors of odd bit count.
Algorithm
The implementation is register-based with no lookup table. A 16-bit CRC register (initialised to all-ones) is loaded before the session payload bytes are processed. For each byte in the session payload, each bit is processed from MSB to LSB. On each bit: the input bit is XORed with the current MSB of the register; if the result is 1, the register is shifted left one position and XORed with the polynomial mask 0x8003 (representing x15 + x + 1 in bit-reflection form); otherwise the register is shifted left only. After all payload bits are processed, the register holds the 15-bit CRC. The CRC is embedded in Layer 1 bytes 7–8 (15 bits, 1 reserved).
Why no lookup table: A table-driven CRC requires a 256-entry × 2-byte table in data memory — feasible in a standard runtime but non-trivial in a no-heap, stack-only assembly environment where memory layout is explicit. The register-based bit iterator uses two registers and a branch per bit. For sessions where the payload is 5 bytes (40 bits), the loop runs exactly 40 iterations — a fixed, auditable cost.
Development Status
Roadmap
The following items represent the current development boundary. Protocol specifications are versioned and stable. The implementation gaps below are implementation work, not specification changes.
The signal slot presence byte is written correctly — declaring which of P1–P13 are active in this frame. The content bytes that should follow at those positions are not yet written. The gap is implementation, not specification: the slot positions and content formats are fully defined.
The spec's footprint table cites 28 bytes for a full BitLedger frame. The named components sum to 22 bytes. The path to 28 runs through which Session Config Extension sub-fields (compound mode marker, nesting level declaration, opposing convention extension) are treated as mandatory for a given session configuration. This needs a normative decision in the spec before the CLI can enforce it.
The Linux ELF64 build (linux_assembly_cli/) has been assembled but not linked or executed on a Linux machine. The primary difference is the syscall convention: Linux uses syscall with rax/rdi/rsi/rdx registers; macOS uses the Mach trap interface. The protocol logic is identical — only the I/O layer changes.
The CLI currently has no usage message. Running ./bitpads without arguments produces no output. The flag documentation exists in this page and in the project readme. A --help flag is straightforward to add — it is a data section of ASCII bytes and a single write(1, help_str, len) syscall.
No formal test vectors exist. Conformance is verified by manual inspection of --dry-run hex output. A test vector file — known-good hex outputs for each frame type with known inputs — would enable automated regression testing and provide a reference for decoder implementors.
The CLI is encode-only. There is no tool to parse a .bp file back into human-readable fields. A decoder would complete the round-trip and enable automated conformance testing: encode a frame, decode it, compare field values to the input flags. The decoder is the natural next major component after the encoder is feature-complete.