@ethernity-cloud/sdk-js 1.5.2 → 1.5.5
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/nodenithy/build/securelock/src/ecld_state.js +44 -3
- package/nodenithy/build/securelock/src/etny_crypto.js +211 -211
- package/nodenithy/build/securelock/src/etny_exec.js +111 -17
- package/nodenithy/build/securelock/src/models.js +275 -275
- package/nodenithy/build/securelock/src/securelock.js.tmpl +586 -586
- package/nodenithy/build/trustedzone/src/etny_exec.js +19 -14
- package/nodenithy/build/validator/src/etny_exec.js +19 -14
- package/nodenithy/run/image_registry.js +1 -1
- package/nodenithy/src/preStart.js +44 -44
- package/package.json +1 -1
- package/pynithy/build/docker-compose.yml +67 -67
- package/pynithy/run/certs/cert.pem +31 -31
- package/pynithy/run/certs/challenge.crt +10 -10
- package/pynithy/run/certs/key.pem +52 -52
- package/pynithy/run/docker-compose-final.yml +59 -59
- package/pynithy/run/docker-compose-swift-stream.yml +27 -27
- package/pynithy/run/docker-compose.yml +55 -55
- package/pynithy/run/image_registry.js +1 -1
- package/nodenithy/run/__pycache__/swift_stream_service.cpython-312.pyc +0 -0
|
@@ -61,6 +61,38 @@ function configure({ identityPriv, swiftStreamService, bucket, contractAddress,
|
|
|
61
61
|
_contractAddress = contractAddress;
|
|
62
62
|
_provider = provider;
|
|
63
63
|
_esrLedger = [];
|
|
64
|
+
_taskLedger = {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Task-scoped ledger of every state key this execution touched, recorded by
|
|
68
|
+
// get()/commit(). ecldResult() snapshots it to attach fresh state to the task
|
|
69
|
+
// result, so callers (and the runner's state cache) get current state in the
|
|
70
|
+
// same result -- no separate read task needed. Reset per task in configure().
|
|
71
|
+
let _taskLedger = {};
|
|
72
|
+
|
|
73
|
+
function ledgerRecord(key, version, cid, state) {
|
|
74
|
+
_taskLedger[key] = { key, version: Number(version), cid, state };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The `esr` attachment for ecldResult: wallet + entries. Synchronous by
|
|
79
|
+
* design (the securelock consumes results synchronously): entries come from
|
|
80
|
+
* the in-memory ledger of keys this task already touched. To attach a key the
|
|
81
|
+
* task did not touch, use the async esrFetch() helper, which awaits the reads
|
|
82
|
+
* (populating the ledger) before building the result.
|
|
83
|
+
*/
|
|
84
|
+
function ledgerSnapshot(includeState = true, keys = null) {
|
|
85
|
+
const reg = new StateRegistry();
|
|
86
|
+
const wanted = keys ? keys : Object.keys(_taskLedger);
|
|
87
|
+
const entries = [];
|
|
88
|
+
for (const k of wanted) {
|
|
89
|
+
const e = _taskLedger[k];
|
|
90
|
+
if (!e) continue;
|
|
91
|
+
const entry = { ...e };
|
|
92
|
+
if (!includeState) delete entry.state;
|
|
93
|
+
entries.push(entry);
|
|
94
|
+
}
|
|
95
|
+
return { wallet: reg.walletAddress, entries };
|
|
64
96
|
}
|
|
65
97
|
|
|
66
98
|
function keccak256(buf) {
|
|
@@ -216,7 +248,10 @@ class StateRegistry {
|
|
|
216
248
|
*/
|
|
217
249
|
async get(key, fallback = {}) {
|
|
218
250
|
const [cid, version] = await this._contract().getState(this.walletAddress, keyHash(key));
|
|
219
|
-
if (!version || !cid)
|
|
251
|
+
if (!version || !cid) {
|
|
252
|
+
ledgerRecord(key, 0, null, fallback);
|
|
253
|
+
return fallback;
|
|
254
|
+
}
|
|
220
255
|
if (!looksLikeCID(cid)) {
|
|
221
256
|
throw new Error(
|
|
222
257
|
`ESR entry for '${key}' holds a pointer that is not a CID (${cid.slice(0, 32)}…). ` +
|
|
@@ -224,7 +259,9 @@ class StateRegistry {
|
|
|
224
259
|
);
|
|
225
260
|
}
|
|
226
261
|
const blob = await this._fetch(key, cid);
|
|
227
|
-
|
|
262
|
+
const state = JSON.parse(decrypt(blob).toString('utf8'));
|
|
263
|
+
ledgerRecord(key, version, cid, state);
|
|
264
|
+
return state;
|
|
228
265
|
}
|
|
229
266
|
|
|
230
267
|
/**
|
|
@@ -255,6 +292,9 @@ class StateRegistry {
|
|
|
255
292
|
try {
|
|
256
293
|
// eslint-disable-next-line no-await-in-loop
|
|
257
294
|
await this._sendCommit(keyHash(key), cid, currentVersion);
|
|
295
|
+
// Record the POST-commit values: this is what the chain will show
|
|
296
|
+
// once the node's relay lands (version increments by one).
|
|
297
|
+
ledgerRecord(key, currentVersion + 1, cid, newState);
|
|
258
298
|
return newState;
|
|
259
299
|
} catch (e) {
|
|
260
300
|
const msg = String(e && e.message ? e.message : e);
|
|
@@ -340,5 +380,6 @@ module.exports = {
|
|
|
340
380
|
StateRegistry,
|
|
341
381
|
configure,
|
|
342
382
|
cidv1Raw,
|
|
343
|
-
looksLikeCID
|
|
383
|
+
looksLikeCID,
|
|
384
|
+
ledgerSnapshot
|
|
344
385
|
};
|
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const elliptic = require('elliptic');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
|
-
const rs = require('jsrsasign');
|
|
5
|
-
const ECKey = require('ec-key');
|
|
6
|
-
const curve = new elliptic.ec('p384');
|
|
7
|
-
const ascii85 = require('ascii85');
|
|
8
|
-
const ethSigUtil = require('@metamask/eth-sig-util');
|
|
9
|
-
const {createHash} = require('crypto');
|
|
10
|
-
|
|
11
|
-
function sha256(content) {
|
|
12
|
-
return createHash('sha256').update(content).digest('hex');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function fileChecksum(file) {
|
|
16
|
-
const fileContent = fs.readFileSync(file, 'utf8');
|
|
17
|
-
return sha256(fileContent);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function fileChecksumFromSwiftStream(swiftStreamClient, bucketName, fileName) {
|
|
21
|
-
const fileContent = await swiftStreamClient.getFileContent(bucketName, fileName);
|
|
22
|
-
//console.log(fileContent);
|
|
23
|
-
return sha256(fileContent);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const getPEMFromPublicFile = (publicKeyPath) => {
|
|
27
|
-
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
|
|
28
|
-
return crypto.createPublicKey(publicKey).export({type: 'spki', format: 'pem'});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const getPEMFromPublic = (publicKey) => {
|
|
32
|
-
return crypto.createPublicKey(publicKey).export({type: 'spki', format: 'pem'});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const loadCertificate = (certificate) => {
|
|
36
|
-
//console.log(certificate)
|
|
37
|
-
const publicKeyPEM = getPEMFromPublic(certificate);
|
|
38
|
-
const key = new ECKey(publicKeyPEM, 'pem');
|
|
39
|
-
//console.log(key);
|
|
40
|
-
return key;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Accepts EITHER a path to a PEM file or the PEM text itself.
|
|
44
|
-
//
|
|
45
|
-
// mainnet keeps passing a path: CAS injects the key into key_file after
|
|
46
|
-
// attestation, and that flow is unchanged. On testnet the enclave self-derives
|
|
47
|
-
// its key and now holds it only in memory (never writes it), so it passes the
|
|
48
|
-
// PEM straight through -- nothing about the key ever reaches the filesystem.
|
|
49
|
-
// Detection is on the PEM header, not on file existence, so a path that does
|
|
50
|
-
// not exist still fails loudly instead of being mistaken for key material.
|
|
51
|
-
function getPrivatePointFromPrivateKey(privateKeyOrFile) {
|
|
52
|
-
const value = String(privateKeyOrFile || '');
|
|
53
|
-
const privateKey = value.includes('-----BEGIN')
|
|
54
|
-
? value
|
|
55
|
-
: fs.readFileSync(value, 'utf8');
|
|
56
|
-
let privateKeyHex = rs.KEYUTIL.getKey(privateKey).prvKeyHex;
|
|
57
|
-
const privateKeyObject = curve.keyFromPrivate(privateKeyHex, 'hex');
|
|
58
|
-
return privateKeyObject.getPrivate();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const generatePublicKey = (x, y) => {
|
|
62
|
-
const pub = {x: x, y};
|
|
63
|
-
return curve.keyFromPublic(pub, 'hex');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function getPublicPointFromCoords(x, y) {
|
|
67
|
-
const publicKey = generatePublicKey(x, y);
|
|
68
|
-
return publicKey.getPublic();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function ecc_point_to_256_bit_key(point) {
|
|
72
|
-
const value = point.getX().toString() + point.getY().toString();
|
|
73
|
-
const hash = crypto.createHash('sha256');
|
|
74
|
-
hash.update(value);
|
|
75
|
-
return hash.digest();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function encryptedDataToBase64Json(encryptedMsg) {
|
|
79
|
-
const key = curve.keyFromPublic(encryptedMsg.cipherTextPublicKey, 'hex');
|
|
80
|
-
const jsonObj = {
|
|
81
|
-
ciphertext: encryptedMsg.ciphertext.toString('hex'),
|
|
82
|
-
nonce: encryptedMsg.nonce.toString('hex'),
|
|
83
|
-
authTag: encryptedMsg.authTag.toString('hex'),
|
|
84
|
-
x: key.getPublic().getX(),
|
|
85
|
-
y: key.getPublic().getY()
|
|
86
|
-
};
|
|
87
|
-
const jsonString = JSON.stringify(jsonObj);
|
|
88
|
-
const binaryData = Buffer.from(jsonString, 'utf8');
|
|
89
|
-
return binaryData.toString('base64');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function encryptedDataFromBase64Json(base64Data) {
|
|
93
|
-
// decode the base64-encoded string back to a JSON string
|
|
94
|
-
const decodedJsonData = Buffer.from(base64Data, 'base64').toString('utf8');
|
|
95
|
-
return JSON.parse(decodedJsonData);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
function encrypt(certificate, message) {
|
|
100
|
-
const key = loadCertificate(certificate);
|
|
101
|
-
return encrypt_ecc(message, generatePublicKey(key.x, key.y));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function encrypt_ecc(msg, publicKey) {
|
|
105
|
-
const cipherTextPrivateKey = crypto.randomBytes(32);
|
|
106
|
-
const sharedEccKey = publicKey.getPublic().mul(cipherTextPrivateKey);
|
|
107
|
-
const secretKey = ecc_point_to_256_bit_key(sharedEccKey);
|
|
108
|
-
const iv = crypto.randomBytes(16);
|
|
109
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', secretKey, iv);
|
|
110
|
-
let encrypted = cipher.update(msg, 'utf8', 'hex');
|
|
111
|
-
encrypted += cipher.final('hex');
|
|
112
|
-
const cipherTextPublicKey = curve.g.mul(cipherTextPrivateKey);
|
|
113
|
-
return {
|
|
114
|
-
ciphertext: encrypted,
|
|
115
|
-
secretKey: secretKey,
|
|
116
|
-
nonce: iv,
|
|
117
|
-
authTag: cipher.getAuthTag().toString("hex"),
|
|
118
|
-
cipherTextPublicKey: cipherTextPublicKey.encode('hex')
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function decrypt(privateKeyFile, encryptedMsg) {
|
|
123
|
-
//console.log(privateKeyFile);
|
|
124
|
-
const privatePoint = getPrivatePointFromPrivateKey(privateKeyFile);
|
|
125
|
-
return decrypt_ecc(encryptedMsg, privatePoint);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function decrypt_ecc(encryptedMsg, privatePoint) {
|
|
129
|
-
try {
|
|
130
|
-
const {ciphertext, nonce, authTag, x, y} = encryptedMsg;
|
|
131
|
-
const publicPoint = getPublicPointFromCoords(`${x}`, `${y}`);
|
|
132
|
-
const sharedEccKey = publicPoint.mul(privatePoint);
|
|
133
|
-
const secretKey = ecc_point_to_256_bit_key(sharedEccKey);
|
|
134
|
-
|
|
135
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, Buffer.from(nonce, 'hex'));
|
|
136
|
-
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
|
|
137
|
-
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
|
|
138
|
-
decrypted += decipher.final('utf8');
|
|
139
|
-
return decrypted;
|
|
140
|
-
} catch (e) {
|
|
141
|
-
console.log(`Error while decrypting ECC:`, e.message);
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function hexToBytes(hexStr) {
|
|
147
|
-
if (hexStr.startsWith("0x")) {
|
|
148
|
-
hexStr = hexStr.slice(2);
|
|
149
|
-
}
|
|
150
|
-
return Buffer.from(hexStr, "hex");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function encryptWithPublicKey(publicKey, data) {
|
|
154
|
-
// Returned object contains 4 properties: version, ephemPublicKey, nonce, ciphertext
|
|
155
|
-
// Each contains data encoded using base64, version is always the same string
|
|
156
|
-
const enc = ethSigUtil.encrypt({
|
|
157
|
-
publicKey: hexToBytes(publicKey).toString('base64'),
|
|
158
|
-
data: ascii85.encode(Buffer.from(data)).toString(),
|
|
159
|
-
version: 'x25519-xsalsa20-poly1305',
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// We want to store the data in smart contract, therefore we concatenate them
|
|
163
|
-
// into single Buffer
|
|
164
|
-
const buf = Buffer.concat([
|
|
165
|
-
Buffer.from(enc.ephemPublicKey, 'base64'),
|
|
166
|
-
Buffer.from(enc.nonce, 'base64'),
|
|
167
|
-
Buffer.from(enc.ciphertext, 'base64'),
|
|
168
|
-
]);
|
|
169
|
-
|
|
170
|
-
// In smart contract we are using `bytes[112]` variable (fixed size byte array)
|
|
171
|
-
// you might need to use `bytes` type for dynamic sized array
|
|
172
|
-
// We are also using ethers.js which requires type `number[]` when passing data
|
|
173
|
-
// for argument of type `bytes` to the smart contract function
|
|
174
|
-
// Next line just converts the buffer to `number[]` required by contract function
|
|
175
|
-
// THIS LINE IS USED IN OUR ORIGINAL CODE:
|
|
176
|
-
// return buf.toJSON().data;
|
|
177
|
-
|
|
178
|
-
// Return just the Buffer to make the function directly compatible with decryptData function
|
|
179
|
-
return buf;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// (async () => {
|
|
183
|
-
// try {
|
|
184
|
-
// const message = 'testabc1';
|
|
185
|
-
// console.log('initial message:', message);
|
|
186
|
-
// const publicKeyPEM = getPEMFromPublic('./app/cert1-ca1-clean.crt');
|
|
187
|
-
// const ECKey = require('ec-key');
|
|
188
|
-
// const key = new ECKey(publicKeyPEM, 'pem');
|
|
189
|
-
// const encrypted_message = encrypt(key.x, key.y, message);
|
|
190
|
-
// console.log(encrypted_message);
|
|
191
|
-
// console.log(encryptedDataToBase64Json(encrypted_message));
|
|
192
|
-
// //
|
|
193
|
-
// // const encrypted_message = 'eyJjaXBoZXJ0ZXh0IjoiZTYzZTcyNDY0MWY4YWRmN2QyNjNmMTI0ZGYxZTdlYzZmNGM5NGU1NCIsIm5vbmNlIjoiYmE0YWU5YjMyOWFhZWRiM2ViZTM5MGU4YTY3YWFiNmMiLCJhdXRoVGFnIjoiOGU2NmI4NTVmNTE3N2E0NzZlMjkwNTE3Y2Q2ZDE3ZmUiLCJ4IjoiN2E1MjhhNGI5YzAzODM4Mjc0ZmFiYzVlM2RiOWQ2ZWFiNzBiZTFiMDA3YWJiOTcyYzI0YmFhNGE1ZmI4OGJjNTVmNDVlNGNkMjZjY2NhNTFhOTk3OGZkNDRhZmE1YjQ0IiwieSI6IjE5M2NjMWYwMzVmOGMxZjEzNGU4MTEzOGIyOTRiODBiZWQyYjkyMzdkZmIyOTMyOGRjOTg1MzlmMjhkNjIzMTJiYTQ2OTU2NDFkYjIyNjk0ZWUyNGFjZjQ4MjBiZDlhMSJ9';
|
|
194
|
-
// // const messageDecrypted = decrypt('./app/cert1-ca1-clean.key', encryptedDataToBase64Json(encrypted_message));
|
|
195
|
-
// // console.log('decrypted message:', messageDecrypted);
|
|
196
|
-
// } catch (e) {
|
|
197
|
-
// console.log(e);
|
|
198
|
-
// }
|
|
199
|
-
//
|
|
200
|
-
// })();
|
|
201
|
-
|
|
202
|
-
module.exports = {
|
|
203
|
-
encryptedDataFromBase64Json,
|
|
204
|
-
encryptedDataToBase64Json,
|
|
205
|
-
encryptWithPublicKey,
|
|
206
|
-
decrypt,
|
|
207
|
-
encrypt,
|
|
208
|
-
sha256,
|
|
209
|
-
fileChecksum,
|
|
210
|
-
fileChecksumFromSwiftStream
|
|
211
|
-
};
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const elliptic = require('elliptic');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const rs = require('jsrsasign');
|
|
5
|
+
const ECKey = require('ec-key');
|
|
6
|
+
const curve = new elliptic.ec('p384');
|
|
7
|
+
const ascii85 = require('ascii85');
|
|
8
|
+
const ethSigUtil = require('@metamask/eth-sig-util');
|
|
9
|
+
const {createHash} = require('crypto');
|
|
10
|
+
|
|
11
|
+
function sha256(content) {
|
|
12
|
+
return createHash('sha256').update(content).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fileChecksum(file) {
|
|
16
|
+
const fileContent = fs.readFileSync(file, 'utf8');
|
|
17
|
+
return sha256(fileContent);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function fileChecksumFromSwiftStream(swiftStreamClient, bucketName, fileName) {
|
|
21
|
+
const fileContent = await swiftStreamClient.getFileContent(bucketName, fileName);
|
|
22
|
+
//console.log(fileContent);
|
|
23
|
+
return sha256(fileContent);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getPEMFromPublicFile = (publicKeyPath) => {
|
|
27
|
+
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
|
|
28
|
+
return crypto.createPublicKey(publicKey).export({type: 'spki', format: 'pem'});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getPEMFromPublic = (publicKey) => {
|
|
32
|
+
return crypto.createPublicKey(publicKey).export({type: 'spki', format: 'pem'});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const loadCertificate = (certificate) => {
|
|
36
|
+
//console.log(certificate)
|
|
37
|
+
const publicKeyPEM = getPEMFromPublic(certificate);
|
|
38
|
+
const key = new ECKey(publicKeyPEM, 'pem');
|
|
39
|
+
//console.log(key);
|
|
40
|
+
return key;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Accepts EITHER a path to a PEM file or the PEM text itself.
|
|
44
|
+
//
|
|
45
|
+
// mainnet keeps passing a path: CAS injects the key into key_file after
|
|
46
|
+
// attestation, and that flow is unchanged. On testnet the enclave self-derives
|
|
47
|
+
// its key and now holds it only in memory (never writes it), so it passes the
|
|
48
|
+
// PEM straight through -- nothing about the key ever reaches the filesystem.
|
|
49
|
+
// Detection is on the PEM header, not on file existence, so a path that does
|
|
50
|
+
// not exist still fails loudly instead of being mistaken for key material.
|
|
51
|
+
function getPrivatePointFromPrivateKey(privateKeyOrFile) {
|
|
52
|
+
const value = String(privateKeyOrFile || '');
|
|
53
|
+
const privateKey = value.includes('-----BEGIN')
|
|
54
|
+
? value
|
|
55
|
+
: fs.readFileSync(value, 'utf8');
|
|
56
|
+
let privateKeyHex = rs.KEYUTIL.getKey(privateKey).prvKeyHex;
|
|
57
|
+
const privateKeyObject = curve.keyFromPrivate(privateKeyHex, 'hex');
|
|
58
|
+
return privateKeyObject.getPrivate();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const generatePublicKey = (x, y) => {
|
|
62
|
+
const pub = {x: x, y};
|
|
63
|
+
return curve.keyFromPublic(pub, 'hex');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getPublicPointFromCoords(x, y) {
|
|
67
|
+
const publicKey = generatePublicKey(x, y);
|
|
68
|
+
return publicKey.getPublic();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ecc_point_to_256_bit_key(point) {
|
|
72
|
+
const value = point.getX().toString() + point.getY().toString();
|
|
73
|
+
const hash = crypto.createHash('sha256');
|
|
74
|
+
hash.update(value);
|
|
75
|
+
return hash.digest();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function encryptedDataToBase64Json(encryptedMsg) {
|
|
79
|
+
const key = curve.keyFromPublic(encryptedMsg.cipherTextPublicKey, 'hex');
|
|
80
|
+
const jsonObj = {
|
|
81
|
+
ciphertext: encryptedMsg.ciphertext.toString('hex'),
|
|
82
|
+
nonce: encryptedMsg.nonce.toString('hex'),
|
|
83
|
+
authTag: encryptedMsg.authTag.toString('hex'),
|
|
84
|
+
x: key.getPublic().getX(),
|
|
85
|
+
y: key.getPublic().getY()
|
|
86
|
+
};
|
|
87
|
+
const jsonString = JSON.stringify(jsonObj);
|
|
88
|
+
const binaryData = Buffer.from(jsonString, 'utf8');
|
|
89
|
+
return binaryData.toString('base64');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function encryptedDataFromBase64Json(base64Data) {
|
|
93
|
+
// decode the base64-encoded string back to a JSON string
|
|
94
|
+
const decodedJsonData = Buffer.from(base64Data, 'base64').toString('utf8');
|
|
95
|
+
return JSON.parse(decodedJsonData);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
function encrypt(certificate, message) {
|
|
100
|
+
const key = loadCertificate(certificate);
|
|
101
|
+
return encrypt_ecc(message, generatePublicKey(key.x, key.y));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function encrypt_ecc(msg, publicKey) {
|
|
105
|
+
const cipherTextPrivateKey = crypto.randomBytes(32);
|
|
106
|
+
const sharedEccKey = publicKey.getPublic().mul(cipherTextPrivateKey);
|
|
107
|
+
const secretKey = ecc_point_to_256_bit_key(sharedEccKey);
|
|
108
|
+
const iv = crypto.randomBytes(16);
|
|
109
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', secretKey, iv);
|
|
110
|
+
let encrypted = cipher.update(msg, 'utf8', 'hex');
|
|
111
|
+
encrypted += cipher.final('hex');
|
|
112
|
+
const cipherTextPublicKey = curve.g.mul(cipherTextPrivateKey);
|
|
113
|
+
return {
|
|
114
|
+
ciphertext: encrypted,
|
|
115
|
+
secretKey: secretKey,
|
|
116
|
+
nonce: iv,
|
|
117
|
+
authTag: cipher.getAuthTag().toString("hex"),
|
|
118
|
+
cipherTextPublicKey: cipherTextPublicKey.encode('hex')
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function decrypt(privateKeyFile, encryptedMsg) {
|
|
123
|
+
//console.log(privateKeyFile);
|
|
124
|
+
const privatePoint = getPrivatePointFromPrivateKey(privateKeyFile);
|
|
125
|
+
return decrypt_ecc(encryptedMsg, privatePoint);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function decrypt_ecc(encryptedMsg, privatePoint) {
|
|
129
|
+
try {
|
|
130
|
+
const {ciphertext, nonce, authTag, x, y} = encryptedMsg;
|
|
131
|
+
const publicPoint = getPublicPointFromCoords(`${x}`, `${y}`);
|
|
132
|
+
const sharedEccKey = publicPoint.mul(privatePoint);
|
|
133
|
+
const secretKey = ecc_point_to_256_bit_key(sharedEccKey);
|
|
134
|
+
|
|
135
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, Buffer.from(nonce, 'hex'));
|
|
136
|
+
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
|
|
137
|
+
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
|
|
138
|
+
decrypted += decipher.final('utf8');
|
|
139
|
+
return decrypted;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.log(`Error while decrypting ECC:`, e.message);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function hexToBytes(hexStr) {
|
|
147
|
+
if (hexStr.startsWith("0x")) {
|
|
148
|
+
hexStr = hexStr.slice(2);
|
|
149
|
+
}
|
|
150
|
+
return Buffer.from(hexStr, "hex");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function encryptWithPublicKey(publicKey, data) {
|
|
154
|
+
// Returned object contains 4 properties: version, ephemPublicKey, nonce, ciphertext
|
|
155
|
+
// Each contains data encoded using base64, version is always the same string
|
|
156
|
+
const enc = ethSigUtil.encrypt({
|
|
157
|
+
publicKey: hexToBytes(publicKey).toString('base64'),
|
|
158
|
+
data: ascii85.encode(Buffer.from(data)).toString(),
|
|
159
|
+
version: 'x25519-xsalsa20-poly1305',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// We want to store the data in smart contract, therefore we concatenate them
|
|
163
|
+
// into single Buffer
|
|
164
|
+
const buf = Buffer.concat([
|
|
165
|
+
Buffer.from(enc.ephemPublicKey, 'base64'),
|
|
166
|
+
Buffer.from(enc.nonce, 'base64'),
|
|
167
|
+
Buffer.from(enc.ciphertext, 'base64'),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
// In smart contract we are using `bytes[112]` variable (fixed size byte array)
|
|
171
|
+
// you might need to use `bytes` type for dynamic sized array
|
|
172
|
+
// We are also using ethers.js which requires type `number[]` when passing data
|
|
173
|
+
// for argument of type `bytes` to the smart contract function
|
|
174
|
+
// Next line just converts the buffer to `number[]` required by contract function
|
|
175
|
+
// THIS LINE IS USED IN OUR ORIGINAL CODE:
|
|
176
|
+
// return buf.toJSON().data;
|
|
177
|
+
|
|
178
|
+
// Return just the Buffer to make the function directly compatible with decryptData function
|
|
179
|
+
return buf;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// (async () => {
|
|
183
|
+
// try {
|
|
184
|
+
// const message = 'testabc1';
|
|
185
|
+
// console.log('initial message:', message);
|
|
186
|
+
// const publicKeyPEM = getPEMFromPublic('./app/cert1-ca1-clean.crt');
|
|
187
|
+
// const ECKey = require('ec-key');
|
|
188
|
+
// const key = new ECKey(publicKeyPEM, 'pem');
|
|
189
|
+
// const encrypted_message = encrypt(key.x, key.y, message);
|
|
190
|
+
// console.log(encrypted_message);
|
|
191
|
+
// console.log(encryptedDataToBase64Json(encrypted_message));
|
|
192
|
+
// //
|
|
193
|
+
// // const encrypted_message = 'eyJjaXBoZXJ0ZXh0IjoiZTYzZTcyNDY0MWY4YWRmN2QyNjNmMTI0ZGYxZTdlYzZmNGM5NGU1NCIsIm5vbmNlIjoiYmE0YWU5YjMyOWFhZWRiM2ViZTM5MGU4YTY3YWFiNmMiLCJhdXRoVGFnIjoiOGU2NmI4NTVmNTE3N2E0NzZlMjkwNTE3Y2Q2ZDE3ZmUiLCJ4IjoiN2E1MjhhNGI5YzAzODM4Mjc0ZmFiYzVlM2RiOWQ2ZWFiNzBiZTFiMDA3YWJiOTcyYzI0YmFhNGE1ZmI4OGJjNTVmNDVlNGNkMjZjY2NhNTFhOTk3OGZkNDRhZmE1YjQ0IiwieSI6IjE5M2NjMWYwMzVmOGMxZjEzNGU4MTEzOGIyOTRiODBiZWQyYjkyMzdkZmIyOTMyOGRjOTg1MzlmMjhkNjIzMTJiYTQ2OTU2NDFkYjIyNjk0ZWUyNGFjZjQ4MjBiZDlhMSJ9';
|
|
194
|
+
// // const messageDecrypted = decrypt('./app/cert1-ca1-clean.key', encryptedDataToBase64Json(encrypted_message));
|
|
195
|
+
// // console.log('decrypted message:', messageDecrypted);
|
|
196
|
+
// } catch (e) {
|
|
197
|
+
// console.log(e);
|
|
198
|
+
// }
|
|
199
|
+
//
|
|
200
|
+
// })();
|
|
201
|
+
|
|
202
|
+
module.exports = {
|
|
203
|
+
encryptedDataFromBase64Json,
|
|
204
|
+
encryptedDataToBase64Json,
|
|
205
|
+
encryptWithPublicKey,
|
|
206
|
+
decrypt,
|
|
207
|
+
encrypt,
|
|
208
|
+
sha256,
|
|
209
|
+
fileChecksum,
|
|
210
|
+
fileChecksumFromSwiftStream
|
|
211
|
+
};
|
|
@@ -30,8 +30,84 @@ try {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Automatic encoding for task results (single code path for all results):
|
|
35
|
+
* JSON-serializable values ride as JSON, strings as text, bytes-like as
|
|
36
|
+
* base64. Anything else is JSON-stringified with a String() fallback -- a
|
|
37
|
+
* task result never fails on encoding.
|
|
38
|
+
*/
|
|
39
|
+
function encodeResultData(data) {
|
|
40
|
+
if (Buffer.isBuffer(data) || data instanceof Uint8Array) {
|
|
41
|
+
return ['base64', Buffer.from(data).toString('base64')];
|
|
42
|
+
}
|
|
43
|
+
if (typeof data === 'string') {
|
|
44
|
+
return ['text', data];
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
JSON.stringify(data);
|
|
48
|
+
return ['json', data === undefined ? null : data];
|
|
49
|
+
} catch (e) {
|
|
50
|
+
try {
|
|
51
|
+
return ['json', JSON.parse(JSON.stringify(data, (k, v) =>
|
|
52
|
+
typeof v === 'bigint' || typeof v === 'function' ? String(v) : v))];
|
|
53
|
+
} catch (e2) {
|
|
54
|
+
return ['text', String(data)];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Return a task result (THE way to end a task).
|
|
61
|
+
*
|
|
62
|
+
* Builds the structured result envelope:
|
|
63
|
+
* { ecld: 1, type: 'json'|'text'|'base64', data: ..., esr: ... }
|
|
64
|
+
*
|
|
65
|
+
* - `data` is encoded automatically (see encodeResultData).
|
|
66
|
+
* - `state: true` (default) attaches the ESR state of every key this task
|
|
67
|
+
* touched; `state: 'meta'` attaches {key, version, cid} without the state
|
|
68
|
+
* payload; `state: false` attaches nothing.
|
|
69
|
+
* - `keys: [...]` restricts the attachment to those keys (already-touched
|
|
70
|
+
* only -- to attach an untouched key, use the async esrFetch helper).
|
|
71
|
+
*
|
|
72
|
+
* ESR attachment is best-effort: an ESR-disabled build (or any attachment
|
|
73
|
+
* error) yields esr: null, never a failed task.
|
|
74
|
+
*/
|
|
75
|
+
function ecldResult(data = null, { state = true, keys = null } = {}) {
|
|
76
|
+
const [type, encoded] = encodeResultData(data);
|
|
77
|
+
const envelope = { ecld: 1, type, data: encoded, esr: null };
|
|
78
|
+
if (state || keys) {
|
|
79
|
+
try {
|
|
80
|
+
// eslint-disable-next-line global-require
|
|
81
|
+
const ecldState = require('./ecld_state');
|
|
82
|
+
envelope.esr = ecldState.ledgerSnapshot(state === true, keys);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
envelope.esr = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return [0, JSON.stringify(envelope)];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Legacy alias: same builder, ESR attachment off -- today's behavior. */
|
|
33
91
|
function ___etny_result___(data) {
|
|
34
|
-
return
|
|
92
|
+
return ecldResult(data, { state: false });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Standard state-fetch task body: awaits the reads (populating the task
|
|
97
|
+
* ledger), then attaches them. The runner's cache-gated read submits this on
|
|
98
|
+
* a cache miss: `await esrFetch('profile-7')`.
|
|
99
|
+
*/
|
|
100
|
+
async function esrFetch(...keys) {
|
|
101
|
+
try {
|
|
102
|
+
// eslint-disable-next-line global-require
|
|
103
|
+
const { StateRegistry } = require('./ecld_state');
|
|
104
|
+
const reg = new StateRegistry();
|
|
105
|
+
for (const k of keys) {
|
|
106
|
+
// eslint-disable-next-line no-await-in-loop
|
|
107
|
+
await reg.get(k);
|
|
108
|
+
}
|
|
109
|
+
} catch (e) { /* ESR-disabled build: esr stays null */ }
|
|
110
|
+
return ecldResult(null, { keys: keys.length ? keys : null });
|
|
35
111
|
}
|
|
36
112
|
|
|
37
113
|
// Names of *_ADDRESS-style enclave config vars that are set but EMPTY.
|
|
@@ -66,7 +142,12 @@ function executeTask(payload, input) {
|
|
|
66
142
|
' image dependencies, or a bad require() inside backend.js.'
|
|
67
143
|
];
|
|
68
144
|
}
|
|
69
|
-
return exec(payload, input, {
|
|
145
|
+
return exec(payload, input, {
|
|
146
|
+
'___etny_result___': ___etny_result___, // legacy alias (no ESR)
|
|
147
|
+
'ecldResult': ecldResult, // the result API
|
|
148
|
+
'esrFetch': esrFetch, // standard state-fetch task
|
|
149
|
+
...backendFunctions,
|
|
150
|
+
});
|
|
70
151
|
}
|
|
71
152
|
|
|
72
153
|
function exec(payload, input, globals = null) {
|
|
@@ -80,7 +161,15 @@ function exec(payload, input, globals = null) {
|
|
|
80
161
|
// the payload by bare name — matching how payloads are written
|
|
81
162
|
// (e.g. `processData(___etny_data_set___)`).
|
|
82
163
|
with (scope) {
|
|
83
|
-
|
|
164
|
+
const value = eval(payload);
|
|
165
|
+
// A payload that called ecldResult/___etny_result___ itself
|
|
166
|
+
// already holds the finished [0, envelopeJson] tuple -- pass
|
|
167
|
+
// it through instead of wrapping the result a second time.
|
|
168
|
+
if (Array.isArray(value) && value.length === 2 &&
|
|
169
|
+
value[0] === 0 && typeof value[1] === 'string') {
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
return ___etny_result___(value);
|
|
84
173
|
}
|
|
85
174
|
} else {
|
|
86
175
|
return [TaskStatus.PAYLOAD_NOT_DEFINED, 'Could not find the source file to execute'];
|
|
@@ -88,24 +177,29 @@ function exec(payload, input, globals = null) {
|
|
|
88
177
|
|
|
89
178
|
// return [TaskStatus.SUCCESS, 'TASK EXECUTED SUCCESSFULLY'];
|
|
90
179
|
} catch (error) {
|
|
180
|
+
// A task returns its result by RETURNING (via ___etny_result___), not by
|
|
181
|
+
// throwing, so anything caught here is a genuine failure. In every branch
|
|
182
|
+
// deliver the FULL stack trace to the data owner AS THE RESULT (visible in
|
|
183
|
+
// their runner output) and never crash the enclave -- always return a
|
|
184
|
+
// normal [code, data] tuple so the trustedzone stops waiting. Honor the
|
|
185
|
+
// embedded-result SUCCESS (code 0) case first.
|
|
186
|
+
try {
|
|
187
|
+
if (error && error.args && error.args[0] && error.args[0][0] === 0) {
|
|
188
|
+
return [TaskStatus.SUCCESS, error.args[0][1]];
|
|
189
|
+
}
|
|
190
|
+
} catch (e) { /* fall through to failure reporting */ }
|
|
191
|
+
|
|
192
|
+
const detail = (error && (error.stack || error.message)) || String(error);
|
|
193
|
+
console.error('Error in payload execution -- full stack follows:\n' + detail);
|
|
194
|
+
|
|
91
195
|
if (error instanceof ReferenceError) {
|
|
92
|
-
return [TaskStatus.SYSTEM_ERROR,
|
|
196
|
+
return [TaskStatus.SYSTEM_ERROR, 'SYSTEM_ERROR (ReferenceError): ' + detail];
|
|
93
197
|
} else if (error instanceof EvalError) {
|
|
94
|
-
return [TaskStatus.KEY_ERROR,
|
|
198
|
+
return [TaskStatus.KEY_ERROR, 'KEY_ERROR (EvalError): ' + detail];
|
|
95
199
|
} else if (error instanceof SyntaxError) {
|
|
96
|
-
return [TaskStatus.SYNTAX_WARNING,
|
|
97
|
-
} else if (error instanceof Error) {
|
|
98
|
-
try {
|
|
99
|
-
if (error.args[0][0] === 0) {
|
|
100
|
-
return [TaskStatus.SUCCESS, error.args[0][1]];
|
|
101
|
-
} else {
|
|
102
|
-
return [TaskStatus.BASE_EXCEPTION, error.args[0]];
|
|
103
|
-
}
|
|
104
|
-
} catch (e) {
|
|
105
|
-
return [TaskStatus.BASE_EXCEPTION, error.message];
|
|
106
|
-
}
|
|
200
|
+
return [TaskStatus.SYNTAX_WARNING, 'SYNTAX_WARNING (SyntaxError): ' + detail];
|
|
107
201
|
} else {
|
|
108
|
-
return [TaskStatus.BASE_EXCEPTION,
|
|
202
|
+
return [TaskStatus.BASE_EXCEPTION, 'BASE_EXCEPTION: ' + detail];
|
|
109
203
|
}
|
|
110
204
|
}
|
|
111
205
|
}
|