Files
2026-05-27 23:29:53 +01:00

4.4 KiB

Capability Spec Delta: bitpacker (restore-crypto-keychain-packer)

Package: @suaveplan/bitpacker Status: PROPOSED

Every requirement below carries a **Verified by:** line at authoring time. (See openspec/AGENTS.md Section 13.)

ADDED Requirements

Requirement: bytes field type for fixed-length byte arrays

The package SHALL support a "bytes" FieldType in defineBitSchema field definitions, alongside the existing uint | int | bool | fixed. A "bytes" field carries a fixed-length Uint8Array payload of byteLength bytes (1 ≤ byteLength ≤ 65535).

A bytes field is byte-aligned: the writer pads the bit cursor to the next byte boundary before writing the field's bytes, and pads again after writing if the next field is not also bytes-aligned. The reader matches: it consumes the alignment padding before and after.

This field type is required by @suaveplan/crypto for key material (32-byte keypairs), domain tags (4-byte ASCII), and signatures (64-byte ed25519). Without it, those payloads must be encoded by interleaving raw BitWriter.writeBytes calls with defineBitSchema-driven structured fields, which loses the typed-schema benefits.

Scenario: schema with a bytes field round-trips

  • WHEN defineBitSchema<{ id: number; key: Uint8Array }>([{ name: "id", type: "uint", bits: 16 }, { name: "key", type: "bytes", byteLength: 32 }]) is called and the schema's pack({ id: 7, key: <32 random bytes> }) is then unpack()ed
  • THEN the result is structurally equal to the input — id === 7 and key is a Uint8Array with the same 32 bytes

Scenario: bytes field aligns to byte boundary

  • WHEN a schema places a bool field (1 bit) immediately before a bytes field
  • THEN the encoder pads 7 bits of zero after the bool so the bytes field starts on a byte boundary; byteSize reports the aligned size

Scenario: byteLength outside [1, 65535] is rejected at schema definition

  • WHEN defineBitSchema([{ name: "x", type: "bytes", byteLength: 0 }]) is called
  • THEN the call throws BitpackerError with code SCHEMA.INVALID_BYTE_LENGTH and identifies the offending field

Scenario: pack rejects payloads of the wrong length

  • WHEN the schema declares byteLength: 32 and pack({ key: <16 bytes> }) is called
  • THEN the call throws BitpackerError with code PACK.BYTES_LENGTH_MISMATCH, naming the field, expected length, and actual length

Scenario: existing schemas using only uint/int/bool/fixed are unchanged

  • WHEN any schema defined before this change is packed and unpacked
  • THEN the byte-for-byte output is identical to the pre-change baseline (KAT-asserted)

Verified by: src/bit-schema/bit-schema.test.ts::"bytes field round-trips a Uint8Array" AND src/bit-schema/bit-schema.test.ts::"bytes field pads to byte boundary after a bool" AND src/bit-schema/bit-schema.test.ts::"byteLength of zero is rejected at schema-definition time" AND src/bit-schema/bit-schema.test.ts::"pack rejects payloads of the wrong length" AND tests/legacy-schema-kat.test.ts::"existing uint/int/bool/fixed schemas produce byte-identical output"


Requirement: BitWriter.byteAlign() and BitReader.byteAlign() are publicly exposed

The package SHALL expose explicit byte-alignment methods on both BitWriter and BitReader for callers that interleave raw writeBytes/readBytes calls with bit-level operations. byteAlign() advances the cursor to the next byte boundary, padding with zero bits on write and skipping bits on read. Calling byteAlign() when already aligned is a no-op.

The bytes schema field type uses these internally; exposing them lets advanced consumers (e.g., custom keychain layouts in @suaveplan/crypto) build composite formats without reaching into private state.

Scenario: byteAlign on an already-aligned writer is a no-op

  • WHEN a writer is at bit position 0 (aligned) and writer.byteAlign() is called
  • THEN the bit position is unchanged and writer.byteSize is unchanged

Scenario: byteAlign pads with zero bits

  • WHEN a writer has written 5 bits and writer.byteAlign() is called
  • THEN the writer pads 3 zero bits and the bit cursor is at position 8

Verified by: src/bit-writer/bit-writer.test.ts::"byteAlign on aligned writer is no-op" AND src/bit-writer/bit-writer.test.ts::"byteAlign pads with zero bits" AND src/bit-reader/bit-reader.test.ts::"byteAlign skips trailing bits"