@fluxpointstudios/orynq-sdk-flight-recorder 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/LICENSE +21 -0
- package/dist/crypto/compression.d.ts +37 -0
- package/dist/crypto/compression.d.ts.map +1 -0
- package/dist/crypto/compression.js +84 -0
- package/dist/crypto/compression.js.map +1 -0
- package/dist/crypto/encryption.d.ts +44 -0
- package/dist/crypto/encryption.d.ts.map +1 -0
- package/dist/crypto/encryption.js +129 -0
- package/dist/crypto/encryption.js.map +1 -0
- package/dist/crypto/hashing.d.ts +63 -0
- package/dist/crypto/hashing.d.ts.map +1 -0
- package/dist/crypto/hashing.js +182 -0
- package/dist/crypto/hashing.js.map +1 -0
- package/dist/crypto/index.d.ts +4 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +4 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.d.ts +2 -0
- package/dist/integration/index.d.ts.map +1 -0
- package/dist/integration/index.js +2 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/openclaw-adapter.d.ts +64 -0
- package/dist/integration/openclaw-adapter.d.ts.map +1 -0
- package/dist/integration/openclaw-adapter.js +137 -0
- package/dist/integration/openclaw-adapter.js.map +1 -0
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +2 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/manifest-builder.d.ts +72 -0
- package/dist/manifest/manifest-builder.d.ts.map +1 -0
- package/dist/manifest/manifest-builder.js +84 -0
- package/dist/manifest/manifest-builder.js.map +1 -0
- package/dist/recorder/chunk-manager.d.ts +56 -0
- package/dist/recorder/chunk-manager.d.ts.map +1 -0
- package/dist/recorder/chunk-manager.js +172 -0
- package/dist/recorder/chunk-manager.js.map +1 -0
- package/dist/recorder/event-buffer.d.ts +61 -0
- package/dist/recorder/event-buffer.d.ts.map +1 -0
- package/dist/recorder/event-buffer.js +101 -0
- package/dist/recorder/event-buffer.js.map +1 -0
- package/dist/recorder/index.d.ts +4 -0
- package/dist/recorder/index.d.ts.map +1 -0
- package/dist/recorder/index.js +4 -0
- package/dist/recorder/index.js.map +1 -0
- package/dist/recorder/stream-recorder.d.ts +66 -0
- package/dist/recorder/stream-recorder.d.ts.map +1 -0
- package/dist/recorder/stream-recorder.js +329 -0
- package/dist/recorder/stream-recorder.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local-adapter.d.ts +65 -0
- package/dist/storage/local-adapter.d.ts.map +1 -0
- package/dist/storage/local-adapter.js +152 -0
- package/dist/storage/local-adapter.js.map +1 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
- package/src/__tests__/flight-recorder.test.ts +211 -0
- package/src/crypto/compression.ts +105 -0
- package/src/crypto/encryption.ts +210 -0
- package/src/crypto/hashing.ts +225 -0
- package/src/crypto/index.ts +3 -0
- package/src/index.ts +96 -0
- package/src/integration/index.ts +1 -0
- package/src/integration/openclaw-adapter.ts +206 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest-builder.ts +152 -0
- package/src/recorder/chunk-manager.ts +224 -0
- package/src/recorder/event-buffer.ts +124 -0
- package/src/recorder/index.ts +3 -0
- package/src/recorder/stream-recorder.ts +427 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/local-adapter.ts +181 -0
- package/src/types.ts +347 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encryption utilities for flight recorder chunks.
|
|
3
|
+
* Uses Web Crypto API for AES-256-GCM encryption.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { webcrypto } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
const crypto = webcrypto as unknown as Crypto;
|
|
9
|
+
|
|
10
|
+
export interface EncryptedData {
|
|
11
|
+
ciphertext: Uint8Array;
|
|
12
|
+
nonce: Uint8Array;
|
|
13
|
+
tag: Uint8Array; // For AES-GCM, tag is appended to ciphertext
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface EncryptionKey {
|
|
17
|
+
keyId: string;
|
|
18
|
+
key: CryptoKey;
|
|
19
|
+
algorithm: "aes-256-gcm" | "chacha20-poly1305";
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a new AES-256-GCM encryption key.
|
|
25
|
+
*/
|
|
26
|
+
export async function generateKey(algorithm: "aes-256-gcm" | "chacha20-poly1305" = "aes-256-gcm"): Promise<EncryptionKey> {
|
|
27
|
+
if (algorithm === "chacha20-poly1305") {
|
|
28
|
+
// ChaCha20-Poly1305 not directly supported in Web Crypto
|
|
29
|
+
// Fall back to AES-GCM for now, can add libsodium later
|
|
30
|
+
console.warn("ChaCha20-Poly1305 not supported, using AES-256-GCM");
|
|
31
|
+
algorithm = "aes-256-gcm";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const key = await crypto.subtle.generateKey(
|
|
35
|
+
{ name: "AES-GCM", length: 256 },
|
|
36
|
+
true, // extractable for export/wrapping
|
|
37
|
+
["encrypt", "decrypt"]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const keyId = generateKeyId();
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
keyId,
|
|
44
|
+
key,
|
|
45
|
+
algorithm,
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate a random key ID.
|
|
52
|
+
*/
|
|
53
|
+
function generateKeyId(): string {
|
|
54
|
+
const bytes = new Uint8Array(16);
|
|
55
|
+
crypto.getRandomValues(bytes);
|
|
56
|
+
return Array.from(bytes)
|
|
57
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
58
|
+
.join("");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate a random nonce/IV for AES-GCM (12 bytes recommended).
|
|
63
|
+
*/
|
|
64
|
+
export function generateNonce(): Uint8Array {
|
|
65
|
+
const nonce = new Uint8Array(12);
|
|
66
|
+
crypto.getRandomValues(nonce);
|
|
67
|
+
return nonce;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Encrypt data using AES-256-GCM.
|
|
72
|
+
*/
|
|
73
|
+
export async function encrypt(
|
|
74
|
+
data: Uint8Array,
|
|
75
|
+
key: EncryptionKey,
|
|
76
|
+
additionalData?: Uint8Array
|
|
77
|
+
): Promise<EncryptedData> {
|
|
78
|
+
const nonce = generateNonce();
|
|
79
|
+
|
|
80
|
+
const params: AesGcmParams = {
|
|
81
|
+
name: "AES-GCM",
|
|
82
|
+
iv: nonce as unknown as ArrayBuffer,
|
|
83
|
+
tagLength: 128, // 16 bytes
|
|
84
|
+
};
|
|
85
|
+
if (additionalData) {
|
|
86
|
+
params.additionalData = additionalData as unknown as ArrayBuffer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const ciphertextWithTag = await crypto.subtle.encrypt(
|
|
90
|
+
params,
|
|
91
|
+
key.key,
|
|
92
|
+
data as unknown as ArrayBuffer
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const ciphertextWithTagArray = new Uint8Array(ciphertextWithTag);
|
|
96
|
+
|
|
97
|
+
// AES-GCM appends the tag to the ciphertext
|
|
98
|
+
// Split them for clarity in our data structure
|
|
99
|
+
const tagStart = ciphertextWithTagArray.length - 16;
|
|
100
|
+
const ciphertext = ciphertextWithTagArray.slice(0, tagStart);
|
|
101
|
+
const tag = ciphertextWithTagArray.slice(tagStart);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
ciphertext,
|
|
105
|
+
nonce,
|
|
106
|
+
tag,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Decrypt data using AES-256-GCM.
|
|
112
|
+
*/
|
|
113
|
+
export async function decrypt(
|
|
114
|
+
encrypted: EncryptedData,
|
|
115
|
+
key: EncryptionKey,
|
|
116
|
+
additionalData?: Uint8Array
|
|
117
|
+
): Promise<Uint8Array> {
|
|
118
|
+
// Reconstruct ciphertext with tag appended
|
|
119
|
+
const ciphertextWithTag = new Uint8Array(encrypted.ciphertext.length + encrypted.tag.length);
|
|
120
|
+
ciphertextWithTag.set(encrypted.ciphertext, 0);
|
|
121
|
+
ciphertextWithTag.set(encrypted.tag, encrypted.ciphertext.length);
|
|
122
|
+
|
|
123
|
+
const decryptParams: AesGcmParams = {
|
|
124
|
+
name: "AES-GCM",
|
|
125
|
+
iv: encrypted.nonce as unknown as ArrayBuffer,
|
|
126
|
+
tagLength: 128,
|
|
127
|
+
};
|
|
128
|
+
if (additionalData) {
|
|
129
|
+
decryptParams.additionalData = additionalData as unknown as ArrayBuffer;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
133
|
+
decryptParams,
|
|
134
|
+
key.key,
|
|
135
|
+
ciphertextWithTag as unknown as ArrayBuffer
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return new Uint8Array(plaintext);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Export key as raw bytes (for sealing/wrapping).
|
|
143
|
+
*/
|
|
144
|
+
export async function exportKey(key: EncryptionKey): Promise<Uint8Array> {
|
|
145
|
+
const rawKey = await crypto.subtle.exportKey("raw", key.key);
|
|
146
|
+
return new Uint8Array(rawKey);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Import key from raw bytes.
|
|
151
|
+
*/
|
|
152
|
+
export async function importKey(
|
|
153
|
+
rawKey: Uint8Array,
|
|
154
|
+
keyId: string,
|
|
155
|
+
algorithm: "aes-256-gcm" | "chacha20-poly1305" = "aes-256-gcm"
|
|
156
|
+
): Promise<EncryptionKey> {
|
|
157
|
+
const key = await crypto.subtle.importKey(
|
|
158
|
+
"raw",
|
|
159
|
+
rawKey as unknown as ArrayBuffer,
|
|
160
|
+
{ name: "AES-GCM", length: 256 },
|
|
161
|
+
true,
|
|
162
|
+
["encrypt", "decrypt"]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
keyId,
|
|
167
|
+
key,
|
|
168
|
+
algorithm,
|
|
169
|
+
createdAt: new Date().toISOString(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Derive a key from a master key using HKDF-SHA256.
|
|
175
|
+
*/
|
|
176
|
+
export async function deriveKey(
|
|
177
|
+
masterKey: Uint8Array,
|
|
178
|
+
info: string,
|
|
179
|
+
salt?: Uint8Array
|
|
180
|
+
): Promise<EncryptionKey> {
|
|
181
|
+
const baseKey = await crypto.subtle.importKey(
|
|
182
|
+
"raw",
|
|
183
|
+
masterKey as unknown as ArrayBuffer,
|
|
184
|
+
"HKDF",
|
|
185
|
+
false,
|
|
186
|
+
["deriveKey"]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const saltBuffer = salt ?? new Uint8Array(32);
|
|
190
|
+
const infoBuffer = new TextEncoder().encode(info);
|
|
191
|
+
const derivedKey = await crypto.subtle.deriveKey(
|
|
192
|
+
{
|
|
193
|
+
name: "HKDF",
|
|
194
|
+
hash: "SHA-256",
|
|
195
|
+
salt: saltBuffer as unknown as ArrayBuffer,
|
|
196
|
+
info: infoBuffer as unknown as ArrayBuffer,
|
|
197
|
+
},
|
|
198
|
+
baseKey,
|
|
199
|
+
{ name: "AES-GCM", length: 256 },
|
|
200
|
+
true,
|
|
201
|
+
["encrypt", "decrypt"]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
keyId: generateKeyId(),
|
|
206
|
+
key: derivedKey,
|
|
207
|
+
algorithm: "aes-256-gcm",
|
|
208
|
+
createdAt: new Date().toISOString(),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashing utilities for flight recorder.
|
|
3
|
+
* Provides consistent SHA-256 hashing with domain separation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { webcrypto } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
const crypto = webcrypto as unknown as Crypto;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hash domain prefixes for different data types.
|
|
12
|
+
*/
|
|
13
|
+
export const HASH_DOMAINS = {
|
|
14
|
+
event: "poi-flight:event:v2|",
|
|
15
|
+
chunk: "poi-flight:chunk:v2|",
|
|
16
|
+
manifest: "poi-flight:manifest:v2|",
|
|
17
|
+
roll: "poi-flight:roll:v2|",
|
|
18
|
+
merkleLeaf: "poi-flight:leaf:v2|",
|
|
19
|
+
merkleNode: "poi-flight:node:v2|",
|
|
20
|
+
encryptedChunk: "poi-flight:encrypted:v2|",
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export type HashDomain = keyof typeof HASH_DOMAINS;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute SHA-256 hash of data with domain separation.
|
|
27
|
+
*/
|
|
28
|
+
export async function sha256(data: Uint8Array, domain?: HashDomain): Promise<string> {
|
|
29
|
+
let input = data;
|
|
30
|
+
|
|
31
|
+
if (domain) {
|
|
32
|
+
const prefix = new TextEncoder().encode(HASH_DOMAINS[domain]);
|
|
33
|
+
const combined = new Uint8Array(prefix.length + data.length);
|
|
34
|
+
combined.set(prefix, 0);
|
|
35
|
+
combined.set(data, prefix.length);
|
|
36
|
+
input = combined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", input as unknown as ArrayBuffer);
|
|
40
|
+
return bufferToHex(new Uint8Array(hashBuffer));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compute SHA-256 hash of a string.
|
|
45
|
+
*/
|
|
46
|
+
export async function sha256String(str: string, domain?: HashDomain): Promise<string> {
|
|
47
|
+
const data = new TextEncoder().encode(str);
|
|
48
|
+
return sha256(data, domain);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compute SHA-256 hash of JSON-serializable data.
|
|
53
|
+
*/
|
|
54
|
+
export async function sha256Json(obj: unknown, domain?: HashDomain): Promise<string> {
|
|
55
|
+
const json = JSON.stringify(obj, null, 0);
|
|
56
|
+
return sha256String(json, domain);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Compute rolling hash by combining previous hash with new data.
|
|
61
|
+
*/
|
|
62
|
+
export async function rollingHash(prevHash: string, newData: Uint8Array): Promise<string> {
|
|
63
|
+
const prevBytes = hexToBuffer(prevHash);
|
|
64
|
+
const combined = new Uint8Array(prevBytes.length + newData.length);
|
|
65
|
+
combined.set(prevBytes, 0);
|
|
66
|
+
combined.set(newData, prevBytes.length);
|
|
67
|
+
return sha256(combined, "roll");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compute Merkle tree leaf hash.
|
|
72
|
+
*/
|
|
73
|
+
export async function merkleLeaf(data: Uint8Array): Promise<string> {
|
|
74
|
+
return sha256(data, "merkleLeaf");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Compute Merkle tree internal node hash.
|
|
79
|
+
*/
|
|
80
|
+
export async function merkleNode(left: string, right: string): Promise<string> {
|
|
81
|
+
const leftBytes = hexToBuffer(left);
|
|
82
|
+
const rightBytes = hexToBuffer(right);
|
|
83
|
+
const combined = new Uint8Array(leftBytes.length + rightBytes.length);
|
|
84
|
+
combined.set(leftBytes, 0);
|
|
85
|
+
combined.set(rightBytes, leftBytes.length);
|
|
86
|
+
return sha256(combined, "merkleNode");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build a Merkle tree from leaf hashes and return the root.
|
|
91
|
+
*/
|
|
92
|
+
export async function buildMerkleRoot(leafHashes: string[]): Promise<string> {
|
|
93
|
+
if (leafHashes.length === 0) {
|
|
94
|
+
// Empty tree - hash of empty string
|
|
95
|
+
return sha256(new Uint8Array(0), "merkleNode");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (leafHashes.length === 1) {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
100
|
+
return leafHashes[0]!;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Build tree level by level
|
|
104
|
+
let currentLevel = leafHashes;
|
|
105
|
+
|
|
106
|
+
while (currentLevel.length > 1) {
|
|
107
|
+
const nextLevel: string[] = [];
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
110
|
+
const left = currentLevel[i];
|
|
111
|
+
const right = currentLevel[i + 1];
|
|
112
|
+
if (left !== undefined && right !== undefined) {
|
|
113
|
+
// Pair exists
|
|
114
|
+
const nodeHash = await merkleNode(left, right);
|
|
115
|
+
nextLevel.push(nodeHash);
|
|
116
|
+
} else if (left !== undefined) {
|
|
117
|
+
// Odd element - promote to next level
|
|
118
|
+
nextLevel.push(left);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
currentLevel = nextLevel;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
126
|
+
return currentLevel[0]!;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate a Merkle proof for a leaf at the given index.
|
|
131
|
+
*/
|
|
132
|
+
export interface MerkleProof {
|
|
133
|
+
leafIndex: number;
|
|
134
|
+
leafHash: string;
|
|
135
|
+
siblings: Array<{ hash: string; position: "left" | "right" }>;
|
|
136
|
+
root: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function generateMerkleProof(
|
|
140
|
+
leafHashes: string[],
|
|
141
|
+
leafIndex: number
|
|
142
|
+
): Promise<MerkleProof> {
|
|
143
|
+
if (leafIndex < 0 || leafIndex >= leafHashes.length) {
|
|
144
|
+
throw new Error(`Invalid leaf index: ${leafIndex}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const siblings: Array<{ hash: string; position: "left" | "right" }> = [];
|
|
148
|
+
let currentLevel = leafHashes;
|
|
149
|
+
let currentIndex = leafIndex;
|
|
150
|
+
|
|
151
|
+
while (currentLevel.length > 1) {
|
|
152
|
+
const isLeft = currentIndex % 2 === 0;
|
|
153
|
+
const siblingIndex = isLeft ? currentIndex + 1 : currentIndex - 1;
|
|
154
|
+
|
|
155
|
+
const siblingHash = currentLevel[siblingIndex];
|
|
156
|
+
if (siblingIndex < currentLevel.length && siblingHash !== undefined) {
|
|
157
|
+
siblings.push({
|
|
158
|
+
hash: siblingHash,
|
|
159
|
+
position: isLeft ? "right" : "left",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Build next level
|
|
164
|
+
const nextLevel: string[] = [];
|
|
165
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
166
|
+
const left = currentLevel[i];
|
|
167
|
+
const right = currentLevel[i + 1];
|
|
168
|
+
if (left !== undefined && right !== undefined) {
|
|
169
|
+
const nodeHash = await merkleNode(left, right);
|
|
170
|
+
nextLevel.push(nodeHash);
|
|
171
|
+
} else if (left !== undefined) {
|
|
172
|
+
nextLevel.push(left);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
currentLevel = nextLevel;
|
|
177
|
+
currentIndex = Math.floor(currentIndex / 2);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const leafHash = leafHashes[leafIndex];
|
|
181
|
+
const root = currentLevel[0];
|
|
182
|
+
if (leafHash === undefined || root === undefined) {
|
|
183
|
+
throw new Error("Invalid merkle tree state");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
leafIndex,
|
|
188
|
+
leafHash,
|
|
189
|
+
siblings,
|
|
190
|
+
root,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Verify a Merkle proof.
|
|
196
|
+
*/
|
|
197
|
+
export async function verifyMerkleProof(proof: MerkleProof): Promise<boolean> {
|
|
198
|
+
let currentHash = proof.leafHash;
|
|
199
|
+
|
|
200
|
+
for (const sibling of proof.siblings) {
|
|
201
|
+
if (sibling.position === "left") {
|
|
202
|
+
currentHash = await merkleNode(sibling.hash, currentHash);
|
|
203
|
+
} else {
|
|
204
|
+
currentHash = await merkleNode(currentHash, sibling.hash);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return currentHash === proof.root;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// === Utility functions ===
|
|
212
|
+
|
|
213
|
+
function bufferToHex(buffer: Uint8Array): string {
|
|
214
|
+
return Array.from(buffer)
|
|
215
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
216
|
+
.join("");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function hexToBuffer(hex: string): Uint8Array {
|
|
220
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
221
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
222
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
223
|
+
}
|
|
224
|
+
return bytes;
|
|
225
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fluxpointstudios/orynq-sdk-flight-recorder
|
|
3
|
+
*
|
|
4
|
+
* Streaming flight recorder for Proof of Inference (PoI).
|
|
5
|
+
* Captures inference events with chunking, compression, and encryption.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { FlightRecorder, LocalStorageAdapter } from '@fluxpointstudios/orynq-sdk-flight-recorder';
|
|
10
|
+
*
|
|
11
|
+
* const storage = new LocalStorageAdapter({ baseDir: './traces' });
|
|
12
|
+
*
|
|
13
|
+
* const recorder = new FlightRecorder({
|
|
14
|
+
* agentId: 'my-agent',
|
|
15
|
+
* chunkSizeBytes: 4 * 1024 * 1024, // 4MB chunks
|
|
16
|
+
* encryption: {
|
|
17
|
+
* algorithm: 'aes-256-gcm',
|
|
18
|
+
* keyDerivation: 'hkdf-sha256',
|
|
19
|
+
* keyMode: { type: 'ephemeral' },
|
|
20
|
+
* },
|
|
21
|
+
* storage,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* await recorder.start();
|
|
25
|
+
*
|
|
26
|
+
* await recorder.record({
|
|
27
|
+
* kind: 'inference:start',
|
|
28
|
+
* requestId: 'req-123',
|
|
29
|
+
* model: 'claude-3-opus',
|
|
30
|
+
* promptHash: '...',
|
|
31
|
+
* params: { temperature: 0.7 },
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const result = await recorder.finalize();
|
|
35
|
+
* console.log('Manifest:', result.manifest);
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// Types
|
|
40
|
+
export * from "./types.js";
|
|
41
|
+
|
|
42
|
+
// Core recorder
|
|
43
|
+
export { FlightRecorder } from "./recorder/stream-recorder.js";
|
|
44
|
+
export { EventBuffer } from "./recorder/event-buffer.js";
|
|
45
|
+
export { ChunkManager } from "./recorder/chunk-manager.js";
|
|
46
|
+
|
|
47
|
+
// Crypto utilities
|
|
48
|
+
export {
|
|
49
|
+
generateKey,
|
|
50
|
+
encrypt,
|
|
51
|
+
decrypt,
|
|
52
|
+
exportKey,
|
|
53
|
+
importKey,
|
|
54
|
+
deriveKey,
|
|
55
|
+
type EncryptionKey,
|
|
56
|
+
type EncryptedData,
|
|
57
|
+
} from "./crypto/encryption.js";
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
compress,
|
|
61
|
+
decompress,
|
|
62
|
+
compressString,
|
|
63
|
+
decompressString,
|
|
64
|
+
isCompressible,
|
|
65
|
+
type CompressionType,
|
|
66
|
+
type CompressionResult,
|
|
67
|
+
} from "./crypto/compression.js";
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
sha256,
|
|
71
|
+
sha256String,
|
|
72
|
+
sha256Json,
|
|
73
|
+
rollingHash,
|
|
74
|
+
merkleLeaf,
|
|
75
|
+
merkleNode,
|
|
76
|
+
buildMerkleRoot,
|
|
77
|
+
generateMerkleProof,
|
|
78
|
+
verifyMerkleProof,
|
|
79
|
+
HASH_DOMAINS,
|
|
80
|
+
type HashDomain,
|
|
81
|
+
type MerkleProof,
|
|
82
|
+
} from "./crypto/hashing.js";
|
|
83
|
+
|
|
84
|
+
// Manifest
|
|
85
|
+
export { ManifestBuilder, type ManifestInput } from "./manifest/manifest-builder.js";
|
|
86
|
+
|
|
87
|
+
// Storage
|
|
88
|
+
export { LocalStorageAdapter, type LocalStorageConfig } from "./storage/local-adapter.js";
|
|
89
|
+
|
|
90
|
+
// Integration
|
|
91
|
+
export {
|
|
92
|
+
OpenClawAdapter,
|
|
93
|
+
type LegacyTraceEvent,
|
|
94
|
+
type LegacyTraceBundle,
|
|
95
|
+
type LegacyManifest,
|
|
96
|
+
} from "./integration/openclaw-adapter.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OpenClawAdapter } from "./openclaw-adapter.js";
|