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. (Seeopenspec/AGENTS.mdSection 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'spack({ id: 7, key: <32 random bytes> })is thenunpack()ed - THEN the result is structurally equal to the input —
id === 7andkeyis aUint8Arraywith the same 32 bytes
Scenario: bytes field aligns to byte boundary
- WHEN a schema places a
boolfield (1 bit) immediately before abytesfield - THEN the encoder pads 7 bits of zero after the bool so the bytes field starts on a byte boundary;
byteSizereports 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
BitpackerErrorwith codeSCHEMA.INVALID_BYTE_LENGTHand identifies the offending field
Scenario: pack rejects payloads of the wrong length
- WHEN the schema declares
byteLength: 32andpack({ key: <16 bytes> })is called - THEN the call throws
BitpackerErrorwith codePACK.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.byteSizeis 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"