gridstamp 1.0.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/.cursorrules +74 -0
- package/CLAUDE.md +61 -0
- package/LICENSE +190 -0
- package/README.md +107 -0
- package/dist/index.js +194 -0
- package/package.json +84 -0
- package/src/antispoofing/detector.ts +509 -0
- package/src/antispoofing/index.ts +7 -0
- package/src/gamification/badges.ts +429 -0
- package/src/gamification/fleet-leaderboard.ts +293 -0
- package/src/gamification/index.ts +44 -0
- package/src/gamification/streaks.ts +243 -0
- package/src/gamification/trust-tiers.ts +393 -0
- package/src/gamification/zone-mastery.ts +256 -0
- package/src/index.ts +341 -0
- package/src/memory/index.ts +9 -0
- package/src/memory/place-cells.ts +279 -0
- package/src/memory/spatial-memory.ts +375 -0
- package/src/navigation/index.ts +1 -0
- package/src/navigation/pathfinding.ts +403 -0
- package/src/perception/camera.ts +249 -0
- package/src/perception/index.ts +2 -0
- package/src/types/index.ts +416 -0
- package/src/utils/crypto.ts +94 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/math.ts +204 -0
- package/src/verification/index.ts +9 -0
- package/src/verification/spatial-proof.ts +442 -0
- package/tests/antispoofing/detector.test.ts +196 -0
- package/tests/gamification/badges.test.ts +163 -0
- package/tests/gamification/fleet-leaderboard.test.ts +181 -0
- package/tests/gamification/streaks.test.ts +158 -0
- package/tests/gamification/trust-tiers.test.ts +165 -0
- package/tests/gamification/zone-mastery.test.ts +143 -0
- package/tests/memory/place-cells.test.ts +128 -0
- package/tests/stress/load.test.ts +499 -0
- package/tests/stress/security.test.ts +378 -0
- package/tests/stress/simulation.test.ts +361 -0
- package/tests/utils/crypto.test.ts +115 -0
- package/tests/utils/math.test.ts +195 -0
- package/tests/verification/spatial-proof.test.ts +299 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gridstamp — Type definitions
|
|
3
|
+
* Embodied spatial memory + payment verification for autonomous robots
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================
|
|
7
|
+
// GEOMETRY & SPATIAL PRIMITIVES
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
/** 3D position in world coordinates (meters) */
|
|
11
|
+
export interface Vec3 {
|
|
12
|
+
readonly x: number;
|
|
13
|
+
readonly y: number;
|
|
14
|
+
readonly z: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Quaternion rotation (unit quaternion, ||q|| = 1) */
|
|
18
|
+
export interface Quaternion {
|
|
19
|
+
readonly w: number;
|
|
20
|
+
readonly x: number;
|
|
21
|
+
readonly y: number;
|
|
22
|
+
readonly z: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 6-DOF pose: position + orientation */
|
|
26
|
+
export interface Pose {
|
|
27
|
+
readonly position: Vec3;
|
|
28
|
+
readonly orientation: Quaternion;
|
|
29
|
+
readonly timestamp: number; // Unix ms
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 4x4 transformation matrix (row-major) */
|
|
33
|
+
export type Mat4 = readonly [
|
|
34
|
+
number, number, number, number,
|
|
35
|
+
number, number, number, number,
|
|
36
|
+
number, number, number, number,
|
|
37
|
+
number, number, number, number,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Axis-aligned bounding box */
|
|
41
|
+
export interface AABB {
|
|
42
|
+
readonly min: Vec3;
|
|
43
|
+
readonly max: Vec3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================
|
|
47
|
+
// CAMERA & PERCEPTION
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
/** Camera intrinsic parameters */
|
|
51
|
+
export interface CameraIntrinsics {
|
|
52
|
+
readonly fx: number; // focal length x (pixels)
|
|
53
|
+
readonly fy: number; // focal length y (pixels)
|
|
54
|
+
readonly cx: number; // principal point x
|
|
55
|
+
readonly cy: number; // principal point y
|
|
56
|
+
readonly width: number;
|
|
57
|
+
readonly height: number;
|
|
58
|
+
readonly distortion?: readonly number[]; // radial + tangential coefficients
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Stereo camera baseline */
|
|
62
|
+
export interface StereoConfig {
|
|
63
|
+
readonly baseline: number; // meters between cameras
|
|
64
|
+
readonly intrinsics: CameraIntrinsics;
|
|
65
|
+
readonly minDepth: number; // meters
|
|
66
|
+
readonly maxDepth: number; // meters
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Raw camera frame with metadata */
|
|
70
|
+
export interface CameraFrame {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly timestamp: number;
|
|
73
|
+
readonly rgb: Uint8Array; // RGB pixel data
|
|
74
|
+
readonly width: number;
|
|
75
|
+
readonly height: number;
|
|
76
|
+
readonly depth?: Float32Array; // depth map in meters
|
|
77
|
+
readonly pose?: Pose; // camera pose at capture time
|
|
78
|
+
readonly hmac?: string; // HMAC-SHA256 signature
|
|
79
|
+
readonly sequenceNumber: number; // monotonic counter for replay detection
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Depth map with confidence */
|
|
83
|
+
export interface DepthMap {
|
|
84
|
+
readonly data: Float32Array;
|
|
85
|
+
readonly width: number;
|
|
86
|
+
readonly height: number;
|
|
87
|
+
readonly minDepth: number;
|
|
88
|
+
readonly maxDepth: number;
|
|
89
|
+
readonly confidence?: Float32Array; // per-pixel confidence [0,1]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Camera hardware abstraction */
|
|
93
|
+
export enum CameraType {
|
|
94
|
+
OAK_D_PRO = 'oak-d-pro',
|
|
95
|
+
OAK_D_LONG_RANGE = 'oak-d-lr',
|
|
96
|
+
OAK_4_D_PRO = 'oak-4-d-pro',
|
|
97
|
+
REALSENSE_D455 = 'realsense-d455',
|
|
98
|
+
ZED_2I = 'zed-2i',
|
|
99
|
+
SIMULATED = 'simulated',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface CameraConfig {
|
|
103
|
+
readonly type: CameraType;
|
|
104
|
+
readonly role: 'foveal' | 'peripheral';
|
|
105
|
+
readonly stereo: StereoConfig;
|
|
106
|
+
readonly fps: number;
|
|
107
|
+
readonly autoExposure: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================================
|
|
111
|
+
// 3D GAUSSIAN SPLATTING
|
|
112
|
+
// ============================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Single 3D Gaussian splat (59 parameters)
|
|
116
|
+
* Position (3) + Covariance/Scale (3) + Rotation (4) + Opacity (1)
|
|
117
|
+
* + SH coefficients (48 for degree 3)
|
|
118
|
+
*/
|
|
119
|
+
export interface GaussianSplat {
|
|
120
|
+
readonly position: Vec3;
|
|
121
|
+
readonly scale: Vec3; // log-scale
|
|
122
|
+
readonly rotation: Quaternion;
|
|
123
|
+
readonly opacity: number; // sigmoid-activated, [0,1]
|
|
124
|
+
readonly shCoeffs: Float32Array; // spherical harmonics (48 floats for degree 3)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Gaussian splat scene (collection of splats) */
|
|
128
|
+
export interface SplatScene {
|
|
129
|
+
readonly id: string;
|
|
130
|
+
readonly splats: GaussianSplat[];
|
|
131
|
+
readonly count: number;
|
|
132
|
+
readonly boundingBox: AABB;
|
|
133
|
+
readonly createdAt: number;
|
|
134
|
+
readonly merkleRoot?: string; // SHA-256 Merkle root of splat data
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Rendered view from a splat scene */
|
|
138
|
+
export interface RenderedView {
|
|
139
|
+
readonly rgb: Uint8Array;
|
|
140
|
+
readonly depth: Float32Array;
|
|
141
|
+
readonly width: number;
|
|
142
|
+
readonly height: number;
|
|
143
|
+
readonly pose: Pose;
|
|
144
|
+
readonly renderTimeMs: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================
|
|
148
|
+
// SPATIAL MEMORY (3-TIER)
|
|
149
|
+
// ============================================================
|
|
150
|
+
|
|
151
|
+
export enum MemoryTier {
|
|
152
|
+
SHORT = 'short', // 1M splats, 30Hz live, ~30s window
|
|
153
|
+
MID = 'mid', // 100K splats, episodic, minutes-hours
|
|
154
|
+
LONG = 'long', // 10K/room, Merkle-signed, persistent
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Short-term memory entry (live perception) */
|
|
158
|
+
export interface ShortTermEntry {
|
|
159
|
+
readonly frame: CameraFrame;
|
|
160
|
+
readonly splats: GaussianSplat[];
|
|
161
|
+
readonly timestamp: number;
|
|
162
|
+
readonly expiresAt: number; // auto-evict after TTL
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Mid-term episodic memory */
|
|
166
|
+
export interface EpisodicMemory {
|
|
167
|
+
readonly id: string;
|
|
168
|
+
readonly scene: SplatScene;
|
|
169
|
+
readonly location: Vec3;
|
|
170
|
+
readonly timestamp: number;
|
|
171
|
+
readonly tags: readonly string[]; // semantic labels
|
|
172
|
+
readonly confidence: number; // [0,1] how reliable
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Long-term persistent memory (Merkle-signed) */
|
|
176
|
+
export interface LongTermMemory {
|
|
177
|
+
readonly id: string;
|
|
178
|
+
readonly roomId: string;
|
|
179
|
+
readonly scene: SplatScene;
|
|
180
|
+
readonly merkleRoot: string;
|
|
181
|
+
readonly merkleProof: readonly string[];
|
|
182
|
+
readonly signature: string; // HMAC-SHA256
|
|
183
|
+
readonly createdAt: number;
|
|
184
|
+
readonly lastVerified: number;
|
|
185
|
+
readonly splatCount: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Memory consolidation event (short → mid → long) */
|
|
189
|
+
export interface ConsolidationEvent {
|
|
190
|
+
readonly from: MemoryTier;
|
|
191
|
+
readonly to: MemoryTier;
|
|
192
|
+
readonly entryCount: number;
|
|
193
|
+
readonly compressionRatio: number;
|
|
194
|
+
readonly timestamp: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================
|
|
198
|
+
// PLACE CELLS & GRID CELLS (Bio-inspired navigation)
|
|
199
|
+
// ============================================================
|
|
200
|
+
|
|
201
|
+
/** Place cell — fires at specific location (O'Keefe, 1971) */
|
|
202
|
+
export interface PlaceCell {
|
|
203
|
+
readonly id: string;
|
|
204
|
+
readonly center: Vec3; // preferred location
|
|
205
|
+
readonly radius: number; // firing field radius (meters)
|
|
206
|
+
readonly peakRate: number; // max firing rate (Hz)
|
|
207
|
+
activation(position: Vec3): number; // Gaussian falloff [0,1]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Grid cell — hexagonal tiling (Moser & Moser, 2005) */
|
|
211
|
+
export interface GridCell {
|
|
212
|
+
readonly id: string;
|
|
213
|
+
readonly spacing: number; // grid period (meters)
|
|
214
|
+
readonly orientation: number; // grid orientation (radians)
|
|
215
|
+
readonly phase: Vec3; // phase offset
|
|
216
|
+
activation(position: Vec3): number; // periodic activation [0,1]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Spatial code — combined place + grid cell population vector */
|
|
220
|
+
export interface SpatialCode {
|
|
221
|
+
readonly placeCellActivations: ReadonlyMap<string, number>;
|
|
222
|
+
readonly gridCellActivations: ReadonlyMap<string, number>;
|
|
223
|
+
readonly estimatedPosition: Vec3;
|
|
224
|
+
readonly confidence: number;
|
|
225
|
+
readonly timestamp: number;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================
|
|
229
|
+
// NAVIGATION
|
|
230
|
+
// ============================================================
|
|
231
|
+
|
|
232
|
+
export enum PathAlgorithm {
|
|
233
|
+
A_STAR = 'a-star',
|
|
234
|
+
RRT_STAR = 'rrt-star',
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export enum ReferenceFrame {
|
|
238
|
+
EGOCENTRIC = 'ego', // robot-centered
|
|
239
|
+
ALLOCENTRIC = 'allo', // world-centered
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Navigation waypoint */
|
|
243
|
+
export interface Waypoint {
|
|
244
|
+
readonly position: Vec3;
|
|
245
|
+
readonly orientation?: Quaternion;
|
|
246
|
+
readonly speed?: number; // m/s
|
|
247
|
+
readonly tolerance: number; // meters
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Planned path */
|
|
251
|
+
export interface Path {
|
|
252
|
+
readonly id: string;
|
|
253
|
+
readonly waypoints: readonly Waypoint[];
|
|
254
|
+
readonly algorithm: PathAlgorithm;
|
|
255
|
+
readonly totalDistance: number; // meters
|
|
256
|
+
readonly estimatedTime: number; // seconds
|
|
257
|
+
readonly cost: number; // path cost metric
|
|
258
|
+
readonly collisionFree: boolean;
|
|
259
|
+
readonly createdAt: number;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ============================================================
|
|
263
|
+
// SPATIAL VERIFICATION & PAYMENT
|
|
264
|
+
// ============================================================
|
|
265
|
+
|
|
266
|
+
/** Spatial similarity metrics */
|
|
267
|
+
export interface SpatialMetrics {
|
|
268
|
+
readonly ssim: number; // Structural Similarity [0,1]
|
|
269
|
+
readonly lpips: number; // Learned Perceptual Image Patch Similarity [0,1] (lower = more similar)
|
|
270
|
+
readonly depthMAE: number; // Depth Mean Absolute Error (meters)
|
|
271
|
+
readonly composite: number; // Weighted composite score [0,1]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Verification thresholds */
|
|
275
|
+
export interface VerificationThresholds {
|
|
276
|
+
readonly minSSIM: number; // default 0.75
|
|
277
|
+
readonly maxLPIPS: number; // default 0.25
|
|
278
|
+
readonly maxDepthMAE: number; // default 0.5 meters
|
|
279
|
+
readonly minComposite: number; // default 0.75
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Spatial proof — cryptographic proof that robot is at claimed location */
|
|
283
|
+
export interface SpatialProof {
|
|
284
|
+
readonly id: string;
|
|
285
|
+
readonly robotId: string;
|
|
286
|
+
readonly claimedPose: Pose;
|
|
287
|
+
readonly actualFrame: CameraFrame;
|
|
288
|
+
readonly expectedRender: RenderedView;
|
|
289
|
+
readonly metrics: SpatialMetrics;
|
|
290
|
+
readonly passed: boolean;
|
|
291
|
+
readonly memoryMerkleRoot: string; // ties to long-term memory
|
|
292
|
+
readonly merkleProof: readonly string[];
|
|
293
|
+
readonly timestamp: number;
|
|
294
|
+
readonly signature: string; // HMAC-SHA256 of proof payload
|
|
295
|
+
readonly nonce: string; // replay prevention
|
|
296
|
+
readonly hardwareAttestation?: string; // device identity
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Payment settlement tied to spatial proof */
|
|
300
|
+
export interface SpatialSettlement {
|
|
301
|
+
readonly id: string;
|
|
302
|
+
readonly proof: SpatialProof;
|
|
303
|
+
readonly amount: number;
|
|
304
|
+
readonly currency: string;
|
|
305
|
+
readonly status: SettlementStatus;
|
|
306
|
+
readonly payerRobotId: string;
|
|
307
|
+
readonly payeeId: string;
|
|
308
|
+
readonly initiatedAt: number;
|
|
309
|
+
readonly settledAt?: number;
|
|
310
|
+
readonly failureReason?: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export enum SettlementStatus {
|
|
314
|
+
PENDING = 'pending',
|
|
315
|
+
VERIFIED = 'verified',
|
|
316
|
+
SETTLED = 'settled',
|
|
317
|
+
FAILED = 'failed',
|
|
318
|
+
DISPUTED = 'disputed',
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ============================================================
|
|
322
|
+
// ANTI-SPOOFING
|
|
323
|
+
// ============================================================
|
|
324
|
+
|
|
325
|
+
export enum ThreatType {
|
|
326
|
+
REPLAY_ATTACK = 'replay',
|
|
327
|
+
ADVERSARIAL_PATCH = 'adversarial-patch',
|
|
328
|
+
DEPTH_INJECTION = 'depth-injection',
|
|
329
|
+
MEMORY_POISONING = 'memory-poisoning',
|
|
330
|
+
CAMERA_TAMPERING = 'camera-tampering',
|
|
331
|
+
MAN_IN_THE_MIDDLE = 'mitm',
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export enum ThreatSeverity {
|
|
335
|
+
LOW = 'low',
|
|
336
|
+
MEDIUM = 'medium',
|
|
337
|
+
HIGH = 'high',
|
|
338
|
+
CRITICAL = 'critical',
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Detected threat */
|
|
342
|
+
export interface ThreatDetection {
|
|
343
|
+
readonly type: ThreatType;
|
|
344
|
+
readonly severity: ThreatSeverity;
|
|
345
|
+
readonly confidence: number; // [0,1]
|
|
346
|
+
readonly details: string;
|
|
347
|
+
readonly frameId?: string;
|
|
348
|
+
readonly timestamp: number;
|
|
349
|
+
readonly mitigationApplied: boolean;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Frame integrity check result */
|
|
353
|
+
export interface FrameIntegrity {
|
|
354
|
+
readonly frameId: string;
|
|
355
|
+
readonly hmacValid: boolean;
|
|
356
|
+
readonly sequenceValid: boolean; // no gaps in sequence numbers
|
|
357
|
+
readonly timingValid: boolean; // within expected jitter bounds
|
|
358
|
+
readonly threats: readonly ThreatDetection[];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ============================================================
|
|
362
|
+
// AGENT API (top-level)
|
|
363
|
+
// ============================================================
|
|
364
|
+
|
|
365
|
+
export interface GridStampConfig {
|
|
366
|
+
readonly robotId: string;
|
|
367
|
+
readonly cameras: readonly CameraConfig[];
|
|
368
|
+
readonly hmacSecret: string; // MUST be provided, no defaults
|
|
369
|
+
readonly verificationThresholds?: Partial<VerificationThresholds>;
|
|
370
|
+
readonly memoryConfig?: {
|
|
371
|
+
readonly shortTermTTL?: number; // ms, default 30000
|
|
372
|
+
readonly midTermMaxEntries?: number; // default 1000
|
|
373
|
+
readonly longTermStoragePath?: string;
|
|
374
|
+
};
|
|
375
|
+
readonly navigationConfig?: {
|
|
376
|
+
readonly defaultAlgorithm?: PathAlgorithm;
|
|
377
|
+
readonly maxPlanningTime?: number; // ms
|
|
378
|
+
readonly safetyMargin?: number; // meters
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Main agent interface */
|
|
383
|
+
export interface GridStampAgent {
|
|
384
|
+
/** Capture and process current camera view */
|
|
385
|
+
see(): Promise<CameraFrame>;
|
|
386
|
+
|
|
387
|
+
/** Store current spatial context to memory */
|
|
388
|
+
remember(tags?: string[]): Promise<EpisodicMemory>;
|
|
389
|
+
|
|
390
|
+
/** Plan and execute navigation to target */
|
|
391
|
+
navigate(target: Vec3, options?: { algorithm?: PathAlgorithm }): Promise<Path>;
|
|
392
|
+
|
|
393
|
+
/** Verify robot is at claimed location via spatial proof */
|
|
394
|
+
verifySpatial(claimedPose?: Pose): Promise<SpatialProof>;
|
|
395
|
+
|
|
396
|
+
/** Settle payment with spatial proof requirement */
|
|
397
|
+
settle(params: {
|
|
398
|
+
amount: number;
|
|
399
|
+
currency: string;
|
|
400
|
+
payeeId: string;
|
|
401
|
+
spatialProof: boolean;
|
|
402
|
+
}): Promise<SpatialSettlement>;
|
|
403
|
+
|
|
404
|
+
/** Get current spatial code (place + grid cell activations) */
|
|
405
|
+
getSpatialCode(): SpatialCode;
|
|
406
|
+
|
|
407
|
+
/** Get memory statistics */
|
|
408
|
+
getMemoryStats(): {
|
|
409
|
+
shortTerm: { count: number; oldestMs: number };
|
|
410
|
+
midTerm: { count: number; totalSplats: number };
|
|
411
|
+
longTerm: { count: number; rooms: number };
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/** Shutdown and persist state */
|
|
415
|
+
shutdown(): Promise<void>;
|
|
416
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic utilities for GridStamp
|
|
3
|
+
* HMAC-SHA256 frame signing, nonce generation, constant-time comparison
|
|
4
|
+
*/
|
|
5
|
+
import { createHmac, createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sign data with HMAC-SHA256
|
|
9
|
+
* Used for: frame signing, memory signatures, spatial proof signing
|
|
10
|
+
*/
|
|
11
|
+
export function hmacSign(data: Buffer | Uint8Array, secret: string): string {
|
|
12
|
+
if (!secret || secret.length < 32) {
|
|
13
|
+
throw new Error('HMAC secret must be at least 32 characters');
|
|
14
|
+
}
|
|
15
|
+
return createHmac('sha256', secret)
|
|
16
|
+
.update(data)
|
|
17
|
+
.digest('hex');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Verify HMAC-SHA256 signature (constant-time to prevent timing attacks)
|
|
22
|
+
*/
|
|
23
|
+
export function hmacVerify(
|
|
24
|
+
data: Buffer | Uint8Array,
|
|
25
|
+
signature: string,
|
|
26
|
+
secret: string,
|
|
27
|
+
): boolean {
|
|
28
|
+
const expected = hmacSign(data, secret);
|
|
29
|
+
if (expected.length !== signature.length) return false;
|
|
30
|
+
return timingSafeEqual(
|
|
31
|
+
Buffer.from(expected, 'hex'),
|
|
32
|
+
Buffer.from(signature, 'hex'),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sign a camera frame's pixel data + metadata
|
|
38
|
+
* Includes timestamp and sequence number to prevent replay
|
|
39
|
+
*/
|
|
40
|
+
export function signFrame(
|
|
41
|
+
rgb: Uint8Array,
|
|
42
|
+
timestamp: number,
|
|
43
|
+
sequenceNumber: number,
|
|
44
|
+
secret: string,
|
|
45
|
+
): string {
|
|
46
|
+
const header = Buffer.alloc(16);
|
|
47
|
+
header.writeDoubleBE(timestamp, 0);
|
|
48
|
+
header.writeDoubleBE(sequenceNumber, 8);
|
|
49
|
+
const payload = Buffer.concat([header, Buffer.from(rgb)]);
|
|
50
|
+
return hmacSign(payload, secret);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Verify a signed frame
|
|
55
|
+
*/
|
|
56
|
+
export function verifyFrame(
|
|
57
|
+
rgb: Uint8Array,
|
|
58
|
+
timestamp: number,
|
|
59
|
+
sequenceNumber: number,
|
|
60
|
+
signature: string,
|
|
61
|
+
secret: string,
|
|
62
|
+
): boolean {
|
|
63
|
+
const header = Buffer.alloc(16);
|
|
64
|
+
header.writeDoubleBE(timestamp, 0);
|
|
65
|
+
header.writeDoubleBE(sequenceNumber, 8);
|
|
66
|
+
const payload = Buffer.concat([header, Buffer.from(rgb)]);
|
|
67
|
+
return hmacVerify(payload, signature, secret);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate cryptographically secure nonce (hex string)
|
|
72
|
+
*/
|
|
73
|
+
export function generateNonce(bytes: number = 32): string {
|
|
74
|
+
return randomBytes(bytes).toString('hex');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* SHA-256 hash of arbitrary data
|
|
79
|
+
*/
|
|
80
|
+
export function sha256(data: Buffer | Uint8Array | string): string {
|
|
81
|
+
return createHash('sha256')
|
|
82
|
+
.update(typeof data === 'string' ? Buffer.from(data) : data)
|
|
83
|
+
.digest('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Derive a sub-key from master secret (for key separation)
|
|
88
|
+
* Different keys for frame signing vs memory signing vs proof signing
|
|
89
|
+
*/
|
|
90
|
+
export function deriveKey(masterSecret: string, context: string): string {
|
|
91
|
+
return createHmac('sha256', masterSecret)
|
|
92
|
+
.update(`gridstamp:${context}`)
|
|
93
|
+
.digest('hex');
|
|
94
|
+
}
|