@ethernity-cloud/sdk-js 1.6.2 → 1.7.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.
Files changed (51) hide show
  1. package/README.md +41 -3
  2. package/cas/config.js +59 -1
  3. package/info.js +14 -5
  4. package/init.js +20 -4
  5. package/nodenithy/build/securelock/Dockerfile.tmpl +34 -32
  6. package/nodenithy/build/securelock/scripts/binary-fs-build.sh +7 -6
  7. package/nodenithy/build.mjs +80 -68
  8. package/nodenithy/run/docker-compose-final.yml.tmpl +36 -31
  9. package/nodenithy/run/docker-compose.yml.tmpl +34 -45
  10. package/nodenithy/run/image_registry.js +17 -4
  11. package/nodenithy/run.js +42 -28
  12. package/package.json +5 -3
  13. package/publish.js +4 -2
  14. package/pynithy/build/securelock/Dockerfile.base.tpl +13 -0
  15. package/pynithy/build/securelock/Dockerfile.tpl +131 -0
  16. package/pynithy/build/securelock/scripts/binary-fs-build.sh +25 -34
  17. package/pynithy/build/securelock/scripts/pyfreeze.sh +32 -0
  18. package/pynithy/build/securelock/src/{pox.abi → abi/etnyImplementationV2.abi} +1489 -1489
  19. package/pynithy/build/securelock/src/abi/image_registry.abi +1 -0
  20. package/pynithy/build/securelock/src/ecld_state.py +906 -0
  21. package/pynithy/build/securelock/src/esr.abi +1 -0
  22. package/pynithy/build/securelock/src/esr_local.py +267 -0
  23. package/pynithy/build/securelock/src/esr_wallet.py +150 -0
  24. package/pynithy/build/securelock/src/etny_crypto.py +208 -167
  25. package/pynithy/build/securelock/src/etny_exec.py +385 -79
  26. package/pynithy/build/securelock/src/etny_exec_flask.py +535 -0
  27. package/pynithy/build/securelock/src/etny_exec_serv.py +353 -0
  28. package/pynithy/build/securelock/src/etny_session_exec.py +245 -0
  29. package/pynithy/build/securelock/src/get_sgx_report.so +0 -0
  30. package/pynithy/build/securelock/src/image_registry.abi +1 -1
  31. package/pynithy/build/securelock/src/key_generation.py +17 -17
  32. package/pynithy/build/securelock/src/local_api.py +189 -0
  33. package/pynithy/build/securelock/src/models.py +200 -176
  34. package/pynithy/build/securelock/src/securelock.py.tmpl +1037 -731
  35. package/pynithy/build/securelock/src/swift_stream_service.py +206 -186
  36. package/pynithy/build.mjs +139 -89
  37. package/pynithy/run/docker-compose-final.yml.tmpl +58 -51
  38. package/pynithy/run/docker-compose.yml.tmpl +62 -48
  39. package/pynithy/run/etny-securelock-test.yaml.tpl +4 -1
  40. package/pynithy/run/image_registry.js +17 -4
  41. package/pynithy/run.js +44 -28
  42. package/pynithy/src/serverless/Dockerfile.serverless +8 -0
  43. package/pynithy/src/serverless/requirements.txt +6 -0
  44. package/run.js +69 -28
  45. package/nodenithy/build/las/Dockerfile +0 -5
  46. package/pynithy/build/docker-compose.yml +0 -70
  47. package/pynithy/build/las/Dockerfile +0 -11
  48. package/pynithy/build/las/src/etny_entrypoint.sh +0 -6
  49. package/pynithy/build/las/src/las_entrypoint.sh +0 -26
  50. package/pynithy/build/scripts/binary-fs-build.sh +0 -28
  51. package/pynithy/build/securelock/Dockerfile.tmpl +0 -95
@@ -1,25 +1,30 @@
1
+ # The compose a node runs for this dApp; its SCONE_* values match
2
+ # docker-compose.yml.tmpl, which measured the securelock at publish. Tagged
3
+ # lines are kept, untagged, only where they apply (cas/config.js
4
+ # renderCompose): __CAS_ONLY__ serves CAS attestation, and __SAFE_ONLY__ is the
5
+ # LAS, which every network but an -unsafe one runs.
1
6
  version: '3.2'
2
7
  services:
3
- las:
4
- container_name: las
5
- privileged: true
6
- image: localhost:5000/etny-las
7
- entrypoint: "/las_entrypoint.sh"
8
- # -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot
9
- # allocate the socket on a host booted with ipv6.disable=1, so it
10
- # crash-loops and every task on that node times out with no attestation.
11
- # The enclaves reach it over IPv4 either way.
12
- command: "/usr/local/bin/las -a 0.0.0.0"
13
- networks:
14
- - ethernity
15
- devices:
16
- - "/dev/sgx_enclave:/dev/sgx_enclave"
17
- restart: on-failure
18
- ports:
19
- - target: 18766
20
- published: 18766
21
- protocol: tcp
22
- mode: host
8
+ las: # __SAFE_ONLY__
9
+ container_name: las # __SAFE_ONLY__
10
+ privileged: true # __SAFE_ONLY__
11
+ image: localhost:5000/etny-las # __SAFE_ONLY__
12
+ entrypoint: "/las_entrypoint.sh" # __SAFE_ONLY__
13
+ # -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot # __SAFE_ONLY__
14
+ # allocate the socket on a host booted with ipv6.disable=1, so it # __SAFE_ONLY__
15
+ # crash-loops and every task on that node times out with no attestation. # __SAFE_ONLY__
16
+ # The enclaves reach it over IPv4 either way. # __SAFE_ONLY__
17
+ command: "/usr/local/bin/las -a 0.0.0.0" # __SAFE_ONLY__
18
+ networks: # __SAFE_ONLY__
19
+ - ethernity # __SAFE_ONLY__
20
+ devices: # __SAFE_ONLY__
21
+ - "/dev/sgx_enclave:/dev/sgx_enclave" # __SAFE_ONLY__
22
+ restart: on-failure # __SAFE_ONLY__
23
+ ports: # __SAFE_ONLY__
24
+ - target: 18766 # __SAFE_ONLY__
25
+ published: 18766 # __SAFE_ONLY__
26
+ protocol: tcp # __SAFE_ONLY__
27
+ mode: host # __SAFE_ONLY__
23
28
 
24
29
  etny-securelock:
25
30
  container_name: etny-securelock
@@ -28,9 +33,9 @@ services:
28
33
  entrypoint: ""
29
34
  command: [ "/usr/local/bin/node", "/etny-securelock/securelock.js" ]
30
35
  environment:
31
- - SCONE_CAS_ADDR=scone-cas.cf
32
- - SCONE_LAS_ADDR=las
33
- - SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
36
+ - SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
37
+ - SCONE_LAS_ADDR=las # __CAS_ONLY__
38
+ - SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application # __CAS_ONLY__
34
39
  - SCONE_HEAP=1024M
35
40
  # Must match the --stack the securelock binary was signed with (build.mjs),
36
41
  # otherwise SCONE recomputes MRENCLAVE at load and re-signs as debug.
@@ -43,8 +48,8 @@ services:
43
48
  - ethernity
44
49
  devices:
45
50
  - "/dev/sgx_enclave:/dev/sgx_enclave"
46
- depends_on:
47
- - las
51
+ depends_on: # __SAFE_ONLY__
52
+ - las # __SAFE_ONLY__
48
53
 
49
54
  etny-trustedzone:
50
55
  container_name: etny-trustedzone
@@ -53,10 +58,10 @@ services:
53
58
  entrypoint: ""
54
59
  command: [ "/usr/local/bin/node", "/etny-trustedzone/trustedzone.js" ]
55
60
  environment:
56
- - SCONE_CAS_ADDR=scone-cas.cf
57
- - SCONE_LAS_ADDR=las
58
- - SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application
59
- - SCONE_HEAP=256M
61
+ - SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
62
+ - SCONE_LAS_ADDR=las # __CAS_ONLY__
63
+ - SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application # __CAS_ONLY__
64
+ - SCONE_HEAP=__TRUSTEDZONE_HEAP__
60
65
  - SCONE_LOG=DEBUG
61
66
  - SCONE_ALLOW_DLOPEN=1
62
67
  - SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
@@ -65,8 +70,8 @@ services:
65
70
  - ethernity
66
71
  devices:
67
72
  - "/dev/sgx_enclave:/dev/sgx_enclave"
68
- depends_on:
69
- - las
73
+ depends_on: # __SAFE_ONLY__
74
+ - las # __SAFE_ONLY__
70
75
  networks:
71
76
  ethernity:
72
77
  external: true
@@ -5,24 +5,29 @@
5
5
  # no local SGX is present, cert extraction falls back to the remote
6
6
  # publickey.ethernity.cloud service. The on-node runtime uses
7
7
  # docker-compose-final.yml, which targets real SGX hardware.
8
+ #
9
+ # Tagged lines are kept, untagged, only where they apply (cas/config.js
10
+ # renderCompose): __CAS_ONLY__ serves CAS attestation (mainnet, or a testnet
11
+ # with a SessionRegistry), and __SAFE_ONLY__ is the LAS, which every network
12
+ # but an -unsafe one runs.
8
13
  version: '3.2'
9
14
  services:
10
- las:
11
- container_name: las
12
- privileged: true
13
- image: localhost:5000/etny-las
14
- entrypoint: "/las_entrypoint.sh"
15
- # -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot
16
- # allocate the socket on a host booted with ipv6.disable=1, so it
17
- # crash-loops and every task on that node times out with no attestation.
18
- # The enclaves reach it over IPv4 either way.
19
- command: "/usr/local/bin/las -a 0.0.0.0"
20
- restart: on-failure
21
- ports:
22
- - target: 18766
23
- published: 18766
24
- protocol: tcp
25
- mode: host
15
+ las: # __SAFE_ONLY__
16
+ container_name: las # __SAFE_ONLY__
17
+ privileged: true # __SAFE_ONLY__
18
+ image: localhost:5000/etny-las # __SAFE_ONLY__
19
+ entrypoint: "/las_entrypoint.sh" # __SAFE_ONLY__
20
+ # -a 0.0.0.0: without it the LAS binds dual-stack (:::18766) and cannot # __SAFE_ONLY__
21
+ # allocate the socket on a host booted with ipv6.disable=1, so it # __SAFE_ONLY__
22
+ # crash-loops and every task on that node times out with no attestation. # __SAFE_ONLY__
23
+ # The enclaves reach it over IPv4 either way. # __SAFE_ONLY__
24
+ command: "/usr/local/bin/las -a 0.0.0.0" # __SAFE_ONLY__
25
+ restart: on-failure # __SAFE_ONLY__
26
+ ports: # __SAFE_ONLY__
27
+ - target: 18766 # __SAFE_ONLY__
28
+ published: 18766 # __SAFE_ONLY__
29
+ protocol: tcp # __SAFE_ONLY__
30
+ mode: host # __SAFE_ONLY__
26
31
 
27
32
  etny-securelock:
28
33
  container_name: etny-securelock
@@ -31,9 +36,9 @@ services:
31
36
  entrypoint: ""
32
37
  command: [ "/usr/local/bin/node", "/etny-securelock/securelock.js" ]
33
38
  environment:
34
- - SCONE_CAS_ADDR=scone-cas.cf
35
- - SCONE_LAS_ADDR=las
36
- - SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application
39
+ - SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
40
+ - SCONE_LAS_ADDR=las # __CAS_ONLY__
41
+ - SCONE_CONFIG_ID=__ENCLAVE_NAME_SECURELOCK__/application # __CAS_ONLY__
37
42
  - SCONE_HEAP=1024M
38
43
  # Must match the --stack the securelock binary was signed with (build.mjs),
39
44
  # otherwise SCONE recomputes MRENCLAVE at load and re-signs as debug.
@@ -42,8 +47,8 @@ services:
42
47
  - SCONE_ALLOW_DLOPEN=1
43
48
  - SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
44
49
  restart: on-failure:5
45
- depends_on:
46
- - las
50
+ depends_on: # __SAFE_ONLY__
51
+ - las # __SAFE_ONLY__
47
52
 
48
53
  etny-trustedzone:
49
54
  container_name: etny-trustedzone
@@ -52,31 +57,15 @@ services:
52
57
  entrypoint: ""
53
58
  command: [ "/usr/local/bin/node", "/etny-trustedzone/trustedzone.js" ]
54
59
  environment:
55
- - SCONE_CAS_ADDR=scone-cas.cf
56
- - SCONE_LAS_ADDR=las
57
- - SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application
58
- - SCONE_HEAP=256M
60
+ - SCONE_CAS_ADDR=scone-cas.cf # __CAS_ONLY__
61
+ - SCONE_LAS_ADDR=las # __CAS_ONLY__
62
+ - SCONE_CONFIG_ID=__ENCLAVE_NAME_TRUSTEDZONE__/application # __CAS_ONLY__
63
+ # The heap the trustedzone was measured and registered with: 1024M for
64
+ # every testnet trustedzone (etny-nodenithy v3/run/docker-compose.yml).
65
+ - SCONE_HEAP=__TRUSTEDZONE_HEAP__
59
66
  - SCONE_LOG=DEBUG
60
67
  - SCONE_ALLOW_DLOPEN=1
61
68
  - SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
62
69
  restart: on-failure
63
- depends_on:
64
- - las
65
-
66
- etny-validator:
67
- container_name: etny-validator
68
- privileged: true
69
- image: localhost:5000/etny-validator
70
- entrypoint: ""
71
- command: [ "/usr/local/bin/node", "/etny-validator/validator.js" ]
72
- environment:
73
- - SCONE_CAS_ADDR=scone-cas.cf
74
- - SCONE_LAS_ADDR=las
75
- - SCONE_CONFIG_ID=ecld-nodenithy/application
76
- - SCONE_HEAP=256M
77
- - SCONE_LOG=DEBUG
78
- - SCONE_ALLOW_DLOPEN=1
79
- - SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
80
- restart: on-failure:5
81
- depends_on:
82
- - las
70
+ depends_on: # __SAFE_ONLY__
71
+ - las # __SAFE_ONLY__
@@ -35,6 +35,10 @@ function setVars(network = "") {
35
35
  GAS = 20000000;
36
36
  GAS_PRICE = 1300000010;
37
37
  }
38
+ } else if (BLOCKCHAIN_NETWORK.includes("LitVM")) {
39
+ NETWORK_RPC = "https://liteforge.rpc.caldera.xyz/infra-partner-http";
40
+ IMAGE_REGISTRY_ADDRESS = "0x55e0ad455Be85162b71a790f00Fc305680E3CE53";
41
+ CHAIN_ID = 4441;
38
42
  }
39
43
  }
40
44
 
@@ -71,7 +75,8 @@ class ImageRegistry {
71
75
  try {
72
76
  this.imageRegistryAbi = this.readContractAbi('image_registry.abi');
73
77
  this.imageRegistryAddress = IMAGE_REGISTRY_ADDRESS;
74
- console.log("imageRegistryAddress: ", this.imageRegistryAddress);
78
+ // stderr: callers read an action's result from stdout.
79
+ console.error("imageRegistryAddress: ", this.imageRegistryAddress);
75
80
  this.provider = new ethers.providers.JsonRpcProvider(NETWORK_RPC);
76
81
 
77
82
  if (PRIVATE_KEY) {
@@ -298,11 +303,19 @@ class ImageRegistry {
298
303
  console.log(`${balance} gas`);
299
304
  process.exit(0);
300
305
  }
306
+ // The certificate of the trustedzone's own record. getLatestImageVersionPublicKey
307
+ // reads the securelock records instead: ECImageRegistryV2 reverts it for
308
+ // a trustedzone, and the bloxberg mainnet and LitVM registries return a
309
+ // different certificate for the same name.
301
310
  if (action === 'getTrustedZoneCert') {
302
311
  const imageRegistry = new ImageRegistry();
303
- const public = (await imageRegistry._getLatestImageVersionPublicKey(projectName, version))[1]
304
- console.log(public);
305
- return public;
312
+ try {
313
+ const latest = await imageRegistry.imageRegistryContract.getLatestTrustedZoneImageCertPublicKey(projectName, version);
314
+ console.log(latest[1]);
315
+ } catch (e) {
316
+ console.error(`No trustedzone ${projectName} (${version}) in image registry ${IMAGE_REGISTRY_ADDRESS}: ${e.reason || e.message}`);
317
+ }
318
+ return;
306
319
  }
307
320
  // The CAS session name recorded for the trustedzone template's latest
308
321
  // image: what the compose's SCONE_CONFIG_ID for the trustedzone must name.
package/nodenithy/run.js CHANGED
@@ -60,13 +60,19 @@ const writeEnv = (key, value) => {
60
60
 
61
61
  let templateName = process.env.TRUSTED_ZONE_IMAGE || 'etny-nodenithy-testnet';
62
62
 
63
- const isMainnet = !templateName.includes('testnet');
64
- // A testnet template with a SessionRegistry is CAS-attested by the
65
- // ethernity-cas validator set: the session is registered on-chain, the
66
- // enclave is provisioned from a validator, and the certificate comes out of
67
- // the CAS session. Other testnets self-sign and register nothing.
68
- const casTestnet = !isMainnet && casConfig.casProvisioned(templateName, isMainnet);
63
+ const isMainnet = casConfig.isMainnetNetwork(process.env.BLOCKCHAIN_NETWORK);
64
+ // Mainnet enclaves are provisioned by the Scontain CAS. A testnet template
65
+ // with a SessionRegistry is CAS-attested by the ethernity-cas validator set:
66
+ // the session is registered on-chain, the enclave is provisioned from a
67
+ // validator, and the certificate comes out of the CAS session. Every other
68
+ // testnet's enclaves self-sign from MR_ENCLAVE and register no session.
69
+ const cas = casConfig.casProvisioned(templateName, isMainnet);
70
+ const casTestnet = !isMainnet && cas;
69
71
  const casChain = casConfig.CHAIN[templateName] || {};
72
+ // An -unsafe network is a self-signing testnet whose compose carries no LAS;
73
+ // its securelock is registered as <project>-unsafe.
74
+ const unsafe = casConfig.isUnsafeNetwork(process.env.BLOCKCHAIN_NETWORK);
75
+ const securelock = casConfig.nameOnNetwork(process.env.PROJECT_NAME, process.env.BLOCKCHAIN_NETWORK);
70
76
 
71
77
  const currentDir = process.cwd();
72
78
  console.log(`currentDir: ${currentDir}`);
@@ -108,14 +114,19 @@ const extractSignedMrenclave = (service) => {
108
114
 
109
115
  const main = async () => {
110
116
  process.env.NODE_NO_WARNINGS = 1
111
- const backupFiles = ['docker-compose.yml.tmpl', 'docker-compose-final.yml.tmpl'];
112
- backupFiles.forEach(file => {
113
- if (!fs.existsSync(file)) {
114
- console.error(`Error: ${file} not found!`);
117
+ // Every testnet trustedzone was measured and registered with a 1024M heap
118
+ // (etny-nodenithy v3/run/docker-compose.yml). The mainnet session named
119
+ // below comes from the features/v3 branches, which ran it with 256M.
120
+ const trustedZoneHeap = isMainnet ? '256M' : '1024M';
121
+ ['docker-compose.yml', 'docker-compose-final.yml'].forEach(file => {
122
+ const template = `${file}.tmpl`;
123
+ if (!fs.existsSync(template)) {
124
+ console.error(`Error: ${template} not found!`);
115
125
  return;
116
126
  }
117
- const backupContent = fs.readFileSync(file, 'utf8');
118
- fs.writeFileSync(file.replace('.tmpl', ''), backupContent, 'utf8');
127
+ const content = casConfig.renderCompose(fs.readFileSync(template, 'utf8'), cas, unsafe)
128
+ .replace(/__TRUSTEDZONE_HEAP__/g, trustedZoneHeap);
129
+ fs.writeFileSync(file, content, 'utf8');
119
130
  });
120
131
 
121
132
 
@@ -134,7 +145,7 @@ const main = async () => {
134
145
  // from the signed binary -- and a runtime recompute yields a DEBUG enclave
135
146
  // that CAS rejects ("Debug mode is enabled"). Refuse to publish rather than
136
147
  // register an untrusted identity on-chain or fail opaquely later.
137
- if (isMainnet || casTestnet) {
148
+ if (cas) {
138
149
  const signedMrenclave = extractSignedMrenclave('etny-securelock');
139
150
  if (!signedMrenclave || signedMrenclave !== process.env.MRENCLAVE_SECURELOCK) {
140
151
  console.error(`Error: securelock runtime MRENCLAVE (${process.env.MRENCLAVE_SECURELOCK}) != signed MRENCLAVE (${signedMrenclave}).`);
@@ -221,15 +232,20 @@ const main = async () => {
221
232
  MRENCLAVE: process.env.MRENCLAVE_SECURELOCK,
222
233
  ENCLAVE_NAME: ENCLAVE_NAME_SECURELOCK
223
234
  };
224
- processYamlTemplate('etny-securelock-test.yaml.tpl', 'etny-securelock-test.yaml', replacementsSecurelock);
235
+ if (cas) {
236
+ processYamlTemplate('etny-securelock-test.yaml.tpl', 'etny-securelock-test.yaml', replacementsSecurelock);
237
+ }
225
238
 
226
239
  // Where the session goes depends on who provisions the securelock. On a
227
240
  // CAS-attested testnet it is registered ON-CHAIN in the ethernity-cas
228
241
  // SessionRegistry, which the validator set reads and which accepts no
229
242
  // POST; the publish then waits until the validators serve it, because the
230
- // public-key harvest below provisions the enclave from them. Every other
231
- // network keeps the Scontain CAS registration that follows.
232
- if (casTestnet) {
243
+ // public-key harvest below provisions the enclave from them. On mainnet it
244
+ // is registered with the Scontain CAS. A self-signing testnet has none: a
245
+ // CAS-issued certificate would not match the key the enclave derives.
246
+ if (!cas) {
247
+ console.log(`\t✔ ${process.env.BLOCKCHAIN_NETWORK}: no CAS session; the securelock self-signs from MR_ENCLAVE`);
248
+ } else if (casTestnet) {
233
249
  const registered = await sessionRegistry.register(
234
250
  casChain.rpcUrl, casChain.chainId, casConfig.SESSION_REGISTRY[templateName],
235
251
  process.env.PRIVATE_KEY, fs.readFileSync('etny-securelock-test.yaml'),
@@ -432,13 +448,9 @@ const main = async () => {
432
448
  console.log(`No __ENCLAVE_NAME_TRUSTEDZONE__ found in ${file}`);
433
449
  }
434
450
 
435
- let ENCLAVE_NAME_TRUSTEDZONE = 'etny-nodenithy-trustedzone-v3-testnet-0.0.8'
436
- if (isMainnet) {
437
- ENCLAVE_NAME_TRUSTEDZONE = 'ecld-nodenithy-trustedzone-v3-3.0.0'
438
- }
439
- if (casTestnet) {
440
- ENCLAVE_NAME_TRUSTEDZONE = trustedZoneSession;
441
- }
451
+ // Named only in a CAS-provisioned compose; a self-signing one has no
452
+ // __CAS_ONLY__ lines left.
453
+ const ENCLAVE_NAME_TRUSTEDZONE = casTestnet ? trustedZoneSession : 'ecld-nodenithy-trustedzone-v3-3.0.0';
442
454
  let updatedContent = fileContentBefore
443
455
  .replace(/__ENCLAVE_NAME_SECURELOCK__/g, ENCLAVE_NAME_SECURELOCK)
444
456
  .replace(/__ENCLAVE_NAME_TRUSTEDZONE__/g, ENCLAVE_NAME_TRUSTEDZONE);
@@ -547,7 +559,7 @@ const main = async () => {
547
559
  console.log()
548
560
 
549
561
  console.log(`ENCLAVE_NAME_SECURELOCK: ${ENCLAVE_NAME_SECURELOCK}`);
550
- execSync(`node ./public_key_service.js --enclave_name "${process.env.PROJECT_NAME}" --protocol_version "v3" --network "${process.env.BLOCKCHAIN_NETWORK}" --template_version "${process.env.VERSION}"`, { stdio: 'inherit' });
562
+ execSync(`node ./public_key_service.js --enclave_name "${securelock}" --protocol_version "v3" --network "${process.env.BLOCKCHAIN_NETWORK}" --template_version "${process.env.VERSION}"`, { stdio: 'inherit' });
551
563
  PUBLIC_KEY_SECURELOCK_RES = fs.readFileSync('PUBLIC_KEY.txt', 'utf8').trim();
552
564
  console.log(`PUBLIC_KEY_SECURELOCK_RES: ${PUBLIC_KEY_SECURELOCK_RES}`);
553
565
  if (!PUBLIC_KEY_SECURELOCK_RES || PUBLIC_KEY_SECURELOCK_RES === '-1') {
@@ -636,10 +648,12 @@ const main = async () => {
636
648
  console.log("IPFS_HASH: ", process.env.IPFS_HASH);
637
649
  writeEnv('IPFS_HASH', process.env.IPFS_HASH);
638
650
  process.chdir(currentDir);
639
- console.log("Adding certificates for SECURELOCK into IMAGE REGISTRY smart contract...");
651
+ console.log(`Adding certificates for SECURELOCK ${securelock} into IMAGE REGISTRY smart contract...`);
652
+ // image_registry.js registers the image under PROJECT_NAME.
653
+ const registryEnv = { ...process.env, PROJECT_NAME: securelock };
640
654
  let existing = false;
641
655
  try {
642
- const existingImages = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${process.env.PROJECT_NAME}" "${process.env.VERSION}" "${process.env.PRIVATE_KEY}" "registerSecureLockImage"`, { stdio: "inherit" });
656
+ const existingImages = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "${securelock}" "${process.env.VERSION}" "${process.env.PRIVATE_KEY}" "registerSecureLockImage"`, { stdio: "inherit", env: registryEnv });
643
657
  if (existingImages.toString().trim().replace('Image hash: ', '') === process.env.IPFS_HASH) {
644
658
  console.log("Certificates for SECURELOCK already added to IMAGE REGISTRY smart contract");
645
659
  }
@@ -651,7 +665,7 @@ const main = async () => {
651
665
  }
652
666
 
653
667
  if (!existing) {
654
- const res = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "" "" "" "registerSecureLockImage"`, { stdio: "inherit" });
668
+ const res = execSync(`node ${runDir}/image_registry.js "${process.env.BLOCKCHAIN_NETWORK}" "" "" "" "registerSecureLockImage"`, { stdio: "inherit", env: registryEnv });
655
669
  }
656
670
  // The on-chain session points at the image it admits, so a validator can
657
671
  // pin the image beside the body it serves.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethernity-cloud/sdk-js",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "A package to run ethernity cloud build scripts",
5
5
  "scripts": {
6
6
  "ecld-build": "cross-env node ./node_modules/@ethernity-cloud/sdk-js/build.js",
@@ -30,10 +30,12 @@
30
30
  "postinstall.js",
31
31
  "test.js",
32
32
  "info.js",
33
- "run.js"
33
+ "run.js",
34
+ "!**/__pycache__/**",
35
+ "!**/*.pyc"
34
36
  ],
35
37
  "dependencies": {
36
- "@ethernity-cloud/runner": "^0.5.0",
38
+ "@ethernity-cloud/runner": "^0.6.0",
37
39
  "acorn": "^8.11.0",
38
40
  "adm-zip": "^0.5.14",
39
41
  "axios": "^1.7.2",
package/publish.js CHANGED
@@ -3,6 +3,7 @@ const readline = require('readline');
3
3
  const { execSync, spawn } = require('child_process');
4
4
  const path = require('path');
5
5
  require('dotenv').config();
6
+ const casConfig = require('./cas/config.js');
6
7
 
7
8
  // Function to write to .env file
8
9
  const writeEnv = (key, value) => {
@@ -73,8 +74,9 @@ async function prompt(question) {
73
74
 
74
75
  console.log(`Available funds: ${result}`);
75
76
  console.log()
76
- console.log(`Checking if project name is available on ${BLOCKCHAIN_NETWORK} network and ownership...`);
77
- result = execSync(`node ${scriptPath} ${BLOCKCHAIN_NETWORK} ${process.env.PROJECT_NAME} ${process.env.VERSION} ${process.env.PRIVATE_KEY}`,).toString().trim();
77
+ const imageName = casConfig.nameOnNetwork(PROJECT_NAME, BLOCKCHAIN_NETWORK);
78
+ console.log(`Checking if project name ${imageName} is available on ${BLOCKCHAIN_NETWORK} network and ownership...`);
79
+ result = execSync(`node ${scriptPath} ${BLOCKCHAIN_NETWORK} ${imageName} ${process.env.VERSION} ${process.env.PRIVATE_KEY}`,).toString().trim();
78
80
  console.log(result);
79
81
  console.log()
80
82
 
@@ -0,0 +1,13 @@
1
+ FROM __DOCKER_REPO_URL__:__BASE_IMAGE_TAG__ AS release
2
+
3
+ # The python-3.14.6-alpine3.24-scone6.0.7 base is a LEAN static CPython 3.14
4
+ # (built off-SCONE with scone-gcc) plus pip/setuptools/wheel only - no
5
+ # application dependencies are baked in. Users add their own Python modules via
6
+ # src/serverless/requirements.txt, which are pip-installed on a normal python
7
+ # during the PyInstaller freeze stage (see Dockerfile.tpl); nothing is compiled
8
+ # under the SCONE-shielded interpreter.
9
+ #
10
+ # Only the system tools the build/runtime scripts call are added here (openssl
11
+ # CLI is needed for the enclave key generation in the final securelock stage).
12
+ RUN apk update
13
+ RUN apk add bash openrc bind-tools sudo binutils curl openssl
@@ -0,0 +1,131 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # Securelock enclave build. The whole build is SGX-free: heavy Python work
4
+ # (pip installs + PyInstaller freeze) runs on a STOCK python:3.14-alpine with
5
+ # no SCONE and no SGX; only the binary-fs packaging and signing use the SCONE
6
+ # build tools, which run in simulation/build mode without an SGX device.
7
+
8
+ ########################################################################
9
+ # Stage 1 - SGX report helper (unchanged, crosscompiler build tool)
10
+ ########################################################################
11
+ FROM registry.ethernity.cloud:443/debuggingdelight/ethernity-cloud-sdk-registry/sconecuratedimages/crosscompilers:alpine-scone6.0.7 AS build-sgx-module
12
+ # The SDK ships the SGX key-gen module ONLY as a PREBUILT .so
13
+ # (get_sgx_report.so) -- never the .c source. The source of truth for
14
+ # get_sgx_report.c lives in the etny-pynithy / etny-nodenithy repos; the .so is
15
+ # produced once from it via scripts/build_keygen_so.sh (scone-gcc, hardened +
16
+ # stripped) and committed here. No source is compiled during the developer's
17
+ # build, so a developer inspecting the wheel or build tree never sees the
18
+ # derivation algorithm.
19
+ #
20
+ # NOTE (see esr_wallet.py): the .so still contains the executable logic and the
21
+ # enclave image is public, so this hides the SOURCE, not the algorithm; on
22
+ # testnet the key remains reproducible because there is no attestation.
23
+ COPY src/get_sgx_report.so /etny-securelock/get_sgx_report.so
24
+
25
+ ########################################################################
26
+ # Stage 2 - PyInstaller freeze on a NORMAL python 3.14 (no SCONE / no SGX)
27
+ # Installs the user's requirements.txt + PyInstaller, renders securelock.py,
28
+ # and runs pyinstaller to produce the frozen COLLECT dir. Same python
29
+ # version + musl as the SCONE runtime python, so the output is compatible.
30
+ ########################################################################
31
+ FROM python:3.14-alpine AS pyfreeze
32
+ RUN apk add --no-cache bash gcc g++ musl-dev libffi-dev openssl-dev rust cargo make libsodium-dev zlib-dev binutils
33
+ ENV SODIUM_INSTALL=system
34
+ WORKDIR /etny-securelock
35
+
36
+ # Core securelock runtime deps + PyInstaller (built here, off-SCONE, where pip
37
+ # and native builds work normally).
38
+ RUN pip install --no-cache-dir \
39
+ pyinstaller \
40
+ python-dotenv "web3>=6" cryptography ecdsa pyasn1 tinyec minio pynacl
41
+
42
+ # User-supplied additional modules (the customization hook).
43
+ COPY ./src/serverless/requirements.txt /requirements.txt
44
+ RUN pip install --no-cache-dir -r /requirements.txt || true
45
+
46
+ COPY ./src /etny-securelock/
47
+ COPY ./scripts/* /etny-securelock/
48
+ COPY --from=build-sgx-module /etny-securelock/get_sgx_report.so /etny-securelock/get_sgx_report.so
49
+
50
+ # Render securelock.py from the template, then run PyInstaller (off-SCONE).
51
+ ENV SECURELOCK_SESSION=__SECURELOCK_SESSION__
52
+ ENV BUCKET_NAME=__BUCKET_NAME__
53
+ ENV SMART_CONTRACT_ADDRESS=__SMART_CONTRACT_ADDRESS__
54
+ ENV IMAGE_REGISTRY_ADDRESS=__IMAGE_REGISTRY_ADDRESS__
55
+ ENV RPC_URL=__RPC_URL__
56
+ ENV CHAIN_ID=__CHAIN_ID__
57
+ ENV TRUSTED_ZONE_IMAGE=__TRUSTED_ZONE_IMAGE__
58
+ ENV NETWORK_TYPE=__NETWORK_TYPE__
59
+ ENV VALIDATOR_REGISTRY_ADDRESS=__VALIDATOR_REGISTRY_ADDRESS__
60
+ RUN chmod +x /etny-securelock/pyfreeze.sh && /etny-securelock/pyfreeze.sh
61
+
62
+ ########################################################################
63
+ # Stage 3 - binary-fs packaging with the SCONE build tools (no SGX).
64
+ # Assembles the SCONE python + stdlib + the (off-SCONE) installed site-packages
65
+ # + the rendered securelock.py + PyInstaller's collected .so list, and bakes
66
+ # them into the measured binary-fs.
67
+ ########################################################################
68
+ FROM registry.ethernity.cloud:443/debuggingdelight/ethernity-cloud-sdk-registry/sconecuratedimages/crosscompilers:alpine-scone6.0.7 AS binfs
69
+ RUN mkdir -p /binary-fs-dir /etny-securelock
70
+ # SCONE-built static python + its stdlib, from the runtime base image
71
+ COPY --from=etny-securelock-serverless /usr/local/bin/python3 /usr/local/bin/python3
72
+ COPY --from=etny-securelock-serverless /usr/local/lib/python3.14 /usr/local/lib/python3.14
73
+ # The ENTIRE /etny-securelock tree (rendered securelock.py + its sibling modules
74
+ # etny_crypto.py, etny_exec.py, models.py, swift_stream_service.py, the abi/
75
+ # dir, get_sgx_report.so, scripts) - all must be baked into binary-fs or the
76
+ # enclave fails at import (ModuleNotFoundError: No module named 'etny_crypto').
77
+ # site-packages + COLLECT toc also come from the OFF-SCONE pyfreeze stage.
78
+ COPY --from=pyfreeze /etny-securelock /etny-securelock
79
+ COPY --from=pyfreeze /etny-securelock/build/securelock/COLLECT-00.toc /etny-securelock/COLLECT-00.toc
80
+ COPY --from=pyfreeze /usr/local/lib/python3.14/site-packages /usr/local/lib/python3.14/site-packages
81
+ RUN chmod +x /etny-securelock/binary-fs-build.sh && /etny-securelock/binary-fs-build.sh
82
+ RUN cd /binary-fs-dir && scone gcc ./binary_fs_blob.s ./libbinary_fs_template.a -shared -o /libbinary-fs.so
83
+
84
+ ########################################################################
85
+ # Stage 4 - final SCONE runtime image (lean scone python base) + sign.
86
+ ########################################################################
87
+ FROM etny-securelock-serverless
88
+
89
+ COPY --from=binfs /usr/local/bin/scone /usr/local/bin/scone
90
+ COPY --from=binfs /libbinary-fs.so /lib/libbinary-fs.so
91
+
92
+ RUN openssl genrsa -3 -out /enclave-key.pem 3072
93
+
94
+ # SCONE enclaves run with no HOME; libraries calling os.path.expanduser('~')
95
+ # abort with "$HOME is not defined" and kill the enclave before it emits its
96
+ # public key. Set a writable HOME so startup (and cert emission) succeeds.
97
+ ENV HOME=/tmp
98
+ ENV SCONE_HEAP=__MEMORY_TO_ALLOCATE__
99
+ ENV SCONE_LOG=FATAL
100
+ ENV SCONE_DEBUG=0
101
+ ENV SCONE_STACK=4M
102
+ __SCONE_ALLOW_DLOPEN__
103
+ ENV SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
104
+ # securelock.py does relative imports of its sibling modules (etny_crypto,
105
+ # models, ...) and reads abi/ by relative path; run from its own directory so
106
+ # those resolve (the base image's build WORKDIR /b/Python-3.14.6 otherwise leaks
107
+ # in as the enclave PWD).
108
+ ENV SCONE_PWD=/etny-securelock
109
+ __ESR_ENV__
110
+ WORKDIR /etny-securelock
111
+
112
+ # Rendered by build.py: signs the EXECUTED binary (/usr/local/bin/python) with
113
+ # explicit enclave-creation params; mainnet adds --production, testnet omits it
114
+ # (non-CAS self-sign mode).
115
+ __SCONE_SIGN__
116
+
117
+ # Record the SIGNED MRENCLAVE so publish.py can assert the runtime enclave
118
+ # measurement matches what was signed BEFORE registering the CAS session /
119
+ # publishing (mismatch => SCONE recomputed at load => debug => CAS rejects).
120
+ # scone-signer info prints two tab-indented "MRENCLAVE:" lines (non-EDMM first;
121
+ # runtime has EDMM disabled, so the first is what SCONE_HASH produces). Capture
122
+ # to a file first: piping into `head` kills scone-signer with SIGPIPE (exit 141)
123
+ # under buildkit's `ash -eo pipefail`.
124
+ RUN scone-signer info /usr/local/bin/python > /tmp/siginfo.txt 2>&1; \
125
+ grep -iE "MRENCLAVE:" /tmp/siginfo.txt | sed -n '1p' \
126
+ | sed -E 's/.*MRENCLAVE:[[:space:]]*//I' | tr -d '[:space:]' > /signed_mrenclave.txt && \
127
+ echo "SIGNED_MRENCLAVE=$(cat /signed_mrenclave.txt)"
128
+
129
+ RUN rm -rf /enclave-key.pem
130
+
131
+ ENTRYPOINT ["/usr/local/bin/python", "/etny-securelock/securelock.py"]
@@ -1,42 +1,33 @@
1
- #!/bin/bash -v
2
-
3
-
4
- pip3 install pyinstaller
5
- apk add binutils
6
-
1
+ #!/bin/bash
2
+ # Runs in the SCONE crosscompiler stage (SCONE build tools, NO SGX needed).
3
+ # The PyInstaller freeze already happened OFF-SCONE in the pyfreeze stage; this
4
+ # stage has received (copied in):
5
+ # /usr/local/bin/python3 - the static SCONE-built python 3.14
6
+ # /usr/local/lib/python3.14/* - its stdlib + the installed site-packages
7
+ # /etny-securelock/securelock.py - the rendered app (run at enclave runtime)
8
+ # /etny-securelock/COLLECT-00.toc - PyInstaller's collected .so list
9
+ # /etny-securelock/get_sgx_report.so
10
+ #
11
+ # We bake the stdlib + site-packages + app + every collected .so into the
12
+ # measured binary-fs; the SCONE python then loads them via SCONE_EXTENSIONS_PATH
13
+ # at runtime (no dlopen from the untrusted host).
14
+ set -e
7
15
  cd /etny-securelock
8
16
 
9
- echo "ENCLAVE_NAME_SECURE_LOCK = ${ENCLAVE_NAME_SECURELOCK}"
10
-
11
- cat securelock.py.tmpl | sed s/"__ENCLAVE_NAME_SECURELOCK__"/"${ENCLAVE_NAME_SECURELOCK}"/g > securelock.py.tmp
12
- sed -i "s/__BUCKET_NAME__/${BUCKET_NAME}/g" securelock.py.tmp
13
- sed -i "s/__SMART_CONTRACT_ADDRESS__/${SMART_CONTRACT_ADDRESS}/g" securelock.py.tmp
14
- sed -i "s/__IMAGE_REGISTRY_ADDRESS__/${IMAGE_REGISTRY_ADDRESS}/g" securelock.py.tmp
15
- sed -i "s/__RPC_URL__/${RPC_URL}/g" securelock.py.tmp
16
- sed -i "s/__CHAIN_ID__/${CHAIN_ID}/g" securelock.py.tmp
17
- sed -i "s/__TRUSTED_ZONE_IMAGE__/${TRUSTED_ZONE_IMAGE}/g" securelock.py.tmp
18
- sed -i "s/__VALIDATOR_REGISTRY_ADDRESS__/${VALIDATOR_REGISTRY_ADDRESS}/g" securelock.py.tmp
19
- mv securelock.py.tmp securelock.py
20
-
21
- pyinstaller securelock.py
22
-
23
- EXEC=(scone binaryfs / /binary-fs-dir -v \
24
- --include '/usr/local/lib/python3.10/*' \
17
+ EXEC=(scone binary-fs / /binary-fs-dir -v \
18
+ --include '/usr/local/bin/python3' \
19
+ --include '/usr/local/lib/python3.14/*' \
25
20
  --include '/etny-securelock/*' \
26
21
  --host-path=/etc/resolv.conf \
27
22
  --host-path=/etc/hosts)
28
23
 
29
-
30
- for FILE in `cat ./build/securelock/COLLECT-00.toc | grep '.so' | grep BINARY | awk -F "'" '{print $4}'`
31
- do
32
- EXEC+=(--include "${FILE}"'*')
33
- done
34
-
35
- rm -rf build dist securelock.spec
24
+ # Add every shared object PyInstaller collected (crypto/web3/etc. native libs).
25
+ if [ -f ./COLLECT-00.toc ]; then
26
+ for FILE in $(grep '.so' ./COLLECT-00.toc | grep BINARY | awk -F "'" '{print $4}'); do
27
+ EXEC+=(--include "${FILE}"'*')
28
+ done
29
+ fi
36
30
 
37
31
  echo "${EXEC[@]}"
38
-
39
- SCONE_MODE=auto
40
- exec "${EXEC[@]}"
41
-
42
- exit
32
+ mkdir -p /binary-fs-dir
33
+ SCONE_MODE=auto exec "${EXEC[@]}"