@ethernity-cloud/sdk-js 1.5.16 → 1.5.17
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/info.js +3 -3
- package/nodenithy/build/securelock/.ecld-test-stage/ecld_state.js +815 -780
- package/nodenithy/build/securelock/.ecld-test-stage/esr_local.js +5 -4
- package/nodenithy/build/securelock/.ecld-test-stage/nonce_ladder.js +165 -146
- package/nodenithy/build/securelock/src/ecld_state.js +815 -780
- package/nodenithy/build/securelock/src/esr_local.js +5 -4
- package/nodenithy/build/securelock/src/etny_exec.js +5 -0
- package/nodenithy/build/securelock/src/task_status.js +6 -0
- package/package.json +1 -1
- package/test.js +2 -0
|
@@ -104,8 +104,10 @@ class MemContract {
|
|
|
104
104
|
return ethers.BigNumber.from(e ? e.version : 0);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
async relayNonce(enclave) {
|
|
108
|
-
|
|
107
|
+
async relayNonce(enclave, keyHash) {
|
|
108
|
+
// Per (enclave, key), like the contract: same-key commits serialize,
|
|
109
|
+
// different keys are independent.
|
|
110
|
+
return ethers.BigNumber.from(this._nonce[key(enclave, keyHash)] || 0);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
async commitDigest(enclave, keyHash, cid, expectedVersion, relayNonce, nonce = 0) {
|
|
@@ -150,8 +152,7 @@ class MemContract {
|
|
|
150
152
|
storedNonce = n;
|
|
151
153
|
}
|
|
152
154
|
this._entries[k] = { cid, version: cur + 1, updatedAt: 0, nonce: storedNonce };
|
|
153
|
-
|
|
154
|
-
this._nonce[e] = (this._nonce[e] || 0) + 1;
|
|
155
|
+
this._nonce[k] = (this._nonce[k] || 0) + 1;
|
|
155
156
|
this._persist();
|
|
156
157
|
}
|
|
157
158
|
}
|
|
@@ -1,146 +1,165 @@
|
|
|
1
|
-
/* On-chain public nonce ladder through the local emulator (js SDK). */
|
|
2
|
-
'use strict';
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
|
|
7
|
-
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'esr-nonce-'));
|
|
8
|
-
process.chdir(d);
|
|
9
|
-
|
|
10
|
-
const ecldState = require(path.join(__dirname, 'ecld_state.js'));
|
|
11
|
-
const esrLocal = require(path.join(__dirname, 'esr_local.js'));
|
|
12
|
-
const { MemContract } = esrLocal;
|
|
13
|
-
|
|
14
|
-
const A = '0xAAAA000000000000000000000000000000000001';
|
|
15
|
-
|
|
16
|
-
async function main() {
|
|
17
|
-
esrLocal.install(ecldState, { caller: A, filePrefix: 'st' });
|
|
18
|
-
const { StateRegistry, StateNonceError } = ecldState;
|
|
19
|
-
const r = new StateRegistry();
|
|
20
|
-
|
|
21
|
-
// 1. first guarded commit: nonce=1 accepted, same-task read-back
|
|
22
|
-
await r.commit('counter', (s) => ({ n: (s.n || 0) + 1 }), { nonce: 1 });
|
|
23
|
-
let st = await r.get('counter');
|
|
24
|
-
if (st.n !== 1) throw new Error('step1 state ' + JSON.stringify(st));
|
|
25
|
-
if ((await r.getNonce('counter')) !== 1) throw new Error('step1 getNonce');
|
|
26
|
-
console.log('1. commit({nonce:1}) + same-task getNonce()==1 ok');
|
|
27
|
-
|
|
28
|
-
// 2. duplicate nonce -> StateNonceError (task code 36)
|
|
29
|
-
let threw = false;
|
|
30
|
-
try {
|
|
31
|
-
await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 1 });
|
|
32
|
-
} catch (e) {
|
|
33
|
-
if (!(e instanceof StateNonceError)) throw e;
|
|
34
|
-
threw = true;
|
|
35
|
-
console.log('2. duplicate nonce rejected ok:', e.message);
|
|
36
|
-
}
|
|
37
|
-
if (!threw) throw new Error('duplicate accepted');
|
|
38
|
-
st = await r.get('counter');
|
|
39
|
-
if (st.n !== 1) throw new Error('duplicate mutated state');
|
|
40
|
-
|
|
41
|
-
// 3. exactly +1 accepted; a gap (skip-ahead) is rejected
|
|
42
|
-
await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 2 });
|
|
43
|
-
st = await r.get('counter');
|
|
44
|
-
if (st.n !== 2 || (await r.getNonce('counter')) !== 2) throw new Error('step3a');
|
|
45
|
-
threw = false;
|
|
46
|
-
try {
|
|
47
|
-
await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 10 });
|
|
48
|
-
} catch (e) {
|
|
49
|
-
if (!(e instanceof StateNonceError)) throw e;
|
|
50
|
-
threw = true;
|
|
51
|
-
}
|
|
52
|
-
if (!threw) throw new Error('gap accepted');
|
|
53
|
-
console.log('3. nonce=2 (+1) accepted, gap nonce=10 rejected ok');
|
|
54
|
-
|
|
55
|
-
// 4. nonce-less commit AUTO-takes the next in sequence (2 -> 3)
|
|
56
|
-
await r.commit('counter', (s) => ({ ...s, note: 'free' }));
|
|
57
|
-
if ((await r.getNonce('counter')) !== 3) throw new Error('step4');
|
|
58
|
-
console.log('4. nonce-less commit auto-advanced the sequence to 3 ok');
|
|
59
|
-
|
|
60
|
-
// 5. stale (reused) nonce rejected; the exact next one still works
|
|
61
|
-
threw = false;
|
|
62
|
-
try {
|
|
63
|
-
await r.commit('counter', (s) => s, { nonce: 1 });
|
|
64
|
-
} catch (e) {
|
|
65
|
-
if (!(e instanceof StateNonceError)) throw e;
|
|
66
|
-
threw = true;
|
|
67
|
-
}
|
|
68
|
-
if (!threw) throw new Error('stale accepted');
|
|
69
|
-
threw = false;
|
|
70
|
-
try {
|
|
71
|
-
await r.commit('counter', (s) => s, { nonce: 0 });
|
|
72
|
-
} catch (e) {
|
|
73
|
-
if (!/reserved/.test(String(e.message))) throw e;
|
|
74
|
-
threw = true;
|
|
75
|
-
}
|
|
76
|
-
if (!threw) throw new Error('explicit 0 accepted');
|
|
77
|
-
await r.commit('counter', (s) => ({ ...s, step: 4 }), { nonce: 4 });
|
|
78
|
-
if ((await r.getNonce('counter')) !== 4) throw new Error('step5');
|
|
79
|
-
console.log('5. stale nonce=1 rejected, explicit nonce=0 refused, next nonce=4 accepted ok');
|
|
80
|
-
|
|
81
|
-
// 6. contract-side (registry) enforcement, bypassing the SDK pre-check
|
|
82
|
-
const mc = new MemContract(null);
|
|
83
|
-
const kh = '0x' + '11'.repeat(32);
|
|
84
|
-
mc.applyCommit('0xE', kh, 'cid1', 0, 1);
|
|
85
|
-
for (const bad of [1, 3]) { // reuse and gap both violate the +1 rule
|
|
86
|
-
threw = false;
|
|
87
|
-
try {
|
|
88
|
-
mc.applyCommit('0xE', kh, 'cid2', 1, bad);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
if (!/NonceOutOfOrder/.test(String(e.message))) throw e;
|
|
91
|
-
threw = true;
|
|
92
|
-
}
|
|
93
|
-
if (!threw) throw new Error('registry accepted nonce ' + bad);
|
|
94
|
-
}
|
|
95
|
-
mc.applyCommit('0xE', kh, 'cid2', 1, 2);
|
|
96
|
-
if (Number(await mc.getNonce('0xE', kh)) !== 2) throw new Error('getNonce view');
|
|
97
|
-
console.log('6. contract-side +1 enforcement ok (reuse and gap both revert)');
|
|
98
|
-
|
|
99
|
-
// 7. chain-first getNonce after a task-memo wipe (fresh process/other task)
|
|
100
|
-
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
101
|
-
const ecld2 = require(path.join(__dirname, 'ecld_state.js'));
|
|
102
|
-
esrLocal.install(ecld2, { caller: A, filePrefix: 'st' });
|
|
103
|
-
const r2 = new ecld2.StateRegistry();
|
|
104
|
-
const n2 = await r2.getNonce('counter');
|
|
105
|
-
if (n2 !== 4) throw new Error('step7 got ' + n2);
|
|
106
|
-
console.log('7. chain-first getNonce()==4 after reload (public read, no memo) ok');
|
|
107
|
-
|
|
108
|
-
// 8. public: nonce read needs no ACL -- content read stays denied for B
|
|
109
|
-
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
110
|
-
const ecld3 = require(path.join(__dirname, 'ecld_state.js'));
|
|
111
|
-
const B = '0xBBBB000000000000000000000000000000000002';
|
|
112
|
-
esrLocal.install(ecld3, { caller: B, filePrefix: 'st' });
|
|
113
|
-
const r3 = new ecld3.StateRegistry();
|
|
114
|
-
threw = false;
|
|
115
|
-
try {
|
|
116
|
-
await r3.get('counter');
|
|
117
|
-
} catch (e) {
|
|
118
|
-
if (!(e instanceof ecld3.StatePermissionError)) throw e;
|
|
119
|
-
threw = true;
|
|
120
|
-
}
|
|
121
|
-
if (!threw) throw new Error('B read A content');
|
|
122
|
-
console.log('8. content read denied for B (ACL intact) while nonce stays public on-chain ok');
|
|
123
|
-
|
|
124
|
-
// 9. tamper detection: registry nonce diverges from the blob's reporting copy
|
|
125
|
-
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
126
|
-
const ecld4 = require(path.join(__dirname, 'ecld_state.js'));
|
|
127
|
-
const env4 = esrLocal.install(ecld4, { caller: A, filePrefix: 'st' });
|
|
128
|
-
for (const k of Object.keys(env4.contract._entries)) {
|
|
129
|
-
const e = env4.contract._entries[k];
|
|
130
|
-
if (e.nonce) e.nonce = Number(e.nonce) + 1; // simulate a torn/tampered entry
|
|
131
|
-
}
|
|
132
|
-
threw = false;
|
|
133
|
-
try {
|
|
134
|
-
await new ecld4.StateRegistry().get('counter');
|
|
135
|
-
} catch (e) {
|
|
136
|
-
if (!/nonce mismatch/.test(String(e.message))) throw e;
|
|
137
|
-
threw = true;
|
|
138
|
-
console.log('9. reporting-copy verification ok (tampered registry nonce refused):');
|
|
139
|
-
console.log(' ', e.message);
|
|
140
|
-
}
|
|
141
|
-
if (!threw) throw new Error('tampered nonce accepted');
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
1
|
+
/* On-chain public nonce ladder through the local emulator (js SDK). */
|
|
2
|
+
'use strict';
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'esr-nonce-'));
|
|
8
|
+
process.chdir(d);
|
|
9
|
+
|
|
10
|
+
const ecldState = require(path.join(__dirname, 'ecld_state.js'));
|
|
11
|
+
const esrLocal = require(path.join(__dirname, 'esr_local.js'));
|
|
12
|
+
const { MemContract } = esrLocal;
|
|
13
|
+
|
|
14
|
+
const A = '0xAAAA000000000000000000000000000000000001';
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
esrLocal.install(ecldState, { caller: A, filePrefix: 'st' });
|
|
18
|
+
const { StateRegistry, StateNonceError } = ecldState;
|
|
19
|
+
const r = new StateRegistry();
|
|
20
|
+
|
|
21
|
+
// 1. first guarded commit: nonce=1 accepted, same-task read-back
|
|
22
|
+
await r.commit('counter', (s) => ({ n: (s.n || 0) + 1 }), { nonce: 1 });
|
|
23
|
+
let st = await r.get('counter');
|
|
24
|
+
if (st.n !== 1) throw new Error('step1 state ' + JSON.stringify(st));
|
|
25
|
+
if ((await r.getNonce('counter')) !== 1) throw new Error('step1 getNonce');
|
|
26
|
+
console.log('1. commit({nonce:1}) + same-task getNonce()==1 ok');
|
|
27
|
+
|
|
28
|
+
// 2. duplicate nonce -> StateNonceError (task code 36)
|
|
29
|
+
let threw = false;
|
|
30
|
+
try {
|
|
31
|
+
await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 1 });
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (!(e instanceof StateNonceError)) throw e;
|
|
34
|
+
threw = true;
|
|
35
|
+
console.log('2. duplicate nonce rejected ok:', e.message);
|
|
36
|
+
}
|
|
37
|
+
if (!threw) throw new Error('duplicate accepted');
|
|
38
|
+
st = await r.get('counter');
|
|
39
|
+
if (st.n !== 1) throw new Error('duplicate mutated state');
|
|
40
|
+
|
|
41
|
+
// 3. exactly +1 accepted; a gap (skip-ahead) is rejected
|
|
42
|
+
await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 2 });
|
|
43
|
+
st = await r.get('counter');
|
|
44
|
+
if (st.n !== 2 || (await r.getNonce('counter')) !== 2) throw new Error('step3a');
|
|
45
|
+
threw = false;
|
|
46
|
+
try {
|
|
47
|
+
await r.commit('counter', (s) => ({ n: s.n + 1 }), { nonce: 10 });
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (!(e instanceof StateNonceError)) throw e;
|
|
50
|
+
threw = true;
|
|
51
|
+
}
|
|
52
|
+
if (!threw) throw new Error('gap accepted');
|
|
53
|
+
console.log('3. nonce=2 (+1) accepted, gap nonce=10 rejected ok');
|
|
54
|
+
|
|
55
|
+
// 4. nonce-less commit AUTO-takes the next in sequence (2 -> 3)
|
|
56
|
+
await r.commit('counter', (s) => ({ ...s, note: 'free' }));
|
|
57
|
+
if ((await r.getNonce('counter')) !== 3) throw new Error('step4');
|
|
58
|
+
console.log('4. nonce-less commit auto-advanced the sequence to 3 ok');
|
|
59
|
+
|
|
60
|
+
// 5. stale (reused) nonce rejected; the exact next one still works
|
|
61
|
+
threw = false;
|
|
62
|
+
try {
|
|
63
|
+
await r.commit('counter', (s) => s, { nonce: 1 });
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (!(e instanceof StateNonceError)) throw e;
|
|
66
|
+
threw = true;
|
|
67
|
+
}
|
|
68
|
+
if (!threw) throw new Error('stale accepted');
|
|
69
|
+
threw = false;
|
|
70
|
+
try {
|
|
71
|
+
await r.commit('counter', (s) => s, { nonce: 0 });
|
|
72
|
+
} catch (e) {
|
|
73
|
+
if (!/reserved/.test(String(e.message))) throw e;
|
|
74
|
+
threw = true;
|
|
75
|
+
}
|
|
76
|
+
if (!threw) throw new Error('explicit 0 accepted');
|
|
77
|
+
await r.commit('counter', (s) => ({ ...s, step: 4 }), { nonce: 4 });
|
|
78
|
+
if ((await r.getNonce('counter')) !== 4) throw new Error('step5');
|
|
79
|
+
console.log('5. stale nonce=1 rejected, explicit nonce=0 refused, next nonce=4 accepted ok');
|
|
80
|
+
|
|
81
|
+
// 6. contract-side (registry) enforcement, bypassing the SDK pre-check
|
|
82
|
+
const mc = new MemContract(null);
|
|
83
|
+
const kh = '0x' + '11'.repeat(32);
|
|
84
|
+
mc.applyCommit('0xE', kh, 'cid1', 0, 1);
|
|
85
|
+
for (const bad of [1, 3]) { // reuse and gap both violate the +1 rule
|
|
86
|
+
threw = false;
|
|
87
|
+
try {
|
|
88
|
+
mc.applyCommit('0xE', kh, 'cid2', 1, bad);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (!/NonceOutOfOrder/.test(String(e.message))) throw e;
|
|
91
|
+
threw = true;
|
|
92
|
+
}
|
|
93
|
+
if (!threw) throw new Error('registry accepted nonce ' + bad);
|
|
94
|
+
}
|
|
95
|
+
mc.applyCommit('0xE', kh, 'cid2', 1, 2);
|
|
96
|
+
if (Number(await mc.getNonce('0xE', kh)) !== 2) throw new Error('getNonce view');
|
|
97
|
+
console.log('6. contract-side +1 enforcement ok (reuse and gap both revert)');
|
|
98
|
+
|
|
99
|
+
// 7. chain-first getNonce after a task-memo wipe (fresh process/other task)
|
|
100
|
+
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
101
|
+
const ecld2 = require(path.join(__dirname, 'ecld_state.js'));
|
|
102
|
+
esrLocal.install(ecld2, { caller: A, filePrefix: 'st' });
|
|
103
|
+
const r2 = new ecld2.StateRegistry();
|
|
104
|
+
const n2 = await r2.getNonce('counter');
|
|
105
|
+
if (n2 !== 4) throw new Error('step7 got ' + n2);
|
|
106
|
+
console.log('7. chain-first getNonce()==4 after reload (public read, no memo) ok');
|
|
107
|
+
|
|
108
|
+
// 8. public: nonce read needs no ACL -- content read stays denied for B
|
|
109
|
+
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
110
|
+
const ecld3 = require(path.join(__dirname, 'ecld_state.js'));
|
|
111
|
+
const B = '0xBBBB000000000000000000000000000000000002';
|
|
112
|
+
esrLocal.install(ecld3, { caller: B, filePrefix: 'st' });
|
|
113
|
+
const r3 = new ecld3.StateRegistry();
|
|
114
|
+
threw = false;
|
|
115
|
+
try {
|
|
116
|
+
await r3.get('counter');
|
|
117
|
+
} catch (e) {
|
|
118
|
+
if (!(e instanceof ecld3.StatePermissionError)) throw e;
|
|
119
|
+
threw = true;
|
|
120
|
+
}
|
|
121
|
+
if (!threw) throw new Error('B read A content');
|
|
122
|
+
console.log('8. content read denied for B (ACL intact) while nonce stays public on-chain ok');
|
|
123
|
+
|
|
124
|
+
// 9. tamper detection: registry nonce diverges from the blob's reporting copy
|
|
125
|
+
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
126
|
+
const ecld4 = require(path.join(__dirname, 'ecld_state.js'));
|
|
127
|
+
const env4 = esrLocal.install(ecld4, { caller: A, filePrefix: 'st' });
|
|
128
|
+
for (const k of Object.keys(env4.contract._entries)) {
|
|
129
|
+
const e = env4.contract._entries[k];
|
|
130
|
+
if (e.nonce) e.nonce = Number(e.nonce) + 1; // simulate a torn/tampered entry
|
|
131
|
+
}
|
|
132
|
+
threw = false;
|
|
133
|
+
try {
|
|
134
|
+
await new ecld4.StateRegistry().get('counter');
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (!/nonce mismatch/.test(String(e.message))) throw e;
|
|
137
|
+
threw = true;
|
|
138
|
+
console.log('9. reporting-copy verification ok (tampered registry nonce refused):');
|
|
139
|
+
console.log(' ', e.message);
|
|
140
|
+
}
|
|
141
|
+
if (!threw) throw new Error('tampered nonce accepted');
|
|
142
|
+
|
|
143
|
+
// 10. per-run commit cap: the 257th commit in one task throws StateLimitError
|
|
144
|
+
delete require.cache[require.resolve(path.join(__dirname, 'ecld_state.js'))];
|
|
145
|
+
const ecld5 = require(path.join(__dirname, 'ecld_state.js'));
|
|
146
|
+
esrLocal.install(ecld5, { caller: A, filePrefix: 'cap' });
|
|
147
|
+
const r5 = new ecld5.StateRegistry();
|
|
148
|
+
for (let i = 0; i < 256; i += 1) {
|
|
149
|
+
// eslint-disable-next-line no-await-in-loop
|
|
150
|
+
await r5.commit(`cap-${i % 7}`, (s) => ({ ...s, i }));
|
|
151
|
+
}
|
|
152
|
+
threw = false;
|
|
153
|
+
try {
|
|
154
|
+
await r5.commit('cap-overflow', (s) => s);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
if (!(e instanceof ecld5.StateLimitError)) throw e;
|
|
157
|
+
threw = true;
|
|
158
|
+
}
|
|
159
|
+
if (!threw) throw new Error('commit #257 accepted');
|
|
160
|
+
console.log('10. per-run cap ok: 256 commits fine, #257 refused (StateLimitError -> code 38)');
|
|
161
|
+
|
|
162
|
+
console.log('\nNONCE LADDER (JS): ALL PASS');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
main().catch((e) => { console.error('FAIL:', e.stack || e); process.exit(1); });
|