1import { assert, describe, it } from "@effect/vitest"
2import { Effect, Redacted } from "effect"
3import type * as EventLog from "effect/eventlog/EventLog"
4import {
5 EncryptionDerivationLabelV1,
6 makeGetIdentityRootSecretMaterial,
7 SigningDerivationLabelV1
8} from "effect/eventlog/internal/identityRootSecretDerivation"
9
10const makeIdentity = (options: {
11 readonly publicKey: string
12 readonly rootSecret: Uint8Array
13}): EventLog.Identity["Service"] => ({
14 publicKey: options.publicKey,
15 privateKey: Redacted.make(options.rootSecret.slice())
16})
17
18const makeRootSecret = () => Uint8Array.from(Array.from({ length: 32 }, (_, index) => (index * 17) % 256))
19
20const extractSigningSeedFromPkcs8 = (pkcs8PrivateKey: Uint8Array): Uint8Array =>
21 pkcs8PrivateKey.slice(pkcs8PrivateKey.byteLength - 32)
22
23const equalBytes = (left: Uint8Array, right: Uint8Array): boolean => {
24 if (left.byteLength !== right.byteLength) {
25 return false
26 }
27 for (let i = 0; i < left.byteLength; i++) {
28 if (left[i] !== right[i]) {
29 return false
30 }
31 }
32 return true
33}
34
35describe("EventLog root secret derivation", () => {
36 it.effect("derives the same material for identities with the same root secret", () =>
37 Effect.gen(function*() {
38 const rootSecret = makeRootSecret()
39 const derive = makeGetIdentityRootSecretMaterial(globalThis.crypto)
40
41 const first = yield* derive(makeIdentity({
42 publicKey: "user-a",
43 rootSecret
44 }))
45 const second = yield* derive(makeIdentity({
46 publicKey: "user-b",
47 rootSecret
48 }))
49
50 assert.strictEqual(EncryptionDerivationLabelV1.includes("/v1/"), true)
51 assert.strictEqual(SigningDerivationLabelV1.includes("/v1/"), true)
52 assert.deepStrictEqual(first.encryptionKeyMaterial, second.encryptionKeyMaterial)
53 assert.deepStrictEqual(first.signingPublicKey, second.signingPublicKey)
54 assert.deepStrictEqual(Redacted.value(first.signingPrivateKey), Redacted.value(second.signingPrivateKey))
55 }))
56
57 it.effect("uses versioned, domain-separated labels for encryption and signing", () =>
58 Effect.gen(function*() {
59 const derive = makeGetIdentityRootSecretMaterial(globalThis.crypto)
60 const derived = yield* derive(makeIdentity({
61 publicKey: "user-a",
62 rootSecret: makeRootSecret()
63 }))
64
65 const signingSeed = extractSigningSeedFromPkcs8(Redacted.value(derived.signingPrivateKey))
66
67 assert.strictEqual(equalBytes(derived.encryptionKeyMaterial, signingSeed), false)
68 assert.notStrictEqual(EncryptionDerivationLabelV1, SigningDerivationLabelV1)
69 }))
70
71 it.effect("memoizes derived material for the same identity instance", () =>
72 Effect.gen(function*() {
73 const derive = makeGetIdentityRootSecretMaterial(globalThis.crypto)
74 const identity = makeIdentity({
75 publicKey: "user-a",
76 rootSecret: makeRootSecret()
77 })
78
79 const first = yield* derive(identity)
80 const second = yield* derive(identity)
81
82 assert.strictEqual(first, second)
83 assert.strictEqual(first.encryptionKey, second.encryptionKey)
84 }))
85})