evernode-js-client 0.4.51 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
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
+ }
@@ -0,0 +1,258 @@
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
+ };
@@ -0,0 +1,41 @@
1
+ const eccrypto = require('./eccrypto') // Using local copy of the eccrypto code file.
2
+
3
+ class EncryptionHelper {
4
+ // Offsets of the properties in the encrypted buffer.
5
+ static ivOffset = 65;
6
+ static macOffset = this.ivOffset + 16;
7
+ static ciphertextOffset = this.macOffset + 32;
8
+ static contentFormat = 'base64';
9
+ static keyFormat = 'hex';
10
+
11
+ static async encrypt(publicKey, json, options = {}) {
12
+ // For the encryption library, both keys and data should be buffers.
13
+ const encrypted = await eccrypto.encrypt(Buffer.from(publicKey, this.keyFormat), Buffer.from(JSON.stringify(json)), options);
14
+ // Concat all the properties of the encrypted object to a single buffer.
15
+ const result = Buffer.concat([encrypted.ephemPublicKey, encrypted.iv, encrypted.mac, encrypted.ciphertext]).toString(this.contentFormat);
16
+ return result;
17
+ }
18
+
19
+ static async decrypt(privateKey, encrypted) {
20
+ // Extract the buffer from the string and prepare encrypt object from buffer offsets for decryption.
21
+ const encryptedBuf = Buffer.from(encrypted, this.contentFormat);
22
+ const encryptedObj = {
23
+ ephemPublicKey: encryptedBuf.slice(0, this.ivOffset),
24
+ iv: encryptedBuf.slice(this.ivOffset, this.macOffset),
25
+ mac: encryptedBuf.slice(this.macOffset, this.ciphertextOffset),
26
+ ciphertext: encryptedBuf.slice(this.ciphertextOffset)
27
+ }
28
+
29
+ const decrypted = await eccrypto.decrypt(Buffer.from(privateKey, this.keyFormat).slice(1), encryptedObj)
30
+ .catch(err => console.log(err));
31
+
32
+ if (!decrypted)
33
+ return null;
34
+
35
+ return JSON.parse(decrypted.toString());
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ EncryptionHelper
41
+ }
@@ -0,0 +1,45 @@
1
+ class EventEmitter {
2
+ constructor() {
3
+ this.handlers = {};
4
+ }
5
+
6
+ on(event, handler) {
7
+ if (!this.handlers[event])
8
+ this.handlers[event] = [];
9
+ this.handlers[event].push({
10
+ once: false,
11
+ func: handler
12
+ });
13
+ }
14
+
15
+ once(event, handler) {
16
+ if (!this.handlers[event])
17
+ this.handlers[event] = [];
18
+ this.handlers[event].push({
19
+ once: true,
20
+ func: handler
21
+ });
22
+ }
23
+
24
+ off(event, handler = null) {
25
+ if (this.handlers[event]) {
26
+ if (handler)
27
+ this.handlers[event] = this.handlers[event].filter(h => h !== handler);
28
+ else
29
+ delete this.handlers[event];
30
+ }
31
+ }
32
+
33
+ emit(event, value, error = null) {
34
+ if (this.handlers[event]) {
35
+ this.handlers[event].forEach(handler => handler.func(value, error));
36
+
37
+ // Rmove all handlers marked as 'once'.
38
+ this.handlers[event] = this.handlers[event].filter(h => !h.once);
39
+ }
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ EventEmitter
45
+ }
@@ -0,0 +1,103 @@
1
+ const EvernodeConstants = {
2
+ EVR: 'EVR',
3
+ NFT_PREFIX_HEX: '657672686F7374', // evrhost
4
+ LEASE_NFT_PREFIX_HEX: '6576726C65617365', // evrlease
5
+ HOOK_NAMESPACE: '01EAF09326B4911554384121FF56FA8FECC215FDDE2EC35D9E59F2C53EC665A0'
6
+ }
7
+
8
+ const MemoTypes = {
9
+ ACQUIRE_LEASE: 'evnAcquireLease',
10
+ ACQUIRE_SUCCESS: 'evnAcquireSuccess',
11
+ ACQUIRE_ERROR: 'evnAcquireError',
12
+ ACQUIRE_REF: 'evnAcquireRef',
13
+ HOST_REG: 'evnHostReg',
14
+ HOST_DEREG: 'evnHostDereg',
15
+ HOST_UPDATE_INFO: 'evnHostUpdateReg',
16
+ HEARTBEAT: 'evnHeartbeat',
17
+ HOST_POST_DEREG: 'evnHostPostDereg',
18
+ EXTEND_LEASE: 'evnExtendLease',
19
+ EXTEND_SUCCESS: 'evnExtendSuccess',
20
+ EXTEND_ERROR: 'evnExtendError',
21
+ EXTEND_REF: 'evnExtendRef',
22
+ REGISTRY_INIT: 'evnInitialize',
23
+ REFUND: 'evnRefund',
24
+ REFUND_REF: 'evnRefundRef',
25
+ DEAD_HOST_PRUNE: 'evnDeadHostPrune'
26
+ }
27
+
28
+ const MemoFormats = {
29
+ TEXT: 'text/plain',
30
+ JSON: 'text/json',
31
+ BASE64: 'base64',
32
+ HEX: 'hex'
33
+ }
34
+
35
+ const ErrorCodes = {
36
+ ACQUIRE_ERR: 'ACQUIRE_ERR',
37
+ EXTEND_ERR: 'EXTEND_ERR'
38
+ }
39
+
40
+ const ErrorReasons = {
41
+ TRANSACTION_FAILURE: 'TRANSACTION_FAILURE',
42
+ NO_OFFER: 'NO_OFFER',
43
+ NO_NFT: 'NO_NFT',
44
+ INTERNAL_ERR: 'INTERNAL_ERR',
45
+ TIMEOUT: 'TIMEOUT',
46
+ HOST_INVALID: 'HOST_INVALID',
47
+ HOST_INACTIVE: 'HOST_INACTIVE',
48
+ NO_STATE_KEY: 'NO_STATE_KEY'
49
+ }
50
+
51
+ // All keys are prefixed with 'EVR' (0x455652)
52
+ // Config keys sub-prefix: 0x01
53
+ const HookStateKeys = {
54
+ // Configuration.
55
+ EVR_ISSUER_ADDR: "4556520100000000000000000000000000000000000000000000000000000001",
56
+ FOUNDATION_ADDR: "4556520100000000000000000000000000000000000000000000000000000002",
57
+ MOMENT_SIZE: "4556520100000000000000000000000000000000000000000000000000000003",
58
+ MINT_LIMIT: "4556520100000000000000000000000000000000000000000000000000000004",
59
+ FIXED_REG_FEE: "4556520100000000000000000000000000000000000000000000000000000005",
60
+ HOST_HEARTBEAT_FREQ: "4556520100000000000000000000000000000000000000000000000000000006",
61
+ PURCHASER_TARGET_PRICE: "4556520100000000000000000000000000000000000000000000000000000007",
62
+ LEASE_ACQUIRE_WINDOW: "4556520100000000000000000000000000000000000000000000000000000008",
63
+ REWARD_CONFIGURATION: "4556520100000000000000000000000000000000000000000000000000000009",
64
+ MAX_TOLERABLE_DOWNTIME: "455652010000000000000000000000000000000000000000000000000000000A",
65
+
66
+ // Singleton
67
+ HOST_COUNT: "4556523200000000000000000000000000000000000000000000000000000000",
68
+ MOMENT_BASE_IDX: "4556523300000000000000000000000000000000000000000000000000000000",
69
+ HOST_REG_FEE: "4556523400000000000000000000000000000000000000000000000000000000",
70
+ MAX_REG: "4556523500000000000000000000000000000000000000000000000000000000",
71
+ REWARD_INFO: "4556523600000000000000000000000000000000000000000000000000000000",
72
+
73
+ // Prefixes
74
+ PREFIX_HOST_TOKENID: "45565202",
75
+ PREFIX_HOST_ADDR: "45565203",
76
+ }
77
+
78
+ const EvernodeEvents = {
79
+ HostRegistered: "HostRegistered",
80
+ HostDeregistered: "HostDeregistered",
81
+ HostPostDeregistered: "HostPostDeregistered",
82
+ AcquireLease: "AcquireLease",
83
+ AcquireSuccess: "AcquireSuccess",
84
+ AcquireError: "AcquireError",
85
+ Heartbeat: "Heartbeat",
86
+ ExtendLease: "ExtendLease",
87
+ ExtendSuccess: "ExtendSuccess",
88
+ ExtendError: "ExtendError",
89
+ HostRegUpdated: "HostRegUpdated",
90
+ HostReRegistered: "HostReRegistered",
91
+ RegistryInitialized: "RegistryInitialized",
92
+ DeadHostPrune: "DeadHostPrune"
93
+ }
94
+
95
+ module.exports = {
96
+ EvernodeConstants,
97
+ MemoTypes,
98
+ MemoFormats,
99
+ ErrorCodes,
100
+ ErrorReasons,
101
+ HookStateKeys,
102
+ EvernodeEvents
103
+ }
@@ -0,0 +1,14 @@
1
+ const { EvernodeConstants } = require('./evernode-common');
2
+
3
+ class EvernodeHelpers {
4
+ static async getLeaseOffers(xrplAcc) {
5
+ const hostNfts = (await xrplAcc.getNfts()).filter(nft => nft.URI.startsWith(EvernodeConstants.LEASE_NFT_PREFIX_HEX));
6
+ const hostTokenIDs = hostNfts.map(nft => nft.NFTokenID);
7
+ const nftOffers = (await xrplAcc.getNftOffers())?.filter(offer => (offer.Flags == 1 && hostTokenIDs.includes(offer.NFTokenID))); // Filter only sell offers
8
+ return nftOffers;
9
+ }
10
+ }
11
+
12
+ module.exports = {
13
+ EvernodeHelpers
14
+ }