Initial Commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# 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"`
|
||||
Reference in New Issue
Block a user