6.7 KiB
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) === trueANDkey.o === 7ANDkey.sis a 32-byteUint8Array
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
.sfields
Scenario: different device ids produce different keys
- WHEN
deriveDeviceKeyFromMaster({ masterKey, deviceId: 1 })andderiveDeviceKeyFromMaster({ masterKey, deviceId: 2 })are called - THEN the two results have different
.sfields
Scenario: invalid master key rejected
- WHEN
deriveDeviceKeyFromMaster({ masterKey: { s: shortBytes } as MasterKey, deviceId: 1 })is called with a master key that failsvalidateMasterKey - THEN it throws
SuaveplanErrorwith codeCRYPTO_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 sameopts - 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 === 5anddepth === 3
Scenario: compareDeviceKeys equals on identical keys
- WHEN
compareDeviceKeys(k, k)is called for anyDeviceKeyk - THEN the result is
true
Scenario: compareDeviceKeys differs on differing s
- WHEN
compareDeviceKeys(k1, k2)is called for keys with different.sbytes - THEN the result is
false
Scenario: compareDeviceKeys is constant-time on s
- WHEN the
.sbytes 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)andgenerateDeviceUUID(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"