evernode-js-client 0.5.13 → 0.5.14
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 +3 -25
- package/index.js +15877 -0
- package/package.json +1 -10
- package/.eslintrc.json +0 -14
- package/LICENSE +0 -21
- package/clean-pkg.sh +0 -4
- package/npm-readme.md +0 -4
- package/remove-versions.sh +0 -10
- package/src/clients/base-evernode-client.js +0 -610
- package/src/clients/host-client.js +0 -560
- package/src/clients/registry-client.js +0 -54
- package/src/clients/tenant-client.js +0 -276
- package/src/defaults.js +0 -21
- package/src/eccrypto.js +0 -258
- package/src/encryption-helper.js +0 -96
- package/src/event-emitter.js +0 -45
- package/src/evernode-common.js +0 -113
- package/src/evernode-helpers.js +0 -45
- package/src/firestore/firestore-handler.js +0 -309
- package/src/index.js +0 -37
- package/src/state-helpers.js +0 -396
- package/src/transaction-helper.js +0 -62
- package/src/util-helpers.js +0 -50
- package/src/xfl-helpers.js +0 -130
- package/src/xrpl-account.js +0 -515
- package/src/xrpl-api.js +0 -301
- package/src/xrpl-common.js +0 -17
- package/test/package-lock.json +0 -884
- package/test/package.json +0 -9
- package/test/test.js +0 -409
@@ -1,276 +0,0 @@
|
|
1
|
-
const { BaseEvernodeClient } = require('./base-evernode-client');
|
2
|
-
const { EvernodeEvents, MemoFormats, MemoTypes, ErrorCodes, ErrorReasons, EvernodeConstants } = require('../evernode-common');
|
3
|
-
const { EncryptionHelper } = require('../encryption-helper');
|
4
|
-
const { XrplAccount } = require('../xrpl-account');
|
5
|
-
const { UtilHelpers } = require('../util-helpers');
|
6
|
-
const { Buffer } = require('buffer');
|
7
|
-
const codec = require('ripple-address-codec');
|
8
|
-
const { EvernodeHelpers } = require('../evernode-helpers');
|
9
|
-
const { TransactionHelper } = require('../transaction-helper');
|
10
|
-
|
11
|
-
const DEFAULT_WAIT_TIMEOUT = 60000;
|
12
|
-
|
13
|
-
const TenantEvents = {
|
14
|
-
AcquireSuccess: EvernodeEvents.AcquireSuccess,
|
15
|
-
AcquireError: EvernodeEvents.AcquireError,
|
16
|
-
ExtendSuccess: EvernodeEvents.ExtendSuccess,
|
17
|
-
ExtendError: EvernodeEvents.ExtendError,
|
18
|
-
}
|
19
|
-
|
20
|
-
class TenantClient extends BaseEvernodeClient {
|
21
|
-
|
22
|
-
/**
|
23
|
-
* Constructs a tenant client instance.
|
24
|
-
* @param {string} xrpAddress XRPL address of the tenant.
|
25
|
-
* @param {string} XRPL secret of the tenant.
|
26
|
-
* @param {object} options [Optional] An object with 'rippledServer' URL and 'registryAddress'.
|
27
|
-
*/
|
28
|
-
constructor(xrpAddress, xrpSecret, options = {}) {
|
29
|
-
super(xrpAddress, xrpSecret, Object.values(TenantEvents), false, options);
|
30
|
-
}
|
31
|
-
|
32
|
-
async prepareAccount() {
|
33
|
-
try {
|
34
|
-
if (!await this.xrplAcc.getMessageKey())
|
35
|
-
await this.xrplAcc.setAccountFields({ MessageKey: this.accKeyPair.publicKey });
|
36
|
-
}
|
37
|
-
catch (err) {
|
38
|
-
console.log("Error in preparing user xrpl account for Evernode.", err);
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
async getLeaseHost(hostAddress) {
|
43
|
-
const host = new XrplAccount(hostAddress, null, { xrplApi: this.xrplApi });
|
44
|
-
// Find an owned NFT with matching Evernode host NFT prefix.
|
45
|
-
const nft = (await host.getNfts()).find(n => n.URI.startsWith(EvernodeConstants.NFT_PREFIX_HEX));
|
46
|
-
if (!nft)
|
47
|
-
throw { reason: ErrorReasons.HOST_INVALID, error: "Host is not registered." };
|
48
|
-
|
49
|
-
// Check whether the token was actually issued from Evernode registry contract.
|
50
|
-
const issuerHex = nft.NFTokenID.substr(8, 40);
|
51
|
-
const issuerAddr = codec.encodeAccountID(Buffer.from(issuerHex, 'hex'));
|
52
|
-
if (issuerAddr != this.registryAddress)
|
53
|
-
throw { reason: ErrorReasons.HOST_INVALID, error: "Host is not registered." };
|
54
|
-
|
55
|
-
// Check whether active.
|
56
|
-
const hostInfo = await this.getHostInfo(host.address);
|
57
|
-
if (!hostInfo)
|
58
|
-
throw { reason: ErrorReasons.HOST_INVALID, error: "Host is not registered." };
|
59
|
-
else if (!hostInfo.active)
|
60
|
-
throw { reason: ErrorReasons.HOST_INACTIVE, error: "Host is not active." };
|
61
|
-
|
62
|
-
return host;
|
63
|
-
}
|
64
|
-
|
65
|
-
/**
|
66
|
-
*
|
67
|
-
* @param {string} hostAddress XRPL address of the host to acquire the lease.
|
68
|
-
* @param {object} requirement The instance requirements and configuration.
|
69
|
-
* @param {object} options [Optional] Options for the XRPL transaction.
|
70
|
-
* @returns The transaction result.
|
71
|
-
*/
|
72
|
-
async acquireLeaseSubmit(hostAddress, requirement, options = {}) {
|
73
|
-
|
74
|
-
const hostAcc = await this.getLeaseHost(hostAddress);
|
75
|
-
let selectedOfferIndex = options.leaseOfferIndex;
|
76
|
-
|
77
|
-
// Attempt to get first available offer, if offer is not specified in options.
|
78
|
-
if (!selectedOfferIndex) {
|
79
|
-
const nftOffers = await EvernodeHelpers.getLeaseOffers(hostAcc);
|
80
|
-
selectedOfferIndex = nftOffers && nftOffers[0] && nftOffers[0].index;
|
81
|
-
|
82
|
-
if (!selectedOfferIndex)
|
83
|
-
throw { reason: ErrorReasons.NO_OFFER, error: "No offers available." };
|
84
|
-
}
|
85
|
-
|
86
|
-
// Encrypt the requirements with the host's encryption key (Specified in MessageKey field of the host account).
|
87
|
-
const encKey = await hostAcc.getMessageKey();
|
88
|
-
if (!encKey)
|
89
|
-
throw { reason: ErrorReasons.INTERNAL_ERR, error: "Host encryption key not set." };
|
90
|
-
|
91
|
-
const ecrypted = await EncryptionHelper.encrypt(encKey, requirement, {
|
92
|
-
iv: options.iv, // Must be null or 16 bytes.
|
93
|
-
ephemPrivateKey: options.ephemPrivateKey // Must be null or 32 bytes.
|
94
|
-
});
|
95
|
-
|
96
|
-
return this.xrplAcc.buyNft(selectedOfferIndex, [{ type: MemoTypes.ACQUIRE_LEASE, format: MemoFormats.BASE64, data: ecrypted }], options.transactionOptions);
|
97
|
-
}
|
98
|
-
|
99
|
-
/**
|
100
|
-
* Watch for the acquire-success response after the acquire request is made.
|
101
|
-
* @param {object} tx The transaction returned by the acquireLeaseSubmit function.
|
102
|
-
* @param {object} options [Optional] Options for the XRPL transaction.
|
103
|
-
* @returns An object including transaction details,instance info, and acquireReference Id.
|
104
|
-
*/
|
105
|
-
async watchAcquireResponse(tx, options = {}) {
|
106
|
-
console.log(`Waiting for acquire response... (txHash: ${tx.id})`);
|
107
|
-
|
108
|
-
return new Promise(async (resolve, reject) => {
|
109
|
-
let rejected = false;
|
110
|
-
const failTimeout = setTimeout(() => {
|
111
|
-
rejected = true;
|
112
|
-
reject({ error: ErrorCodes.ACQUIRE_ERR, reason: ErrorReasons.TIMEOUT });
|
113
|
-
}, options.timeout || DEFAULT_WAIT_TIMEOUT);
|
114
|
-
|
115
|
-
let relevantTx = null;
|
116
|
-
while (!rejected && !relevantTx) {
|
117
|
-
const txList = await this.xrplAcc.getAccountTrx(tx.details.ledger_index);
|
118
|
-
for (let t of txList) {
|
119
|
-
t.tx.Memos = TransactionHelper.deserializeMemos(t.tx?.Memos);
|
120
|
-
const res = await this.extractEvernodeEvent(t.tx);
|
121
|
-
if ((res?.name === EvernodeEvents.AcquireSuccess || res?.name === EvernodeEvents.AcquireError) && res?.data?.acquireRefId === tx.id) {
|
122
|
-
clearTimeout(failTimeout);
|
123
|
-
relevantTx = res;
|
124
|
-
break;
|
125
|
-
}
|
126
|
-
}
|
127
|
-
await new Promise(resolveSleep => setTimeout(resolveSleep, 2000));
|
128
|
-
}
|
129
|
-
|
130
|
-
if (!rejected) {
|
131
|
-
if (relevantTx?.name === TenantEvents.AcquireSuccess) {
|
132
|
-
resolve({
|
133
|
-
transaction: relevantTx?.data.transaction,
|
134
|
-
instance: relevantTx?.data.payload.content,
|
135
|
-
acquireRefId: relevantTx?.data.acquireRefId
|
136
|
-
});
|
137
|
-
} else if (relevantTx?.name === TenantEvents.AcquireError) {
|
138
|
-
reject({
|
139
|
-
error: ErrorCodes.ACQUIRE_ERR,
|
140
|
-
transaction: relevantTx?.data.transaction,
|
141
|
-
reason: relevantTx?.data.reason,
|
142
|
-
acquireRefId: relevantTx?.data.acquireRefId
|
143
|
-
});
|
144
|
-
}
|
145
|
-
}
|
146
|
-
});
|
147
|
-
}
|
148
|
-
|
149
|
-
/**
|
150
|
-
* Acquire an instance from a host
|
151
|
-
* @param {string} hostAddress XRPL address of the host to acquire the lease.
|
152
|
-
* @param {object} requirement The instance requirements and configuration.
|
153
|
-
* @param {object} options [Optional] Options for the XRPL transaction.
|
154
|
-
* @returns An object including transaction details,instance info, and acquireReference Id.
|
155
|
-
*/
|
156
|
-
acquireLease(hostAddress, requirement, options = {}) {
|
157
|
-
return new Promise(async (resolve, reject) => {
|
158
|
-
const tx = await this.acquireLeaseSubmit(hostAddress, requirement, options).catch(error => {
|
159
|
-
reject({ error: ErrorCodes.ACQUIRE_ERR, reason: error.reason || ErrorReasons.TRANSACTION_FAILURE, content: error.error || error });
|
160
|
-
});
|
161
|
-
if (tx) {
|
162
|
-
try {
|
163
|
-
const response = await this.watchAcquireResponse(tx, options);
|
164
|
-
resolve(response);
|
165
|
-
} catch (error) {
|
166
|
-
reject(error);
|
167
|
-
}
|
168
|
-
}
|
169
|
-
});
|
170
|
-
}
|
171
|
-
|
172
|
-
/**
|
173
|
-
* This function is called by a tenant client to submit the extend lease transaction in certain host. This function will be called inside extendLease function. This function can take four parameters as follows.
|
174
|
-
* @param {string} hostAddress XRPL account address of the host.
|
175
|
-
* @param {number} amount Cost for the extended moments , in EVRs.
|
176
|
-
* @param {string} tokenID Tenant received instance name. this name can be retrieve by performing acquire Lease.
|
177
|
-
* @param {object} options This is an optional field and contains necessary details for the transactions.
|
178
|
-
* @returns The transaction result.
|
179
|
-
*/
|
180
|
-
async extendLeaseSubmit(hostAddress, amount, tokenID, options = {}) {
|
181
|
-
const host = await this.getLeaseHost(hostAddress);
|
182
|
-
return this.xrplAcc.makePayment(host.address, amount.toString(), EvernodeConstants.EVR, this.config.evrIssuerAddress,
|
183
|
-
[{ type: MemoTypes.EXTEND_LEASE, format: MemoFormats.HEX, data: tokenID }], options.transactionOptions);
|
184
|
-
}
|
185
|
-
|
186
|
-
/**
|
187
|
-
* This function watches for an extendlease-success response(transaction) and returns the response or throws the error response on extendlease-error response from the host XRPL account. This function is called within the extendLease function.
|
188
|
-
* @param {object} tx Response of extendLeaseSubmit.
|
189
|
-
* @param {object} options This is an optional field and contains necessary details for the transactions.
|
190
|
-
* @returns An object including transaction details.
|
191
|
-
*/
|
192
|
-
async watchExtendResponse(tx, options = {}) {
|
193
|
-
console.log(`Waiting for extend lease response... (txHash: ${tx.id})`);
|
194
|
-
|
195
|
-
return new Promise(async (resolve, reject) => {
|
196
|
-
let rejected = false;
|
197
|
-
const failTimeout = setTimeout(() => {
|
198
|
-
rejected = true;
|
199
|
-
reject({ error: ErrorCodes.EXTEND_ERR, reason: ErrorReasons.TIMEOUT });
|
200
|
-
}, options.timeout || DEFAULT_WAIT_TIMEOUT);
|
201
|
-
|
202
|
-
let relevantTx = null;
|
203
|
-
while (!rejected && !relevantTx) {
|
204
|
-
const txList = await this.xrplAcc.getAccountTrx(tx.details.ledger_index);
|
205
|
-
for (let t of txList) {
|
206
|
-
t.tx.Memos = TransactionHelper.deserializeMemos(t.tx.Memos);
|
207
|
-
const res = await this.extractEvernodeEvent(t.tx);
|
208
|
-
if ((res?.name === TenantEvents.ExtendSuccess || res?.name === TenantEvents.ExtendError) && res?.data?.extendRefId === tx.id) {
|
209
|
-
clearTimeout(failTimeout);
|
210
|
-
relevantTx = res;
|
211
|
-
break;
|
212
|
-
}
|
213
|
-
}
|
214
|
-
await new Promise(resolveSleep => setTimeout(resolveSleep, 1000));
|
215
|
-
}
|
216
|
-
|
217
|
-
if (!rejected) {
|
218
|
-
if (relevantTx?.name === TenantEvents.ExtendSuccess) {
|
219
|
-
resolve({
|
220
|
-
transaction: relevantTx?.data.transaction,
|
221
|
-
expiryMoment: relevantTx?.data.expiryMoment,
|
222
|
-
extendeRefId: relevantTx?.data.extendRefId
|
223
|
-
});
|
224
|
-
} else if (relevantTx?.name === TenantEvents.ExtendError) {
|
225
|
-
reject({
|
226
|
-
error: ErrorCodes.EXTEND_ERR,
|
227
|
-
transaction: relevantTx?.data.transaction,
|
228
|
-
reason: relevantTx?.data.reason
|
229
|
-
});
|
230
|
-
}
|
231
|
-
}
|
232
|
-
});
|
233
|
-
}
|
234
|
-
|
235
|
-
/**
|
236
|
-
* This function is called by a tenant client to extend an available instance in certain host. This function can take four parameters as follows.
|
237
|
-
* @param {string} hostAddress XRPL account address of the host.
|
238
|
-
* @param {number} moments 1190 ledgers (est. 1 hour).
|
239
|
-
* @param {string} instanceName Tenant received instance name. this name can be retrieve by performing acquire Lease.
|
240
|
-
* @param {object} options This is an optional field and contains necessary details for the transactions.
|
241
|
-
* @returns An object including transaction details.
|
242
|
-
*/
|
243
|
-
extendLease(hostAddress, moments, instanceName, options = {}) {
|
244
|
-
return new Promise(async (resolve, reject) => {
|
245
|
-
const tokenID = instanceName;
|
246
|
-
const nft = (await this.xrplAcc.getNfts())?.find(n => n.NFTokenID == tokenID);
|
247
|
-
|
248
|
-
if (!nft) {
|
249
|
-
reject({ error: ErrorCodes.EXTEND_ERR, reason: ErrorReasons.NO_NFT, content: 'Could not find the nft for lease extend request.' });
|
250
|
-
return;
|
251
|
-
}
|
252
|
-
|
253
|
-
let minLedgerIndex = this.xrplApi.ledgerIndex;
|
254
|
-
|
255
|
-
// Get the agreement lease amount from the nft and calculate EVR amount to be sent.
|
256
|
-
const uriInfo = UtilHelpers.decodeLeaseNftUri(nft.URI);
|
257
|
-
const tx = await this.extendLeaseSubmit(hostAddress, moments * uriInfo.leaseAmount, tokenID, options).catch(error => {
|
258
|
-
reject({ error: ErrorCodes.EXTEND_ERR, reason: error.reason || ErrorReasons.TRANSACTION_FAILURE, content: error.error || error });
|
259
|
-
});
|
260
|
-
|
261
|
-
if (tx) {
|
262
|
-
try {
|
263
|
-
const response = await this.watchExtendResponse(tx, minLedgerIndex, options)
|
264
|
-
resolve(response);
|
265
|
-
} catch (error) {
|
266
|
-
reject(error);
|
267
|
-
}
|
268
|
-
}
|
269
|
-
});
|
270
|
-
}
|
271
|
-
}
|
272
|
-
|
273
|
-
module.exports = {
|
274
|
-
TenantEvents,
|
275
|
-
TenantClient
|
276
|
-
}
|
package/src/defaults.js
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
const DefaultValues = {
|
2
|
-
registryAddress: 'raaFre81618XegCrzTzVotAmarBcqNSAvK',
|
3
|
-
rippledServer: 'wss://hooks-testnet-v2.xrpl-labs.com',
|
4
|
-
xrplApi: null,
|
5
|
-
stateIndexId: 'evernodeindex'
|
6
|
-
}
|
7
|
-
|
8
|
-
class Defaults {
|
9
|
-
static set(newDefaults) {
|
10
|
-
Object.assign(DefaultValues, newDefaults)
|
11
|
-
}
|
12
|
-
|
13
|
-
static get() {
|
14
|
-
return { ...DefaultValues };
|
15
|
-
}
|
16
|
-
}
|
17
|
-
|
18
|
-
module.exports = {
|
19
|
-
DefaultValues,
|
20
|
-
Defaults
|
21
|
-
}
|
package/src/eccrypto.js
DELETED
@@ -1,258 +0,0 @@
|
|
1
|
-
// Code taken from https://github.com/bitchan/eccrypto/blob/master/browser.js
|
2
|
-
// We are using this code file directly because the full eccrypto library causes a conflict with
|
3
|
-
// tiny-secp256k1 used by xrpl libs during ncc/webpack build.
|
4
|
-
|
5
|
-
var EC = require("elliptic").ec;
|
6
|
-
var ec = new EC("secp256k1");
|
7
|
-
var browserCrypto = global.crypto || global.msCrypto || {};
|
8
|
-
var subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
|
9
|
-
|
10
|
-
var nodeCrypto = require('crypto');
|
11
|
-
|
12
|
-
const EC_GROUP_ORDER = Buffer.from('fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', 'hex');
|
13
|
-
const ZERO32 = Buffer.alloc(32, 0);
|
14
|
-
|
15
|
-
function assert(condition, message) {
|
16
|
-
if (!condition) {
|
17
|
-
throw new Error(message || "Assertion failed");
|
18
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
function isScalar(x) {
|
22
|
-
return Buffer.isBuffer(x) && x.length === 32;
|
23
|
-
}
|
24
|
-
|
25
|
-
function isValidPrivateKey(privateKey) {
|
26
|
-
if (!isScalar(privateKey)) {
|
27
|
-
return false;
|
28
|
-
}
|
29
|
-
return privateKey.compare(ZERO32) > 0 && // > 0
|
30
|
-
privateKey.compare(EC_GROUP_ORDER) < 0; // < G
|
31
|
-
}
|
32
|
-
|
33
|
-
// Compare two buffers in constant time to prevent timing attacks.
|
34
|
-
function equalConstTime(b1, b2) {
|
35
|
-
if (b1.length !== b2.length) {
|
36
|
-
return false;
|
37
|
-
}
|
38
|
-
var res = 0;
|
39
|
-
for (var i = 0; i < b1.length; i++) {
|
40
|
-
res |= b1[i] ^ b2[i]; // jshint ignore:line
|
41
|
-
}
|
42
|
-
return res === 0;
|
43
|
-
}
|
44
|
-
|
45
|
-
/* This must check if we're in the browser or
|
46
|
-
not, since the functions are different and does
|
47
|
-
not convert using browserify */
|
48
|
-
function randomBytes(size) {
|
49
|
-
var arr = new Uint8Array(size);
|
50
|
-
if (typeof browserCrypto.getRandomValues === 'undefined') {
|
51
|
-
return Buffer.from(nodeCrypto.randomBytes(size));
|
52
|
-
} else {
|
53
|
-
browserCrypto.getRandomValues(arr);
|
54
|
-
}
|
55
|
-
return Buffer.from(arr);
|
56
|
-
}
|
57
|
-
|
58
|
-
function sha512(msg) {
|
59
|
-
return new Promise(function (resolve) {
|
60
|
-
var hash = nodeCrypto.createHash('sha512');
|
61
|
-
var result = hash.update(msg).digest();
|
62
|
-
resolve(new Uint8Array(result));
|
63
|
-
});
|
64
|
-
}
|
65
|
-
|
66
|
-
function getAes(op) {
|
67
|
-
return function (iv, key, data) {
|
68
|
-
return new Promise(function (resolve) {
|
69
|
-
if (subtle) {
|
70
|
-
var importAlgorithm = { name: "AES-CBC" };
|
71
|
-
var keyp = subtle.importKey("raw", key, importAlgorithm, false, [op]);
|
72
|
-
return keyp.then(function (cryptoKey) {
|
73
|
-
var encAlgorithm = { name: "AES-CBC", iv: iv };
|
74
|
-
return subtle[op](encAlgorithm, cryptoKey, data);
|
75
|
-
}).then(function (result) {
|
76
|
-
resolve(Buffer.from(new Uint8Array(result)));
|
77
|
-
});
|
78
|
-
} else {
|
79
|
-
if (op === 'encrypt') {
|
80
|
-
var cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
|
81
|
-
let firstChunk = cipher.update(data);
|
82
|
-
let secondChunk = cipher.final();
|
83
|
-
resolve(Buffer.concat([firstChunk, secondChunk]));
|
84
|
-
}
|
85
|
-
else if (op === 'decrypt') {
|
86
|
-
var decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
|
87
|
-
let firstChunk = decipher.update(data);
|
88
|
-
let secondChunk = decipher.final();
|
89
|
-
resolve(Buffer.concat([firstChunk, secondChunk]));
|
90
|
-
}
|
91
|
-
}
|
92
|
-
});
|
93
|
-
};
|
94
|
-
}
|
95
|
-
|
96
|
-
var aesCbcEncrypt = getAes("encrypt");
|
97
|
-
var aesCbcDecrypt = getAes("decrypt");
|
98
|
-
|
99
|
-
function hmacSha256Sign(key, msg) {
|
100
|
-
return new Promise(function (resolve) {
|
101
|
-
var hmac = nodeCrypto.createHmac('sha256', Buffer.from(key));
|
102
|
-
hmac.update(msg);
|
103
|
-
var result = hmac.digest();
|
104
|
-
resolve(result);
|
105
|
-
});
|
106
|
-
}
|
107
|
-
|
108
|
-
function hmacSha256Verify(key, msg, sig) {
|
109
|
-
return new Promise(function (resolve) {
|
110
|
-
var hmac = nodeCrypto.createHmac('sha256', Buffer.from(key));
|
111
|
-
hmac.update(msg);
|
112
|
-
var expectedSig = hmac.digest();
|
113
|
-
resolve(equalConstTime(expectedSig, sig));
|
114
|
-
});
|
115
|
-
}
|
116
|
-
|
117
|
-
/**
|
118
|
-
* Generate a new valid private key. Will use the window.crypto or window.msCrypto as source
|
119
|
-
* depending on your browser.
|
120
|
-
* @return {Buffer} A 32-byte private key.
|
121
|
-
* @function
|
122
|
-
*/
|
123
|
-
exports.generatePrivate = function () {
|
124
|
-
var privateKey = randomBytes(32);
|
125
|
-
while (!isValidPrivateKey(privateKey)) {
|
126
|
-
privateKey = randomBytes(32);
|
127
|
-
}
|
128
|
-
return privateKey;
|
129
|
-
};
|
130
|
-
|
131
|
-
var getPublic = exports.getPublic = function (privateKey) {
|
132
|
-
// This function has sync API so we throw an error immediately.
|
133
|
-
assert(privateKey.length === 32, "Bad private key");
|
134
|
-
assert(isValidPrivateKey(privateKey), "Bad private key");
|
135
|
-
// XXX(Kagami): `elliptic.utils.encode` returns array for every
|
136
|
-
// encoding except `hex`.
|
137
|
-
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic("arr"));
|
138
|
-
};
|
139
|
-
|
140
|
-
/**
|
141
|
-
* Get compressed version of public key.
|
142
|
-
*/
|
143
|
-
var getPublicCompressed = exports.getPublicCompressed = function (privateKey) { // jshint ignore:line
|
144
|
-
assert(privateKey.length === 32, "Bad private key");
|
145
|
-
assert(isValidPrivateKey(privateKey), "Bad private key");
|
146
|
-
// See https://github.com/wanderer/secp256k1-node/issues/46
|
147
|
-
let compressed = true;
|
148
|
-
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, "arr"));
|
149
|
-
};
|
150
|
-
|
151
|
-
// NOTE(Kagami): We don't use promise shim in Browser implementation
|
152
|
-
// because it's supported natively in new browsers (see
|
153
|
-
// <http://caniuse.com/#feat=promises>) and we can use only new browsers
|
154
|
-
// because of the WebCryptoAPI (see
|
155
|
-
// <http://caniuse.com/#feat=cryptography>).
|
156
|
-
exports.sign = function (privateKey, msg) {
|
157
|
-
return new Promise(function (resolve) {
|
158
|
-
assert(privateKey.length === 32, "Bad private key");
|
159
|
-
assert(isValidPrivateKey(privateKey), "Bad private key");
|
160
|
-
assert(msg.length > 0, "Message should not be empty");
|
161
|
-
assert(msg.length <= 32, "Message is too long");
|
162
|
-
resolve(Buffer.from(ec.sign(msg, privateKey, { canonical: true }).toDER()));
|
163
|
-
});
|
164
|
-
};
|
165
|
-
|
166
|
-
exports.verify = function (publicKey, msg, sig) {
|
167
|
-
return new Promise(function (resolve, reject) {
|
168
|
-
assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key");
|
169
|
-
if (publicKey.length === 65) {
|
170
|
-
assert(publicKey[0] === 4, "Bad public key");
|
171
|
-
}
|
172
|
-
if (publicKey.length === 33) {
|
173
|
-
assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key");
|
174
|
-
}
|
175
|
-
assert(msg.length > 0, "Message should not be empty");
|
176
|
-
assert(msg.length <= 32, "Message is too long");
|
177
|
-
if (ec.verify(msg, sig, publicKey)) {
|
178
|
-
resolve(null);
|
179
|
-
} else {
|
180
|
-
reject(new Error("Bad signature"));
|
181
|
-
}
|
182
|
-
});
|
183
|
-
};
|
184
|
-
|
185
|
-
var derive = exports.derive = function (privateKeyA, publicKeyB) {
|
186
|
-
return new Promise(function (resolve) {
|
187
|
-
assert(Buffer.isBuffer(privateKeyA), "Bad private key");
|
188
|
-
assert(Buffer.isBuffer(publicKeyB), "Bad public key");
|
189
|
-
assert(privateKeyA.length === 32, "Bad private key");
|
190
|
-
assert(isValidPrivateKey(privateKeyA), "Bad private key");
|
191
|
-
assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key");
|
192
|
-
if (publicKeyB.length === 65) {
|
193
|
-
assert(publicKeyB[0] === 4, "Bad public key");
|
194
|
-
}
|
195
|
-
if (publicKeyB.length === 33) {
|
196
|
-
assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key");
|
197
|
-
}
|
198
|
-
var keyA = ec.keyFromPrivate(privateKeyA);
|
199
|
-
var keyB = ec.keyFromPublic(publicKeyB);
|
200
|
-
var Px = keyA.derive(keyB.getPublic()); // BN instance
|
201
|
-
resolve(Buffer.from(Px.toArray()));
|
202
|
-
});
|
203
|
-
};
|
204
|
-
|
205
|
-
exports.encrypt = function (publicKeyTo, msg, opts) {
|
206
|
-
opts = opts || {};
|
207
|
-
// Tmp variables to save context from flat promises;
|
208
|
-
var iv, ephemPublicKey, ciphertext, macKey;
|
209
|
-
return new Promise(function (resolve) {
|
210
|
-
var ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
|
211
|
-
// There is a very unlikely possibility that it is not a valid key
|
212
|
-
while (!isValidPrivateKey(ephemPrivateKey)) {
|
213
|
-
ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);
|
214
|
-
}
|
215
|
-
ephemPublicKey = getPublic(ephemPrivateKey);
|
216
|
-
resolve(derive(ephemPrivateKey, publicKeyTo));
|
217
|
-
}).then(function (Px) {
|
218
|
-
return sha512(Px);
|
219
|
-
}).then(function (hash) {
|
220
|
-
iv = opts.iv || randomBytes(16);
|
221
|
-
var encryptionKey = hash.slice(0, 32);
|
222
|
-
macKey = hash.slice(32);
|
223
|
-
return aesCbcEncrypt(iv, encryptionKey, msg);
|
224
|
-
}).then(function (data) {
|
225
|
-
ciphertext = data;
|
226
|
-
var dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]);
|
227
|
-
return hmacSha256Sign(macKey, dataToMac);
|
228
|
-
}).then(function (mac) {
|
229
|
-
return {
|
230
|
-
iv: iv,
|
231
|
-
ephemPublicKey: ephemPublicKey,
|
232
|
-
ciphertext: ciphertext,
|
233
|
-
mac: mac,
|
234
|
-
};
|
235
|
-
});
|
236
|
-
};
|
237
|
-
|
238
|
-
exports.decrypt = function (privateKey, opts) {
|
239
|
-
// Tmp variable to save context from flat promises;
|
240
|
-
var encryptionKey;
|
241
|
-
return derive(privateKey, opts.ephemPublicKey).then(function (Px) {
|
242
|
-
return sha512(Px);
|
243
|
-
}).then(function (hash) {
|
244
|
-
encryptionKey = hash.slice(0, 32);
|
245
|
-
var macKey = hash.slice(32);
|
246
|
-
var dataToMac = Buffer.concat([
|
247
|
-
opts.iv,
|
248
|
-
opts.ephemPublicKey,
|
249
|
-
opts.ciphertext
|
250
|
-
]);
|
251
|
-
return hmacSha256Verify(macKey, dataToMac, opts.mac);
|
252
|
-
}).then(function (macGood) {
|
253
|
-
assert(macGood, "Bad MAC");
|
254
|
-
return aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext);
|
255
|
-
}).then(function (msg) {
|
256
|
-
return Buffer.from(new Uint8Array(msg));
|
257
|
-
});
|
258
|
-
};
|
package/src/encryption-helper.js
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
class ed25519 {
|
2
|
-
static async #getLibrary() {
|
3
|
-
const _sodium = require('libsodium-wrappers');
|
4
|
-
await _sodium.ready;
|
5
|
-
return _sodium;
|
6
|
-
}
|
7
|
-
|
8
|
-
static async encrypt(publicKeyBuf, messageBuf) {
|
9
|
-
const sodium = await this.#getLibrary();
|
10
|
-
const curve25519PublicKey = sodium.crypto_sign_ed25519_pk_to_curve25519(publicKeyBuf.slice(1));
|
11
|
-
return Buffer.from(sodium.crypto_box_seal(messageBuf, curve25519PublicKey));
|
12
|
-
}
|
13
|
-
|
14
|
-
static async decrypt(privateKeyBuf, encryptedBuf) {
|
15
|
-
const sodium = await this.#getLibrary();
|
16
|
-
const keyPair = sodium.crypto_sign_seed_keypair(privateKeyBuf.slice(1));
|
17
|
-
const curve25519PublicKey_ = sodium.crypto_sign_ed25519_pk_to_curve25519(keyPair.publicKey);
|
18
|
-
const curve25519PrivateKey = sodium.crypto_sign_ed25519_sk_to_curve25519(keyPair.privateKey);
|
19
|
-
return Buffer.from(sodium.crypto_box_seal_open(encryptedBuf, curve25519PublicKey_, curve25519PrivateKey));
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
class secp256k1 {
|
24
|
-
// Offsets of the properties in the encrypted buffer.
|
25
|
-
static ivOffset = 65;
|
26
|
-
static macOffset = this.ivOffset + 16;
|
27
|
-
static ciphertextOffset = this.macOffset + 32;
|
28
|
-
|
29
|
-
static #getLibrary() {
|
30
|
-
const eccrypto = require('./eccrypto') // Using local copy of the eccrypto code file.
|
31
|
-
return eccrypto;
|
32
|
-
}
|
33
|
-
|
34
|
-
static async encrypt(publicKeyBuf, messageBuf, options = {}) {
|
35
|
-
const eccrypto = this.#getLibrary();
|
36
|
-
// For the encryption library, both keys and data should be buffers.
|
37
|
-
const encrypted = await eccrypto.encrypt(publicKeyBuf, messageBuf, options);
|
38
|
-
// Concat all the properties of the encrypted object to a single buffer.
|
39
|
-
return Buffer.concat([encrypted.ephemPublicKey, encrypted.iv, encrypted.mac, encrypted.ciphertext]);
|
40
|
-
}
|
41
|
-
|
42
|
-
static async decrypt(privateKeyBuf, encryptedBuf) {
|
43
|
-
const eccrypto = this.#getLibrary();
|
44
|
-
// Extract the buffer from the string and prepare encrypt object from buffer offsets for decryption.
|
45
|
-
const encryptedObj = {
|
46
|
-
ephemPublicKey: encryptedBuf.slice(0, this.ivOffset),
|
47
|
-
iv: encryptedBuf.slice(this.ivOffset, this.macOffset),
|
48
|
-
mac: encryptedBuf.slice(this.macOffset, this.ciphertextOffset),
|
49
|
-
ciphertext: encryptedBuf.slice(this.ciphertextOffset)
|
50
|
-
}
|
51
|
-
|
52
|
-
const decrypted = await eccrypto.decrypt(privateKeyBuf.slice(1), encryptedObj)
|
53
|
-
.catch(err => console.log(err));
|
54
|
-
|
55
|
-
return decrypted;
|
56
|
-
}
|
57
|
-
}
|
58
|
-
|
59
|
-
class EncryptionHelper {
|
60
|
-
static contentFormat = 'base64';
|
61
|
-
static keyFormat = 'hex';
|
62
|
-
static ed25519KeyType = 'ed25519';
|
63
|
-
static secp256k1KeyType = 'ecdsa-secp256k1';
|
64
|
-
|
65
|
-
static #getAlgorithmFromKey(key) {
|
66
|
-
const bytes = Buffer.from(key, this.keyFormat);
|
67
|
-
return bytes.length === 33 && bytes.at(0) === 0xed
|
68
|
-
? this.ed25519KeyType
|
69
|
-
: this.secp256k1KeyType;
|
70
|
-
}
|
71
|
-
|
72
|
-
static #getEncryptor(key) {
|
73
|
-
const format = this.#getAlgorithmFromKey(key);
|
74
|
-
return format === this.secp256k1KeyType ? secp256k1 : ed25519;
|
75
|
-
}
|
76
|
-
|
77
|
-
static async encrypt(publicKey, message, options = {}) {
|
78
|
-
const publicKeyBuf = Buffer.from(publicKey, this.keyFormat);
|
79
|
-
const messageBuf = Buffer.from(JSON.stringify(message));
|
80
|
-
const encryptor = this.#getEncryptor(publicKey);
|
81
|
-
const result = await encryptor.encrypt(publicKeyBuf, messageBuf, options);
|
82
|
-
return result ? result.toString(this.contentFormat) : null;
|
83
|
-
}
|
84
|
-
|
85
|
-
static async decrypt(privateKey, encrypted) {
|
86
|
-
const privateKeyBuf = Buffer.from(privateKey, this.keyFormat);
|
87
|
-
const encryptedBuf = Buffer.from(encrypted, this.contentFormat);
|
88
|
-
const encryptor = this.#getEncryptor(privateKey);
|
89
|
-
const decrypted = await encryptor.decrypt(privateKeyBuf, encryptedBuf);
|
90
|
-
return decrypted ? JSON.parse(decrypted.toString()) : null;
|
91
|
-
}
|
92
|
-
}
|
93
|
-
|
94
|
-
module.exports = {
|
95
|
-
EncryptionHelper
|
96
|
-
}
|