@ethernity-cloud/sdk-js 1.6.2 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -48
- package/cas/config.js +61 -3
- package/info.js +14 -5
- package/init.js +24 -6
- package/network.js +51 -0
- package/nodenithy/build/securelock/Dockerfile.tmpl +34 -32
- package/nodenithy/build/securelock/scripts/binary-fs-build.sh +7 -6
- package/nodenithy/build.mjs +80 -68
- package/nodenithy/run/docker-compose-final.yml.tmpl +36 -31
- package/nodenithy/run/docker-compose.yml.tmpl +34 -45
- package/nodenithy/run/image_registry.js +17 -4
- package/nodenithy/run.js +42 -28
- package/nodenithy/src/ec_helloworld_example.js +46 -42
- package/nodenithy/src/preStart.mjs +41 -51
- package/package.json +6 -3
- package/publish.js +4 -2
- package/pynithy/build/securelock/Dockerfile.base.tpl +13 -0
- package/pynithy/build/securelock/Dockerfile.tpl +131 -0
- package/pynithy/build/securelock/scripts/binary-fs-build.sh +25 -34
- package/pynithy/build/securelock/scripts/pyfreeze.sh +32 -0
- package/pynithy/build/securelock/src/{pox.abi → abi/etnyImplementationV2.abi} +1489 -1489
- package/pynithy/build/securelock/src/abi/image_registry.abi +1 -0
- package/pynithy/build/securelock/src/ecld_state.py +906 -0
- package/pynithy/build/securelock/src/esr.abi +1 -0
- package/pynithy/build/securelock/src/esr_local.py +267 -0
- package/pynithy/build/securelock/src/esr_wallet.py +150 -0
- package/pynithy/build/securelock/src/etny_crypto.py +208 -167
- package/pynithy/build/securelock/src/etny_exec.py +385 -79
- package/pynithy/build/securelock/src/etny_exec_flask.py +535 -0
- package/pynithy/build/securelock/src/etny_exec_serv.py +353 -0
- package/pynithy/build/securelock/src/etny_session_exec.py +245 -0
- package/pynithy/build/securelock/src/get_sgx_report.so +0 -0
- package/pynithy/build/securelock/src/image_registry.abi +1 -1
- package/pynithy/build/securelock/src/key_generation.py +17 -17
- package/pynithy/build/securelock/src/local_api.py +189 -0
- package/pynithy/build/securelock/src/models.py +200 -176
- package/pynithy/build/securelock/src/securelock.py.tmpl +1037 -731
- package/pynithy/build/securelock/src/swift_stream_service.py +206 -186
- package/pynithy/build.mjs +139 -89
- package/pynithy/run/docker-compose-final.yml.tmpl +58 -51
- package/pynithy/run/docker-compose.yml.tmpl +62 -48
- package/pynithy/run/etny-securelock-test.yaml.tpl +4 -1
- package/pynithy/run/image_registry.js +17 -4
- package/pynithy/run.js +44 -28
- package/pynithy/src/ec_helloworld_example.js +46 -42
- package/pynithy/src/preStart.mjs +41 -51
- package/pynithy/src/serverless/Dockerfile.serverless +8 -0
- package/pynithy/src/serverless/requirements.txt +6 -0
- package/run.js +37 -34
- package/nodenithy/build/las/Dockerfile +0 -5
- package/nodenithy/src/preStart.js +0 -45
- package/pynithy/build/docker-compose.yml +0 -70
- package/pynithy/build/las/Dockerfile +0 -11
- package/pynithy/build/las/src/etny_entrypoint.sh +0 -6
- package/pynithy/build/las/src/las_entrypoint.sh +0 -26
- package/pynithy/build/scripts/binary-fs-build.sh +0 -28
- package/pynithy/build/securelock/Dockerfile.tmpl +0 -95
package/README.md
CHANGED
|
@@ -43,17 +43,52 @@ The sdk has been tested on the following operating systems:
|
|
|
43
43
|
provisioned by the ethernity-cas validator set exactly as on mainnet.
|
|
44
44
|
`ecld-publish` registers the securelock session on-chain in the
|
|
45
45
|
SessionRegistry (`0xcb1F389b...`, see `cas/config.js`), resolves a CAS
|
|
46
|
-
from the ValidatorRegistry (`
|
|
46
|
+
from the ValidatorRegistry (`0x0014aB10...`, or `ECLD_CAS_ADDR=host:port`)
|
|
47
47
|
for the compose and the certificate harvest, takes the trustedzone
|
|
48
48
|
session name from the ImageRegistry (ECImageRegistryV2,
|
|
49
49
|
`0x99A84C62...`), and links the session to the published image. `ecld-build` signs the securelock `--production`.
|
|
50
|
-
Harvesting the certificate needs SGX.
|
|
51
|
-
|
|
50
|
+
Harvesting the certificate needs SGX.
|
|
51
|
+
- Testnet Unsafe - the unsafe network beside the testnet (see below).
|
|
52
52
|
- Mainnet - to be provided during the following updates
|
|
53
|
+
- LitVM:
|
|
54
|
+
- LiteForge - CAS-attested the same way, by LitVM's own ethernity-cas
|
|
55
|
+
validator set (SessionRegistry `0x8ad24b3F...`, ValidatorRegistry
|
|
56
|
+
`0xbE3759f3...`).
|
|
57
|
+
- LiteForge Unsafe - the unsafe network beside LiteForge (see below).
|
|
53
58
|
- Polyhon:
|
|
54
59
|
- Amoy Testnet - to be provided during the following updates
|
|
55
60
|
- Mainnet - to be provided during the following updates
|
|
56
61
|
|
|
62
|
+
The remaining testnets (Amoy, IoTeX, Sepolia) have no ethernity-cas
|
|
63
|
+
deployment: their enclaves are debug-signed and sign their own certificates
|
|
64
|
+
from their measurement, and `ecld-publish` registers no CAS session for them.
|
|
65
|
+
|
|
66
|
+
### The testnets and their unsafe networks
|
|
67
|
+
`Bloxberg Testnet` and `LitVM LiteForge` run your securelock in an enclave
|
|
68
|
+
attested through that chain's ethernity-cas validator set, on nodes whose
|
|
69
|
+
platform the CAS can attest (DCAP). Each has an unsafe network beside it,
|
|
70
|
+
`Bloxberg Testnet Unsafe` (`Bloxberg_Testnet_Unsafe`) and `LitVM LiteForge
|
|
71
|
+
Unsafe` (`LitVM_LiteForge_Unsafe`): the same chain and contracts, for hardware
|
|
72
|
+
SGX platforms the CAS cannot attest (EPID-only, SGX1). No CAS and no LAS: the
|
|
73
|
+
enclaves are debug-signed and sign their own certificates from their
|
|
74
|
+
measurement, so a result proves which image ran but not that an enclave ran
|
|
75
|
+
it. You choose an unsafe network by name in `ecld-init`, and the runner runs an
|
|
76
|
+
`-unsafe` trustedzone on nothing else, so a dApp that runs without a CAS
|
|
77
|
+
always says so in the network it names. A dApp publishes one securelock per
|
|
78
|
+
network from two project directories with the same project name: the unsafe
|
|
79
|
+
one is registered as `<project>-unsafe` and runs on the
|
|
80
|
+
`etny-<nodenithy|pynithy>-testnet-unsafe`
|
|
81
|
+
(`ecld-<nodenithy|pynithy>-litvm-testnet-unsafe`) trustedzone. Both dApp types
|
|
82
|
+
build for both networks. Mainnet has no unsafe network.
|
|
83
|
+
|
|
84
|
+
### Pynithy dApps
|
|
85
|
+
The Pynithy securelock is built exactly as `ethernity-cloud-sdk-py` builds it
|
|
86
|
+
(`pynithy/build/securelock` is its copy): a SCONE 6.0.7 Python 3.14 enclave
|
|
87
|
+
your backend is frozen into. `src/serverless/requirements.txt` lists the
|
|
88
|
+
Python packages your backend imports, and `src/serverless/Dockerfile.serverless`
|
|
89
|
+
adds system packages to the securelock base image. `ECLD_MEMORY_TO_ALLOCATE`
|
|
90
|
+
sets the enclave heap (default `1024M`).
|
|
91
|
+
|
|
57
92
|
### Scripts
|
|
58
93
|
|
|
59
94
|
- **Initialize**: To initialize the project, run:
|
|
@@ -97,6 +132,7 @@ The sdk has been tested on the following operating systems:
|
|
|
97
132
|
npx ecld-run --file payload.js
|
|
98
133
|
npx ecld-run --input data.json 'processData(___etny_data_set___)'
|
|
99
134
|
npx ecld-run --json 'esrIncrement()'
|
|
135
|
+
npx ecld-run --unsafe 'hello("World")'
|
|
100
136
|
```
|
|
101
137
|
This is the network-side sibling of `ecld-test`: instead of executing locally
|
|
102
138
|
it drives the runner end to end — encrypt → IPFS → on-chain request → wait for
|
|
@@ -106,7 +142,9 @@ The sdk has been tested on the following operating systems:
|
|
|
106
142
|
`--network`/`--securelock`/`--trustedzone`; the signing key is `PRIVATE_KEY`
|
|
107
143
|
(or `ECLD_PRIVATE_KEY`) — a funded `0x` key that pays for the order. Resources
|
|
108
144
|
are tunable with `--task-price`/`--cpu`/`--memory`/`--storage`/`--bandwidth`/`--duration`/`--validators`.
|
|
109
|
-
Exit code 0 on a `SUCCESS` task result, 1 otherwise.
|
|
145
|
+
Exit code 0 on a `SUCCESS` task result, 1 otherwise. `--unsafe` runs the
|
|
146
|
+
network's unsafe variant; on an unsafe network the securelock and
|
|
147
|
+
trustedzone default to their `-unsafe` names.
|
|
110
148
|
|
|
111
149
|
- **Inspect (read-only)**: To read enclave and on-chain diagnostics — network,
|
|
112
150
|
trustedzone/securelock registration, and ESR state — without spending gas, run:
|
|
@@ -151,63 +189,63 @@ function hello(msg='World') {
|
|
|
151
189
|
|
|
152
190
|
module.exports = { hello };
|
|
153
191
|
```
|
|
154
|
-
From your frontend application, using the ethernity cloud runner library, you will be calling the function as seen in the below example, where we pass `hello("World");` to be executed on the backend which will run in the Blockchain
|
|
192
|
+
From your frontend application, using the ethernity cloud runner library (`@ethernity-cloud/runner`, installed by `ecld-init` at the version this SDK depends on), you will be calling the function as seen in the below example, where we pass `hello("World");` to be executed on the backend which will run in the Blockchain. `npm start` runs `src/preStart.mjs` first, which fills the constants at the top from the project's `.env`: the securelock name the dApp is published as, the trustedzone it was built against, the network's token address and chain id, the `-unsafe` network when the dApp runs without a CAS, and the IPFS endpoint.
|
|
155
193
|
```js
|
|
156
|
-
|
|
194
|
+
import './App.css';
|
|
157
195
|
import EthernityCloudRunner from "@ethernity-cloud/runner";
|
|
158
|
-
import {
|
|
159
|
-
import Web3 from 'web3';
|
|
196
|
+
import { ECStatus } from "@ethernity-cloud/runner/enums";
|
|
160
197
|
|
|
198
|
+
// Filled in from the project's .env by src/preStart.mjs on `npm start`.
|
|
161
199
|
const PROJECT_NAME = "";
|
|
200
|
+
const TRUSTED_ZONE_IMAGE = "";
|
|
201
|
+
const NETWORK_ADDRESS = "";
|
|
202
|
+
const CHAIN_ID = null;
|
|
203
|
+
const UNSAFE_NETWORK = null;
|
|
162
204
|
const IPFS_ENDPOINT = "";
|
|
163
205
|
|
|
206
|
+
// A call into src/serverless/backend.js; its return value is the task result.
|
|
164
207
|
const code = `hello("World");`;
|
|
165
208
|
|
|
166
209
|
function App() {
|
|
167
210
|
const executeTask = async () => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
console.log(PROJECT_NAME)
|
|
173
|
-
const onTaskProgress = (e) => {
|
|
174
|
-
if (e.detail.status === ECStatus.ERROR) {
|
|
175
|
-
console.error(e.detail.message);
|
|
176
|
-
} else {
|
|
177
|
-
console.log(e.detail.message);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const onTaskCompleted = (e) => {
|
|
182
|
-
console.log(`Task Result: ${e.detail.message.result}`);
|
|
183
|
-
// display the result in page below the buttons
|
|
184
|
-
const result = document.createElement("p");
|
|
185
|
-
result.innerHTML = `Task Result: ${e.detail.message.result}`;
|
|
186
|
-
document.body.appendChild(result);
|
|
211
|
+
// The browser wallet (MetaMask) signs and pays for the order.
|
|
212
|
+
const runner = new EthernityCloudRunner(NETWORK_ADDRESS, {}, CHAIN_ID || undefined);
|
|
213
|
+
if (UNSAFE_NETWORK) {
|
|
214
|
+
await runner.setNetwork(...UNSAFE_NETWORK);
|
|
187
215
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
runner.initializeStorage(IPFS_ENDPOINT);
|
|
217
|
+
|
|
218
|
+
// Events are named by task status; detail is { message, status, progress }.
|
|
219
|
+
runner.addEventListener(ECStatus.DEFAULT, (e) => {
|
|
220
|
+
console.log(`[${e.detail.progress}] ${e.detail.message}`);
|
|
221
|
+
});
|
|
222
|
+
runner.addEventListener(ECStatus.ERROR, (e) => {
|
|
223
|
+
console.error(e.detail.message);
|
|
224
|
+
});
|
|
225
|
+
runner.addEventListener(ECStatus.SUCCESS, async () => {
|
|
226
|
+
const result = await runner.getResult();
|
|
227
|
+
console.log(`Task Result: ${result}`);
|
|
228
|
+
const line = document.createElement("p");
|
|
229
|
+
line.textContent = `Task Result: ${result}`;
|
|
230
|
+
document.body.appendChild(line);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const resources = { taskPrice: 10, cpu: 1, memory: 1, storage: 10, bandwidth: 1, duration: 1, validators: 1 };
|
|
234
|
+
await runner.run(resources, PROJECT_NAME, code, '', TRUSTED_ZONE_IMAGE);
|
|
196
235
|
};
|
|
236
|
+
|
|
197
237
|
const connectWallet = async () => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
};
|
|
238
|
+
if (!window.ethereum) {
|
|
239
|
+
console.log('Please install MetaMask!');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
244
|
+
console.log("Wallet connected");
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error("User denied account access");
|
|
247
|
+
}
|
|
248
|
+
};
|
|
211
249
|
|
|
212
250
|
return (
|
|
213
251
|
<div className="container">
|
package/cas/config.js
CHANGED
|
@@ -14,23 +14,81 @@
|
|
|
14
14
|
// Kept in sync with etny-{pynithy,nodenithy}/v3/networks.yaml
|
|
15
15
|
// (cas_session_registry_address, validator_registry_address).
|
|
16
16
|
const VALIDATOR_REGISTRY = {
|
|
17
|
-
'etny-pynithy-testnet': '
|
|
18
|
-
'etny-nodenithy-testnet': '
|
|
17
|
+
'etny-pynithy-testnet': '0x0014aB102FEa6b02e3D793FF4b9a0Af309fB4E14',
|
|
18
|
+
'etny-nodenithy-testnet': '0x0014aB102FEa6b02e3D793FF4b9a0Af309fB4E14',
|
|
19
|
+
'ecld-pynithy-litvm-testnet': '0xbE3759f327e8643fC5A1717fb2072A0dAcBAD29E',
|
|
20
|
+
'ecld-nodenithy-litvm-testnet': '0xbE3759f327e8643fC5A1717fb2072A0dAcBAD29E',
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
const SESSION_REGISTRY = {
|
|
22
24
|
'etny-pynithy-testnet': '0xcb1F389bF4524d1D61EDcbC24eC1F1F9C3FF4Fa6',
|
|
23
25
|
'etny-nodenithy-testnet': '0xcb1F389bF4524d1D61EDcbC24eC1F1F9C3FF4Fa6',
|
|
26
|
+
'ecld-pynithy-litvm-testnet': '0x8ad24b3F406A41a0F8D3440021792EB203957F43',
|
|
27
|
+
'ecld-nodenithy-litvm-testnet': '0x8ad24b3F406A41a0F8D3440021792EB203957F43',
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
// The chain the registries above live on.
|
|
27
31
|
const CHAIN = {
|
|
28
32
|
'etny-pynithy-testnet': { rpcUrl: 'https://bloxberg.ethernity.cloud', chainId: 8995 },
|
|
29
33
|
'etny-nodenithy-testnet': { rpcUrl: 'https://bloxberg.ethernity.cloud', chainId: 8995 },
|
|
34
|
+
'ecld-pynithy-litvm-testnet': { rpcUrl: 'https://liteforge.rpc.caldera.xyz/infra-partner-http', chainId: 4441 },
|
|
35
|
+
'ecld-nodenithy-litvm-testnet': { rpcUrl: 'https://liteforge.rpc.caldera.xyz/infra-partner-http', chainId: 4441 },
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
// Whether the securelock takes its certificate from a CAS session: mainnet
|
|
33
39
|
// (Scontain CAS), or a testnet template with a SessionRegistry (ethernity-cas).
|
|
34
40
|
const casProvisioned = (templateName, isMainnet) => isMainnet || Boolean(SESSION_REGISTRY[templateName]);
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
// The mainnets, by BLOCKCHAIN_NETWORK. A template name does not tell: the Amoy
|
|
43
|
+
// templates (ecld-*-amoy) do not contain "testnet".
|
|
44
|
+
const MAINNETS = ['Bloxberg_Mainnet', 'Polygon_Mainnet'];
|
|
45
|
+
const isMainnetNetwork = (network) =>
|
|
46
|
+
MAINNETS.some((name) => name.toUpperCase() === String(network || '').toUpperCase());
|
|
47
|
+
|
|
48
|
+
// The -unsafe networks, by BLOCKCHAIN_NETWORK, each with the network it is the
|
|
49
|
+
// unsafe variant of: the same chain and contracts, for SGX platforms the CAS
|
|
50
|
+
// cannot attest (EPID-only, SGX1). No CAS and no LAS: the securelock is
|
|
51
|
+
// debug-signed and self-signs from MR_ENCLAVE, is registered as
|
|
52
|
+
// <project>-unsafe, and runs only against an -unsafe trustedzone. Mainnet has
|
|
53
|
+
// none. Kept in sync with the *_unsafe entries of
|
|
54
|
+
// etny-{pynithy,nodenithy}/v3/networks.yaml.
|
|
55
|
+
const UNSAFE_NETWORKS = {
|
|
56
|
+
Bloxberg_Testnet_Unsafe: 'Bloxberg_Testnet',
|
|
57
|
+
LitVM_LiteForge_Unsafe: 'LitVM_LiteForge',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Case-insensitive: ecld-init writes Bloxberg_Testnet_Unsafe, a --network
|
|
61
|
+
// flag may say BLOXBERG_TESTNET_UNSAFE.
|
|
62
|
+
const isUnsafeNetwork = (network) =>
|
|
63
|
+
Object.keys(UNSAFE_NETWORKS).some((name) => name.toUpperCase() === String(network || '').toUpperCase());
|
|
64
|
+
|
|
65
|
+
// The name an image is registered and run under on `network`: <name>-unsafe
|
|
66
|
+
// on an -unsafe network. A dApp's securelock is <project>-unsafe there, so its
|
|
67
|
+
// two variants never share an image name, and an -unsafe network's
|
|
68
|
+
// trustedzone is the -unsafe variant of its sibling's.
|
|
69
|
+
const nameOnNetwork = (name, network) =>
|
|
70
|
+
!name || !isUnsafeNetwork(network) || name.endsWith('-unsafe') ? name : `${name}-unsafe`;
|
|
71
|
+
|
|
72
|
+
// The network part of ENCLAVE_NAME_SECURELOCK: the second word of
|
|
73
|
+
// BLOCKCHAIN_NETWORK, with _unsafe on an -unsafe network so the two variants
|
|
74
|
+
// never share a session name.
|
|
75
|
+
const sessionTag = (network) =>
|
|
76
|
+
`${network.split('_')[1].toLowerCase()}${isUnsafeNetwork(network) ? '_unsafe' : ''}`;
|
|
77
|
+
|
|
78
|
+
// A compose template rendered for its network. A line tagged `# __CAS_ONLY__`
|
|
79
|
+
// serves CAS attestation and is kept where a CAS provisions the enclaves, one
|
|
80
|
+
// tagged `# __NO_CAS_ONLY__` where they self-sign, and one tagged
|
|
81
|
+
// `# __SAFE_ONLY__` (the LAS) on every network but an -unsafe one. Kept lines
|
|
82
|
+
// lose their tag.
|
|
83
|
+
const renderCompose = (content, cas, unsafe) => content
|
|
84
|
+
.split('\n')
|
|
85
|
+
.filter((line) => !(/# __CAS_ONLY__$/.test(line) && !cas))
|
|
86
|
+
.filter((line) => !(/# __NO_CAS_ONLY__$/.test(line) && cas))
|
|
87
|
+
.filter((line) => !(/# __SAFE_ONLY__$/.test(line) && unsafe))
|
|
88
|
+
.map((line) => line.replace(/\s*# __(?:CAS|NO_CAS|SAFE)_ONLY__$/, ''))
|
|
89
|
+
.join('\n');
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
VALIDATOR_REGISTRY, SESSION_REGISTRY, CHAIN, casProvisioned, isMainnetNetwork,
|
|
93
|
+
UNSAFE_NETWORKS, isUnsafeNetwork, nameOnNetwork, sessionTag, renderCompose,
|
|
94
|
+
};
|
package/info.js
CHANGED
|
@@ -33,26 +33,34 @@ const fs = require('fs');
|
|
|
33
33
|
const path = require('path');
|
|
34
34
|
const { ethers } = require('ethers');
|
|
35
35
|
try { require('dotenv').config(); } catch (e) { /* dotenv optional */ }
|
|
36
|
+
const casConfig = require('./cas/config.js');
|
|
36
37
|
|
|
37
38
|
// Authoritative per-network values (mirror of the Python SDK's network table).
|
|
38
39
|
const NETWORKS = {
|
|
39
40
|
BLOXBERG_MAINNET: {
|
|
40
|
-
type: 'mainnet', chainId: 8995, rpc: 'https://
|
|
41
|
+
type: 'mainnet', chainId: 8995, rpc: 'https://bloxberg.ethernity.cloud',
|
|
41
42
|
protocol: '0x549A6E06BB2084100148D50F51CF77a3436C3Ae7',
|
|
42
43
|
imageRegistry: '0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31',
|
|
43
44
|
esr: '0x4Bf5cDE3BFD73dd10B707f8B123Ba631D2EBEAD2',
|
|
44
45
|
},
|
|
45
46
|
BLOXBERG_TESTNET: {
|
|
46
|
-
type: 'testnet', chainId: 8995, rpc: 'https://
|
|
47
|
+
type: 'testnet', chainId: 8995, rpc: 'https://bloxberg.ethernity.cloud',
|
|
47
48
|
protocol: '0x02882F03097fE8cD31afbdFbB5D72a498B41112c',
|
|
48
49
|
imageRegistry: '0x99A84C624C028bdf0a855A1E9E3f2fcf7275B3D8',
|
|
49
50
|
esr: '0x0Ea1728EAE108FD3B9340ae91451348E2Cc6b4E4',
|
|
50
51
|
},
|
|
51
52
|
LITVM_LITEFORGE: {
|
|
52
|
-
type: 'testnet', chainId:
|
|
53
|
-
protocol: '',
|
|
53
|
+
type: 'testnet', chainId: 4441, rpc: 'https://liteforge.rpc.caldera.xyz/infra-partner-http',
|
|
54
|
+
protocol: '0x29D3eC870565B6A1510232bd950A8Bc8336f0EB2',
|
|
55
|
+
imageRegistry: '0x55e0ad455Be85162b71a790f00Fc305680E3CE53',
|
|
56
|
+
esr: '0x709052Fe77Af543d3d9FE2Ac06a15c635c8D4Be5',
|
|
54
57
|
},
|
|
55
58
|
};
|
|
59
|
+
// The -unsafe networks: the chain and contracts of the network each is the
|
|
60
|
+
// unsafe variant of, without a CAS.
|
|
61
|
+
for (const [unsafe, sibling] of Object.entries(casConfig.UNSAFE_NETWORKS)) {
|
|
62
|
+
NETWORKS[unsafe.toUpperCase()] = { ...NETWORKS[sibling.toUpperCase()], type: 'unsafe testnet, no CAS' };
|
|
63
|
+
}
|
|
56
64
|
|
|
57
65
|
const IMAGE_REGISTRY_ABI = [
|
|
58
66
|
'function imageDetails(string) view returns (address owner, string ipfsHash, string version, string session, uint256 fee, address rewardAddress, bool validated, bool published, string certPublicKey, string dockerComposeHash, string name)',
|
|
@@ -296,7 +304,8 @@ async function main() {
|
|
|
296
304
|
const prov = provider(net.rpc);
|
|
297
305
|
|
|
298
306
|
const ipfs = args.ipfs || process.env.IPFS_HASH || null;
|
|
299
|
-
|
|
307
|
+
// <PROJECT_NAME>-unsafe on an -unsafe network, which is how publish registers it.
|
|
308
|
+
const projName = args.name || casConfig.nameOnNetwork(process.env.PROJECT_NAME, netName) || null;
|
|
300
309
|
|
|
301
310
|
if (section === 'esr') {
|
|
302
311
|
args.esrCmd = args._[1];
|
package/init.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require("fs");
|
|
|
2
2
|
const readline = require("readline");
|
|
3
3
|
const { spawn, execSync } = require("child_process");
|
|
4
4
|
const path = require("path");
|
|
5
|
+
const casConfig = require("./cas/config.js");
|
|
5
6
|
|
|
6
7
|
const rl = readline.createInterface({
|
|
7
8
|
input: process.stdin,
|
|
@@ -179,6 +180,8 @@ const main = async () => {
|
|
|
179
180
|
"IoTeX Testnet",
|
|
180
181
|
"Ethereum Sepolia",
|
|
181
182
|
"LitVM LiteForge",
|
|
183
|
+
"Bloxberg Testnet Unsafe",
|
|
184
|
+
"LitVM LiteForge Unsafe",
|
|
182
185
|
];
|
|
183
186
|
const blockchainNetwork = await promptOptions(
|
|
184
187
|
"On which Blockchain network do you want to have the app set up, as a starting point? (default is Bloxberg Testnet): ",
|
|
@@ -186,9 +189,20 @@ const main = async () => {
|
|
|
186
189
|
"Bloxberg Testnet",
|
|
187
190
|
);
|
|
188
191
|
console.log();
|
|
192
|
+
const network = blockchainNetwork.replace(/ /g, "_");
|
|
193
|
+
if (casConfig.isUnsafeNetwork(network)) {
|
|
194
|
+
const sibling = casConfig.UNSAFE_NETWORKS[network].replace(/_/g, " ");
|
|
195
|
+
console.log(`${blockchainNetwork} runs your dApp WITHOUT a CAS, on the chain and contracts of ${sibling},`);
|
|
196
|
+
console.log("for SGX platforms the CAS cannot attest (EPID-only, SGX1). Its enclaves are debug-signed and");
|
|
197
|
+
console.log("sign their own certificates from their measurement: a result proves which image ran, not that");
|
|
198
|
+
console.log(`an enclave ran it. The securelock is registered as <project>-unsafe; choose ${sibling} for`);
|
|
199
|
+
console.log("a CAS-attested run.");
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
202
|
+
const imageName = casConfig.nameOnNetwork(projectName.replace(/ /g, "-"), network);
|
|
189
203
|
|
|
190
204
|
console.log(
|
|
191
|
-
`Checking if the project name (image name) is available on the ${
|
|
205
|
+
`Checking if the project name (image name ${imageName}) is available on the ${network} network and ownership...`,
|
|
192
206
|
);
|
|
193
207
|
// const { execSync } = require('child_process');
|
|
194
208
|
// execSync(`python $(pwd)/node_modules/@ethernity-cloud/sdk-js/nodenithy/run/image_registry.py "${blockchainNetwork.replace(/ /g, "_")}" "${projectName.replace(/ /g, "-")}" v3`);
|
|
@@ -198,8 +212,8 @@ const main = async () => {
|
|
|
198
212
|
new Promise((resolve, reject) => {
|
|
199
213
|
const child = spawn("node", [
|
|
200
214
|
scriptPath,
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
network,
|
|
216
|
+
imageName,
|
|
203
217
|
"v3",
|
|
204
218
|
]);
|
|
205
219
|
// Handle stdout data
|
|
@@ -296,9 +310,11 @@ const main = async () => {
|
|
|
296
310
|
);
|
|
297
311
|
}
|
|
298
312
|
console.log("Installing required packages...");
|
|
299
|
-
//
|
|
313
|
+
// The app runs on the runner at the range this SDK depends on; the spec is
|
|
314
|
+
// quoted because cmd.exe treats a bare ^ as its escape character.
|
|
315
|
+
const runnerSpec = `@ethernity-cloud/runner@${require("./package.json").dependencies["@ethernity-cloud/runner"]}`;
|
|
300
316
|
execSync(
|
|
301
|
-
|
|
317
|
+
`npm install "${runnerSpec}" @testing-library/jest-dom@5.17.0 @testing-library/react@13.4.0 @testing-library/user-event@13.5.0 react@18.3.1 react-dom@18.3.1 react-scripts@5.0.1 web-vitals@2.1.4 dotenv@16.4.5`,
|
|
302
318
|
{ stdio: "inherit" },
|
|
303
319
|
);
|
|
304
320
|
} else {
|
|
@@ -342,12 +358,14 @@ const main = async () => {
|
|
|
342
358
|
"IoTeX Testnet": `ecld-${svc}-iotex-testnet`,
|
|
343
359
|
"Ethereum Sepolia": `ecld-${svc}-ethereum-sepolia`,
|
|
344
360
|
"LitVM LiteForge": `ecld-${svc}-litvm-testnet`,
|
|
361
|
+
"Bloxberg Testnet Unsafe": `etny-${svc}-testnet-unsafe`,
|
|
362
|
+
"LitVM LiteForge Unsafe": `ecld-${svc}-litvm-testnet-unsafe`,
|
|
345
363
|
};
|
|
346
364
|
const trustedZoneImage = imageByNetwork[blockchainNetwork] || `etny-${svc}-testnet`;
|
|
347
365
|
writeEnv("TRUSTED_ZONE_IMAGE", trustedZoneImage);
|
|
348
366
|
|
|
349
367
|
|
|
350
|
-
writeEnv("BLOCKCHAIN_NETWORK",
|
|
368
|
+
writeEnv("BLOCKCHAIN_NETWORK", network);
|
|
351
369
|
writeEnv("IPFS_ENDPOINT", customUrl);
|
|
352
370
|
console.log();
|
|
353
371
|
await configureEsr();
|
package/network.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* The runner's view of a project's network.
|
|
2
|
+
*
|
|
3
|
+
* BLOCKCHAIN_NETWORK, as ecld-init writes it (spaces -> underscores), names a
|
|
4
|
+
* network; the runner is constructed with that network's token ADDRESS, the
|
|
5
|
+
* chain id of a testnet whose token address other testnets share, and for an
|
|
6
|
+
* -unsafe network is set to the (network, type) that runs only -unsafe
|
|
7
|
+
* trustedzones. The table is built from the runner's own enums so the two
|
|
8
|
+
* cannot drift; the enums are an argument because the runner is ESM and this
|
|
9
|
+
* module is loaded from CJS (ecld-run) and ESM (a dApp's preStart) alike.
|
|
10
|
+
* Shared by ecld-run and the Hello World template's src/preStart.mjs. */
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const casConfig = require('./cas/config.js');
|
|
15
|
+
|
|
16
|
+
/* Accepts a few spellings per network. Returns the normalised key, the
|
|
17
|
+
* network ({ address, chainId?, unsafe? }) or null, and the known keys. */
|
|
18
|
+
function networkFor({ ECAddress, ECNetworkByChainId }, raw) {
|
|
19
|
+
const key = String(raw || '').trim().toUpperCase().replace(/\s+/g, '_');
|
|
20
|
+
const bloxbergTestnet = { address: ECAddress.BLOXBERG.TESTNET_ADDRESS };
|
|
21
|
+
const amoy = { address: ECAddress.POLYGON.TESTNET_ADDRESS };
|
|
22
|
+
const sepolia = { address: ECAddress.SEPOLIA.TESTNET_ADDRESS, chainId: ECNetworkByChainId.SEPOLIA.TESTNET };
|
|
23
|
+
const litvm = { address: ECAddress.LITVM.TESTNET_ADDRESS, chainId: ECNetworkByChainId.LITVM.TESTNET };
|
|
24
|
+
const table = {
|
|
25
|
+
BLOXBERG_TESTNET: bloxbergTestnet,
|
|
26
|
+
BLOXBERG_TESTNET_UNSAFE: { ...bloxbergTestnet, unsafe: ['BLOXBERG', 'TESTNET_UNSAFE'] },
|
|
27
|
+
BLOXBERG_MAINNET: { address: ECAddress.BLOXBERG.MAINNET_ADDRESS },
|
|
28
|
+
POLYGON_MAINNET: { address: ECAddress.POLYGON.MAINNET_ADDRESS },
|
|
29
|
+
POLYGON_AMOY: amoy,
|
|
30
|
+
POLYGON_AMOY_TESTNET: amoy,
|
|
31
|
+
IOTEX_TESTNET: { address: ECAddress.IOTEX.TESTNET_ADDRESS, chainId: ECNetworkByChainId.IOTEX.TESTNET },
|
|
32
|
+
ETHEREUM_SEPOLIA: sepolia,
|
|
33
|
+
SEPOLIA: sepolia,
|
|
34
|
+
LITVM_LITEFORGE: litvm,
|
|
35
|
+
LITVM: litvm,
|
|
36
|
+
LITVM_LITEFORGE_UNSAFE: { ...litvm, unsafe: ['LITVM', 'TESTNET_UNSAFE'] },
|
|
37
|
+
};
|
|
38
|
+
return { key, network: table[key] || null, known: Object.keys(table) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* The -unsafe network a network token names, or whose sibling it names; null
|
|
42
|
+
* when the network has no -unsafe variant. */
|
|
43
|
+
function unsafeVariant(raw) {
|
|
44
|
+
const key = String(raw || '').trim().replace(/\s+/g, '_');
|
|
45
|
+
if (casConfig.isUnsafeNetwork(key)) return key;
|
|
46
|
+
const variant = Object.keys(casConfig.UNSAFE_NETWORKS)
|
|
47
|
+
.find((name) => casConfig.UNSAFE_NETWORKS[name].toUpperCase() === key.toUpperCase());
|
|
48
|
+
return variant || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { networkFor, unsafeVariant };
|
|
@@ -1,20 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
# SCONE_NODE (build.mjs passes it) is the nodenithy trustedzone image this
|
|
2
|
+
# securelock is bundled with: node:22-alpine with the SCONE 6.0.7 runtime and
|
|
3
|
+
# CLI (sconecuratedimages/node:node-22-alpine, the base the etny-nodenithy CI
|
|
4
|
+
# builds the trustedzones on), the release the trustedzones, the LAS and the
|
|
5
|
+
# CAS run. Its own enclave is replaced below: /lib/libbinary-fs.so is
|
|
6
|
+
# overwritten and /usr/local/bin/node re-signed.
|
|
7
|
+
ARG SCONE_NODE
|
|
8
|
+
|
|
9
|
+
# The dependencies are plain JavaScript, installed with a stock node of the
|
|
10
|
+
# same major version; no SCONE binary runs at build time.
|
|
11
|
+
FROM node:22-alpine AS deps
|
|
2
12
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
RUN
|
|
13
|
+
COPY ./src /etny-securelock/
|
|
14
|
+
WORKDIR /etny-securelock
|
|
15
|
+
RUN npm install
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
FROM ${SCONE_NODE} AS release
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
# As in the runtime release stage: the scone CLI below runs on a build host
|
|
20
|
+
# without SGX.
|
|
21
|
+
ENV SCONE_MODE=SIM
|
|
10
22
|
ENV SCONE_LOG=DEBUG
|
|
11
23
|
ENV ENCLAVE_NAME_SECURELOCK=__ENCLAVE_NAME_SECURELOCK__
|
|
24
|
+
# "mainnet" or "testnet": the securelock self-signs from MR_ENCLAVE on a
|
|
25
|
+
# testnet without a ValidatorRegistry, and takes its certificate from the CAS
|
|
26
|
+
# otherwise.
|
|
27
|
+
ENV NETWORK_TYPE=__NETWORK_TYPE__
|
|
12
28
|
ENV BUCKET_NAME=__BUCKET_NAME__
|
|
13
29
|
ENV SMART_CONTRACT_ADDRESS=__SMART_CONTRACT_ADDRESS__
|
|
14
30
|
ENV IMAGE_REGISTRY_ADDRESS=__IMAGE_REGISTRY_ADDRESS__
|
|
15
31
|
ENV RPC_URL=__RPC_URL__
|
|
16
32
|
ENV CHAIN_ID=__CHAIN_ID__
|
|
17
|
-
ENV TRUSTED_ZONE_IMAGE=__TRUSTED_ZONE_IMAGE__
|
|
18
33
|
ENV VALIDATOR_REGISTRY_ADDRESS=__ETNY_VALIDATOR_REGISTRY_ADDRESS__
|
|
19
34
|
# ESR (RFC §5.4): rendered by build.mjs to `ENV ESR_CONTRACT_ADDRESS=0x...` only
|
|
20
35
|
# when ESR is enabled, and to nothing otherwise -- so non-ESR images keep exactly
|
|
@@ -22,36 +37,25 @@ ENV VALIDATOR_REGISTRY_ADDRESS=__ETNY_VALIDATOR_REGISTRY_ADDRESS__
|
|
|
22
37
|
# gates all ESR behaviour on this variable being non-empty.
|
|
23
38
|
__ESR_ENV__
|
|
24
39
|
|
|
25
|
-
RUN mkdir binary-fs-dir
|
|
40
|
+
RUN mkdir /binary-fs-dir
|
|
26
41
|
|
|
27
|
-
COPY
|
|
42
|
+
COPY --from=deps /etny-securelock /etny-securelock/
|
|
28
43
|
COPY scripts/* /etny-securelock/
|
|
29
44
|
|
|
30
|
-
WORKDIR /etny-securelock
|
|
31
|
-
RUN pwd
|
|
32
|
-
RUN npm install
|
|
33
|
-
RUN ls
|
|
34
|
-
|
|
35
45
|
WORKDIR /
|
|
36
46
|
|
|
37
47
|
RUN ["chmod", "+x", "etny-securelock/binary-fs-build.sh"]
|
|
38
48
|
RUN etny-securelock/binary-fs-build.sh
|
|
39
49
|
|
|
40
|
-
FROM registry.ethernity.cloud:443/debuggingdelight/ethernity-cloud-sdk-registry/sconecuratedimages/crosscompilers as build
|
|
50
|
+
FROM registry.ethernity.cloud:443/debuggingdelight/ethernity-cloud-sdk-registry/sconecuratedimages/crosscompilers:alpine-scone6.0.7 as build
|
|
41
51
|
|
|
42
52
|
COPY --from=release /binary-fs-dir /.
|
|
43
53
|
|
|
44
54
|
RUN scone gcc ./binary_fs_blob.s ./libbinary_fs_template.a -shared -o /libbinary-fs.so
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
FROM registry.ethernity.cloud:443/debuggingdelight/ethernity-cloud-sdk-registry/ethernity/node:16.13.1-alpine3.15-scone5.8-production
|
|
56
|
+
FROM ${SCONE_NODE}
|
|
48
57
|
|
|
49
58
|
COPY --from=build /libbinary-fs.so /lib/libbinary-fs.so
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
RUN apk update
|
|
53
|
-
|
|
54
|
-
RUN apk add openssl
|
|
55
59
|
|
|
56
60
|
RUN openssl genrsa -3 -out /enclave-key.pem 3072
|
|
57
61
|
|
|
@@ -64,24 +68,22 @@ ENV SCONE_HEAP=1024M
|
|
|
64
68
|
ENV SCONE_STACK=4M
|
|
65
69
|
ENV SCONE_ALLOW_DLOPEN=1
|
|
66
70
|
ENV SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
71
|
+
ENV SCONE_LOG=DEBUG
|
|
67
72
|
|
|
68
73
|
# Rendered by build.mjs. Signs the EXECUTED binary (/usr/local/bin/node -- the
|
|
69
74
|
# ENTRYPOINT and the run/publish compose command both run /usr/local/bin/node).
|
|
70
|
-
#
|
|
71
|
-
# self-
|
|
75
|
+
# A CAS-provisioned securelock (mainnet, or a testnet with a SessionRegistry) is
|
|
76
|
+
# signed --production; a self-signing testnet one is signed debug.
|
|
72
77
|
__SCONE_SIGN__
|
|
73
78
|
|
|
74
|
-
# Record the SIGNED MRENCLAVE so publish (run.js) can assert
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# drift) => debug re-sign =>
|
|
78
|
-
#
|
|
79
|
-
# is skipped. Capture to a file first: piping scone-signer into `head` kills it
|
|
79
|
+
# Record the SIGNED MRENCLAVE so publish (run.js) can assert, for a
|
|
80
|
+
# CAS-provisioned securelock, that the runtime enclave measurement matches what
|
|
81
|
+
# was signed BEFORE registering the CAS session. A mismatch means SCONE
|
|
82
|
+
# recomputed the enclave at load (params drift) => debug re-sign => the CAS
|
|
83
|
+
# rejects it. Capture to a file first: piping scone-signer into `head` kills it
|
|
80
84
|
# with SIGPIPE (exit 141) under buildkit's pipefail shell.
|
|
81
85
|
__SIGNED_MRENCLAVE__
|
|
82
86
|
|
|
83
87
|
RUN rm -rf /enclave-key.pem
|
|
84
88
|
|
|
85
|
-
#RUN scone cas attest scone-cas.cf 3061b9feb7fa67f3815336a085f629a13f04b0a1667c93b14ff35581dc8271e4 -GCS --only_for_testing-debug --only_for_testing-ignore-signer
|
|
86
|
-
RUN ls /usr/local/bin
|
|
87
89
|
ENTRYPOINT ["/usr/local/bin/node", "/etny-securelock/securelock.js"]
|
|
@@ -7,12 +7,13 @@ cat .env
|
|
|
7
7
|
echo "####################"
|
|
8
8
|
|
|
9
9
|
cat securelock.js.tmpl | sed s/"__ENCLAVE_NAME_SECURELOCK__"/"${ENCLAVE_NAME_SECURELOCK}"/g > securelock.js.tmp
|
|
10
|
-
|
|
11
|
-
sed -i "s
|
|
12
|
-
sed -i "s
|
|
13
|
-
sed -i "s
|
|
14
|
-
sed -i "s
|
|
15
|
-
sed -i "s
|
|
10
|
+
# "|" delimits: the RPC URL contains "/".
|
|
11
|
+
sed -i "s|__ETNY_NETWORK_TYPE__|${NETWORK_TYPE}|g" securelock.js.tmp
|
|
12
|
+
sed -i "s|__ETNY_BUCKET__|${BUCKET_NAME}|g" securelock.js.tmp
|
|
13
|
+
sed -i "s|__ETNY_SMART_CONTRACT_ADDRESS__|${SMART_CONTRACT_ADDRESS}|g" securelock.js.tmp
|
|
14
|
+
sed -i "s|__ETNY_IMAGE_REGISTRY_ADDRESS__|${IMAGE_REGISTRY_ADDRESS}|g" securelock.js.tmp
|
|
15
|
+
sed -i "s|__ETNY_WEB3_PROVIDER__|${RPC_URL}|g" securelock.js.tmp
|
|
16
|
+
sed -i "s|__ETNY_CHAIN_ID__|${CHAIN_ID}|g" securelock.js.tmp
|
|
16
17
|
# ethernity-cas ValidatorRegistry for CAS self-attestation. Deliberately NOT
|
|
17
18
|
# overridable from the runtime environment (unlike the addresses above): the
|
|
18
19
|
# session env comes from the CAS itself, and a rogue CAS must not get to
|