1import * as NodeCrypto from "node:crypto";
2import { KeyPair, KeyPairProvider } from "@/KeyPair";
3import * as Provider from "@/Provider";
4import { inMemoryState } from "@/State";
5import * as Test from "@/Test/Alchemy";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Redacted from "effect/Redacted";
9
10const { test } = Test.make({
11 providers: KeyPairProvider(),
12 state: inMemoryState(),
13});
14
15const assertPemKeyPair = (attrs: {
16 privateKey: Redacted.Redacted<string>;
17 publicKey: string;
18}) => {
19 const priv = Redacted.value(attrs.privateKey);
20 expect(priv).toMatch(/^-----BEGIN PRIVATE KEY-----/);
21 expect(priv).toMatch(/-----END PRIVATE KEY-----/);
22 expect(attrs.publicKey).toMatch(/^-----BEGIN PUBLIC KEY-----/);
23 expect(attrs.publicKey).toMatch(/-----END PUBLIC KEY-----/);
24 // Node throws if the PEM is malformed.
25 NodeCrypto.createPrivateKey(priv);
26 NodeCrypto.createPublicKey(attrs.publicKey);
27};
28
29describe("Alchemy.KeyPair", { tags: ["unit", "local"] }, () => {
30 test.provider("mints an ed25519 keypair by default", (stack) =>
31 Effect.gen(function* () {
32 const attrs = yield* stack.deploy(
33 Effect.gen(function* () {
34 return yield* KeyPair("ed25519-default");
35 }),
36 );
37 expect(attrs.algorithm).toBe("ed25519");
38 assertPemKeyPair(attrs);
39 }),
40 );
41
42 test.provider("mints an rsa keypair when requested", (stack) =>
43 Effect.gen(function* () {
44 const attrs = yield* stack.deploy(
45 Effect.gen(function* () {
46 return yield* KeyPair("rsa-key", {
47 algorithm: "rsa",
48 modulusLength: 2048,
49 });
50 }),
51 );
52 expect(attrs.algorithm).toBe("rsa");
53 assertPemKeyPair(attrs);
54 }),
55 );
56
57 test.provider("mints an ec keypair when requested", (stack) =>
58 Effect.gen(function* () {
59 const attrs = yield* stack.deploy(
60 Effect.gen(function* () {
61 return yield* KeyPair("ec-key", {
62 algorithm: "ec",
63 namedCurve: "P-256",
64 });
65 }),
66 );
67 expect(attrs.algorithm).toBe("ec");
68 assertPemKeyPair(attrs);
69 }),
70 );
71
72 test.provider("preserves the keypair across deploys", (stack) =>
73 Effect.gen(function* () {
74 const program = Effect.gen(function* () {
75 return yield* KeyPair("stable-key");
76 });
77
78 const first = yield* stack.deploy(program);
79 const second = yield* stack.deploy(program);
80
81 expect(Redacted.value(second.privateKey)).toBe(
82 Redacted.value(first.privateKey),
83 );
84 expect(second.publicKey).toBe(first.publicKey);
85 }),
86 );
87
88 test.provider("list returns [] for the non-listable keypair", (stack) =>
89 Effect.gen(function* () {
90 yield* stack.destroy();
91
92 yield* stack.deploy(
93 Effect.gen(function* () {
94 return yield* KeyPair("list-key");
95 }),
96 );
97
98 const provider = yield* Provider.findProvider(KeyPair);
99 expect(yield* provider.list()).toEqual([]);
100
101 yield* stack.destroy();
102 }),
103 );
104});