@orbitmem/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/agent/agent-adapter.d.ts +3 -0
- package/dist/agent/agent-adapter.d.ts.map +1 -0
- package/dist/agent/agent-adapter.js +3 -0
- package/dist/agent/agent-adapter.js.map +1 -0
- package/dist/agent/client.d.ts +5 -0
- package/dist/agent/client.d.ts.map +1 -0
- package/dist/agent/client.js +146 -0
- package/dist/agent/client.js.map +1 -0
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +118 -0
- package/dist/client.js.map +1 -0
- package/dist/contracts.d.ts +19 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +28 -0
- package/dist/contracts.js.map +1 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +5 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/orbitdb.d.ts +10 -0
- package/dist/data/orbitdb.d.ts.map +1 -0
- package/dist/data/orbitdb.js +39 -0
- package/dist/data/orbitdb.js.map +1 -0
- package/dist/data/pricing.d.ts +7 -0
- package/dist/data/pricing.d.ts.map +1 -0
- package/dist/data/pricing.js +55 -0
- package/dist/data/pricing.js.map +1 -0
- package/dist/data/serialization.d.ts +28 -0
- package/dist/data/serialization.d.ts.map +1 -0
- package/dist/data/serialization.js +76 -0
- package/dist/data/serialization.js.map +1 -0
- package/dist/data/vault.d.ts +21 -0
- package/dist/data/vault.d.ts.map +1 -0
- package/dist/data/vault.js +284 -0
- package/dist/data/vault.js.map +1 -0
- package/dist/discovery/discovery-layer.d.ts +3 -0
- package/dist/discovery/discovery-layer.d.ts.map +1 -0
- package/dist/discovery/discovery-layer.js +205 -0
- package/dist/discovery/discovery-layer.js.map +1 -0
- package/dist/discovery/index.d.ts +4 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +4 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/mock-registry.d.ts +30 -0
- package/dist/discovery/mock-registry.d.ts.map +1 -0
- package/dist/discovery/mock-registry.js +71 -0
- package/dist/discovery/mock-registry.js.map +1 -0
- package/dist/discovery/on-chain-registry.d.ts +35 -0
- package/dist/discovery/on-chain-registry.d.ts.map +1 -0
- package/dist/discovery/on-chain-registry.js +199 -0
- package/dist/discovery/on-chain-registry.js.map +1 -0
- package/dist/encryption/aes.d.ts +15 -0
- package/dist/encryption/aes.d.ts.map +1 -0
- package/dist/encryption/aes.js +63 -0
- package/dist/encryption/aes.js.map +1 -0
- package/dist/encryption/encryption-layer.d.ts +8 -0
- package/dist/encryption/encryption-layer.d.ts.map +1 -0
- package/dist/encryption/encryption-layer.js +82 -0
- package/dist/encryption/encryption-layer.js.map +1 -0
- package/dist/encryption/index.d.ts +6 -0
- package/dist/encryption/index.d.ts.map +1 -0
- package/dist/encryption/index.js +4 -0
- package/dist/encryption/index.js.map +1 -0
- package/dist/encryption/lit.d.ts +23 -0
- package/dist/encryption/lit.d.ts.map +1 -0
- package/dist/encryption/lit.js +113 -0
- package/dist/encryption/lit.js.map +1 -0
- package/dist/encryption/vault-key.d.ts +37 -0
- package/dist/encryption/vault-key.d.ts.map +1 -0
- package/dist/encryption/vault-key.js +43 -0
- package/dist/encryption/vault-key.js.map +1 -0
- package/dist/identity/identity-layer.d.ts +3 -0
- package/dist/identity/identity-layer.d.ts.map +1 -0
- package/dist/identity/identity-layer.js +99 -0
- package/dist/identity/identity-layer.js.map +1 -0
- package/dist/identity/index.d.ts +4 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +4 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/ows-adapter.d.ts +15 -0
- package/dist/identity/ows-adapter.d.ts.map +1 -0
- package/dist/identity/ows-adapter.js +67 -0
- package/dist/identity/ows-adapter.js.map +1 -0
- package/dist/identity/session.d.ts +10 -0
- package/dist/identity/session.d.ts.map +1 -0
- package/dist/identity/session.js +36 -0
- package/dist/identity/session.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/create-agent.d.ts +11 -0
- package/dist/persistence/create-agent.d.ts.map +1 -0
- package/dist/persistence/create-agent.js +47 -0
- package/dist/persistence/create-agent.js.map +1 -0
- package/dist/persistence/index.d.ts +3 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +3 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/persistence-layer.d.ts +12 -0
- package/dist/persistence/persistence-layer.d.ts.map +1 -0
- package/dist/persistence/persistence-layer.js +194 -0
- package/dist/persistence/persistence-layer.js.map +1 -0
- package/dist/transport/index.d.ts +3 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +3 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/relay-session.d.ts +41 -0
- package/dist/transport/relay-session.d.ts.map +1 -0
- package/dist/transport/relay-session.js +86 -0
- package/dist/transport/relay-session.js.map +1 -0
- package/dist/transport/transport-layer.d.ts +32 -0
- package/dist/transport/transport-layer.d.ts.map +1 -0
- package/dist/transport/transport-layer.js +110 -0
- package/dist/transport/transport-layer.js.map +1 -0
- package/dist/types.d.ts +1319 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +91 -0
- package/src/__tests__/client.test.ts +30 -0
- package/src/__tests__/orbitdb-availability.ts +8 -0
- package/src/agent/__tests__/agent-adapter.test.ts +50 -0
- package/src/agent/__tests__/client.test.ts +50 -0
- package/src/agent/agent-adapter.ts +2 -0
- package/src/agent/client.ts +158 -0
- package/src/agent/index.ts +1 -0
- package/src/client.ts +134 -0
- package/src/contracts.ts +44 -0
- package/src/data/__tests__/pricing.test.ts +73 -0
- package/src/data/__tests__/vault-encryption.test.ts +346 -0
- package/src/data/__tests__/vault.test.ts +75 -0
- package/src/data/index.ts +8 -0
- package/src/data/orbitdb.ts +47 -0
- package/src/data/pricing.ts +63 -0
- package/src/data/serialization.ts +108 -0
- package/src/data/vault.ts +382 -0
- package/src/discovery/__tests__/discovery.test.ts +49 -0
- package/src/discovery/__tests__/on-chain-registry.test.ts +176 -0
- package/src/discovery/discovery-layer.ts +244 -0
- package/src/discovery/index.ts +3 -0
- package/src/discovery/mock-registry.ts +96 -0
- package/src/discovery/on-chain-registry.ts +237 -0
- package/src/encryption/__tests__/aes.test.ts +64 -0
- package/src/encryption/__tests__/encryption-layer.test.ts +80 -0
- package/src/encryption/__tests__/lit.test.ts +97 -0
- package/src/encryption/aes.ts +109 -0
- package/src/encryption/encryption-layer.ts +100 -0
- package/src/encryption/index.ts +5 -0
- package/src/encryption/lit.ts +161 -0
- package/src/encryption/vault-key.ts +63 -0
- package/src/identity/__tests__/identity.test.ts +31 -0
- package/src/identity/__tests__/ows-adapter.test.ts +47 -0
- package/src/identity/identity-layer.ts +123 -0
- package/src/identity/index.ts +3 -0
- package/src/identity/ows-adapter.ts +80 -0
- package/src/identity/session.ts +57 -0
- package/src/index.ts +12 -0
- package/src/persistence/__tests__/create-agent.test.ts +9 -0
- package/src/persistence/__tests__/persistence.test.ts +242 -0
- package/src/persistence/create-agent.ts +55 -0
- package/src/persistence/index.ts +2 -0
- package/src/persistence/persistence-layer.ts +236 -0
- package/src/transport/__tests__/solana-transport.test.ts +112 -0
- package/src/transport/__tests__/transport.test.ts +84 -0
- package/src/transport/index.ts +2 -0
- package/src/transport/relay-session.ts +118 -0
- package/src/transport/transport-layer.ts +171 -0
- package/src/types/orbitdb.d.ts +9 -0
- package/src/types.ts +1496 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { createEncryptionLayer } from "../encryption-layer.js";
|
|
4
|
+
|
|
5
|
+
describe("EncryptionLayer", () => {
|
|
6
|
+
const layer = createEncryptionLayer({
|
|
7
|
+
defaultEngine: "aes",
|
|
8
|
+
aes: { kdf: "hkdf-sha256" },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("encrypt/decrypt with AES via unified interface", async () => {
|
|
12
|
+
const data = new TextEncoder().encode("unified test");
|
|
13
|
+
const rawKey = crypto.getRandomValues(new Uint8Array(32));
|
|
14
|
+
|
|
15
|
+
const encrypted = await layer.encrypt(data, {
|
|
16
|
+
engine: "aes",
|
|
17
|
+
keySource: { type: "raw", key: rawKey },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(encrypted.engine).toBe("aes");
|
|
21
|
+
|
|
22
|
+
const decrypted = await layer.decrypt(encrypted, {
|
|
23
|
+
keySource: { type: "raw", key: rawKey },
|
|
24
|
+
});
|
|
25
|
+
expect(new TextDecoder().decode(decrypted)).toBe("unified test");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("deriveAESKey returns CryptoKey", async () => {
|
|
29
|
+
const key = await layer.deriveAESKey({
|
|
30
|
+
type: "raw",
|
|
31
|
+
key: crypto.getRandomValues(new Uint8Array(32)),
|
|
32
|
+
});
|
|
33
|
+
expect(key.type).toBe("secret");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("canDecrypt returns true for AES", async () => {
|
|
37
|
+
const rawKey = crypto.getRandomValues(new Uint8Array(32));
|
|
38
|
+
const encrypted = await layer.encrypt(new TextEncoder().encode("test"), {
|
|
39
|
+
engine: "aes",
|
|
40
|
+
keySource: { type: "raw", key: rawKey },
|
|
41
|
+
});
|
|
42
|
+
const result = await layer.canDecrypt(encrypted);
|
|
43
|
+
expect(result).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("Lit decrypt throws clear error without authSig", async () => {
|
|
47
|
+
const litLayer = createEncryptionLayer({
|
|
48
|
+
defaultEngine: "lit",
|
|
49
|
+
aes: { kdf: "hkdf-sha256" },
|
|
50
|
+
lit: { network: "cayenne" },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const fakeLitBlob = {
|
|
54
|
+
engine: "lit" as const,
|
|
55
|
+
ciphertext: new Uint8Array([1, 2, 3]),
|
|
56
|
+
dataToEncryptHash: "abc",
|
|
57
|
+
accessControlConditions: [],
|
|
58
|
+
chain: "base" as const,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
await expect(litLayer.decrypt(fakeLitBlob)).rejects.toThrow(/authSig/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("Lit decrypt without lit config throws", async () => {
|
|
65
|
+
const noLitLayer = createEncryptionLayer({
|
|
66
|
+
defaultEngine: "aes",
|
|
67
|
+
aes: { kdf: "hkdf-sha256" },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const fakeLitBlob = {
|
|
71
|
+
engine: "lit" as const,
|
|
72
|
+
ciphertext: new Uint8Array([1, 2, 3]),
|
|
73
|
+
dataToEncryptHash: "abc",
|
|
74
|
+
accessControlConditions: [],
|
|
75
|
+
chain: "base" as const,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
await expect(noLitLayer.decrypt(fakeLitBlob)).rejects.toThrow(/Lit Protocol not configured/);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { LitEngine } from "../lit.js";
|
|
4
|
+
|
|
5
|
+
describe("LitEngine", () => {
|
|
6
|
+
test("creates access conditions correctly", () => {
|
|
7
|
+
const engine = new LitEngine({ network: "datil-dev" });
|
|
8
|
+
const condition = engine.createAddressCondition(
|
|
9
|
+
"0x1234567890abcdef1234567890abcdef12345678",
|
|
10
|
+
"base",
|
|
11
|
+
);
|
|
12
|
+
expect(condition.conditionType).toBe("evmBasic");
|
|
13
|
+
expect(condition.returnValueTest.value).toBe("0x1234567890abcdef1234567890abcdef12345678");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("createReputationCondition builds correct contract call condition", () => {
|
|
17
|
+
const engine = new LitEngine({ network: "datil-dev" });
|
|
18
|
+
const condition = engine.createReputationCondition({
|
|
19
|
+
registryAddress: "0xREP_REGISTRY",
|
|
20
|
+
minScore: 80,
|
|
21
|
+
chain: "base",
|
|
22
|
+
});
|
|
23
|
+
expect(condition.conditionType).toBe("evmContract");
|
|
24
|
+
expect(condition.returnValueTest.comparator).toBe(">=");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("getSessionSigs calls litClient.getSessionSigs with authNeededCallback", async () => {
|
|
28
|
+
const engine = new LitEngine({ network: "datil-dev" });
|
|
29
|
+
|
|
30
|
+
const mockSessionSigs = { "https://node1.lit": { sig: "abc", address: "0x123" } };
|
|
31
|
+
const mockClient = {
|
|
32
|
+
getSessionSigs: mock(async () => mockSessionSigs),
|
|
33
|
+
getLatestBlockhash: mock(async () => "0xblockhash"),
|
|
34
|
+
};
|
|
35
|
+
// Inject mock client
|
|
36
|
+
(engine as any).client = mockClient;
|
|
37
|
+
|
|
38
|
+
const authSig = {
|
|
39
|
+
sig: "0xsig",
|
|
40
|
+
derivedVia: "web3.eth.personal.sign",
|
|
41
|
+
signedMessage: "Sign in to OrbitMem",
|
|
42
|
+
address: "0x1234567890abcdef1234567890abcdef12345678",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = await engine.getSessionSigs(authSig, "base");
|
|
46
|
+
expect(result).toEqual(mockSessionSigs);
|
|
47
|
+
expect(mockClient.getSessionSigs).toHaveBeenCalledTimes(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("decrypt resolves authSig to sessionSigs when authSig object is passed", async () => {
|
|
51
|
+
const engine = new LitEngine({ network: "datil-dev" });
|
|
52
|
+
|
|
53
|
+
const mockClient = {
|
|
54
|
+
getSessionSigs: mock(async () => ({ "https://node1.lit": { sig: "abc" } })),
|
|
55
|
+
getLatestBlockhash: mock(async () => "0xblockhash"),
|
|
56
|
+
};
|
|
57
|
+
(engine as any).client = mockClient;
|
|
58
|
+
|
|
59
|
+
const authSig = {
|
|
60
|
+
sig: "0xsig",
|
|
61
|
+
derivedVia: "web3.eth.personal.sign",
|
|
62
|
+
signedMessage: "Sign in",
|
|
63
|
+
address: "0xABCD",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const encrypted = {
|
|
67
|
+
engine: "lit" as const,
|
|
68
|
+
ciphertext: new Uint8Array([10, 20]),
|
|
69
|
+
dataToEncryptHash: "abc123",
|
|
70
|
+
accessControlConditions: [
|
|
71
|
+
{
|
|
72
|
+
conditionType: "evmBasic" as const,
|
|
73
|
+
contractAddress: "" as `0x${string}`,
|
|
74
|
+
standardContractType: "" as const,
|
|
75
|
+
chain: "base" as const,
|
|
76
|
+
method: "",
|
|
77
|
+
parameters: [":userAddress"],
|
|
78
|
+
returnValueTest: { comparator: "=" as const, value: "0xABCD" },
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
chain: "base" as const,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Spy on getSessionSigs to verify it's called when authSig is provided
|
|
85
|
+
const getSessionSigsSpy = mock(async () => ({ "https://node1.lit": { sig: "abc" } }));
|
|
86
|
+
engine.getSessionSigs = getSessionSigsSpy;
|
|
87
|
+
|
|
88
|
+
// decrypt will fail at the actual Lit decryption (no real Lit network),
|
|
89
|
+
// but we can verify getSessionSigs was called
|
|
90
|
+
try {
|
|
91
|
+
await engine.decrypt(encrypted, authSig);
|
|
92
|
+
} catch {
|
|
93
|
+
// Expected — dynamic import of @lit-protocol/encryption will fail in test
|
|
94
|
+
}
|
|
95
|
+
expect(getSessionSigsSpy).toHaveBeenCalledTimes(1);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { AESEncryptedData, AESKeySource } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface AESConfig {
|
|
4
|
+
kdf: "hkdf-sha256" | "pbkdf2-sha256";
|
|
5
|
+
iterations?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class AESEngine {
|
|
9
|
+
private config: AESConfig;
|
|
10
|
+
private lastSalt: Uint8Array = new Uint8Array(32);
|
|
11
|
+
private lastSource: "wallet-signature" | "password" | "raw" = "raw";
|
|
12
|
+
|
|
13
|
+
constructor(config: AESConfig) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async deriveKey(source: AESKeySource, walletSignature?: Uint8Array): Promise<CryptoKey> {
|
|
18
|
+
if (source.type === "raw") {
|
|
19
|
+
this.lastSource = "raw";
|
|
20
|
+
this.lastSalt = new Uint8Array(0);
|
|
21
|
+
return crypto.subtle.importKey(
|
|
22
|
+
"raw",
|
|
23
|
+
source.key as BufferSource,
|
|
24
|
+
{ name: "AES-GCM" },
|
|
25
|
+
false,
|
|
26
|
+
["encrypt", "decrypt"],
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (source.type === "wallet-signature") {
|
|
31
|
+
if (!walletSignature) throw new Error("walletSignature required for wallet-signature source");
|
|
32
|
+
const ikm = await crypto.subtle.importKey(
|
|
33
|
+
"raw",
|
|
34
|
+
walletSignature as BufferSource,
|
|
35
|
+
"HKDF",
|
|
36
|
+
false,
|
|
37
|
+
["deriveKey"],
|
|
38
|
+
);
|
|
39
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
40
|
+
const info = new TextEncoder().encode("orbitmem-aes-256-gcm");
|
|
41
|
+
this.lastSource = "wallet-signature";
|
|
42
|
+
this.lastSalt = salt;
|
|
43
|
+
return crypto.subtle.deriveKey(
|
|
44
|
+
{ name: "HKDF", hash: "SHA-256", salt, info },
|
|
45
|
+
ikm,
|
|
46
|
+
{ name: "AES-GCM", length: 256 },
|
|
47
|
+
false,
|
|
48
|
+
["encrypt", "decrypt"],
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (source.type === "password") {
|
|
53
|
+
const enc = new TextEncoder().encode(source.password);
|
|
54
|
+
const ikm = await crypto.subtle.importKey("raw", enc, "PBKDF2", false, ["deriveKey"]);
|
|
55
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
56
|
+
this.lastSource = "password";
|
|
57
|
+
this.lastSalt = salt;
|
|
58
|
+
return crypto.subtle.deriveKey(
|
|
59
|
+
{ name: "PBKDF2", hash: "SHA-256", salt, iterations: this.config.iterations ?? 100000 },
|
|
60
|
+
ikm,
|
|
61
|
+
{ name: "AES-GCM", length: 256 },
|
|
62
|
+
false,
|
|
63
|
+
["encrypt", "decrypt"],
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`Unknown key source type: ${(source as any).type}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async encrypt(data: Uint8Array, key: CryptoKey): Promise<AESEncryptedData> {
|
|
71
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
72
|
+
const ciphertextWithTag = await crypto.subtle.encrypt(
|
|
73
|
+
{ name: "AES-GCM", iv: iv as BufferSource, tagLength: 128 },
|
|
74
|
+
key,
|
|
75
|
+
data as BufferSource,
|
|
76
|
+
);
|
|
77
|
+
// AES-GCM appends the 16-byte auth tag to the ciphertext
|
|
78
|
+
const raw = new Uint8Array(ciphertextWithTag);
|
|
79
|
+
const ciphertext = raw.slice(0, raw.length - 16);
|
|
80
|
+
const authTag = raw.slice(raw.length - 16);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
engine: "aes",
|
|
84
|
+
ciphertext,
|
|
85
|
+
iv,
|
|
86
|
+
authTag,
|
|
87
|
+
keyDerivation: {
|
|
88
|
+
source: this.lastSource,
|
|
89
|
+
salt: new Uint8Array(this.lastSalt),
|
|
90
|
+
kdf: this.config.kdf === "hkdf-sha256" ? "hkdf-sha256" : "pbkdf2-sha256",
|
|
91
|
+
...(this.lastSource === "password" ? { iterations: this.config.iterations ?? 100000 } : {}),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async decrypt(encrypted: AESEncryptedData, key: CryptoKey): Promise<Uint8Array> {
|
|
97
|
+
// Reconstruct ciphertext + authTag
|
|
98
|
+
const combined = new Uint8Array(encrypted.ciphertext.length + encrypted.authTag.length);
|
|
99
|
+
combined.set(encrypted.ciphertext, 0);
|
|
100
|
+
combined.set(encrypted.authTag, encrypted.ciphertext.length);
|
|
101
|
+
|
|
102
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
103
|
+
{ name: "AES-GCM", iv: encrypted.iv as BufferSource, tagLength: 128 },
|
|
104
|
+
key,
|
|
105
|
+
combined,
|
|
106
|
+
);
|
|
107
|
+
return new Uint8Array(plaintext);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AESEncryptedData,
|
|
3
|
+
EncryptAESOptions,
|
|
4
|
+
EncryptionConfig,
|
|
5
|
+
EncryptLitOptions,
|
|
6
|
+
IEncryptionLayer,
|
|
7
|
+
LitAccessCondition,
|
|
8
|
+
LitEncryptedData,
|
|
9
|
+
} from "../types.js";
|
|
10
|
+
import { AESEngine } from "./aes.js";
|
|
11
|
+
import { LitEngine } from "./lit.js";
|
|
12
|
+
|
|
13
|
+
export function createEncryptionLayer(config: EncryptionConfig): IEncryptionLayer & {
|
|
14
|
+
aes: AESEngine;
|
|
15
|
+
lit: LitEngine | null;
|
|
16
|
+
} {
|
|
17
|
+
const aes = new AESEngine({
|
|
18
|
+
kdf: config.aes?.kdf ?? "hkdf-sha256",
|
|
19
|
+
iterations: config.aes?.iterations,
|
|
20
|
+
});
|
|
21
|
+
const lit = config.lit
|
|
22
|
+
? new LitEngine({
|
|
23
|
+
network:
|
|
24
|
+
config.lit.network === "cayenne"
|
|
25
|
+
? "datil-dev"
|
|
26
|
+
: config.lit.network === "manzano"
|
|
27
|
+
? "datil-test"
|
|
28
|
+
: config.lit.network === "habanero"
|
|
29
|
+
? "datil"
|
|
30
|
+
: (config.lit.network as any),
|
|
31
|
+
debug: config.lit.debug,
|
|
32
|
+
})
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
aes,
|
|
37
|
+
lit,
|
|
38
|
+
|
|
39
|
+
async encrypt(data, opts) {
|
|
40
|
+
if (opts.engine === "aes") {
|
|
41
|
+
const aesOpts = opts as EncryptAESOptions;
|
|
42
|
+
const key = await aes.deriveKey(aesOpts.keySource);
|
|
43
|
+
return aes.encrypt(data instanceof Uint8Array ? data : new TextEncoder().encode(data), key);
|
|
44
|
+
}
|
|
45
|
+
if (opts.engine === "lit") {
|
|
46
|
+
if (!lit) throw new Error("Lit Protocol not configured");
|
|
47
|
+
const litOpts = opts as EncryptLitOptions;
|
|
48
|
+
const raw = data instanceof Uint8Array ? data : new TextEncoder().encode(data);
|
|
49
|
+
return lit.encrypt(raw, litOpts.accessConditions, litOpts.chain as string);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Unknown engine: ${(opts as any).engine}`);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async decrypt(encrypted, opts) {
|
|
55
|
+
if (encrypted.engine === "aes") {
|
|
56
|
+
const aesData = encrypted as AESEncryptedData;
|
|
57
|
+
if (!opts?.keySource) throw new Error("keySource required for AES decryption");
|
|
58
|
+
const key = await aes.deriveKey(opts.keySource);
|
|
59
|
+
return aes.decrypt(aesData, key);
|
|
60
|
+
}
|
|
61
|
+
if (encrypted.engine === "lit") {
|
|
62
|
+
if (!lit) throw new Error("Lit Protocol not configured");
|
|
63
|
+
if (!opts?.authSig) throw new Error("Lit decryption requires authSig in DecryptOptions");
|
|
64
|
+
return lit.decrypt(encrypted as LitEncryptedData, opts.authSig);
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Unknown engine: ${(encrypted as any).engine}`);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async grantAccess(encrypted, agentAddress, opts) {
|
|
70
|
+
if (!lit) throw new Error("Lit Protocol not configured");
|
|
71
|
+
const newCondition = lit.createAddressCondition(
|
|
72
|
+
agentAddress as string,
|
|
73
|
+
(opts?.chain ?? "base") as any,
|
|
74
|
+
);
|
|
75
|
+
const updatedConditions: LitAccessCondition[] = [
|
|
76
|
+
...encrypted.accessControlConditions,
|
|
77
|
+
{ operator: "or" as const },
|
|
78
|
+
newCondition,
|
|
79
|
+
];
|
|
80
|
+
return { ...encrypted, accessControlConditions: updatedConditions };
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async revokeAccess(encrypted, agentAddress) {
|
|
84
|
+
const filtered = encrypted.accessControlConditions.filter(
|
|
85
|
+
(c: any) => !("returnValueTest" in c && c.returnValueTest?.value === agentAddress),
|
|
86
|
+
);
|
|
87
|
+
return { ...encrypted, accessControlConditions: filtered };
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async deriveAESKey(source) {
|
|
91
|
+
return aes.deriveKey(source);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async canDecrypt(encrypted) {
|
|
95
|
+
if (encrypted.engine === "aes") return true; // caller must have key
|
|
96
|
+
if (encrypted.engine === "lit") return lit !== null;
|
|
97
|
+
return false;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EvmAddress,
|
|
3
|
+
EvmChain,
|
|
4
|
+
LitAccessCondition,
|
|
5
|
+
LitAuthSig,
|
|
6
|
+
LitEncryptedData,
|
|
7
|
+
LitEvmCondition,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
|
|
10
|
+
export interface LitConfig {
|
|
11
|
+
network: "datil-dev" | "datil-test" | "datil";
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class LitEngine {
|
|
16
|
+
private config: LitConfig;
|
|
17
|
+
private client: any | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(config: LitConfig) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Lazy-initialize the Lit client (heavy import) */
|
|
24
|
+
async getClient(): Promise<any> {
|
|
25
|
+
if (this.client) return this.client;
|
|
26
|
+
const { LitNodeClient } = await import("@lit-protocol/lit-node-client");
|
|
27
|
+
const { LIT_NETWORK } = await import("@lit-protocol/constants");
|
|
28
|
+
const networkMap: Record<string, string> = {
|
|
29
|
+
"datil-dev": LIT_NETWORK.DatilDev,
|
|
30
|
+
"datil-test": LIT_NETWORK.DatilTest,
|
|
31
|
+
datil: LIT_NETWORK.Datil,
|
|
32
|
+
};
|
|
33
|
+
this.client = new LitNodeClient({
|
|
34
|
+
litNetwork: networkMap[this.config.network] as any,
|
|
35
|
+
debug: this.config.debug ?? false,
|
|
36
|
+
});
|
|
37
|
+
await this.client.connect();
|
|
38
|
+
return this.client;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getSessionSigs(authSig: LitAuthSig, chain: string): Promise<any> {
|
|
42
|
+
const client = await this.getClient();
|
|
43
|
+
const { LitAbility } = await import("@lit-protocol/constants");
|
|
44
|
+
const { LitAccessControlConditionResource, createSiweMessageWithRecaps, generateAuthSig } =
|
|
45
|
+
await import("@lit-protocol/auth-helpers");
|
|
46
|
+
|
|
47
|
+
const litResource = new LitAccessControlConditionResource("*");
|
|
48
|
+
|
|
49
|
+
return client.getSessionSigs({
|
|
50
|
+
chain,
|
|
51
|
+
resourceAbilityRequests: [
|
|
52
|
+
{ resource: litResource, ability: LitAbility.AccessControlConditionDecryption },
|
|
53
|
+
],
|
|
54
|
+
authNeededCallback: async (params: {
|
|
55
|
+
uri?: string;
|
|
56
|
+
expiration?: string;
|
|
57
|
+
resourceAbilityRequests?: any[];
|
|
58
|
+
}) => {
|
|
59
|
+
const toSign = await createSiweMessageWithRecaps({
|
|
60
|
+
uri: params.uri!,
|
|
61
|
+
expiration: params.expiration!,
|
|
62
|
+
resources: params.resourceAbilityRequests!,
|
|
63
|
+
walletAddress: authSig.address,
|
|
64
|
+
nonce: await client.getLatestBlockhash(),
|
|
65
|
+
litNodeClient: client,
|
|
66
|
+
});
|
|
67
|
+
return generateAuthSig({
|
|
68
|
+
signer: {
|
|
69
|
+
signMessage: async () => authSig.sig,
|
|
70
|
+
getAddress: async () => authSig.address,
|
|
71
|
+
} as any,
|
|
72
|
+
toSign,
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async encrypt(
|
|
79
|
+
data: Uint8Array,
|
|
80
|
+
accessConditions: LitAccessCondition[],
|
|
81
|
+
chain: string = "ethereum",
|
|
82
|
+
): Promise<LitEncryptedData> {
|
|
83
|
+
const client = await this.getClient();
|
|
84
|
+
const { encryptUint8Array } = await import("@lit-protocol/encryption");
|
|
85
|
+
const { ciphertext, dataToEncryptHash } = await encryptUint8Array(
|
|
86
|
+
{ accessControlConditions: accessConditions as any, dataToEncrypt: data },
|
|
87
|
+
client,
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
engine: "lit",
|
|
91
|
+
ciphertext:
|
|
92
|
+
typeof ciphertext === "string" ? new TextEncoder().encode(ciphertext) : ciphertext,
|
|
93
|
+
dataToEncryptHash,
|
|
94
|
+
accessControlConditions: accessConditions,
|
|
95
|
+
chain: chain as any,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async decrypt(encrypted: LitEncryptedData, sessionSigsOrAuthSig: any): Promise<Uint8Array> {
|
|
100
|
+
let sessionSigs = sessionSigsOrAuthSig;
|
|
101
|
+
|
|
102
|
+
// If an authSig object is passed, resolve it to sessionSigs
|
|
103
|
+
if (sessionSigsOrAuthSig?.sig && sessionSigsOrAuthSig?.address) {
|
|
104
|
+
sessionSigs = await this.getSessionSigs(
|
|
105
|
+
sessionSigsOrAuthSig as LitAuthSig,
|
|
106
|
+
encrypted.chain as string,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const client = await this.getClient();
|
|
111
|
+
const { decryptToUint8Array } = await import("@lit-protocol/encryption");
|
|
112
|
+
return decryptToUint8Array(
|
|
113
|
+
{
|
|
114
|
+
accessControlConditions: encrypted.accessControlConditions as any,
|
|
115
|
+
chain: encrypted.chain as string,
|
|
116
|
+
ciphertext:
|
|
117
|
+
typeof encrypted.ciphertext === "string"
|
|
118
|
+
? encrypted.ciphertext
|
|
119
|
+
: new TextDecoder().decode(encrypted.ciphertext),
|
|
120
|
+
dataToEncryptHash: encrypted.dataToEncryptHash,
|
|
121
|
+
sessionSigs,
|
|
122
|
+
},
|
|
123
|
+
client,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
createAddressCondition(address: string, chain: EvmChain): LitEvmCondition {
|
|
128
|
+
return {
|
|
129
|
+
conditionType: "evmBasic",
|
|
130
|
+
contractAddress: "" as EvmAddress,
|
|
131
|
+
standardContractType: "",
|
|
132
|
+
chain,
|
|
133
|
+
method: "",
|
|
134
|
+
parameters: [":userAddress"],
|
|
135
|
+
returnValueTest: { comparator: "=", value: address },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
createReputationCondition(opts: {
|
|
140
|
+
registryAddress: string;
|
|
141
|
+
minScore: number;
|
|
142
|
+
chain: EvmChain;
|
|
143
|
+
}): LitEvmCondition {
|
|
144
|
+
return {
|
|
145
|
+
conditionType: "evmContract",
|
|
146
|
+
contractAddress: opts.registryAddress as EvmAddress,
|
|
147
|
+
standardContractType: "",
|
|
148
|
+
chain: opts.chain,
|
|
149
|
+
method: "getScore",
|
|
150
|
+
parameters: [":userAddress"],
|
|
151
|
+
returnValueTest: { comparator: ">=", value: String(opts.minScore) },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async disconnect(): Promise<void> {
|
|
156
|
+
if (this.client) {
|
|
157
|
+
await this.client.disconnect();
|
|
158
|
+
this.client = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault key derivation with optional caching.
|
|
3
|
+
*
|
|
4
|
+
* Derives a deterministic AES-256 key from a wallet signature
|
|
5
|
+
* and caches the signature in storage to avoid re-prompting.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AESEngine } from "./aes.js";
|
|
9
|
+
|
|
10
|
+
const CACHE_PREFIX = "orbitmem:vk";
|
|
11
|
+
const aes = new AESEngine({ kdf: "hkdf-sha256" });
|
|
12
|
+
|
|
13
|
+
export interface VaultKeyConfig {
|
|
14
|
+
/** Wallet address (used as cache key discriminator) */
|
|
15
|
+
address: string;
|
|
16
|
+
/** Wallet signMessage function — returns hex signature */
|
|
17
|
+
signMessage: (message: string) => Promise<string>;
|
|
18
|
+
/** Storage adapter for caching (default: sessionStorage if available) */
|
|
19
|
+
storage?: {
|
|
20
|
+
getItem(key: string): string | null;
|
|
21
|
+
setItem(key: string, value: string): void;
|
|
22
|
+
removeItem(key: string): void;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Derive a vault encryption key, caching the signature to avoid
|
|
28
|
+
* re-prompting the wallet on page reload.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* ```ts
|
|
32
|
+
* const { key, clear } = await deriveVaultKeyWithCache({
|
|
33
|
+
* address: "0x...",
|
|
34
|
+
* signMessage: (msg) => wagmiSignMessage({ message: msg }),
|
|
35
|
+
* });
|
|
36
|
+
* // Use `key` for encryption/decryption
|
|
37
|
+
* // Call `clear()` on disconnect
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function deriveVaultKeyWithCache(
|
|
41
|
+
config: VaultKeyConfig,
|
|
42
|
+
): Promise<{ key: CryptoKey; clear: () => void }> {
|
|
43
|
+
const { address, signMessage } = config;
|
|
44
|
+
const storage = config.storage ?? (typeof sessionStorage !== "undefined" ? sessionStorage : null);
|
|
45
|
+
const cacheKey = `${CACHE_PREFIX}:${address}`;
|
|
46
|
+
|
|
47
|
+
let sig = storage?.getItem(cacheKey) ?? null;
|
|
48
|
+
if (!sig) {
|
|
49
|
+
sig = await signMessage("OrbitMem Vault Key v1");
|
|
50
|
+
storage?.setItem(cacheKey, sig);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sigBytes = new Uint8Array((sig.slice(2).match(/.{2}/g) ?? []).map((b) => parseInt(b, 16)));
|
|
54
|
+
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", sigBytes as BufferSource));
|
|
55
|
+
const key = await aes.deriveKey({ type: "raw", key: hash });
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
key,
|
|
59
|
+
clear() {
|
|
60
|
+
storage?.removeItem(cacheKey);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { deriveSessionKey } from "../session.js";
|
|
4
|
+
|
|
5
|
+
describe("IdentityLayer", () => {
|
|
6
|
+
test("creates session key from EVM signature", async () => {
|
|
7
|
+
const session = await deriveSessionKey({
|
|
8
|
+
family: "evm",
|
|
9
|
+
signature: new Uint8Array(65).fill(1),
|
|
10
|
+
parentAddress: "0x1234567890abcdef1234567890abcdef12345678",
|
|
11
|
+
permissions: [{ type: "vault:read" }, { type: "vault:write" }],
|
|
12
|
+
ttl: 3600,
|
|
13
|
+
});
|
|
14
|
+
expect(session.id).toBeTruthy();
|
|
15
|
+
expect(session.family).toBe("evm");
|
|
16
|
+
expect(session.permissions).toHaveLength(2);
|
|
17
|
+
expect(session.isActive).toBe(true);
|
|
18
|
+
expect(session.expiresAt).toBeGreaterThan(Date.now());
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("session key expires", async () => {
|
|
22
|
+
const session = await deriveSessionKey({
|
|
23
|
+
family: "evm",
|
|
24
|
+
signature: new Uint8Array(65).fill(1),
|
|
25
|
+
parentAddress: "0x1234567890abcdef1234567890abcdef12345678",
|
|
26
|
+
permissions: [{ type: "vault:read" }],
|
|
27
|
+
ttl: -1, // already expired
|
|
28
|
+
});
|
|
29
|
+
expect(session.isActive).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { createOwsAdapter } from "../ows-adapter.js";
|
|
4
|
+
|
|
5
|
+
const WALLET = "orbitmem-test-adapter";
|
|
6
|
+
const CHAIN = "eip155:84532"; // Base Sepolia
|
|
7
|
+
|
|
8
|
+
describe("ows-adapter", () => {
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
const { createWallet, listWallets } = await import("@open-wallet-standard/core");
|
|
11
|
+
const existing = listWallets().find((w) => w.name === WALLET);
|
|
12
|
+
if (!existing) {
|
|
13
|
+
createWallet(WALLET);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
try {
|
|
19
|
+
const { deleteWallet } = await import("@open-wallet-standard/core");
|
|
20
|
+
deleteWallet(WALLET);
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("getAddress returns a valid EVM address", async () => {
|
|
27
|
+
const adapter = createOwsAdapter(WALLET, CHAIN);
|
|
28
|
+
const address = await adapter.getAddress();
|
|
29
|
+
expect(address).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("signMessage returns a Uint8Array signature", async () => {
|
|
33
|
+
const adapter = createOwsAdapter(WALLET, CHAIN);
|
|
34
|
+
const result = await adapter.signMessage("hello");
|
|
35
|
+
expect(result.signature).toBeInstanceOf(Uint8Array);
|
|
36
|
+
expect(result.signature.length).toBeGreaterThan(0);
|
|
37
|
+
expect(result.algorithm).toBe("ecdsa-secp256k1");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("toViemAccount returns account with address and sign methods", async () => {
|
|
41
|
+
const adapter = createOwsAdapter(WALLET, CHAIN);
|
|
42
|
+
const account = await adapter.toViemAccount();
|
|
43
|
+
expect(account.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
44
|
+
expect(typeof account.signMessage).toBe("function");
|
|
45
|
+
expect(typeof account.signTransaction).toBe("function");
|
|
46
|
+
});
|
|
47
|
+
});
|