@ethernity-cloud/sdk-js 1.1.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.js +29 -0
- package/init.js +1 -1
- package/nodenithy/build/securelock/Dockerfile.tmpl +21 -5
- package/nodenithy/build/securelock/src/README-keygen.md +41 -0
- package/nodenithy/build/securelock/src/etny_exec.js +46 -18
- package/nodenithy/build/securelock/src/models.js +275 -261
- package/nodenithy/build/securelock/src/securelock.js.tmpl +444 -421
- package/nodenithy/build/securelock/src/self_sign.js +76 -0
- package/nodenithy/build/securelock/src/sgx_report.js +50 -0
- package/nodenithy/build/securelock/src/sgx_report.node +0 -0
- package/nodenithy/build/securelock/src/task_status.js +3 -0
- package/nodenithy/build.js +22 -0
- package/nodenithy/build.mjs +78 -5
- package/nodenithy/ipfs.mjs +340 -426
- package/nodenithy/run/docker-compose-final.yml.tmpl +3 -0
- package/nodenithy/run/docker-compose.yml.tmpl +10 -8
- package/nodenithy/run.js +44 -1
- package/package.json +50 -48
- package/pynithy/build/securelock/Dockerfile.tmpl +18 -2
- package/pynithy/build.mjs +31 -5
- package/pynithy/ipfs.mjs +376 -155
- package/pynithy/run/docker-compose-final.yml.tmpl +9 -0
- package/pynithy/run/docker-compose.yml.tmpl +10 -6
- package/pynithy/run.js +40 -1
- package/test.js +204 -0
package/build.js
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
const { execSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
require('dotenv').config();
|
|
4
5
|
|
|
6
|
+
// Increment VERSION on every build, mirroring the Python SDK's build.py
|
|
7
|
+
// (config VERSION += 1). VERSION is the immutable per-build registry channel and
|
|
8
|
+
// the enclave-name suffix, so each build must advance it. Existing projects seed
|
|
9
|
+
// VERSION as "v1"; parse whatever trailing integer is present (v1 -> 1, 22 -> 22,
|
|
10
|
+
// empty -> 0), increment, and persist as a plain integer. It must never equal
|
|
11
|
+
// "v3" (the protocol channel that acts as the moving "latest" pointer); plain
|
|
12
|
+
// integers can't collide with the "v3" string, so this is safe.
|
|
13
|
+
const writeEnv = (key, value) => {
|
|
14
|
+
const envFile = '.env';
|
|
15
|
+
let content = fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf8') : '';
|
|
16
|
+
const regex = new RegExp(`^${key}=.*`, 'm');
|
|
17
|
+
if (regex.test(content)) content = content.replace(regex, `${key}=${value}`);
|
|
18
|
+
else content += (content && !content.endsWith('\n') ? '\n' : '') + `${key}=${value}`;
|
|
19
|
+
fs.writeFileSync(envFile, content, 'utf8');
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const bumpVersion = () => {
|
|
23
|
+
const raw = (process.env.VERSION || '0').toString();
|
|
24
|
+
const digits = raw.match(/\d+/);
|
|
25
|
+
const next = (digits ? parseInt(digits[0], 10) : 0) + 1;
|
|
26
|
+
process.env.VERSION = String(next);
|
|
27
|
+
writeEnv('VERSION', String(next));
|
|
28
|
+
console.log(`Version incremented to ${next}`);
|
|
29
|
+
return next;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
bumpVersion();
|
|
33
|
+
|
|
5
34
|
const serviceType = process.env.SERVICE_TYPE;
|
|
6
35
|
|
|
7
36
|
if (serviceType === "Nodenithy") {
|
package/init.js
CHANGED
|
@@ -6,7 +6,6 @@ RUN cd /
|
|
|
6
6
|
|
|
7
7
|
RUN apk add bash
|
|
8
8
|
|
|
9
|
-
ENV SCONE_MODE=SIM
|
|
10
9
|
ENV SCONE_HEAP=256M
|
|
11
10
|
ENV SCONE_LOG=DEBUG
|
|
12
11
|
ENV ENCLAVE_NAME_SECURELOCK=__ENCLAVE_NAME_SECURELOCK__
|
|
@@ -50,14 +49,31 @@ RUN apk add openssl
|
|
|
50
49
|
|
|
51
50
|
RUN openssl genrsa -3 -out /enclave-key.pem 3072
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
# SCONE runtime env. These MUST match the enclave-creation params passed to
|
|
53
|
+
# scone-signer below (SCONE embeds defaults for anything not signed explicitly;
|
|
54
|
+
# any drift makes SCONE recompute + debug re-sign the enclave at load, which CAS
|
|
55
|
+
# rejects on mainnet). Values mirror the publish/run docker-compose securelock
|
|
56
|
+
# service (SCONE_HEAP=1024M, SCONE_STACK=4M, dlopen=1, extensions=libbinary-fs).
|
|
55
57
|
ENV SCONE_HEAP=1024M
|
|
58
|
+
ENV SCONE_STACK=4M
|
|
56
59
|
ENV SCONE_ALLOW_DLOPEN=1
|
|
57
60
|
ENV SCONE_EXTENSIONS_PATH=/lib/libbinary-fs.so
|
|
58
61
|
|
|
59
|
-
#
|
|
60
|
-
#
|
|
62
|
+
# Rendered by build.mjs. Signs the EXECUTED binary (/usr/local/bin/node -- the
|
|
63
|
+
# ENTRYPOINT and the run/publish compose command both run /usr/local/bin/node).
|
|
64
|
+
# Mainnet adds --production (CAS/DCAP attestation); testnet omits it (non-CAS
|
|
65
|
+
# self-sign). On testnet builds this line is a no-op comment.
|
|
66
|
+
__SCONE_SIGN__
|
|
67
|
+
|
|
68
|
+
# Record the SIGNED MRENCLAVE so publish (run.js) can assert the runtime enclave
|
|
69
|
+
# measurement matches what was signed BEFORE registering the CAS session /
|
|
70
|
+
# publishing. A mismatch means SCONE recomputed the enclave at load (params
|
|
71
|
+
# drift) => debug re-sign => CAS rejects the DCAP quote on mainnet. Only produced
|
|
72
|
+
# for --production (mainnet) signs; on testnet the file is absent and the check
|
|
73
|
+
# is skipped. Capture to a file first: piping scone-signer into `head` kills it
|
|
74
|
+
# with SIGPIPE (exit 141) under buildkit's pipefail shell.
|
|
75
|
+
__SIGNED_MRENCLAVE__
|
|
76
|
+
|
|
61
77
|
RUN rm -rf /enclave-key.pem
|
|
62
78
|
|
|
63
79
|
#RUN scone cas attest scone-cas.cf 3061b9feb7fa67f3815336a085f629a13f04b0a1667c93b14ff35581dc8271e4 -GCS --only_for_testing-debug --only_for_testing-ignore-signer
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# SGX key-gen module (`sgx_report.node`)
|
|
2
|
+
|
|
3
|
+
The JS SDK ships this module **only as a prebuilt N-API addon**
|
|
4
|
+
(`sgx_report.node`) — never the C source (`get_sgx_report.c`,
|
|
5
|
+
`sgx_report_napi.c`). The identity-key derivation (SGX EREPORT + obfuscated
|
|
6
|
+
MR_SIGNER + HKDF-SHA512 domain-separated derivation) lives entirely in the
|
|
7
|
+
compiled binary, so a developer inspecting the package or build tree never sees
|
|
8
|
+
the algorithm. `get_sgx_report.c` and `sgx_report_napi.c` are gitignored here
|
|
9
|
+
so they can never be committed.
|
|
10
|
+
|
|
11
|
+
## Where the source lives
|
|
12
|
+
|
|
13
|
+
The source of truth is the runtime repo, not this package:
|
|
14
|
+
|
|
15
|
+
- `etny-nodenithy` → `v3/build/{securelock,trustedzone,validator}/src/`
|
|
16
|
+
(`get_sgx_report.c` + `sgx_report_napi.c`)
|
|
17
|
+
|
|
18
|
+
## How the `.node` is produced
|
|
19
|
+
|
|
20
|
+
`sgx_report.node` is a **build artifact of the etny-nodenithy pipeline** — its
|
|
21
|
+
Dockerfile compiles `sgx_report_napi.c` (which `#include`s `get_sgx_report.c`)
|
|
22
|
+
into the addon with a single gcc command against node's `node_api.h`. To refresh
|
|
23
|
+
the copy the SDK ships, take the `sgx_report.node` that pipeline produces and
|
|
24
|
+
commit it here as `securelock/src/sgx_report.node`.
|
|
25
|
+
|
|
26
|
+
The JS wrappers `sgx_report.js` (loader) and `self_sign.js` (calls
|
|
27
|
+
`deriveIdentityScalar`) ship as source — they contain no algorithm, only calls
|
|
28
|
+
into the addon.
|
|
29
|
+
|
|
30
|
+
## Why not compile it in the SDK build
|
|
31
|
+
|
|
32
|
+
The securelock Dockerfile bakes `src/` into binary-fs but does **not** compile
|
|
33
|
+
the addon, so the SDK must carry the prebuilt `.node`. `build.mjs` fails fast if
|
|
34
|
+
`src/sgx_report.node` is missing.
|
|
35
|
+
|
|
36
|
+
## Honest scope
|
|
37
|
+
|
|
38
|
+
This hides the SOURCE, not the algorithm: the `.node` still contains the
|
|
39
|
+
executable logic and the enclave image is public. On testnet (no attestation)
|
|
40
|
+
the derived key is reproducible by anyone who runs the image. Key exclusivity
|
|
41
|
+
comes from attestation, not from shipping only the binary.
|
|
@@ -2,34 +2,62 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { TaskStatus } = require('./task_status');
|
|
4
4
|
|
|
5
|
-
// Load
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
5
|
+
// Load EVERY export of ./serverless/backend into the payload's scope. The
|
|
6
|
+
// backend is copied next to this file by ecld-build (and staged the same way
|
|
7
|
+
// by ecld-test locally), so one code path serves both the enclave and local
|
|
8
|
+
// testing. Previously only a function literally named `hello` was reachable
|
|
9
|
+
// (hardcoded destructure); any other exported function threw ReferenceError.
|
|
10
|
+
// A missing backend leaves only ___etny_result___ in scope, which is the
|
|
11
|
+
// stock-enclave behaviour.
|
|
12
|
+
let backendFunctions = {};
|
|
13
|
+
// If the backend fails to load, remember WHY. A silently-swallowed failure
|
|
14
|
+
// (missing npm module, bad require inside backend.js) makes every task die
|
|
15
|
+
// later with a misleading "X is not defined". Only a genuinely absent backend
|
|
16
|
+
// stays silent — that is the stock-enclave configuration.
|
|
17
|
+
let backendImportError = null;
|
|
18
|
+
try {
|
|
19
|
+
backendFunctions = { ...require('./serverless/backend') };
|
|
20
|
+
} catch (e) {
|
|
21
|
+
backendFunctions = {};
|
|
22
|
+
// MODULE_NOT_FOUND messages embed the whole require stack (which always
|
|
23
|
+
// mentions serverless/backend.js), so parse out WHICH module is missing:
|
|
24
|
+
// only a missing backend module itself means "no backend shipped".
|
|
25
|
+
const m = /Cannot find module '([^']+)'/.exec(e.message || '');
|
|
26
|
+
const missingBackendItself =
|
|
27
|
+
e.code === 'MODULE_NOT_FOUND' && m && /serverless[\\/]backend/.test(m[1]);
|
|
28
|
+
if (!missingBackendItself) {
|
|
29
|
+
backendImportError = `${e.name || 'Error'}: ${(e.message || '').split('\n')[0]}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
17
32
|
|
|
18
33
|
function ___etny_result___(data) {
|
|
19
34
|
return [0, data];
|
|
20
35
|
}
|
|
36
|
+
|
|
21
37
|
function executeTask(payload, input) {
|
|
22
|
-
|
|
38
|
+
if (backendImportError !== null) {
|
|
39
|
+
return [
|
|
40
|
+
TaskStatus.IMPORT_ERROR,
|
|
41
|
+
'BACKEND IMPORT ERROR: ' + backendImportError +
|
|
42
|
+
' | The serverless backend failed to load inside the enclave, so none of its' +
|
|
43
|
+
' functions are available. Common causes: a module missing from the enclave' +
|
|
44
|
+
' image dependencies, or a bad require() inside backend.js.'
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
return exec(payload, input, { '___etny_result___': ___etny_result___, ...backendFunctions });
|
|
23
48
|
}
|
|
49
|
+
|
|
24
50
|
function exec(payload, input, globals = null) {
|
|
25
51
|
try {
|
|
26
52
|
if (payload && payload !== "") {
|
|
53
|
+
const scope = globals || {};
|
|
27
54
|
if (input && input !== "") {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
scope['___etny_data_set___'] = input;
|
|
56
|
+
}
|
|
57
|
+
// `with` exposes every backend function and ___etny_data_set___ to
|
|
58
|
+
// the payload by bare name — matching how payloads are written
|
|
59
|
+
// (e.g. `processData(___etny_data_set___)`).
|
|
60
|
+
with (scope) {
|
|
33
61
|
return ___etny_result___(eval(payload));
|
|
34
62
|
}
|
|
35
63
|
} else {
|