Initial Commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: crypto-keys (crypto-completion)
|
||||
|
||||
**Package**: `@suaveplan/crypto-keys`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Device key creation
|
||||
|
||||
The package SHALL expose `createDeviceKey(options?)` from `keypair/device/device.ts` that generates a new `DeviceKey` carrying `BaseKeySchema` metadata (`s` secret bytes, `v` version, `m` master version, `u` user id, `d` depth, `o` key offset). Device keys SHALL always have `o === 7`. Generated keys SHALL pass `isValidDeviceKey`; an internally-failed validation SHALL throw `SuaveplanError` with code `CRYPTO_KEYS.DEVICE_INVALID_KEY`.
|
||||
|
||||
#### Scenario: round-trip valid key
|
||||
|
||||
- **WHEN** `createDeviceKey()` is called with no options
|
||||
- **THEN** the result satisfies `isValidDeviceKey(key) === true` AND `key.o === 7` AND `key.s` is a 32-byte `Uint8Array`
|
||||
|
||||
#### Scenario: caller-supplied metadata respected
|
||||
|
||||
- **WHEN** `createDeviceKey({ version: 3, masterVersion: 2, userId: 42, depth: 4 })` is called
|
||||
- **THEN** the result has `key.v === 3`, `key.m === 2`, `key.u === 42`, `key.d === 4`, `key.o === 7`
|
||||
|
||||
**Verified by:** `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"createDeviceKey produces a valid device key with offset 7"` AND `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"createDeviceKey honors version / masterVersion / userId / depth options"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Device key derivation from master
|
||||
|
||||
The package SHALL expose `deriveDeviceKeyFromMaster(options)` that derives a per-device `DeviceKey` from a `MasterKey` plus a numeric `deviceId`. Derivation SHALL use the existing `generateDerivedKey` (from `crypto-keys/keypair/derivation`) with key offset `7` and a context derived from the device id. The same `(masterKey, deviceId)` SHALL always produce the same `DeviceKey.s` byte sequence. Invalid master keys SHALL throw `CRYPTO_KEYS.DEVICE_INVALID_MASTER`.
|
||||
|
||||
#### Scenario: deterministic derivation
|
||||
|
||||
- **WHEN** `deriveDeviceKeyFromMaster({ masterKey, deviceId: 1 })` is called twice
|
||||
- **THEN** both results have byte-equal `.s` fields
|
||||
|
||||
#### Scenario: different device ids produce different keys
|
||||
|
||||
- **WHEN** `deriveDeviceKeyFromMaster({ masterKey, deviceId: 1 })` and `deriveDeviceKeyFromMaster({ masterKey, deviceId: 2 })` are called
|
||||
- **THEN** the two results have different `.s` fields
|
||||
|
||||
#### Scenario: invalid master key rejected
|
||||
|
||||
- **WHEN** `deriveDeviceKeyFromMaster({ masterKey: { s: shortBytes } as MasterKey, deviceId: 1 })` is called with a master key that fails `validateMasterKey`
|
||||
- **THEN** it throws `SuaveplanError` with code `CRYPTO_KEYS.DEVICE_INVALID_MASTER`
|
||||
|
||||
**Verified by:** `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"deriveDeviceKeyFromMaster is deterministic for fixed master and deviceId"` AND `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"different deviceIds derive different keys"` AND `packages/crypto/crypto-keys/src/keypair/device/device.spec-conformance.test.ts::"throws DEVICE_INVALID_MASTER for an invalid master key"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Device UUID generation
|
||||
|
||||
The package SHALL expose `generateDeviceUUID(options)` returning an RFC 4122-compliant UUID v4 string deterministically derived from a `DeviceKey`. The version nibble (bits 12-15 of `time_hi_and_version`) SHALL be set to `4`; the variant bits (bits 6-7 of `clock_seq_hi`) SHALL be set to `10`. The same `DeviceKey` plus context SHALL always produce the same UUID.
|
||||
|
||||
#### Scenario: UUID structure conforms to v4
|
||||
|
||||
- **WHEN** `generateDeviceUUID({ deviceMasterKey })` is called
|
||||
- **THEN** the result matches `/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/`
|
||||
|
||||
#### Scenario: deterministic for fixed inputs
|
||||
|
||||
- **WHEN** `generateDeviceUUID(opts)` is called twice with the same `opts`
|
||||
- **THEN** both calls return the same string
|
||||
|
||||
**Verified by:** `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"generateDeviceUUID returns an RFC 4122 v4 UUID"` AND `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"generateDeviceUUID is deterministic for fixed inputs"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Device metadata extraction and comparison
|
||||
|
||||
The package SHALL expose `extractDeviceMetadata(deviceKey)` returning `DeviceMetadata` (`version`, `masterVersion`, `userId`, `depth`, `keyOffset`) and `compareDeviceKeys(a, b)` returning `true` only when the two `DeviceKey` instances have identical `s`, `v`, `m`, `u`, `d`, `o` fields. The `s`-byte comparison SHALL be constant-time. The package SHALL also expose `isValidDeviceKey(deviceKey)` returning `true` only for keys passing structural validation (correct field types and `o === 7`).
|
||||
|
||||
#### Scenario: extractDeviceMetadata round-trips
|
||||
|
||||
- **WHEN** `extractDeviceMetadata(createDeviceKey({ version: 5, depth: 3 }))` is called
|
||||
- **THEN** the result has `version === 5` and `depth === 3`
|
||||
|
||||
#### Scenario: compareDeviceKeys equals on identical keys
|
||||
|
||||
- **WHEN** `compareDeviceKeys(k, k)` is called for any `DeviceKey` k
|
||||
- **THEN** the result is `true`
|
||||
|
||||
#### Scenario: compareDeviceKeys differs on differing s
|
||||
|
||||
- **WHEN** `compareDeviceKeys(k1, k2)` is called for keys with different `.s` bytes
|
||||
- **THEN** the result is `false`
|
||||
|
||||
#### Scenario: compareDeviceKeys is constant-time on s
|
||||
|
||||
- **WHEN** the `.s` bytes differ at byte 0 vs byte 31 (last byte)
|
||||
- **THEN** the timing distribution observed across many invocations does not reveal which byte differs (statistical timing test)
|
||||
|
||||
**Verified by:** `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"extractDeviceMetadata round-trips a created device key"` AND `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"compareDeviceKeys is true for identical keys"` AND `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"compareDeviceKeys is false for keys with differing s"` AND `packages/crypto/crypto-keys/src/keypair/device/device.spec-conformance.test.ts::"compareDeviceKeys runs in constant time over s"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: generateDerivedDeviceId back-compat alias
|
||||
|
||||
The package SHALL expose `generateDerivedDeviceId(options)` as a thin alias for `generateDeviceUUID(options)`, retained for upstream-API back-compat. The alias SHALL NOT use `@deprecated` JSDoc (banned in genesis per CLAUDE.md §2). Its sibling `.md` SHALL document it with prose: "Backwards-compatibility alias for `generateDeviceUUID`."
|
||||
|
||||
#### Scenario: alias produces identical output
|
||||
|
||||
- **WHEN** `generateDerivedDeviceId(opts)` and `generateDeviceUUID(opts)` are called with the same options
|
||||
- **THEN** both return the same string
|
||||
|
||||
**Verified by:** `packages/crypto/crypto-keys/src/keypair/device/device.test.ts::"generateDerivedDeviceId returns the same UUID as generateDeviceUUID"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
Reference in New Issue
Block a user