@occ-core/stub 0.1.0 → 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/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/stub-host.d.ts +1 -93
- package/dist/stub-host.d.ts.map +1 -1
- package/dist/stub-host.js +2 -58
- package/dist/stub-host.js.map +1 -1
- package/package.json +5 -32
- package/src/index.ts +3 -0
- package/src/stub-host.ts +5 -115
- package/tsconfig.json +23 -0
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,mCAAmC;AAEnC;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/stub-host.d.ts
CHANGED
|
@@ -1,119 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
* @occ/stub — StubHost
|
|
3
|
-
*
|
|
4
|
-
* An in-process implementation of HostCapabilities for development and
|
|
5
|
-
* testing. All TEE-specific operations are performed in software using
|
|
6
|
-
* Node.js built-ins and @noble/ed25519.
|
|
7
|
-
*
|
|
8
|
-
* SECURITY WARNING: This implementation provides NO security guarantees.
|
|
9
|
-
* Keys are held in process memory (or a plaintext JSON file for persistence),
|
|
10
|
-
* the counter is software-only, and there is no hardware-backed attestation.
|
|
11
|
-
*
|
|
12
|
-
* DO NOT use StubHost in production or in any security-sensitive context.
|
|
13
|
-
* Its sole purpose is to allow developers to run, test, and explore
|
|
14
|
-
* occ-core locally without a real TEE.
|
|
15
|
-
*/
|
|
16
|
-
import type { HostCapabilities } from "@occ/core";
|
|
1
|
+
import type { HostCapabilities } from "occproof";
|
|
17
2
|
export interface StubHostOptions {
|
|
18
|
-
/**
|
|
19
|
-
* Ed25519 private key (32 bytes).
|
|
20
|
-
* If omitted, a fresh random key is generated.
|
|
21
|
-
* Providing a fixed key is useful for deterministic tests.
|
|
22
|
-
*/
|
|
23
3
|
privateKey?: Uint8Array;
|
|
24
|
-
/**
|
|
25
|
-
* Measurement string returned by getMeasurement().
|
|
26
|
-
* Defaults to a fixed placeholder string.
|
|
27
|
-
*/
|
|
28
4
|
measurement?: string;
|
|
29
|
-
/**
|
|
30
|
-
* Initial counter value. Defaults to 0.
|
|
31
|
-
*/
|
|
32
5
|
initialCounter?: bigint;
|
|
33
|
-
/**
|
|
34
|
-
* If true, secureTime() is available and returns Date.now().
|
|
35
|
-
* Defaults to true.
|
|
36
|
-
*/
|
|
37
6
|
enableTime?: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* If true, nextCounter() is available.
|
|
40
|
-
* Defaults to true.
|
|
41
|
-
*/
|
|
42
7
|
enableCounter?: boolean;
|
|
43
8
|
}
|
|
44
9
|
export interface PersistentStubHostOptions {
|
|
45
|
-
/**
|
|
46
|
-
* Path to a JSON file where identity and counter state is persisted.
|
|
47
|
-
* If the file does not exist, it is created with a fresh keypair and
|
|
48
|
-
* counter starting at 0. If it exists, the stored key and counter
|
|
49
|
-
* are loaded and resumed.
|
|
50
|
-
*/
|
|
51
10
|
statePath: string;
|
|
52
|
-
/**
|
|
53
|
-
* Measurement string returned by getMeasurement().
|
|
54
|
-
* Defaults to "stub:measurement:not-a-real-tee".
|
|
55
|
-
*/
|
|
56
11
|
measurement?: string;
|
|
57
|
-
/**
|
|
58
|
-
* If true, secureTime() is available and returns Date.now().
|
|
59
|
-
* Defaults to true.
|
|
60
|
-
*/
|
|
61
12
|
enableTime?: boolean;
|
|
62
|
-
/**
|
|
63
|
-
* If true, nextCounter() is available and persists across restarts.
|
|
64
|
-
* Defaults to true.
|
|
65
|
-
*/
|
|
66
13
|
enableCounter?: boolean;
|
|
67
14
|
}
|
|
68
|
-
/**
|
|
69
|
-
* StubHost wraps a HostCapabilities object built from in-process crypto.
|
|
70
|
-
*
|
|
71
|
-
* Optional capabilities are conditionally attached to the plain object
|
|
72
|
-
* returned by `.host` — this avoids TypeScript exactOptionalPropertyTypes
|
|
73
|
-
* conflicts that arise when implementing optional interface methods as class
|
|
74
|
-
* fields that could be undefined.
|
|
75
|
-
*/
|
|
76
15
|
export declare class StubHost {
|
|
77
16
|
#private;
|
|
78
17
|
private constructor();
|
|
79
|
-
/**
|
|
80
|
-
* Record the hash of the last produced proof into persistent state.
|
|
81
|
-
* Call this after each successful commit when using persistent mode to
|
|
82
|
-
* enable automatic proof chaining across restarts.
|
|
83
|
-
*/
|
|
84
18
|
setLastProofHash(hashB64: string): void;
|
|
85
|
-
/**
|
|
86
|
-
* Read the last proof hash from persistent state (for chaining).
|
|
87
|
-
* Returns undefined if no proof has been committed yet.
|
|
88
|
-
*/
|
|
89
19
|
getLastProofHash(): string | undefined;
|
|
90
|
-
/**
|
|
91
|
-
* Create a StubHost with ephemeral (in-memory) state.
|
|
92
|
-
* Counter resets to 0 on every process restart.
|
|
93
|
-
* Async because key derivation is async in @noble/ed25519.
|
|
94
|
-
*/
|
|
95
20
|
static create(opts?: StubHostOptions): Promise<StubHost>;
|
|
96
|
-
/**
|
|
97
|
-
* Create a StubHost that persists identity and counter to a JSON file.
|
|
98
|
-
*
|
|
99
|
-
* On first call: generates a fresh keypair and writes it to `statePath`.
|
|
100
|
-
* On subsequent calls: loads the existing keypair and counter and resumes.
|
|
101
|
-
*
|
|
102
|
-
* This lets a local demo service maintain a stable identity and
|
|
103
|
-
* monotonically increasing counter across restarts.
|
|
104
|
-
*
|
|
105
|
-
* The state file is plaintext JSON — not for production use.
|
|
106
|
-
*/
|
|
107
21
|
static createPersistent(opts: PersistentStubHostOptions): Promise<StubHost>;
|
|
108
|
-
/**
|
|
109
|
-
* Return the HostCapabilities object to pass to Constructor.initialize().
|
|
110
|
-
*/
|
|
111
22
|
get host(): HostCapabilities;
|
|
112
|
-
/** Return the raw private key bytes (test use only). */
|
|
113
23
|
get privateKeyBytes(): Uint8Array;
|
|
114
|
-
/** Return the raw public key bytes. */
|
|
115
24
|
get publicKeyBytes(): Uint8Array;
|
|
116
|
-
/** Return the current counter value without advancing it. */
|
|
117
25
|
get currentCounter(): bigint;
|
|
118
26
|
}
|
|
119
27
|
//# sourceMappingURL=stub-host.d.ts.map
|
package/dist/stub-host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stub-host.d.ts","sourceRoot":"","sources":["../src/stub-host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stub-host.d.ts","sourceRoot":"","sources":["../src/stub-host.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAMjD,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAgBD,qBAAa,QAAQ;;IAQnB,OAAO;IA8CP,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAevC,gBAAgB,IAAI,MAAM,GAAG,SAAS;WAUzB,MAAM,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC;WAkBrD,gBAAgB,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,QAAQ,CAAC;IA4CjF,IAAI,IAAI,IAAI,gBAAgB,CAE3B;IAED,IAAI,eAAe,IAAI,UAAU,CAEhC;IAED,IAAI,cAAc,IAAI,UAAU,CAE/B;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;CACF"}
|
package/dist/stub-host.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2024-2026 Mike Argento
|
|
1
3
|
/**
|
|
2
4
|
* @occ/stub — StubHost
|
|
3
5
|
*
|
|
@@ -6,12 +8,7 @@
|
|
|
6
8
|
* Node.js built-ins and @noble/ed25519.
|
|
7
9
|
*
|
|
8
10
|
* SECURITY WARNING: This implementation provides NO security guarantees.
|
|
9
|
-
* Keys are held in process memory (or a plaintext JSON file for persistence),
|
|
10
|
-
* the counter is software-only, and there is no hardware-backed attestation.
|
|
11
|
-
*
|
|
12
11
|
* DO NOT use StubHost in production or in any security-sensitive context.
|
|
13
|
-
* Its sole purpose is to allow developers to run, test, and explore
|
|
14
|
-
* occ-core locally without a real TEE.
|
|
15
12
|
*/
|
|
16
13
|
import { getPublicKeyAsync, signAsync, utils } from "@noble/ed25519";
|
|
17
14
|
import { randomBytes } from "crypto";
|
|
@@ -20,14 +17,6 @@ import { dirname } from "path";
|
|
|
20
17
|
// ---------------------------------------------------------------------------
|
|
21
18
|
// StubHost
|
|
22
19
|
// ---------------------------------------------------------------------------
|
|
23
|
-
/**
|
|
24
|
-
* StubHost wraps a HostCapabilities object built from in-process crypto.
|
|
25
|
-
*
|
|
26
|
-
* Optional capabilities are conditionally attached to the plain object
|
|
27
|
-
* returned by `.host` — this avoids TypeScript exactOptionalPropertyTypes
|
|
28
|
-
* conflicts that arise when implementing optional interface methods as class
|
|
29
|
-
* fields that could be undefined.
|
|
30
|
-
*/
|
|
31
20
|
export class StubHost {
|
|
32
21
|
#host;
|
|
33
22
|
#privateKey;
|
|
@@ -41,7 +30,6 @@ export class StubHost {
|
|
|
41
30
|
this.#measurement = opts.measurement;
|
|
42
31
|
this.#counter = opts.initialCounter;
|
|
43
32
|
this.#statePath = statePath;
|
|
44
|
-
// Build the base host with required capabilities
|
|
45
33
|
const base = {
|
|
46
34
|
enforcementTier: "stub",
|
|
47
35
|
getMeasurement: async () => this.#measurement,
|
|
@@ -49,7 +37,6 @@ export class StubHost {
|
|
|
49
37
|
sign: async (data) => signAsync(data, this.#privateKey),
|
|
50
38
|
getPublicKey: async () => this.#publicKey,
|
|
51
39
|
};
|
|
52
|
-
// Conditionally attach optional capabilities
|
|
53
40
|
if (opts.enableCounter) {
|
|
54
41
|
base.nextCounter = async () => {
|
|
55
42
|
this.#counter += 1n;
|
|
@@ -64,9 +51,6 @@ export class StubHost {
|
|
|
64
51
|
}
|
|
65
52
|
this.#host = base;
|
|
66
53
|
}
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// Persistence helpers
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
54
|
#persistState() {
|
|
71
55
|
if (this.#statePath === undefined)
|
|
72
56
|
return;
|
|
@@ -76,11 +60,6 @@ export class StubHost {
|
|
|
76
60
|
};
|
|
77
61
|
writeFileSync(this.#statePath, JSON.stringify(state, null, 2), "utf8");
|
|
78
62
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Record the hash of the last produced proof into persistent state.
|
|
81
|
-
* Call this after each successful commit when using persistent mode to
|
|
82
|
-
* enable automatic proof chaining across restarts.
|
|
83
|
-
*/
|
|
84
63
|
setLastProofHash(hashB64) {
|
|
85
64
|
if (this.#statePath === undefined)
|
|
86
65
|
return;
|
|
@@ -97,10 +76,6 @@ export class StubHost {
|
|
|
97
76
|
existing.lastProofHashB64 = hashB64;
|
|
98
77
|
writeFileSync(this.#statePath, JSON.stringify(existing, null, 2), "utf8");
|
|
99
78
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Read the last proof hash from persistent state (for chaining).
|
|
102
|
-
* Returns undefined if no proof has been committed yet.
|
|
103
|
-
*/
|
|
104
79
|
getLastProofHash() {
|
|
105
80
|
if (this.#statePath === undefined)
|
|
106
81
|
return undefined;
|
|
@@ -112,14 +87,6 @@ export class StubHost {
|
|
|
112
87
|
return undefined;
|
|
113
88
|
}
|
|
114
89
|
}
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// Factory methods
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
/**
|
|
119
|
-
* Create a StubHost with ephemeral (in-memory) state.
|
|
120
|
-
* Counter resets to 0 on every process restart.
|
|
121
|
-
* Async because key derivation is async in @noble/ed25519.
|
|
122
|
-
*/
|
|
123
90
|
static async create(opts = {}) {
|
|
124
91
|
const privateKey = opts.privateKey ?? utils.randomPrivateKey();
|
|
125
92
|
if (privateKey.length !== 32) {
|
|
@@ -135,22 +102,10 @@ export class StubHost {
|
|
|
135
102
|
};
|
|
136
103
|
return new StubHost(privateKey, publicKey, resolved);
|
|
137
104
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Create a StubHost that persists identity and counter to a JSON file.
|
|
140
|
-
*
|
|
141
|
-
* On first call: generates a fresh keypair and writes it to `statePath`.
|
|
142
|
-
* On subsequent calls: loads the existing keypair and counter and resumes.
|
|
143
|
-
*
|
|
144
|
-
* This lets a local demo service maintain a stable identity and
|
|
145
|
-
* monotonically increasing counter across restarts.
|
|
146
|
-
*
|
|
147
|
-
* The state file is plaintext JSON — not for production use.
|
|
148
|
-
*/
|
|
149
105
|
static async createPersistent(opts) {
|
|
150
106
|
const { statePath, measurement, enableTime, enableCounter } = opts;
|
|
151
107
|
let privateKey;
|
|
152
108
|
let initialCounter;
|
|
153
|
-
// Try to load existing state
|
|
154
109
|
let existingState;
|
|
155
110
|
try {
|
|
156
111
|
existingState = JSON.parse(readFileSync(statePath, "utf8"));
|
|
@@ -163,10 +118,8 @@ export class StubHost {
|
|
|
163
118
|
initialCounter = BigInt(existingState.counter);
|
|
164
119
|
}
|
|
165
120
|
else {
|
|
166
|
-
// First run — generate a fresh key
|
|
167
121
|
privateKey = utils.randomPrivateKey();
|
|
168
122
|
initialCounter = 0n;
|
|
169
|
-
// Ensure the directory exists
|
|
170
123
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
171
124
|
const initialState = {
|
|
172
125
|
privateKeyB64: Buffer.from(privateKey).toString("base64"),
|
|
@@ -187,24 +140,15 @@ export class StubHost {
|
|
|
187
140
|
};
|
|
188
141
|
return new StubHost(privateKey, publicKey, resolved, statePath);
|
|
189
142
|
}
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
// Accessors
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
/**
|
|
194
|
-
* Return the HostCapabilities object to pass to Constructor.initialize().
|
|
195
|
-
*/
|
|
196
143
|
get host() {
|
|
197
144
|
return this.#host;
|
|
198
145
|
}
|
|
199
|
-
/** Return the raw private key bytes (test use only). */
|
|
200
146
|
get privateKeyBytes() {
|
|
201
147
|
return this.#privateKey;
|
|
202
148
|
}
|
|
203
|
-
/** Return the raw public key bytes. */
|
|
204
149
|
get publicKeyBytes() {
|
|
205
150
|
return this.#publicKey;
|
|
206
151
|
}
|
|
207
|
-
/** Return the current counter value without advancing it. */
|
|
208
152
|
get currentCounter() {
|
|
209
153
|
return this.#counter;
|
|
210
154
|
}
|
package/dist/stub-host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stub-host.js","sourceRoot":"","sources":["../src/stub-host.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"stub-host.js","sourceRoot":"","sources":["../src/stub-host.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,mCAAmC;AAEnC;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAgC/B,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,MAAM,OAAO,QAAQ;IACV,KAAK,CAAmB;IACxB,WAAW,CAAa;IACxB,UAAU,CAAa;IACvB,YAAY,CAAS;IAC9B,QAAQ,CAAS;IACR,UAAU,CAAqB;IAExC,YACE,UAAsB,EACtB,SAAqB,EACrB,IAA+B,EAC/B,SAAkB;QAElB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC;QACpC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAE5B,MAAM,IAAI,GAAqB;YAC7B,eAAe,EAAE,MAAM;YACvB,cAAc,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,YAAY;YAC7C,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YAC1D,IAAI,EAAE,KAAK,EAAE,IAAgB,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC;YACnE,YAAY,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,UAAU;SAC1C,CAAC;QAEF,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,IAAI,CAAC,WAAW,GAAG,KAAK,IAAqB,EAAE;gBAC7C,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;gBACpB,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;oBAClC,IAAI,CAAC,aAAa,EAAE,CAAC;gBACvB,CAAC;gBACD,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,UAAU,GAAG,KAAK,IAAqB,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,aAAa;QACX,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO;QAC1C,MAAM,KAAK,GAAmB;YAC5B,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC/D,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;SAC/B,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACzE,CAAC;IAED,gBAAgB,CAAC,OAAe;QAC9B,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO;QAC1C,IAAI,QAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAmB,CAAC;QACjF,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG;gBACT,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAC/D,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;aAC/B,CAAC;QACJ,CAAC;QACD,QAAQ,CAAC,gBAAgB,GAAG,OAAO,CAAC;QACpC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,CAAC;IAED,gBAAgB;QACd,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAmB,CAAC;YAChF,OAAO,GAAG,CAAC,gBAAgB,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAwB,EAAE;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC/D,IAAI,UAAU,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,UAAU,CAAC,uCAAuC,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAA8B;YAC1C,UAAU;YACV,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,iCAAiC;YAClE,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE;YACzC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YACnC,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;SAC1C,CAAC;QAEF,OAAO,IAAI,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAA+B;QAC3D,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;QAEnE,IAAI,UAAsB,CAAC;QAC3B,IAAI,cAAsB,CAAC;QAE3B,IAAI,aAAyC,CAAC;QAC9C,IAAI,CAAC;YACH,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAmB,CAAC;QAChF,CAAC;QAAC,MAAM,CAAC;YACP,iDAAiD;QACnD,CAAC;QAED,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;YAChC,UAAU,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;YAChF,cAAc,GAAG,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,KAAK,CAAC,gBAAgB,EAAE,CAAC;YACtC,cAAc,GAAG,EAAE,CAAC;YACpB,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,YAAY,GAAmB;gBACnC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBACzD,OAAO,EAAE,MAAM,CAAC,cAAc,CAAC;aAChC,CAAC;YACF,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,UAAU,CAAC,uDAAuD,CAAC,CAAC;QAChF,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;QAEtD,MAAM,QAAQ,GAA8B;YAC1C,UAAU;YACV,WAAW,EAAE,WAAW,IAAI,iCAAiC;YAC7D,cAAc;YACd,UAAU,EAAE,UAAU,IAAI,IAAI;YAC9B,aAAa,EAAE,aAAa,IAAI,IAAI;SACrC,CAAC;QAEF,OAAO,IAAI,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,58 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@occ-core/stub",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "In-process
|
|
5
|
-
"keywords": [
|
|
6
|
-
"occ",
|
|
7
|
-
"origin-controlled-computing",
|
|
8
|
-
"tee",
|
|
9
|
-
"stub",
|
|
10
|
-
"dev",
|
|
11
|
-
"ed25519",
|
|
12
|
-
"tamper-evident"
|
|
13
|
-
],
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "In-process stub adapter for OCC development and testing",
|
|
14
5
|
"author": "Mike Argento",
|
|
15
6
|
"license": "Apache-2.0",
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "https://github.com/mikeargento/occ-core.git",
|
|
19
|
-
"directory": "packages/occ-core-stub"
|
|
20
|
-
},
|
|
21
|
-
"homepage": "https://github.com/mikeargento/occ-core#readme",
|
|
22
|
-
"bugs": {
|
|
23
|
-
"url": "https://github.com/mikeargento/occ-core/issues"
|
|
24
|
-
},
|
|
25
7
|
"type": "module",
|
|
26
8
|
"main": "./dist/index.js",
|
|
27
9
|
"types": "./dist/index.d.ts",
|
|
28
|
-
"sideEffects": false,
|
|
29
10
|
"exports": {
|
|
30
11
|
".": {
|
|
31
12
|
"import": "./dist/index.js",
|
|
32
13
|
"types": "./dist/index.d.ts"
|
|
33
14
|
}
|
|
34
15
|
},
|
|
35
|
-
"files": [
|
|
36
|
-
"dist",
|
|
37
|
-
"!dist/__tests__",
|
|
38
|
-
"src",
|
|
39
|
-
"!src/__tests__"
|
|
40
|
-
],
|
|
41
16
|
"scripts": {
|
|
42
17
|
"build": "tsc",
|
|
43
|
-
"typecheck": "tsc --noEmit"
|
|
44
|
-
"test": "npm run build && node --test dist/__tests__/*.test.js",
|
|
45
|
-
"prepublishOnly": "npm run build && npm test"
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
46
19
|
},
|
|
47
20
|
"dependencies": {
|
|
48
21
|
"@noble/ed25519": "^2.1.0",
|
|
49
|
-
"
|
|
22
|
+
"occproof": "^1.0.2"
|
|
50
23
|
},
|
|
51
24
|
"devDependencies": {
|
|
52
25
|
"@types/node": "^20.0.0",
|
|
53
26
|
"typescript": "^5.4.0"
|
|
54
27
|
},
|
|
55
28
|
"engines": {
|
|
56
|
-
"node": ">=
|
|
29
|
+
"node": ">=20.0.0"
|
|
57
30
|
}
|
|
58
31
|
}
|
package/src/index.ts
CHANGED
package/src/stub-host.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2024-2026 Mike Argento
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @occ/stub — StubHost
|
|
3
6
|
*
|
|
@@ -6,98 +9,41 @@
|
|
|
6
9
|
* Node.js built-ins and @noble/ed25519.
|
|
7
10
|
*
|
|
8
11
|
* SECURITY WARNING: This implementation provides NO security guarantees.
|
|
9
|
-
* Keys are held in process memory (or a plaintext JSON file for persistence),
|
|
10
|
-
* the counter is software-only, and there is no hardware-backed attestation.
|
|
11
|
-
*
|
|
12
12
|
* DO NOT use StubHost in production or in any security-sensitive context.
|
|
13
|
-
* Its sole purpose is to allow developers to run, test, and explore
|
|
14
|
-
* occ-core locally without a real TEE.
|
|
15
13
|
*/
|
|
16
14
|
|
|
17
15
|
import { getPublicKeyAsync, signAsync, utils } from "@noble/ed25519";
|
|
18
16
|
import { randomBytes } from "crypto";
|
|
19
17
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
20
18
|
import { dirname } from "path";
|
|
21
|
-
import type { HostCapabilities } from "
|
|
19
|
+
import type { HostCapabilities } from "occproof";
|
|
22
20
|
|
|
23
21
|
// ---------------------------------------------------------------------------
|
|
24
22
|
// StubHost options
|
|
25
23
|
// ---------------------------------------------------------------------------
|
|
26
24
|
|
|
27
25
|
export interface StubHostOptions {
|
|
28
|
-
/**
|
|
29
|
-
* Ed25519 private key (32 bytes).
|
|
30
|
-
* If omitted, a fresh random key is generated.
|
|
31
|
-
* Providing a fixed key is useful for deterministic tests.
|
|
32
|
-
*/
|
|
33
26
|
privateKey?: Uint8Array;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Measurement string returned by getMeasurement().
|
|
37
|
-
* Defaults to a fixed placeholder string.
|
|
38
|
-
*/
|
|
39
27
|
measurement?: string;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Initial counter value. Defaults to 0.
|
|
43
|
-
*/
|
|
44
28
|
initialCounter?: bigint;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* If true, secureTime() is available and returns Date.now().
|
|
48
|
-
* Defaults to true.
|
|
49
|
-
*/
|
|
50
29
|
enableTime?: boolean;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* If true, nextCounter() is available.
|
|
54
|
-
* Defaults to true.
|
|
55
|
-
*/
|
|
56
30
|
enableCounter?: boolean;
|
|
57
31
|
}
|
|
58
32
|
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Persistent StubHost options
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
33
|
export interface PersistentStubHostOptions {
|
|
64
|
-
/**
|
|
65
|
-
* Path to a JSON file where identity and counter state is persisted.
|
|
66
|
-
* If the file does not exist, it is created with a fresh keypair and
|
|
67
|
-
* counter starting at 0. If it exists, the stored key and counter
|
|
68
|
-
* are loaded and resumed.
|
|
69
|
-
*/
|
|
70
34
|
statePath: string;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Measurement string returned by getMeasurement().
|
|
74
|
-
* Defaults to "stub:measurement:not-a-real-tee".
|
|
75
|
-
*/
|
|
76
35
|
measurement?: string;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* If true, secureTime() is available and returns Date.now().
|
|
80
|
-
* Defaults to true.
|
|
81
|
-
*/
|
|
82
36
|
enableTime?: boolean;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* If true, nextCounter() is available and persists across restarts.
|
|
86
|
-
* Defaults to true.
|
|
87
|
-
*/
|
|
88
37
|
enableCounter?: boolean;
|
|
89
38
|
}
|
|
90
39
|
|
|
91
40
|
// ---------------------------------------------------------------------------
|
|
92
|
-
// Persisted state shape
|
|
41
|
+
// Persisted state shape
|
|
93
42
|
// ---------------------------------------------------------------------------
|
|
94
43
|
|
|
95
44
|
interface PersistedState {
|
|
96
|
-
/** Base64-encoded Ed25519 private key (32 bytes). */
|
|
97
45
|
privateKeyB64: string;
|
|
98
|
-
/** Current counter value as a decimal string (survives BigInt round-trip). */
|
|
99
46
|
counter: string;
|
|
100
|
-
/** Base64 of the last proof's canonical hash — populated by the service layer. */
|
|
101
47
|
lastProofHashB64?: string;
|
|
102
48
|
}
|
|
103
49
|
|
|
@@ -105,14 +51,6 @@ interface PersistedState {
|
|
|
105
51
|
// StubHost
|
|
106
52
|
// ---------------------------------------------------------------------------
|
|
107
53
|
|
|
108
|
-
/**
|
|
109
|
-
* StubHost wraps a HostCapabilities object built from in-process crypto.
|
|
110
|
-
*
|
|
111
|
-
* Optional capabilities are conditionally attached to the plain object
|
|
112
|
-
* returned by `.host` — this avoids TypeScript exactOptionalPropertyTypes
|
|
113
|
-
* conflicts that arise when implementing optional interface methods as class
|
|
114
|
-
* fields that could be undefined.
|
|
115
|
-
*/
|
|
116
54
|
export class StubHost {
|
|
117
55
|
readonly #host: HostCapabilities;
|
|
118
56
|
readonly #privateKey: Uint8Array;
|
|
@@ -133,7 +71,6 @@ export class StubHost {
|
|
|
133
71
|
this.#counter = opts.initialCounter;
|
|
134
72
|
this.#statePath = statePath;
|
|
135
73
|
|
|
136
|
-
// Build the base host with required capabilities
|
|
137
74
|
const base: HostCapabilities = {
|
|
138
75
|
enforcementTier: "stub",
|
|
139
76
|
getMeasurement: async () => this.#measurement,
|
|
@@ -142,7 +79,6 @@ export class StubHost {
|
|
|
142
79
|
getPublicKey: async () => this.#publicKey,
|
|
143
80
|
};
|
|
144
81
|
|
|
145
|
-
// Conditionally attach optional capabilities
|
|
146
82
|
if (opts.enableCounter) {
|
|
147
83
|
base.nextCounter = async (): Promise<string> => {
|
|
148
84
|
this.#counter += 1n;
|
|
@@ -160,10 +96,6 @@ export class StubHost {
|
|
|
160
96
|
this.#host = base;
|
|
161
97
|
}
|
|
162
98
|
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// Persistence helpers
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
|
|
167
99
|
#persistState(): void {
|
|
168
100
|
if (this.#statePath === undefined) return;
|
|
169
101
|
const state: PersistedState = {
|
|
@@ -173,11 +105,6 @@ export class StubHost {
|
|
|
173
105
|
writeFileSync(this.#statePath, JSON.stringify(state, null, 2), "utf8");
|
|
174
106
|
}
|
|
175
107
|
|
|
176
|
-
/**
|
|
177
|
-
* Record the hash of the last produced proof into persistent state.
|
|
178
|
-
* Call this after each successful commit when using persistent mode to
|
|
179
|
-
* enable automatic proof chaining across restarts.
|
|
180
|
-
*/
|
|
181
108
|
setLastProofHash(hashB64: string): void {
|
|
182
109
|
if (this.#statePath === undefined) return;
|
|
183
110
|
let existing: PersistedState;
|
|
@@ -193,10 +120,6 @@ export class StubHost {
|
|
|
193
120
|
writeFileSync(this.#statePath, JSON.stringify(existing, null, 2), "utf8");
|
|
194
121
|
}
|
|
195
122
|
|
|
196
|
-
/**
|
|
197
|
-
* Read the last proof hash from persistent state (for chaining).
|
|
198
|
-
* Returns undefined if no proof has been committed yet.
|
|
199
|
-
*/
|
|
200
123
|
getLastProofHash(): string | undefined {
|
|
201
124
|
if (this.#statePath === undefined) return undefined;
|
|
202
125
|
try {
|
|
@@ -207,15 +130,6 @@ export class StubHost {
|
|
|
207
130
|
}
|
|
208
131
|
}
|
|
209
132
|
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
// Factory methods
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Create a StubHost with ephemeral (in-memory) state.
|
|
216
|
-
* Counter resets to 0 on every process restart.
|
|
217
|
-
* Async because key derivation is async in @noble/ed25519.
|
|
218
|
-
*/
|
|
219
133
|
static async create(opts: StubHostOptions = {}): Promise<StubHost> {
|
|
220
134
|
const privateKey = opts.privateKey ?? utils.randomPrivateKey();
|
|
221
135
|
if (privateKey.length !== 32) {
|
|
@@ -234,24 +148,12 @@ export class StubHost {
|
|
|
234
148
|
return new StubHost(privateKey, publicKey, resolved);
|
|
235
149
|
}
|
|
236
150
|
|
|
237
|
-
/**
|
|
238
|
-
* Create a StubHost that persists identity and counter to a JSON file.
|
|
239
|
-
*
|
|
240
|
-
* On first call: generates a fresh keypair and writes it to `statePath`.
|
|
241
|
-
* On subsequent calls: loads the existing keypair and counter and resumes.
|
|
242
|
-
*
|
|
243
|
-
* This lets a local demo service maintain a stable identity and
|
|
244
|
-
* monotonically increasing counter across restarts.
|
|
245
|
-
*
|
|
246
|
-
* The state file is plaintext JSON — not for production use.
|
|
247
|
-
*/
|
|
248
151
|
static async createPersistent(opts: PersistentStubHostOptions): Promise<StubHost> {
|
|
249
152
|
const { statePath, measurement, enableTime, enableCounter } = opts;
|
|
250
153
|
|
|
251
154
|
let privateKey: Uint8Array;
|
|
252
155
|
let initialCounter: bigint;
|
|
253
156
|
|
|
254
|
-
// Try to load existing state
|
|
255
157
|
let existingState: PersistedState | undefined;
|
|
256
158
|
try {
|
|
257
159
|
existingState = JSON.parse(readFileSync(statePath, "utf8")) as PersistedState;
|
|
@@ -263,10 +165,8 @@ export class StubHost {
|
|
|
263
165
|
privateKey = new Uint8Array(Buffer.from(existingState.privateKeyB64, "base64"));
|
|
264
166
|
initialCounter = BigInt(existingState.counter);
|
|
265
167
|
} else {
|
|
266
|
-
// First run — generate a fresh key
|
|
267
168
|
privateKey = utils.randomPrivateKey();
|
|
268
169
|
initialCounter = 0n;
|
|
269
|
-
// Ensure the directory exists
|
|
270
170
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
271
171
|
const initialState: PersistedState = {
|
|
272
172
|
privateKeyB64: Buffer.from(privateKey).toString("base64"),
|
|
@@ -292,28 +192,18 @@ export class StubHost {
|
|
|
292
192
|
return new StubHost(privateKey, publicKey, resolved, statePath);
|
|
293
193
|
}
|
|
294
194
|
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
// Accessors
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Return the HostCapabilities object to pass to Constructor.initialize().
|
|
301
|
-
*/
|
|
302
195
|
get host(): HostCapabilities {
|
|
303
196
|
return this.#host;
|
|
304
197
|
}
|
|
305
198
|
|
|
306
|
-
/** Return the raw private key bytes (test use only). */
|
|
307
199
|
get privateKeyBytes(): Uint8Array {
|
|
308
200
|
return this.#privateKey;
|
|
309
201
|
}
|
|
310
202
|
|
|
311
|
-
/** Return the raw public key bytes. */
|
|
312
203
|
get publicKeyBytes(): Uint8Array {
|
|
313
204
|
return this.#publicKey;
|
|
314
205
|
}
|
|
315
206
|
|
|
316
|
-
/** Return the current counter value without advancing it. */
|
|
317
207
|
get currentCounter(): bigint {
|
|
318
208
|
return this.#counter;
|
|
319
209
|
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noUncheckedIndexedAccess": true,
|
|
14
|
+
"exactOptionalPropertyTypes": true,
|
|
15
|
+
"noImplicitReturns": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"esModuleInterop": false,
|
|
19
|
+
"skipLibCheck": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["src/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules", "dist"]
|
|
23
|
+
}
|