evernode-js-client 0.5.13 → 0.5.14
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
}
|