@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
|
@@ -1,58 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
import './App.css';
|
|
2
2
|
import EthernityCloudRunner from "@ethernity-cloud/runner";
|
|
3
|
-
import {
|
|
4
|
-
import Web3 from 'web3';
|
|
3
|
+
import { ECStatus } from "@ethernity-cloud/runner/enums";
|
|
5
4
|
|
|
5
|
+
// Filled in from the project's .env by src/preStart.mjs on `npm start`: the
|
|
6
|
+
// securelock this dApp is published as, the trustedzone it was built against,
|
|
7
|
+
// the network's token address and chain id, the -unsafe network when the dApp
|
|
8
|
+
// runs without a CAS (null otherwise), and the IPFS endpoint.
|
|
6
9
|
const PROJECT_NAME = "";
|
|
10
|
+
const TRUSTED_ZONE_IMAGE = "";
|
|
11
|
+
const NETWORK_ADDRESS = "";
|
|
12
|
+
const CHAIN_ID = null;
|
|
13
|
+
const UNSAFE_NETWORK = null;
|
|
7
14
|
const IPFS_ENDPOINT = "";
|
|
8
15
|
|
|
16
|
+
// A call into src/serverless/backend.js; its return value is the task result.
|
|
9
17
|
const code = `hello("World");`;
|
|
10
18
|
|
|
11
19
|
function App() {
|
|
12
20
|
const executeTask = async () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
console.log(PROJECT_NAME)
|
|
18
|
-
const onTaskProgress = (e) => {
|
|
19
|
-
if (e.detail.status === ECStatus.ERROR) {
|
|
20
|
-
console.error(e.detail.message);
|
|
21
|
-
} else {
|
|
22
|
-
console.log(e.detail.message);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const onTaskCompleted = (e) => {
|
|
27
|
-
console.log(`Task Result: ${e.detail.message.result}`);
|
|
28
|
-
// display the result in page below the buttons
|
|
29
|
-
const result = document.createElement("p");
|
|
30
|
-
result.innerHTML = `Task Result: ${e.detail.message.result}`;
|
|
31
|
-
document.body.appendChild(result);
|
|
21
|
+
// The browser wallet (MetaMask) signs and pays for the order.
|
|
22
|
+
const runner = new EthernityCloudRunner(NETWORK_ADDRESS, {}, CHAIN_ID || undefined);
|
|
23
|
+
if (UNSAFE_NETWORK) {
|
|
24
|
+
await runner.setNetwork(...UNSAFE_NETWORK);
|
|
32
25
|
}
|
|
26
|
+
runner.initializeStorage(IPFS_ENDPOINT);
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
runner.addEventListener(
|
|
28
|
+
// Events are named by task status; detail is { message, status, progress }.
|
|
29
|
+
runner.addEventListener(ECStatus.DEFAULT, (e) => {
|
|
30
|
+
console.log(`[${e.detail.progress}] ${e.detail.message}`);
|
|
31
|
+
});
|
|
32
|
+
runner.addEventListener(ECStatus.ERROR, (e) => {
|
|
33
|
+
console.error(e.detail.message);
|
|
34
|
+
});
|
|
35
|
+
runner.addEventListener(ECStatus.SUCCESS, async () => {
|
|
36
|
+
const result = await runner.getResult();
|
|
37
|
+
console.log(`Task Result: ${result}`);
|
|
38
|
+
// display the result in page below the buttons
|
|
39
|
+
const line = document.createElement("p");
|
|
40
|
+
line.textContent = `Task Result: ${result}`;
|
|
41
|
+
document.body.appendChild(line);
|
|
42
|
+
});
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'',
|
|
40
|
-
{ taskPrice: 10, cpu: 1, memory: 1, storage: 10, bandwidth: 1, duration: 1, validators: 1 });
|
|
44
|
+
const resources = { taskPrice: 10, cpu: 1, memory: 1, storage: 10, bandwidth: 1, duration: 1, validators: 1 };
|
|
45
|
+
await runner.run(resources, PROJECT_NAME, code, '', TRUSTED_ZONE_IMAGE);
|
|
41
46
|
};
|
|
47
|
+
|
|
42
48
|
const connectWallet = async () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
};
|
|
49
|
+
if (!window.ethereum) {
|
|
50
|
+
console.log('Please install MetaMask!');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
55
|
+
console.log("Wallet connected");
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error("User denied account access");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
56
60
|
|
|
57
61
|
return (
|
|
58
62
|
<div className="container">
|
|
@@ -61,4 +65,4 @@ function App() {
|
|
|
61
65
|
</div>
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
|
-
export default App;
|
|
68
|
+
export default App;
|
|
@@ -1,57 +1,47 @@
|
|
|
1
|
+
// Runs before `react-scripts start` (the project's `npm start`): fills the
|
|
2
|
+
// constants at the top of src/ec_helloworld_example.js from the project's
|
|
3
|
+
// .env, the way ecld-run reads the same values. PROJECT_NAME and
|
|
4
|
+
// TRUSTED_ZONE_IMAGE take their -unsafe form on an -unsafe network; the
|
|
5
|
+
// network's token address, chain id and -unsafe (network, type) come from the
|
|
6
|
+
// runner's own enums through the SDK's network table.
|
|
1
7
|
import fs from 'fs';
|
|
8
|
+
import { createRequire } from 'module';
|
|
2
9
|
import dotenv from 'dotenv';
|
|
3
|
-
|
|
10
|
+
import * as enums from '@ethernity-cloud/runner/enums.js';
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'ecld-
|
|
12
|
+
dotenv.config();
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const casConfig = require('@ethernity-cloud/sdk-js/cas/config.js');
|
|
15
|
+
const { networkFor } = require('@ethernity-cloud/sdk-js/network.js');
|
|
16
|
+
|
|
17
|
+
const { PROJECT_NAME, TRUSTED_ZONE_IMAGE, BLOCKCHAIN_NETWORK, IPFS_ENDPOINT } = process.env;
|
|
18
|
+
if (!PROJECT_NAME || !BLOCKCHAIN_NETWORK) {
|
|
19
|
+
console.error('preStart: PROJECT_NAME and BLOCKCHAIN_NETWORK are not set in .env; run `npm run ecld-init` first.');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const { network, known } = networkFor(enums, BLOCKCHAIN_NETWORK);
|
|
23
|
+
if (!network) {
|
|
24
|
+
console.error(`preStart: unknown BLOCKCHAIN_NETWORK '${BLOCKCHAIN_NETWORK}' in .env. Known: ${known.join(', ')}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const constants = {
|
|
29
|
+
PROJECT_NAME: casConfig.nameOnNetwork(PROJECT_NAME, BLOCKCHAIN_NETWORK),
|
|
30
|
+
TRUSTED_ZONE_IMAGE: casConfig.nameOnNetwork(TRUSTED_ZONE_IMAGE, BLOCKCHAIN_NETWORK) || '',
|
|
31
|
+
NETWORK_ADDRESS: network.address,
|
|
32
|
+
CHAIN_ID: network.chainId || null,
|
|
33
|
+
UNSAFE_NETWORK: network.unsafe || null,
|
|
34
|
+
IPFS_ENDPOINT: IPFS_ENDPOINT || 'https://ipfs.ethernity.cloud',
|
|
13
35
|
};
|
|
14
36
|
|
|
15
37
|
const filePath = 'src/ec_helloworld_example.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
.
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const imageRegistryPath = 'node_modules/@ethernity-cloud/runner/contract/operation/imageRegistryContract.js';
|
|
28
|
-
const imageRegistryContent = fs.readFileSync(imageRegistryPath, 'utf8');
|
|
29
|
-
|
|
30
|
-
// Log the original content for debugging
|
|
31
|
-
// console.log('Original imageRegistryContent:', imageRegistryContent);
|
|
32
|
-
|
|
33
|
-
// Replace the content in imageRegistryContract.js
|
|
34
|
-
// const updatedImageRegistryContent = imageRegistryContent.replace(/getLatestTrustedZoneImageCertPublicKey\([^)]*\);/, "getLatestTrustedZoneImageCertPublicKey('" + process.env.ENCLAVE_NAME_TRUSTEDZONE + "', 'v3')").replace(/getLatestImageVersionPublicKey\([^)]*\)/, "getLatestImageVersionPublicKey(imageName, '" + process.env.VERSION + "')");
|
|
35
|
-
const updatedImageRegistryContent = imageRegistryContent.replace(
|
|
36
|
-
/async getEnclaveDetailsV3\(.*?\{[\s\S]*?\}\s*\}/,
|
|
37
|
-
`async getEnclaveDetailsV3(imageName, version) {
|
|
38
|
-
try {
|
|
39
|
-
const trustedZonePublicKey = (await this.contract.getLatestTrustedZoneImageCertPublicKey('${process.env.ENCLAVE_NAME_TRUSTEDZONE}', 'v3'));
|
|
40
|
-
const imageDetails = await this.contract.getLatestImageVersionPublicKey(imageName, '${process.env.VERSION}');
|
|
41
|
-
return [imageDetails[0], trustedZonePublicKey[1], imageDetails[2]];
|
|
42
|
-
} catch (e) {
|
|
43
|
-
console.log(e);
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}`
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
fs.writeFileSync(imageRegistryPath, updatedImageRegistryContent, 'utf8');
|
|
51
|
-
|
|
52
|
-
const runnerPath = 'node_modules/@ethernity-cloud/runner/runner.js';
|
|
53
|
-
const runnerContent = fs.readFileSync(runnerPath, 'utf8');
|
|
54
|
-
|
|
55
|
-
const updatedRunnerContent = runnerContent.replace(/this.#enclaveImageIPFSHash}:.*?:/, "this.#enclaveImageIPFSHash}:"+process.env.ENCLAVE_NAME_TRUSTEDZONE+":").replace(/new ImageRegistryContract\([^)]*\);/, "new ImageRegistryContract(this.#networkAddress, '"+process.env.ENCLAVE_NAME_TRUSTEDZONE+"');");
|
|
56
|
-
|
|
57
|
-
fs.writeFileSync(runnerPath, updatedRunnerContent, 'utf8');
|
|
38
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
39
|
+
for (const [name, value] of Object.entries(constants)) {
|
|
40
|
+
const line = new RegExp(`^const ${name} = .*;$`, 'm');
|
|
41
|
+
if (!line.test(content)) {
|
|
42
|
+
console.error(`preStart: ${filePath} has no 'const ${name} = ...;' line to fill.`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
content = content.replace(line, `const ${name} = ${JSON.stringify(value)};`);
|
|
46
|
+
}
|
|
47
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethernity-cloud/sdk-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
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,13 @@
|
|
|
30
30
|
"postinstall.js",
|
|
31
31
|
"test.js",
|
|
32
32
|
"info.js",
|
|
33
|
-
"run.js"
|
|
33
|
+
"run.js",
|
|
34
|
+
"network.js",
|
|
35
|
+
"!**/__pycache__/**",
|
|
36
|
+
"!**/*.pyc"
|
|
34
37
|
],
|
|
35
38
|
"dependencies": {
|
|
36
|
-
"@ethernity-cloud/runner": "^0.
|
|
39
|
+
"@ethernity-cloud/runner": "^0.6.1",
|
|
37
40
|
"acorn": "^8.11.0",
|
|
38
41
|
"adm-zip": "^0.5.14",
|
|
39
42
|
"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
|
-
|
|
77
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
31
|
-
do
|
|
32
|
-
|
|
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[@]}"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Runs on a STOCK python:3.14-alpine (NO SCONE, NO SGX). Renders securelock.py
|
|
3
|
+
# from its template and runs PyInstaller purely as a dependency-analysis pass:
|
|
4
|
+
# PyInstaller resolves every module/shared-object securelock needs and records
|
|
5
|
+
# them in build/securelock/COLLECT-00.toc. The frozen executable itself is not
|
|
6
|
+
# used at runtime (the enclave runs the plain securelock.py with the SCONE
|
|
7
|
+
# python); we only need the collected .so list + the installed site-packages,
|
|
8
|
+
# which the later SCONE stage bakes into the measured binary-fs.
|
|
9
|
+
set -e
|
|
10
|
+
cd /etny-securelock
|
|
11
|
+
|
|
12
|
+
echo "SECURELOCK_SESSION = ${SECURELOCK_SESSION}"
|
|
13
|
+
|
|
14
|
+
# Use '|' as the sed delimiter so values containing '/' (e.g. the RPC URL
|
|
15
|
+
# https://...) don't break the substitution ("sed: bad option in substitution").
|
|
16
|
+
cp securelock.py.tmpl securelock.py.tmp
|
|
17
|
+
sed -i "s|__SECURELOCK_SESSION__|${SECURELOCK_SESSION}|g" securelock.py.tmp
|
|
18
|
+
sed -i "s|__BUCKET_NAME__|${BUCKET_NAME}|g" securelock.py.tmp
|
|
19
|
+
sed -i "s|__SMART_CONTRACT_ADDRESS__|${SMART_CONTRACT_ADDRESS}|g" securelock.py.tmp
|
|
20
|
+
sed -i "s|__IMAGE_REGISTRY_ADDRESS__|${IMAGE_REGISTRY_ADDRESS}|g" securelock.py.tmp
|
|
21
|
+
sed -i "s|__RPC_URL__|${RPC_URL}|g" securelock.py.tmp
|
|
22
|
+
sed -i "s|__CHAIN_ID__|${CHAIN_ID}|g" securelock.py.tmp
|
|
23
|
+
sed -i "s|__TRUSTED_ZONE_IMAGE__|${TRUSTED_ZONE_IMAGE}|g" securelock.py.tmp
|
|
24
|
+
sed -i "s|__NETWORK_TYPE__|${NETWORK_TYPE}|g" securelock.py.tmp
|
|
25
|
+
sed -i "s|__VALIDATOR_REGISTRY_ADDRESS__|${VALIDATOR_REGISTRY_ADDRESS}|g" securelock.py.tmp
|
|
26
|
+
mv securelock.py.tmp securelock.py
|
|
27
|
+
|
|
28
|
+
# Dependency-analysis freeze (off-SCONE). --collect-all pulls in data/binaries
|
|
29
|
+
# for the crypto/web3 stack so the COLLECT toc is complete.
|
|
30
|
+
pyinstaller --onedir --noconfirm securelock.py
|
|
31
|
+
|
|
32
|
+
echo "pyfreeze complete: COLLECT-00.toc + site-packages ready for binary-fs"
|