From 9100c6beea636c0a2d65f7443d11d012049ed7b0 Mon Sep 17 00:00:00 2001 From: HenryNguyen5 Date: Wed, 18 Jul 2018 16:51:54 -0400 Subject: [PATCH] Refactor/cryptonote utils (#17) * Change parse to be a static property * First passthrough * Change mutability of const -> let where needed * Fix redundant type checks / global variables * Reorg codebase + fix majority imports * Add more types * Add moment dep * Add types to test * More type err fixes * Loop refactoring + more types * Convert more errors to objects, fix more type errs, use functional constructs over loops * Remove unused functions * Add typescript dep * Change string errors to obj * Fix more tsc errors * skirt around tsc errors for unimplemented bulletproofs * Fix biginteger error * Add ts-jest * Fix rest of tsc errors, fix tests --- .gitignore | 3 +- .../MG_sigs.spec.ts | 57 +- .../borromean/borromean_1.spec.ts | 9 +- .../borromean/borromean_2.spec.ts | 8 +- .../borromean/borromean_3.spec.ts | 8 +- .../borromean/borromean_4.spec.ts | 9 +- .../borromean/test_parameters.ts | 43 +- .../cryptonote_utils.spec.ts | 91 +- tests/ecdh.spec.js => __test__/ecdh.spec.ts | 36 +- __test__/fixtures/log-modified.log | 669 +++ .../fixtures/mymonero.com-1530837258185.log | 100 + __test__/fixtures/requests-modified.json | 499 ++ __test__/fixtures/requests.json | 4403 +++++++++++++++++ .../range_proofs/range_proofs.spec.ts | 67 +- .../range_proofs_with_fee.spec.ts | 68 +- .../range_proofs/simple.spec.ts | 43 +- .../range_proofs/test_utils.ts | 69 +- cryptonote_utils/cryptonote_utils.js | 2890 ----------- cryptonote_utils/internal_libs/bs58/index.js | 6 - .../internal_libs/cn_crypto/index.js | 3 - .../internal_libs/fast_cn/index.js | 9 - .../internal_libs/fast_cn/nacl-fast-cn.js | 571 --- index.js | 46 - package.json | 28 +- .../biginteger}/biginteger.d.ts | 2 +- .../biginteger}/biginteger.js | 0 src/biginteger/index.ts | 4 + src/cryptonote_utils/cryptonote_utils.ts | 2789 +++++++++++ src/cryptonote_utils/formatters.ts | 141 + src/cryptonote_utils/index.ts | 2 + .../internal_libs/bs58/cryptonote_base58.d.ts | 0 .../internal_libs/bs58/cryptonote_base58.js | 24 +- .../internal_libs/bs58/index.ts | 0 .../cryptonote_crypto_EMSCRIPTEN.d.ts | 5 +- .../cn_crypto/cryptonote_crypto_EMSCRIPTEN.js | 0 .../internal_libs/cn_crypto/index.ts | 0 .../internal_libs/fast_cn/index.ts | 0 .../internal_libs/fast_cn/nacl-fast-cn.d.ts | 0 .../internal_libs/fast_cn/nacl-fast-cn.js | 1026 ++++ .../cryptonote_utils}/nettype.ts | 34 +- .../extern-types}/keccakjs.d.ts | 4 +- src/hostAPI/index.ts | 2 + {hostAPI => src/hostAPI}/net_service_utils.ts | 0 .../hostAPI}/response_parser_utils/index.ts | 0 .../response_parser_utils.ts | 16 +- .../hostAPI}/response_parser_utils/types.ts | 7 +- .../hostAPI}/response_parser_utils/utils.ts | 17 +- .../index.ts | 13 +- src/monero_utils/index.ts | 6 + .../monero_utils}/key_image_utils.ts | 10 +- .../monero_utils}/monero_config.ts | 13 +- .../monero_utils}/monero_paymentID_utils.ts | 8 +- .../monero_utils}/monero_txParsing_utils.ts | 53 +- .../monero_utils}/request_funds_uri_utils.ts | 14 +- .../monero_utils}/sending_funds/index.ts | 0 .../sending_funds/internal_libs/amt_utils.ts | 12 +- .../sending_funds/internal_libs/arr_utils.ts | 0 .../internal_libs/async_node_api.ts | 6 +- .../construct_tx_and_send.ts | 0 .../construct_tx_and_send/index.ts | 0 .../construct_tx_and_send/types.ts | 14 +- .../sending_funds/internal_libs/errors.ts | 10 +- .../sending_funds/internal_libs/fee_utils.ts | 16 +- .../sending_funds/internal_libs/key_utils.ts | 4 +- .../sending_funds/internal_libs/logger.ts | 84 +- .../internal_libs/open_alias_lite.ts | 0 .../internal_libs/output_selection.ts | 8 +- .../internal_libs/parse_target.ts | 8 +- .../sending_funds/internal_libs/pid_utils.ts | 10 +- .../internal_libs/tx_utils/index.ts | 0 .../internal_libs/tx_utils/tx_utils.ts | 44 +- .../internal_libs/tx_utils/types.ts | 28 +- .../sending_funds/internal_libs/types.ts | 4 +- .../sending_funds/mixin_utils.ts | 0 .../sending_funds/sending_funds.ts | 15 +- .../sending_funds/status_update_constants.ts | 0 src/types.ts | 11 + tsconfig.json | 5 +- types.ts | 6 - yarn.lock | 426 +- 80 files changed, 10601 insertions(+), 4035 deletions(-) rename tests/MG_sigs.spec.js => __test__/MG_sigs.spec.ts (69%) rename tests/borromean/borromean_1.spec.js => __test__/borromean/borromean_1.spec.ts (86%) rename tests/borromean/borromean_2.spec.js => __test__/borromean/borromean_2.spec.ts (87%) rename tests/borromean/borromean_3.spec.js => __test__/borromean/borromean_3.spec.ts (87%) rename tests/borromean/borromean_4.spec.js => __test__/borromean/borromean_4.spec.ts (86%) rename tests/borromean/test_parameters.js => __test__/borromean/test_parameters.ts (71%) rename tests/cryptonote_utils.spec.js => __test__/cryptonote_utils.spec.ts (68%) rename tests/ecdh.spec.js => __test__/ecdh.spec.ts (75%) create mode 100644 __test__/fixtures/log-modified.log create mode 100644 __test__/fixtures/mymonero.com-1530837258185.log create mode 100644 __test__/fixtures/requests-modified.json create mode 100644 __test__/fixtures/requests.json rename tests/range_proofs/range_proofs.spec.js => __test__/range_proofs/range_proofs.spec.ts (67%) rename tests/range_proofs/range_proofs_with_fee.spec.js => __test__/range_proofs/range_proofs_with_fee.spec.ts (69%) rename tests/range_proofs/simple.spec.js => __test__/range_proofs/simple.spec.ts (76%) rename tests/range_proofs/test_utils.js => __test__/range_proofs/test_utils.ts (66%) delete mode 100644 cryptonote_utils/cryptonote_utils.js delete mode 100644 cryptonote_utils/internal_libs/bs58/index.js delete mode 100644 cryptonote_utils/internal_libs/cn_crypto/index.js delete mode 100644 cryptonote_utils/internal_libs/fast_cn/index.js delete mode 100644 cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js delete mode 100644 index.js rename {cryptonote_utils => src/biginteger}/biginteger.d.ts (99%) rename {cryptonote_utils => src/biginteger}/biginteger.js (100%) create mode 100644 src/biginteger/index.ts create mode 100644 src/cryptonote_utils/cryptonote_utils.ts create mode 100644 src/cryptonote_utils/formatters.ts create mode 100644 src/cryptonote_utils/index.ts rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/bs58/cryptonote_base58.d.ts (100%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/bs58/cryptonote_base58.js (92%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/bs58/index.ts (100%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts (68%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.js (100%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/cn_crypto/index.ts (100%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/fast_cn/index.ts (100%) rename {cryptonote_utils => src/cryptonote_utils}/internal_libs/fast_cn/nacl-fast-cn.d.ts (100%) create mode 100644 src/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js rename {cryptonote_utils => src/cryptonote_utils}/nettype.ts (83%) rename {extern-types => src/extern-types}/keccakjs.d.ts (76%) create mode 100644 src/hostAPI/index.ts rename {hostAPI => src/hostAPI}/net_service_utils.ts (100%) rename {hostAPI => src/hostAPI}/response_parser_utils/index.ts (100%) rename {hostAPI => src/hostAPI}/response_parser_utils/response_parser_utils.ts (95%) rename {hostAPI => src/hostAPI}/response_parser_utils/types.ts (93%) rename {hostAPI => src/hostAPI}/response_parser_utils/utils.ts (91%) rename monero_utils/monero_cryptonote_utils_instance.js => src/index.ts (84%) create mode 100644 src/monero_utils/index.ts rename {monero_utils => src/monero_utils}/key_image_utils.ts (94%) rename {monero_utils => src/monero_utils}/monero_config.ts (92%) rename {monero_utils => src/monero_utils}/monero_paymentID_utils.ts (93%) rename {monero_utils => src/monero_utils}/monero_txParsing_utils.ts (60%) rename {monero_utils => src/monero_utils}/request_funds_uri_utils.ts (94%) rename {monero_utils => src/monero_utils}/sending_funds/index.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/amt_utils.ts (80%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/arr_utils.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/async_node_api.ts (94%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/construct_tx_and_send/construct_tx_and_send.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/construct_tx_and_send/index.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/construct_tx_and_send/types.ts (82%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/errors.ts (86%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/fee_utils.ts (66%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/key_utils.ts (64%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/logger.ts (62%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/open_alias_lite.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/output_selection.ts (89%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/parse_target.ts (84%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/pid_utils.ts (80%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/tx_utils/index.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/tx_utils/tx_utils.ts (90%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/tx_utils/types.ts (68%) rename {monero_utils => src/monero_utils}/sending_funds/internal_libs/types.ts (92%) rename {monero_utils => src/monero_utils}/sending_funds/mixin_utils.ts (100%) rename {monero_utils => src/monero_utils}/sending_funds/sending_funds.ts (95%) rename {monero_utils => src/monero_utils}/sending_funds/status_update_constants.ts (100%) create mode 100644 src/types.ts delete mode 100644 types.ts diff --git a/.gitignore b/.gitignore index 4e3b3d7..50e1fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ coverage .vscode yarn-error.log -tests/fixtures \ No newline at end of file +tests/fixtures +dist \ No newline at end of file diff --git a/tests/MG_sigs.spec.js b/__test__/MG_sigs.spec.ts similarity index 69% rename from tests/MG_sigs.spec.js rename to __test__/MG_sigs.spec.ts index b1b05fb..387d64c 100644 --- a/tests/MG_sigs.spec.js +++ b/__test__/MG_sigs.spec.ts @@ -1,3 +1,13 @@ +import { + skGen, + ge_scalarmult_base, + identity, + ge_scalarmult, + hashToPoint, + MLSAG_Gen, + MLSAG_ver, +} from "cryptonote_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -26,21 +36,19 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../").monero_utils; - it("MG_sigs", () => { - function skvGen(len) { - let skVec = []; + function skvGen(len: number) { + const skVec: string[] = []; for (let i = 0; i < len; i++) { - skVec.push(monero_utils.skGen()); + skVec.push(skGen()); } return skVec; } //initializes a key matrix; //first parameter is rows, //second is columns - function keyMInit(rows, cols) { - let rv = []; + function keyMInit(cols: number) { + let rv: string[][] = []; for (let i = 0; i < cols; i++) { rv.push([]); } @@ -53,7 +61,7 @@ it("MG_sigs", () => { let N = 3; // cols let R = 2; // rows - let xm = keyMInit(R, N); // = [[None]*N] #just used to generate test public keys + let xm = keyMInit(N); // = [[None]*N] #just used to generate test public keys let sk = skvGen(R); // [ @@ -63,15 +71,15 @@ it("MG_sigs", () => { // [pubkeyn, commitmentn]] // // Gen creates a signature which proves that for some column in the keymatrix "pk" // the signer knows a secret key for each row in that column - let P = keyMInit(R, N); // = keyM[[None]*N] #stores the public keys; + let P = keyMInit(N); // = keyM[[None]*N] #stores the public keys; let ind = 2; let i = 0; for (j = 0; j < R; j++) { for (i = 0; i < N; i++) { - xm[i][j] = monero_utils.skGen(); - P[i][j] = monero_utils.ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit] + xm[i][j] = skGen(); + P[i][j] = ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit] } } @@ -80,34 +88,27 @@ it("MG_sigs", () => { sk[j] = xm[ind][j]; } - let message = monero_utils.identity(); - let kimg = monero_utils.ge_scalarmult( - monero_utils.hashToPoint(P[ind][0]), - sk[0], - ); - let rv = monero_utils.MLSAG_Gen(message, P, sk, kimg, ind); - let c = monero_utils.MLSAG_ver(message, P, rv, kimg); + let message = identity(); + let kimg = ge_scalarmult(hashToPoint(P[ind][0]), sk[0]); + let rv = MLSAG_Gen(message, P, sk, kimg, ind); + let c = MLSAG_ver(message, P, rv, kimg); expect(c).toEqual(true); - xtmp = skvGen(R); - xm = keyMInit(R, N); // = [[None]*N] #just used to generate test public keys + xm = keyMInit(N); // = [[None]*N] #just used to generate test public keys sk = skvGen(R); for (j = 0; j < R; j++) { for (i = 0; i < N; i++) { - xm[i][j] = monero_utils.skGen(); - P[i][j] = monero_utils.ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit] + xm[i][j] = skGen(); + P[i][j] = ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit] } } sk[1] = skGen(); //assume we don't know one of the private keys.. - kimg = monero_utils.ge_scalarmult( - monero_utils.hashToPoint(P[ind][0]), - sk[0], - ); - rv = monero_utils.MLSAG_Gen(message, P, sk, kimg, ind); - c = monero_utils.MLSAG_ver(message, P, rv, kimg); + kimg = ge_scalarmult(hashToPoint(P[ind][0]), sk[0]); + rv = MLSAG_Gen(message, P, sk, kimg, ind); + c = MLSAG_ver(message, P, rv, kimg); expect(c).toEqual(false); }); diff --git a/tests/borromean/borromean_1.spec.js b/__test__/borromean/borromean_1.spec.ts similarity index 86% rename from tests/borromean/borromean_1.spec.js rename to __test__/borromean/borromean_1.spec.ts index f9ded42..a514647 100644 --- a/tests/borromean/borromean_1.spec.js +++ b/__test__/borromean/borromean_1.spec.ts @@ -1,3 +1,6 @@ +import { generate_parameters } from "./test_parameters"; +import { genBorromean, verifyBorromean } from "cryptonote_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -26,13 +29,11 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../../").monero_utils; -const { generate_parameters } = require("./test_parameters"); const { indi, P1v, P2v, xv, N } = generate_parameters(); it("borromean_3", () => { // #true one - const bb = monero_utils.genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/ - const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/ + const bb = genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/ + const valid = verifyBorromean(bb, P1v, P2v); /*?.*/ expect(valid).toBe(true); }); diff --git a/tests/borromean/borromean_2.spec.js b/__test__/borromean/borromean_2.spec.ts similarity index 87% rename from tests/borromean/borromean_2.spec.js rename to __test__/borromean/borromean_2.spec.ts index 669fcc9..b33698c 100644 --- a/tests/borromean/borromean_2.spec.js +++ b/__test__/borromean/borromean_2.spec.ts @@ -26,14 +26,14 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../../").monero_utils; -const { generate_parameters } = require("./test_parameters"); +import { generate_parameters } from "./test_parameters"; +import { genBorromean, verifyBorromean } from "cryptonote_utils"; const { indi, P1v, P2v, xv, N } = generate_parameters(); it("borromean_2", () => { //#false one indi[3] = `${(+indi[3] + 1) % 2}`; - const bb = monero_utils.genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/ - const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/ + const bb = genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/ + const valid = verifyBorromean(bb, P1v, P2v); /*?.*/ expect(valid).toBe(false); }); diff --git a/tests/borromean/borromean_3.spec.js b/__test__/borromean/borromean_3.spec.ts similarity index 87% rename from tests/borromean/borromean_3.spec.js rename to __test__/borromean/borromean_3.spec.ts index 3ba8366..2593df3 100644 --- a/tests/borromean/borromean_3.spec.js +++ b/__test__/borromean/borromean_3.spec.ts @@ -26,8 +26,8 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../../").monero_utils; -const { generate_parameters } = require("./test_parameters"); +import { generate_parameters } from "./test_parameters"; +import { genBorromean, verifyBorromean } from "cryptonote_utils"; const { indi, P1v, P2v, xv, N } = generate_parameters(); it("borromean_3", () => { @@ -35,7 +35,7 @@ it("borromean_3", () => { indi[3] = `${(+indi[3] + 1) % 2}`; indi[3] = `${(+indi[3] + 1) % 2}`; - const bb = monero_utils.genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/ - const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/ + const bb = genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/ + const valid = verifyBorromean(bb, P1v, P2v); /*?.*/ expect(valid).toBe(true); }); diff --git a/tests/borromean/borromean_4.spec.js b/__test__/borromean/borromean_4.spec.ts similarity index 86% rename from tests/borromean/borromean_4.spec.js rename to __test__/borromean/borromean_4.spec.ts index e0591ee..68822d8 100644 --- a/tests/borromean/borromean_4.spec.js +++ b/__test__/borromean/borromean_4.spec.ts @@ -1,3 +1,5 @@ +import { genBorromean, verifyBorromean } from "cryptonote_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -26,13 +28,12 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../../").monero_utils; -const { generate_parameters } = require("./test_parameters"); +import { generate_parameters } from "./test_parameters"; const { indi, P1v, P2v, xv, N } = generate_parameters(); it("borromean_4", () => { // #false one - const bb = monero_utils.genBorromean(xv, [P2v, P1v], indi, 2, N); /*?.*/ - const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/ + const bb = genBorromean(xv, [P2v, P1v], indi, 2, N); /*?.*/ + const valid = verifyBorromean(bb, P1v, P2v); /*?.*/ expect(valid).toBe(false); }); diff --git a/tests/borromean/test_parameters.js b/__test__/borromean/test_parameters.ts similarity index 71% rename from tests/borromean/test_parameters.js rename to __test__/borromean/test_parameters.ts index b94c82d..c9c743d 100644 --- a/tests/borromean/test_parameters.js +++ b/__test__/borromean/test_parameters.ts @@ -26,36 +26,35 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../../").monero_utils; -const { randomBytes } = require("crypto"); +import { randomBytes } from "crypto"; +import { + skGen, + ge_scalarmult_base, + ge_add, + ge_sub, + H2, +} from "cryptonote_utils"; +import { padLeft } from "cryptonote_utils/formatters"; function randomBit() { // get random 8 bits in hex const rand8bits = randomBytes(1).toString("hex"); // take 4 bits "nibble" and convert to binary // then take last index - return monero_utils.padLeft( - parseInt(rand8bits[0], 16).toString(2), - 4, - 0, - )[3]; + return padLeft(parseInt(rand8bits[0], 16).toString(2), 4, "0")[3]; } //Tests for Borromean signatures //#boro true one, false one, C != sum Ci, and one out of the range.. const N = 64; -let xv = [], // vector of secret keys, 1 per ring (nrings) - P1v = [], //key64, arr of commitments Ci - P2v = [], //key64 - indi = []; // vector of secret indexes, 1 per ring (nrings), can be a string - -let indi_2 = []; -let indi_3 = []; -let indi_4 = []; +let xv: string[] = [], // vector of secret keys, 1 per ring (nrings) + P1v: string[] = [], //key64, arr of commitments Ci + P2v: string[] = [], //key64 + indi: string[] = []; // vector of secret indexes, 1 per ring (nrings), can be a string let generated = false; -function generate_parameters() { +export function generate_parameters() { if (generated) { const indiCopy = [...indi]; @@ -64,20 +63,18 @@ function generate_parameters() { for (let j = 0; j < N; j++) { indi[j] = randomBit(); /*?.*/ - xv[j] = monero_utils.skGen(); /*?.*/ + xv[j] = skGen(); /*?.*/ if (+indi[j] === 0) { - P1v[j] = monero_utils.ge_scalarmult_base(xv[j]); /*?.*/ + P1v[j] = ge_scalarmult_base(xv[j]); /*?.*/ } else { - P1v[j] = monero_utils.ge_scalarmult_base(xv[j]); // calculate aG = xv[j].G /*?.*/ - P1v[j] = monero_utils.ge_add(P1v[j], monero_utils.H2[j]); // calculate aG + H2 /*?.*/ + P1v[j] = ge_scalarmult_base(xv[j]); // calculate aG = xv[j].G /*?.*/ + P1v[j] = ge_add(P1v[j], H2[j]); // calculate aG + H2 /*?.*/ } - P2v[j] = monero_utils.ge_sub(P1v[j], monero_utils.H2[j]); /*?.*/ + P2v[j] = ge_sub(P1v[j], H2[j]); /*?.*/ } generated = true; return { xv, P1v, P2v, indi, N }; } } - -module.exports = { generate_parameters }; diff --git a/tests/cryptonote_utils.spec.js b/__test__/cryptonote_utils.spec.ts similarity index 68% rename from tests/cryptonote_utils.spec.js rename to __test__/cryptonote_utils.spec.ts index df8622e..7a162cb 100644 --- a/tests/cryptonote_utils.spec.js +++ b/__test__/cryptonote_utils.spec.ts @@ -26,122 +26,105 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -"use strict"; -const mymonero = require("../"); -const assert = require("assert"); +import { + cn_fast_hash, + NetType, + valid_hex, + generate_key_derivation, + decode_address, + hash_to_scalar, + derivation_to_scalar, + derive_public_key, + derive_subaddress_public_key, +} from "cryptonote_utils"; -var public_key = +const public_key = "904e49462268d771cc1649084c35aa1296bfb214880fe2e7f373620a3e2ba597"; -var private_key = +const private_key = "52aa4c69b93b780885c9d7f51e6fd5795904962c61a2e07437e130784846f70d"; -var nettype = mymonero.nettype_utils.network_type.MAINNET; +const nettype = NetType.MAINNET; describe("cryptonote_utils tests", function() { it("is valid hex", function() { - var valid = mymonero.monero_utils.valid_hex(private_key); - assert.strictEqual(valid, true); + const valid = valid_hex(private_key); + expect(valid).toEqual(true); }); it("fast hash / keccak-256", function() { - var hash = mymonero.monero_utils.cn_fast_hash( - private_key, - private_key.length, - ); - assert.equal( - hash, + const hash = cn_fast_hash(private_key); + expect(hash).toEqual( "64997ff54f0d82ee87d51e971a0329d4315481eaeb4ad2403c65d5843480c414", ); }); it("generate key derivation", function() { - var derivation = mymonero.monero_utils.generate_key_derivation( - public_key, - private_key, - ); - assert.equal( - derivation, + const derivation = generate_key_derivation(public_key, private_key); + expect(derivation).toEqual( "591c749f1868c58f37ec3d2a9d2f08e7f98417ac4f8131e3a57c1fd71273ad00", ); }); it("decode mainnet primary address", function() { - var decoded = mymonero.monero_utils.decode_address( + const decoded = decode_address( "49qwWM9y7j1fvaBK684Y5sMbN8MZ3XwDLcSaqcKwjh5W9kn9qFigPBNBwzdq6TCAm2gKxQWrdZuEZQBMjQodi9cNRHuCbTr", nettype, ); - var expected = { + const expected = { spend: "d8f1e81ecbe25ce8b596d426fb02fe7b1d4bb8d14c06b3d3e371a60eeea99534", view: "576f0e61e250d941746ed147f602b5eb1ea250ca385b028a935e166e18f74bd7", }; - assert.deepEqual(decoded, expected); + expect(decoded).toEqual(expected); }); it("decode mainnet integrated address", function() { - var decoded = mymonero.monero_utils.decode_address( + const decoded = decode_address( "4KYcX9yTizXfvaBK684Y5sMbN8MZ3XwDLcSaqcKwjh5W9kn9qFigPBNBwzdq6TCAm2gKxQWrdZuEZQBMjQodi9cNd3mZpgrjXBKMx9ee7c", nettype, ); - var expected = { + const expected = { spend: "d8f1e81ecbe25ce8b596d426fb02fe7b1d4bb8d14c06b3d3e371a60eeea99534", view: "576f0e61e250d941746ed147f602b5eb1ea250ca385b028a935e166e18f74bd7", intPaymentId: "83eab71fbee84eb9", }; - assert.deepEqual(decoded, expected); + expect(decoded).toEqual(expected); }); it("hash_to_scalar", function() { - var scalar = mymonero.monero_utils.hash_to_scalar(private_key); - assert.equal( - scalar, + const scalar = hash_to_scalar(private_key); + expect(scalar).toEqual( "77c5899835aa6f96b13827f43b094abf315481eaeb4ad2403c65d5843480c404", ); }); it("derivation_to_scalar", function() { - var derivation = mymonero.monero_utils.generate_key_derivation( - public_key, - private_key, - ); - var scalar = mymonero.monero_utils.derivation_to_scalar(derivation, 1); - assert.equal( - scalar, + const derivation = generate_key_derivation(public_key, private_key); + const scalar = derivation_to_scalar(derivation, 1); + expect(scalar).toEqual( "201ce3c258e09eeb6132ec266d24ee1ca957828f384ce052d5bc217c2c55160d", ); }); it("derive public key", function() { - var derivation = mymonero.monero_utils.generate_key_derivation( - public_key, - private_key, - ); - var output_key = mymonero.monero_utils.derive_public_key( - derivation, - 1, - public_key, - ); - assert.equal( - output_key, + const derivation = generate_key_derivation(public_key, private_key); + const output_key = derive_public_key(derivation, 1, public_key); + expect(output_key).toEqual( "da26518ddb54cde24ccfc59f36df13bbe9bdfcb4ef1b223d9ab7bef0a50c8be3", ); }); it("derive subaddress public key", function() { - var derivation = mymonero.monero_utils.generate_key_derivation( - public_key, - private_key, - ); - var subaddress_public_key = mymonero.monero_utils.derive_subaddress_public_key( + const derivation = generate_key_derivation(public_key, private_key); + const subaddress_public_key = derive_subaddress_public_key( public_key, derivation, 1, ); - assert.equal( - subaddress_public_key, + expect(subaddress_public_key).toEqual( "dfc9e4a0039e913204c1c0f78e954a7ec7ce291d8ffe88265632f0da9d8de1be", ); }); diff --git a/tests/ecdh.spec.js b/__test__/ecdh.spec.ts similarity index 75% rename from tests/ecdh.spec.js rename to __test__/ecdh.spec.ts index 5ade692..8ff1ef0 100644 --- a/tests/ecdh.spec.js +++ b/__test__/ecdh.spec.ts @@ -1,3 +1,6 @@ +import { BigInt } from "biginteger"; +import { skGen, d2s, encode_rct_ecdh, decode_rct_ecdh } from "cryptonote_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -26,29 +29,26 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const JSBigInt = require("../cryptonote_utils/biginteger").BigInteger; -const monero_utils = require("../").monero_utils; - it("ecdh_roundtrip", () => { const test_amounts = [ - new JSBigInt(1), - new JSBigInt(1), - new JSBigInt(2), - new JSBigInt(3), - new JSBigInt(4), - new JSBigInt(5), - new JSBigInt(10000), + new BigInt(1), + new BigInt(1), + new BigInt(2), + new BigInt(3), + new BigInt(4), + new BigInt(5), + new BigInt(10000), - new JSBigInt("10000000000000000000"), - new JSBigInt("10203040506070809000"), + new BigInt("10000000000000000000"), + new BigInt("10203040506070809000"), - new JSBigInt("123456789123456789"), + new BigInt("123456789123456789"), ]; for (const amount of test_amounts) { - const k = monero_utils.skGen(); - const scalar = monero_utils.skGen(); /*?*/ - const amt = monero_utils.d2s(amount.toString()); + const k = skGen(); + const scalar = skGen(); /*?*/ + const amt = d2s(amount.toString()); const t0 = { mask: scalar, amount: amt, @@ -57,9 +57,9 @@ it("ecdh_roundtrip", () => { // both are strings so we can shallow copy let t1 = { ...t0 }; - t1 = monero_utils.encode_rct_ecdh(t1, k); + t1 = encode_rct_ecdh(t1, k); - t1 = monero_utils.decode_rct_ecdh(t1, k); + t1 = decode_rct_ecdh(t1, k); expect(t1.mask).toEqual(t0.mask); expect(t1.amount).toEqual(t0.amount); } diff --git a/__test__/fixtures/log-modified.log b/__test__/fixtures/log-modified.log new file mode 100644 index 0000000..7420c1c --- /dev/null +++ b/__test__/fixtures/log-modified.log @@ -0,0 +1,669 @@ +send_coins.js?2:200 Parsed destinations: +[ + { + "address":"46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA", + "amount":{"_d":[0,1000],"_s":1} + } +] + +send_coins.js?2:201 Total before fee: 0.01 +send_coins.js?2:229 Destinations: +send_coins.js?2:232 46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA: 0.010000000000 + + +send_coins.js?2:285 Output used as mixin (c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b/4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7) +send_coins.js?2:289 Unspent outs: +[ + { + "amount":"296850150000", + "public_key":"af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", + "index":0, + "global_index":6251022, + "rct":"35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06", + "tx_id":4751309, + "tx_hash":"3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9", + "tx_pub_key":"5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "tx_prefix_hash":"1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58", + "spend_key_images":["4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7"], + "timestamp":"2018-07-06T00:25:48Z", + "height":1600105 + } +] + +send_coins.js?2:389 Balance required: 0.01223054 XMR +send_coins.js?2:367 Selecting outputs to use. Current total: 0 target: 0.01223054 +send_coins.js?2:373 Using output: 0.29685015 - +{ + "amount":"296850150000", + "public_key":"af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", + "index":0, + "global_index":6251022, + "rct":"35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06", + "tx_id":4751309, + "tx_hash":"3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9", + "tx_pub_key":"5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "tx_prefix_hash":"1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58", + "spend_key_images":["4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7"], + "timestamp":"2018-07-06T00:25:48Z", + "height":1600105 +} + +send_coins.js?2:433 Sending change of 0.28461961 XMR to 47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX +send_coins.js?2:458 Fetching mix outs... +send_coins.js?2:481 Destinations: +cn_util.js?3:1851 46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA: 0.010000000000 +cn_util.js?3:1851 47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX: 0.284619610000 +send_coins.js?2:488 Decomposed destinations: +cn_util.js?3:1851 46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA: 0.010000000000 +cn_util.js?3:1851 47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX: 0.284619610000 + +cn_util.js?3:1741 Selected transfers: +[ + { + "amount": "296850150000", + "public_key": "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", + "index": 0, + "global_index": 6251022, + "rct": "35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06", + "tx_id": 4751309, + "tx_hash": "3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9", + "tx_pub_key": "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "tx_prefix_hash": "1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58", + "spend_key_images": [ + "4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7" + ], + "timestamp": "2018-07-06T00:25:48Z", + "height": 1600105 + } +] + + + +cn_util.js?3:1812 sources: +[ + { + "outputs": [ + { + "index": "2080088", + "key": "e3b02dc808097301664c989a4faf7c5f2ec8976777b74dbe4b8cfecf8c44ca11", + "commit": "bdeba6a579f63f9dfe344ae4754c8db083e1f87a75956bb2c90f93210b3d02a1" + }, + { + "index": "2799561", + "key": "69d3eb381e3a43fd41b19cc41004866c00274e9daeb07de3303bf7a949719d50", + "commit": "733270f6b344c77dc8e463a95524fb90626874c2d8ae15b02578fe8404be8491" + }, + { + "index": "4203500", + "key": "4d9eed420a1844a32e588317c7a713fbe59d9281b45b7f3504b9bda307c2c3c1", + "commit": "dc1afdf383ca54068da77efcd29ae628882db2fc93adf8c1e4a96da7d066d1a1" + }, + { + "index": "4281075", + "key": "7c82f043d142a221f9f0a5f9bc4337270a58e3f2d8d4dfb6f8619842bfe0d1c0", + "commit": "636a54cdae73d0517334a02cdc9b6c6081c261e250f510284c1289ca3ed5e26c" + }, + { + "index": "5980172", + "key": "3fb2a5f3016c21721349e27f98ba734bf3d0cb577ba4640fe5db5904a2bb146e", + "commit": "df525ea20e203f3d0c6ed5caf9e083e679ce6daee56c992216ec39849b12aaac" + }, + { + "index": "6002676", + "key": "bcfd234d10151e10eff22c7b84bcf7064f9a35c3e0c53dd315394cd66d1ddc8e", + "commit": "5206721c74d96343283bb82ad0abde561f10913e1217b3826ed5cf33fe7c5355" + }, + { + "index": "6251022", + "key": "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", + "commit": "35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f9" + } + ], + "amount": "296850150000", + "real_out_tx_key": "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "real_out": 6, + "real_out_in_tx": 0, + "mask": "70273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05", + "key_image": "c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b", + "in_ephemeral": { + "pub": "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", + "sec": "0b645a7041b8709a977fba1c34e70b5ca3eb8511a023142442f4cd0516064f07", + "mask": "61b1c6138c261599e8e40f3e437212b36d3d48fee73daeb502b141a26f777008" + } + } +] + +cn_util.js?3:1560 +{ + sec: "b9a57d04249992a0faca9a7da08109f31c5955bb8bd60a3dc2872abb1bd49b05", + pub: "c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3" +} + + +cn_util.js?3:1593 Sources: +cn_util.js?3:1597 0: 0.296850150000 +cn_util.js?3:1234 Time take for range proof 0: 922 +cn_util.js?3:1234 Time take for range proof 1: 904 + +cn_util.js?3:1700 +{ + "unlock_time": 0, + "version": 2, + "extra": "01c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3", + "vin": [ + { + "type": "input_to_key", + "amount": "0", + "k_image": "c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b", + "key_offsets": [ + "2080088", + "719473", + "1403939", + "77575", + "1699097", + "22504", + "248346" + ] + } + ], + "vout": [ + { + "amount": "0", + "target": { + "type": "txout_to_key", + "key": "105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b1" + } + }, + { + "amount": "0", + "target": { + "type": "txout_to_key", + "key": "d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d31" + } + } + ], + "rct_signatures": { + "type": 1, + "message": "6ffddc62bc7c37b8b2c5dfca7fe2c8be1fc8099d86a4e779946c58dc8f5f2128", + "outPk": [ + "200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b", + "6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e" + ], + "p": { + "rangeSigs": [ + { + "Ci": [ + "02836fed5f531a64dd41aad00a1dc39ec13b49d1ebd7a2a6b5867d7d71db0e50", + "a393ec33f28531624bdfa3ddab00ab9239b2bd54ea858920f34c26430045898f", + "52b419f7f04f7b3b8e604bf11b20ed058e8ecdeae5be4845aa5f14f16c1f953d", + "8e653029b087bcd4ab9953c1937cee84f81c393285c8bc3341a5eb5bae580b5d", + "55cf6f0ee2afbea795764bf5d4ed0735367a0bf65b6f69732449e3dfec547d93", + "d22056ada1eb1c35c2ca1924e07c0e6120b818d6941755efc4dae89d0cc03976", + "2bf5e003763a01910d434d3f0c3e71af286c55d30c9aadb2a480b8d0938ce43c", + "202968df06659dc14e713dd6b2899be1501718e3003be5220b7e90034244ab3e", + "9db020600bdb9b2a17b9d87e150356cb3f3a7150bf305d03efb0aab95cf43e5c", + "f124a8f5db6c37b81f7841b81a7cc5c2ed6d3b8124ff95f9981fc93d732dbf47", + "0403a44e0f46ffff9bbef09e2fedbf9b42600918567617ea28d8f42e6b1d764a", + "340ea317a4462b1083062b29560d25f2b35a82bfdf000033f8a0db28e7314d4d", + "784f6b9d0df70928cc30b94119cf02085884a237b2ff7470e299726d06f430f7", + "2c73a1727446f060a3d98ee0c867ea162c197bacce848ebc423f3dc6c4835e8b", + "bce299084ecf73139b024c33f215bc132fe77cf326e2da2cd18b3008bf16447f", + "f4b2a4a5679987564d369e8936270b4adb3128e6e88eb75205f9de36088fde3a", + "7d294c7c38cf0d0bb02c1160a6695550423737668f1646ca65b93d95547097a2", + "fa8bd70f74a623e5a3c9c3c6b70097bde54853b7517c76c0d36e59cabce4bb69", + "1bff14524d2a0eac2155a9af73a4e87f0d6ec67d4a27840ed0b7134fa93d7515", + "88a77416daca2b68d054a7e74dc752a9506090e79596c9b558f085853055d878", + "52c251baa10a9607b86c009ba3ecba6ed2457d499128f26fe632c39e5ccc46fb", + "4b7e225ee282d9883abaed6e531b4f844df3d5cb547152742d0a0744e40fb928", + "4637a34dbd3a4ff4dae9a07904b36e539f56eec6ce7baf51ed8e8189b242effc", + "3bc60908d6b62739b6d6e8206e4223160eb293eeea2992c0dcaf60c73632ce19", + "6441692c1f5131526818f5bff1cd83a75e414e595e13a082a47985bd505cf256", + "f5ec516ae79c1117f9f7820594d696a7714d2151450127dee286f8d18de13517", + "b24eda31f18547032200489505d2f53101c962641b7275b8a48296f339402f7f", + "48ac2674f669841615752eaf495a6dac6bd4d6cae58be8c779890d201904221e", + "f4a1a0f87c523b027fa43f52f37ea06f8a19070fe04f7533dfb529d802a22d7d", + "299f3abb6974611456060915173bcca396febe1bc943150131d94a2406565957", + "950fea0f11421c7ae2b7e900f46a8c947b7e5e724bd981c00a1c93070a6dd51b", + "f406b876c5141f7fb5bd84cb788dda0b822bdc4263e500c85c079aee409cc00e", + "411628ef38f9c948a3ca7989efe4592d7702aa846b3a1cfbfc5e4a1801ad6a63", + "386bdaa004af3bd3182a082ae5f17d30862c5a0de12078aba63b51ad2ad9c695", + "257e90cf997464da236ace2f90020547003d30e8f63998ab705aa56fd4031397", + "d36526e09faa72c4789380a4df227104cba21542a16f64b19ba96a84103513d9", + "4336e5077c76eb3a301e3bcdf507c9a0ab6b7f98d6533d6cdfe04f72d2404247", + "2cd55b68ff9d9e1d8155c21e7df17066751a99ccc5147d0a828053f77b059879", + "160522a71a95f14e058a51bf443264700870541f074c182b1ca3d1250b6e0337", + "7dea966eeba2bd820d680f35dc7226e4a8faebc88da83b49da2b5e7dc897dfc7", + "998961a5fb82372fc03ddcbb6043284c80358ec0f2bf9cba7e3bbce8696dd16b", + "c32819e8182b5528b3ac82df464b286b944a13704dc17befb80ed61d24a2b240", + "7fd2bbf0fd1e547b1437db807e113c9e514048342558691bb18c8e2fb7318a5d", + "d730f570722b73446476e28fe2c85f1fb908cf4898025d519f075bc16c0db7c5", + "80f8d5eab655c7fb355eff1463026b61b092594a971e573cbc71f2c6fd237821", + "71dd7c5a41d980ac1830093a2ba8be6de21cfe8a07463deb4428bcf1382326c0", + "2edf5e1cc806779da45ac34d5c0db4f2382f5e08be2c31fcdfd93be10f4f6c08", + "42938f9080674ac1ce67acacb5727ac6bfc7995b29f02e324e59f291a5bb4751", + "f66dd6200ca2c9c8af94a1d6c5531e6d332d8a91c4e00c21fc1248cede72d88c", + "8fa61f98513bddd06b910456ee60635081b1627a0e01c67e8312f0e0fed42145", + "e9c514547cf5b5bc53634a11729b9eb6ddd217568a49772e18e020d0d5ddd222", + "0d938c753a044fb4bd49d3b2002e951849e32ad1f37a7058750c627f4a94b03d", + "048a524eee29bc8de8ea1f76255b402b63f9b4ff050aec4af6c6ff1ed7cdd62d", + "5f9e38e5deb73068b2a49a460adc7b98f64e72729ffe7d63662e25ea421d7399", + "5c22633e12ba5892f6c2b1dfdbe1f85bf5c22208bf1399bcefb5f9357db7c84b", + "ca879fbddc966d7adad62121490505b5b95cbd545753fb900ddf50a6a6588828", + "f8f95252b9c7aaf321303f19c496bfa2af90104416a10771ca02d7daf9204daa", + "3c0d9c8eccc7fa80234995db0869947e7412e5443851495e89259f92bee8e5b3", + "3c51e08f3dcfdcfb2a3b989ba51c16e820ca2f3bbd34c913d8ffd75d36447bb2", + "539e2f424297ac39ac5090463289f332a4555c6243315c3e2c241c5a2534c08c", + "6397ee82dffd9b8b2dca1d64ca3cc29cbd07e67bf095ab47273a7a70b59cbe93", + "6cd018be9d0c3f8a224c405de0a58cc5bc2cac7b6e6f514089b036eee6b0904b", + "1979e6077b6132d9fd669788f5031d3f88107a857e35117054f9196a197e97ce", + "89507701e23077c93b98401ad75a8a30a77317952fca30ec28eef2179699f93d" + ], + "bsig": { + "s": [ + [ + "094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08", + "b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c", + "2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60a", + "e93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810e", + "b57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630c", + "ad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff02", + "0f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003", + "bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e", + "92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01", + "adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a906", + "7d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e", + "0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405", + "d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b", + "9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a994004", + "0331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b05", + "40d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705", + "e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb01", + "06374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d14600", + "1d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a0208", + "7c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0e", + "df3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c", + "6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50b", + "bb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e", + "8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302", + "bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605", + "d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a25950908", + "32aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f608", + "73f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca4609", + "9248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc1205", + "2a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50c", + "a90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd100", + "18ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e", + "4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05", + "d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03", + "296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d10184108", + "63d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508", + "645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0a", + "f1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300", + "a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b05", + "9b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001", + "fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0c", + "e21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804", + "824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0c", + "fc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f", + "94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa702", + "0850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601", + "a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700", + "dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf16507", + "4813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c", + "1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01", + "c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f05", + "56be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780f", + "a7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb04", + "2dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d", + "0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501", + "e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b", + "241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced06", + "8d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202", + "270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a", + "72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302", + "b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a", + "578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403", + "823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e", + "0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70b" + ], + [ + "b3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801", + "e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0a", + "cc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da069afb8b06", + "e63c93d5e36e33e4ba022fe2352061f55f4e5115d048e4c36ab56d9d558daf00", + "15a8ec96436dd72e4ee610e0806adbb382a7ef9d3dd9774ed070abeec25d460e", + "304ebc4b2d097bfa0f6982abe3f7490f0374a278f1464505e0233a1195e7af01", + "ae7e06f814cb7266ca60722c53e2d3eb66a5a1f8cff91bad0da26b5716ab7106", + "fceb4b4a1a613014804c5284e7c8d785512730c7f109d4c225f9fa00cff4d10c", + "f7f120e75d1c37c5e5b4e9117f30fb30c3cbcf86a33225fb4f63f2a207168204", + "5dfb733111c50730556e62bd49a471b71c9421b312e8805358ba53af071b6103", + "99e917fa1b4687093313e330b453dd98b3f7866bcbe737f5e11f4bd1c0043b09", + "75b84a92c4cfef856688345ebf4ba84b95aaad4ef573774e448d7ea93f6ba109", + "37ec41a174bc0965d667918f608b6358d424628859d0933cccdf51ec7d55f906", + "438342b45be0e9796bb57225e7bb184602fb97dfb48260bcfa427f787a759c0f", + "5a2a8929697f47c854bebca692175efb36276a5f9fb92437836e65d87bb3b70a", + "09663579a35c801c1aa654eb28517013b5e877a003be057b875b35df2fe22709", + "0c58358156115c37d72c290912ee288600cdabb492312227fe659dc238df6e03", + "def5668fd22573e63b245586cd10bc66d68942e01bc9dfb9d152a1a7898ce002", + "daef4fbe574991fe37e0b8b69ff269e098b7b1640a47b370fb38f410eb0d5703", + "536bbe4bcc900df2a526d80a413b9c9ed3ab75124ff7198d3a44e2ca0cce410f", + "edf5f2ef7b33f93b43a26f4d30751f2bfb4b13ca42a3105196bb17e5c2653e0c", + "00adac72a44baa60b2d2411fb239d0d0dfd68251d362bb04ab0a3cc169cf6104", + "9bdc73cf11aee0b90f7b26cbcdab987873ab69542cdef0bb8c125da7034e320d", + "c9bc4f84a9827cd927165c3455ae9fd8f5325e606b58e3cb6db67bc46b434c0b", + "f78b1ef5530c0996205b419dbde1a8b1a9ed21abf38e3c5a16d34d6091a1e406", + "5c79fe8d78b95184e779434993a52c23e6e9c6db90e9db5b90e97a29def79302", + "922934dd5413a93d183cd83ad88ab3d439ef39753c84588a8269a74f0d197208", + "0fbb35971f624d31041842ed430c70d09171c20f0f00e0d0c4e8ef8149b9af03", + "8846db4d5b2d8a586e5f1b34803e52cab395311ac5bac25f2ce3cb2203d80703", + "54da89b48c24c0da18b4db9f87d210388ec2655f48fcc42bcb78c367bef2aa0c", + "543f7e562adc23a6b22445ca1389156587c0af0fa16aeaa1aa5e6508228e9c04", + "620aafcb6408e66ad86f0258cdd04a376ca1f41a623e7cf36a800d5da9158302", + "9bb46e73b038d27c370d948571c442f65302d09c1562ba3ebde7e7e803f32602", + "799726043b23a4eca27e62986009cc9e4419fe95cece9504cabdf6d92f01f105", + "beee7e9ece3fb49b495922bfc23337e53baafebcad53a250b66d168a87b3a406", + "4440985fe7becf778fadbbf8eb4d01c024039a4cdfd5f27edab16f4702b4c205", + "90dce3157b8c727316ba8c25de3cab3112c385f8ca0895e5c775e5f34c17520c", + "c8cf80d28be593f186ad7f6f4666f702df2566c16a5d5e1aad21694774f41f00", + "03fbcb4502fcb5e1deff0fa2a458f6ff88bbc91cfa2e21cb42b0077da5d43007", + "d68610e05172ac51036ab6a52140e14e1707dc325fe8efcc39c74883780e9404", + "bcb78fdf38b2763ddd7458db79ffaf28305aef1534d7ec4112316a8acfbe3504", + "277ef2e7dd36cc72869f1d53689d904cabd23085165d2933ea15fa491da5ed01", + "f00468c47ba2185b241405d89952922a74c02ceb8617543b70f353a62f017b02", + "3dc53beb7b858019493aa1d2bdcf2d6eb7683d453d62dfd391b189c296bee006", + "d7c85bd22dd02bf6448cb7af0460cc319913f9fbc357875bf5396f9e33fab505", + "0aeb928f76e42175bb300f1e254f981d8bf22079c66b03169e4d04879495e80a", + "395187afeb4d7e73edf5d4320ce49cc4774560697ed4c58e8b4214fd03d3e808", + "d666a22ab6104dbafa95c6c0db8af94ea49f24fbd5c36d00fc009c451ce09708", + "3f84b72f2e5b9b1dec327c4f14d43da764ad36c5e8e8389453755fb77157dd03", + "f9e97ed405828144904fb6a2f578e1d39f7f30621d9a483291ce50391c53690a", + "76b217cdc7d7ac7a1ca3add04d303a920713db149ca022ad49a83e72ea7fba0c", + "6c29a44633d1c056ddbffcfd43cd1ad4b810cddf796ba994ddda1d63cebf8705", + "e743ff022e0ecd99912346a9c2b892e5e4ebc3e52b98e60262d96f3367717703", + "49b69d7560d297a73658c22441c5ce1b294065ff4f3da35227f7684e3a445001", + "910975c311f461abd40ee2b56ab71f105770a4bc1a581b1221495023353aaa01", + "d4bd115f5a8d9ba37b3b41dab0cb181f07e60624b755e79e1abfc7da826a6d08", + "cab5edf981c43ec78bdcbf74313af449bbfe3b4806a205de6adbd2249015aa0b", + "0efe4e92b667f66f2b8899084941fce413edc2e4a5ab44c066236c6b2a527707", + "fe52311427d13f55a87cb3a361b196e0faecc106ca6bf40e968b057f64cde106", + "b2b446d99da75974b26266573f1a641b4c7b477c61b370116d63267471132806", + "f9bdb81ab2a81f03165e2a5b2d32f1f2b2cbe86d95e3c6f8bf8ac6a2296ccd0c", + "2e6412ed8f74082f073b6bc2dd63e59ead35112c5c9362610a6561531b931b09", + "f21e5bef71a01433b29402e008ecebaadda2bd8bed28d0cdf910ffd573434c0a", + "c5781c5eee63cd99e97b5b319cd3925607dbb371a360e2d5e070f161a063f109" + ] + ], + "ee": "46793313125f55f30bbabb52e61db65e5ae507cef64f08f501417edef3da5e02" + } + }, + { + "Ci": [ + "524227a74d9da9b38d2ddeb8649eb9d46bafca1453fb6ab89112544597bf2dd8", + "55511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e81048", + "40abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739e", + "dc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be", + "46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34", + "893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e310", + "6117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1", + "e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b", + "0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b", + "165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479", + "e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e", + "302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b", + "6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f047", + "1ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee0", + "94a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c72", + "72a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8", + "b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8e", + "bec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125", + "d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e", + "8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939", + "f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462", + "c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e50", + "5e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9ef", + "be1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c", + "8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f2", + "5d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189a", + "c688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61", + "459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f76", + "98bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4", + "030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda8", + "8b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0dee", + "fbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505ea", + "baa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8", + "609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284b", + "df8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150", + "d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc855", + "8fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65", + "a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa6", + "8a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b49185", + "98a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999d", + "c40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9be", + "c704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f73", + "2b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0c", + "a7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046", + "a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04", + "100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce", + "64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb", + "6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916d", + "e7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c", + "99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb", + "256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee4", + "49da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd", + "37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3b", + "f7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da", + "3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae", + "0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda", + "2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7a", + "fd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c", + "3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0", + "127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693", + "bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043f", + "aba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7a", + "af0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c", + "7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a" + ], + "bsig": { + "s": [ + [ + "d1d07b998118930c481260cffe5d098a81048b0853bc957f61c376a392bc740d", + "4bc083e9bc4b62ee04daee640c5339121174af2f2c3c78d716d97497c0518d0a", + "7468d5ab25b59fab995c815f65550d52567ab22ce7215eadb2a5bf07ff899100", + "7d381ad3b18f9af8307e97282b681c37a74cc1083bc079e49b79d08e8d15d602", + "b65d2f153c27e54407d80a4dbda35acc29220996375f7819919ebd9ec6999a0a", + "a1ee2b5a2f8cd95c1ed40ed310ec102b872a2f0ae40a383036398a467a845003", + "2b8dc50fb3aead263d9004dee7f10e12aea154ab97f04f4a016d39b97742d00c", + "837350002903c83f056c5d64316c7aafd83fe355a64802fbef14516b197dfa00", + "1695103d494c48359e58bbcd358b0eb20d1ef42e3184594fcf51443ad3cfb404", + "7ced01f064c638698c690b9179b61aa68d369e35d5ac8bc65dae702cbc74890d", + "cbedb029dd98055213866576a3d5e0617d063dc2470e8d8e5b83574b5687b209", + "90a81ccea7787be7d38ba9f21b5ba3443049ddd82328d4e8fbe6b2817bf9380b", + "46f9e643f6a593081f7f1d5d97c69b0fe58c9cc6074c373f8a71c90a12604b08", + "84716acb458e7dd490b5f0057d917e586c69cafe9fbe47f69bd64c7e61b5b80a", + "6825520c5327772c2ea8d7450c3d764c4d08cdaa1562a1d990eb0878134d3501", + "2482c2f5c4ec38fe5161e00eaf669187f2ec98584a6735a9341cf66b4827dc0b", + "f512a1d4ead70ad08445b1a9af017ac6b375f9c5f0103d0755e1c13046d2c102", + "a041f2c79577ed00c330e1a7c8e3a6d474dbf6d9ef9c4e3217cc3fe611967a0b", + "c5b96d0815ad46b04d043dd39ade61805d12aff54f4f60ca89f2ad77433c3908", + "22c633e75cba3a385958fe1015c502315ce63e440baab882dbeb4ba118994103", + "676b9e83e6f6d2d53db075f662469e733778dd8082c36cb246b03f9701c58001", + "5d55bf2ca24a02bd3888eb95597f654d04e84fcd79aefa00125091db32898f05", + "45a6b7695838c01d0be144395d40af9d8e69b0ce2f1326bcc3d57a244be24000", + "31cbf5ff387e2439cc3b492466343d35a6a7ae244c0abdcada6cd0e37fb8170a", + "d2c7582aafefc50fe74f4f05bd1b1aa0a0391c18b2e54f7b8078517ce5072305", + "e4a509c5ceb5165a148a78c67fc1de9d97d4a7c0a9e6d152b47e2320721c200c", + "830a015279a3d84acd32f7070af60f82db8fe6f035973f4250dba240999a5005", + "f96f27a773fc9e7b68558f5f98699aeee07c645097111ec1aaabdefea7746904", + "82af54c967848b2cd65ebd7f9bd4657292864f46e4620210ae41c1642357eb09", + "85f8b1dd6e9d87fcca833964873224c9128e899acd85f74ee625809e9f045907", + "e717b4e15183bf2fc55a1cae983526e833255b1a38a532c3319ae67428e6b604", + "f1aa90a29afe47935795d9b629abe0654a9375268d11f91a7f0e13a74c9a380a", + "e66988a69672f086bb939df9590f0e495364a72c8cb298e98bf04a0709722f00", + "449422874e793b6029a95e26724609dc06fe9086f6ae690b34dc1b29427b4303", + "50a43678dfb0ddde1db147da17c2e6b3fd44dbe186dabc204f47ab241c2f560d", + "4c103099635d550200103c05232988f48838d4b56b401242852c4be6ef991203", + "fa32db1bac264e7663b866f21314c05d3eb006a4fc2e00fabf30b4197b5edc06", + "b8777c5560b10549622a28ef19132a9e3960f54f3c449c3039985b24be69e80f", + "17df718ef65afb8fbd4b1d6eb3fa982590962d8863720a6f7080f6d73251360a", + "62b8674d157eb1a2d313631aa446635ec453090c75cb7470cbe60044f329130b", + "829cf76313e13a5a58dd5378ba3d1bd7c7a6b931e63f233204c7caf9d4fb8d0b", + "036910d5fd088dff93e7f6a43888096b38b5e0d39bb94b2c570e94258ba8f601", + "43f516aef8442b816bcf88ce21eb62bf58874644dfed54d13ecaec398846950a", + "01b76ab69ed1324cdda07cf14831bf6554f1ad9952defc2186c66da67a626504", + "9e01bb01a69c67b81bc0977ce81b13cd3058ca82bc212d17514ac19cc40c9703", + "cd8a3c8253d0163d494a9bce334b798aaeddece4c546509ed61953f8ed357c0e", + "861c7bd6ec632e7831b57a9d796b0513a715ab7348db38f3dcfc8474a82b8107", + "6e69287de84cc3bb1e8326f467b961f0dddcd70a9d104cfad472d3661bada40b", + "1aafdc9b6fcaf21ba61e8e5367aba0374fb927c375f90b87350bb0e0dbd8e105", + "bbc62c5fc32f99f4bed49baa2f3db5a4705c4d2feaf3cd244d4d28d6f6a1400b", + "5384e8f695e1449db4ef23f7b77618ed7a366eef3ae38d0b36aa19541a5f1d0b", + "26c006bdaca798027322f8294933ad9d877fb79cce3cd756b94ed2cd311cb40d", + "b8cd46406725c9e7fd92893cdba0d1001796a286d9b18707f4ee433f90665607", + "f60f5caadfa08817e1ab0f440b26dfb5163469c871bd7e0bfda5597597a2d402", + "65d270c8303c48231c97b1807ddd444105aa6e195df4c208a0d07ab7721e4909", + "6e2b8edcaab76fc8729fd3bab94895518e3549dfcfe5238ef3f92e912e23bc0f", + "032af4ad5abedd0c10613e50c26c9522902074202bd2c93ca60a7fdf4c5dff06", + "e27594188bd5c6fed2874df4acf8b6d2f412959c2f274ffba18d040909985f09", + "038124541070db29455f8d58c33323798cb03cfa2dc0298a856e64d4d7eb190f", + "9c47bbbf7c075e8cc3ac5f980cad86287d1940dd8dc0ace92241ddb49d454605", + "cd9229fe4080ca8e4b34bf3c35c3b95436d64f61d321a30f402f2695b6452d04", + "f4aa87a45504333b39c487f05fab5ac541893bcccaccc27313293c20da1df10a", + "1fe1edcb7962bd4c9b211472947196e01ee99d2d3f518710241dfc5f06a3ea0f", + "53b36bc3696808aeba745a58c8c8b27f5bfeda5676a95596cfc24e7aebec110f" + ], + [ + "851e464baf54513501af9262618d44cb81eff6d451d2ef90868a0b76fee1ae0f", + "2392eca80014d625cf976c763f86186cf984943a0324bb38b8d23d97a2dae700", + "2590aa56836825bee4770d74ded76b9187ed26ce8795d96e4c211056b7d8380f", + "3e2ee2b7f4b37342968ebb1706e85233ad8fa15a39cda0417f86dacd35829a00", + "53eec03966ac62876503efabf4407da1fd5eb13b542cbfa9ee755725cfe9c30f", + "1235645468cbe11d875c5ebf96363b25abefa920867af543bcbc4148d5f1e50a", + "c520452ced483e258ffb7ace14531c5c7de112f4827c594cbdaa373e52f54506", + "833713e2d2fab34ecb5228c7c1c1cf402be69654c5ace0376d34ef7339383f0a", + "2243fba2d420eb964593f871e18f5764824976f2f4c28f8624d9921237d45106", + "8ed944427dd822c8789064dbcdadca5c14b766706c7c3ac034875d44e94bf604", + "7e1e989bc3506445c88c08b7805bf5a9ff3bf1636b93e98f039cb762d667ae02", + "eca0fa2e30835f037dc359ce12edaafd46f064e0aa513d1e3239e0b63af87909", + "0a56c3d72748837b84bbd59a55a15c15b4e44d110532a020b9da108bfcf9530c", + "3e1a49bf0c90143ecdd1324ad2beb6a4e1d033f46ceb918f4259d5b17bad3304", + "e40fa0c77edf3afee63799c94b470a4dc3c89d18a43dc8dcd31aac6d33a23801", + "a3548cd48aae4fcc39d0c2775ea1a26290f6d7c6d49506ad8772b1a356a8170b", + "d0be3d0d451aa618d6312d6be5a86a2a398ad051a7e5660a454a4c45da41630f", + "c9e3af071abd46b85a07749d1ee6287a4afb898f7c49655763e34a9ab47b9d0d", + "750e5c0b8526960a576d55862c3e75f97716f211903d808ec860f41361129c0f", + "c8cf440475e64a92338a37ccdef482ad7d9ad75a0a94bfb9007db701548dad00", + "97312ecdb25219b47f5f44a707846ff2c180198747102dad0e37f3bdda0cba06", + "0aa8fafcabd48672879641167142c0024458aa118c2b6189ff95812c56515c06", + "adf80b43718fe494419e883abe0f33e36d4e18f6cdfeb8edb121b81d0db1770f", + "2705face099e6bbdae0cd485d486a8c2018d49bfcd956704f669edb352807c08", + "6c9edd1d0fac0eefed63077d6022aff763c8d34e51d69b26016740638b477606", + "f68a07a3089aca2204035f772b06945823375fff78fd21fa554b16688ecb210b", + "7291f1617493c6913bae6c90eda5935cc53a50c933e7b3b453b68f3871885b07", + "3c7a25563a66104e4dc28007990565ec9e2a3382f9924c6a63953af4929d4d0c", + "d3282c0bb4c5cb3f2aa4c1d88751f15a5cb7ae8687034b6ab38d46b76b0bcf09", + "67ba42db29eb1d7c458e923e6c6474e79ef469e92805b30a3a6e6e9101c53e0c", + "e747701d014c338171df6ba79f8aa6234a49e78a8039e2ea9b671feddffa7d03", + "cba531d055be1808d6fc2a6e229087b77a3be35e40ab4a6f378c216ea05d1900", + "cd1aa21bf3bf0afe40e95c406000b49a429bdeeb563f32b87bedc1adc6e5e501", + "c051a69901925053f064ae1e8054e34500b97eff9f974292eafbbd85715c2500", + "e45a20f16556e09b7bb530764582a59ec40a4af28d95e6fdfea3f10ce35e2b04", + "16e209c92a5045b8d7af4762cebf2d6d97b8960d9e9565e0549a2488a9e8b50c", + "985f5395774fb807d49a0f84e0983fec0e32fb5342a3704c5d198dddc945d806", + "c3262dc93f96a1b1a09c2bd461f324c068aae59d5473deba8ba6036ad96c670f", + "42667ada96da901567e544157b12f56159bb7c3f83b90abd1b9e7eb948f2e702", + "c9eca93744830f0dff8b7bc73a1458b0f8d40e679afd1e22b09c93f2c9729602", + "c6c81026f8f02991c094a38ed0e6062508974a8bc69e8d6e6741a45eabc7db06", + "65f8a2d879de17fdaf7ad1b210f337b90aec1c1c3676f244aa3852a77c05db0c", + "05b9936bc10f0a327602aa2109bdd115f8a3e1081c1b0c56649638207d2dd908", + "6c6ab885512fec9650ba288df5133c9dc87cb7fe81919fafca783f1252c29303", + "32559bbafcdc830223837a7b78a7ce137e9f4a4e3e7d1195a494fa6d0ca9a106", + "59c0df4ee0c9db91dba17acad08e5f6ccae71012e2c6e4b37894520fffe9620d", + "66a72a1e4f04e5fa2abe93ef7f4b2e9e837c9e0f9d962b5c80f5e83408746103", + "58576c23403b2d742c587e3827d17c6f41b31f9da4030207aaa2f4c32059f401", + "7bfa4c324be2ac00a0aeabc7756ff2e16bdf39ed6b0c313f01b1acce8bc2b408", + "a0cb3eaf9af9b68dd17f258c0bf0c536b19040d6bebc0a9dac7fbc59398b5208", + "91a741c4183a851c1cbcf3f78f30924d85ea2d907a6ac8db9a1455e4c672800a", + "88629853fefb86800fec6631d77edf591d56199d5980bf3c86d9254773e02508", + "be6a488b032686134049415bb194ad11ab8e4b1e6304274091beccc795e7b604", + "649f106b395bef6f30a1dca330561709cdf7295557410d040543c279e169a908", + "dc3d94c4809f0a7dd92ad8cefb014f5d47681b21d6b955fa894eff5b57cc5d0d", + "1b08c01c4e892af363e6560a02212edc1fa645cdbce380b3e010d3c7f473960e", + "c86e8ab8d2c235f594cbb2c2fa62f27b4ea4ab9796b8534688fd9819ea529c02", + "3ab370c6e482633801e3d588f3a4433cef5aaeb633547e922dbb35410af8a803", + "156b64daf9633636063a37c4adc43e492023e09137099a715a690a480119f60d", + "9c2f1a110a6590ff1b7774e0fe3e4a7419226448634c129aa1213e560814cf0b", + "28d28fbd4029688f0d920a5f45c2a06bdea6c38f731a8bf741ba106bb8a3ac0e", + "35fd8c015995d55c0733a18e66705dc84dab552aff7d2ae654b903f6fc9f1305", + "da5a5bafbfbb22df241e211416fa902eaec589c7a22d6bf160a884e5534c5e0a", + "685c312527c037f3196d4122f4d42c5a683d0e555af2a271d8d68faea80dfa04" + ] + ], + "ee": "bc27bdbca4d4b3c14ae0745ea453174a6e6e3b3014dbd23ac3c8a413b5e29d08" + } + } + ], + "MGs": [ + { + "ss": [ + [ + "02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0a", + "de13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05" + ], + [ + "738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae504", + "6b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0a" + ], + [ + "f06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f", + "5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea261507" + ], + [ + "0aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a507", + "4932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40a" + ], + [ + "b801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d", + "6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d07" + ], + [ + "6ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101", + "c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec8408" + ], + [ + "10b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d258804", + "83bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b00" + ] + ], + "cc": "8187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908" + } + ] + }, + "ecdhInfo": [ + { + "mask": "417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f7808", + "amount": "64294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b03" + }, + { + "mask": "67826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c01", + "amount": "0db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301" + } + ], + "txnFee": "2230540000", + "pseudoOuts": [] + } +} + + + +send_coins.js?2:504 raw_tx and hash: +send_coins.js?2:505 +{ + "raw": "020001020007d8fa7ef1f42ba3d85587de0499da67e8af019a940fc0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b020002105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b10002d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d312101c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df301e0adcda708417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f780864294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b0367826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c010db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60ae93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810eb57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630cad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff020f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a9067d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a9940040331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b0540d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb0106374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d146001d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a02087c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0edf3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50bbb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a2595090832aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f60873f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca46099248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc12052a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50ca90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd10018ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d1018410863d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0af1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b059b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0ce21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0cfc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa7020850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf165074813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f0556be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780fa7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb042dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced068d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70bb3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0acc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da069afb8b06e63c93d5e36e33e4ba022fe2352061f55f4e5115d048e4c36ab56d9d558daf0015a8ec96436dd72e4ee610e0806adbb382a7ef9d3dd9774ed070abeec25d460e304ebc4b2d097bfa0f6982abe3f7490f0374a278f1464505e0233a1195e7af01ae7e06f814cb7266ca60722c53e2d3eb66a5a1f8cff91bad0da26b5716ab7106fceb4b4a1a613014804c5284e7c8d785512730c7f109d4c225f9fa00cff4d10cf7f120e75d1c37c5e5b4e9117f30fb30c3cbcf86a33225fb4f63f2a2071682045dfb733111c50730556e62bd49a471b71c9421b312e8805358ba53af071b610399e917fa1b4687093313e330b453dd98b3f7866bcbe737f5e11f4bd1c0043b0975b84a92c4cfef856688345ebf4ba84b95aaad4ef573774e448d7ea93f6ba10937ec41a174bc0965d667918f608b6358d424628859d0933cccdf51ec7d55f906438342b45be0e9796bb57225e7bb184602fb97dfb48260bcfa427f787a759c0f5a2a8929697f47c854bebca692175efb36276a5f9fb92437836e65d87bb3b70a09663579a35c801c1aa654eb28517013b5e877a003be057b875b35df2fe227090c58358156115c37d72c290912ee288600cdabb492312227fe659dc238df6e03def5668fd22573e63b245586cd10bc66d68942e01bc9dfb9d152a1a7898ce002daef4fbe574991fe37e0b8b69ff269e098b7b1640a47b370fb38f410eb0d5703536bbe4bcc900df2a526d80a413b9c9ed3ab75124ff7198d3a44e2ca0cce410fedf5f2ef7b33f93b43a26f4d30751f2bfb4b13ca42a3105196bb17e5c2653e0c00adac72a44baa60b2d2411fb239d0d0dfd68251d362bb04ab0a3cc169cf61049bdc73cf11aee0b90f7b26cbcdab987873ab69542cdef0bb8c125da7034e320dc9bc4f84a9827cd927165c3455ae9fd8f5325e606b58e3cb6db67bc46b434c0bf78b1ef5530c0996205b419dbde1a8b1a9ed21abf38e3c5a16d34d6091a1e4065c79fe8d78b95184e779434993a52c23e6e9c6db90e9db5b90e97a29def79302922934dd5413a93d183cd83ad88ab3d439ef39753c84588a8269a74f0d1972080fbb35971f624d31041842ed430c70d09171c20f0f00e0d0c4e8ef8149b9af038846db4d5b2d8a586e5f1b34803e52cab395311ac5bac25f2ce3cb2203d8070354da89b48c24c0da18b4db9f87d210388ec2655f48fcc42bcb78c367bef2aa0c543f7e562adc23a6b22445ca1389156587c0af0fa16aeaa1aa5e6508228e9c04620aafcb6408e66ad86f0258cdd04a376ca1f41a623e7cf36a800d5da91583029bb46e73b038d27c370d948571c442f65302d09c1562ba3ebde7e7e803f32602799726043b23a4eca27e62986009cc9e4419fe95cece9504cabdf6d92f01f105beee7e9ece3fb49b495922bfc23337e53baafebcad53a250b66d168a87b3a4064440985fe7becf778fadbbf8eb4d01c024039a4cdfd5f27edab16f4702b4c20590dce3157b8c727316ba8c25de3cab3112c385f8ca0895e5c775e5f34c17520cc8cf80d28be593f186ad7f6f4666f702df2566c16a5d5e1aad21694774f41f0003fbcb4502fcb5e1deff0fa2a458f6ff88bbc91cfa2e21cb42b0077da5d43007d68610e05172ac51036ab6a52140e14e1707dc325fe8efcc39c74883780e9404bcb78fdf38b2763ddd7458db79ffaf28305aef1534d7ec4112316a8acfbe3504277ef2e7dd36cc72869f1d53689d904cabd23085165d2933ea15fa491da5ed01f00468c47ba2185b241405d89952922a74c02ceb8617543b70f353a62f017b023dc53beb7b858019493aa1d2bdcf2d6eb7683d453d62dfd391b189c296bee006d7c85bd22dd02bf6448cb7af0460cc319913f9fbc357875bf5396f9e33fab5050aeb928f76e42175bb300f1e254f981d8bf22079c66b03169e4d04879495e80a395187afeb4d7e73edf5d4320ce49cc4774560697ed4c58e8b4214fd03d3e808d666a22ab6104dbafa95c6c0db8af94ea49f24fbd5c36d00fc009c451ce097083f84b72f2e5b9b1dec327c4f14d43da764ad36c5e8e8389453755fb77157dd03f9e97ed405828144904fb6a2f578e1d39f7f30621d9a483291ce50391c53690a76b217cdc7d7ac7a1ca3add04d303a920713db149ca022ad49a83e72ea7fba0c6c29a44633d1c056ddbffcfd43cd1ad4b810cddf796ba994ddda1d63cebf8705e743ff022e0ecd99912346a9c2b892e5e4ebc3e52b98e60262d96f336771770349b69d7560d297a73658c22441c5ce1b294065ff4f3da35227f7684e3a445001910975c311f461abd40ee2b56ab71f105770a4bc1a581b1221495023353aaa01d4bd115f5a8d9ba37b3b41dab0cb181f07e60624b755e79e1abfc7da826a6d08cab5edf981c43ec78bdcbf74313af449bbfe3b4806a205de6adbd2249015aa0b0efe4e92b667f66f2b8899084941fce413edc2e4a5ab44c066236c6b2a527707fe52311427d13f55a87cb3a361b196e0faecc106ca6bf40e968b057f64cde106b2b446d99da75974b26266573f1a641b4c7b477c61b370116d63267471132806f9bdb81ab2a81f03165e2a5b2d32f1f2b2cbe86d95e3c6f8bf8ac6a2296ccd0c2e6412ed8f74082f073b6bc2dd63e59ead35112c5c9362610a6561531b931b09f21e5bef71a01433b29402e008ecebaadda2bd8bed28d0cdf910ffd573434c0ac5781c5eee63cd99e97b5b319cd3925607dbb371a360e2d5e070f161a063f10946793313125f55f30bbabb52e61db65e5ae507cef64f08f501417edef3da5e0202836fed5f531a64dd41aad00a1dc39ec13b49d1ebd7a2a6b5867d7d71db0e50a393ec33f28531624bdfa3ddab00ab9239b2bd54ea858920f34c26430045898f52b419f7f04f7b3b8e604bf11b20ed058e8ecdeae5be4845aa5f14f16c1f953d8e653029b087bcd4ab9953c1937cee84f81c393285c8bc3341a5eb5bae580b5d55cf6f0ee2afbea795764bf5d4ed0735367a0bf65b6f69732449e3dfec547d93d22056ada1eb1c35c2ca1924e07c0e6120b818d6941755efc4dae89d0cc039762bf5e003763a01910d434d3f0c3e71af286c55d30c9aadb2a480b8d0938ce43c202968df06659dc14e713dd6b2899be1501718e3003be5220b7e90034244ab3e9db020600bdb9b2a17b9d87e150356cb3f3a7150bf305d03efb0aab95cf43e5cf124a8f5db6c37b81f7841b81a7cc5c2ed6d3b8124ff95f9981fc93d732dbf470403a44e0f46ffff9bbef09e2fedbf9b42600918567617ea28d8f42e6b1d764a340ea317a4462b1083062b29560d25f2b35a82bfdf000033f8a0db28e7314d4d784f6b9d0df70928cc30b94119cf02085884a237b2ff7470e299726d06f430f72c73a1727446f060a3d98ee0c867ea162c197bacce848ebc423f3dc6c4835e8bbce299084ecf73139b024c33f215bc132fe77cf326e2da2cd18b3008bf16447ff4b2a4a5679987564d369e8936270b4adb3128e6e88eb75205f9de36088fde3a7d294c7c38cf0d0bb02c1160a6695550423737668f1646ca65b93d95547097a2fa8bd70f74a623e5a3c9c3c6b70097bde54853b7517c76c0d36e59cabce4bb691bff14524d2a0eac2155a9af73a4e87f0d6ec67d4a27840ed0b7134fa93d751588a77416daca2b68d054a7e74dc752a9506090e79596c9b558f085853055d87852c251baa10a9607b86c009ba3ecba6ed2457d499128f26fe632c39e5ccc46fb4b7e225ee282d9883abaed6e531b4f844df3d5cb547152742d0a0744e40fb9284637a34dbd3a4ff4dae9a07904b36e539f56eec6ce7baf51ed8e8189b242effc3bc60908d6b62739b6d6e8206e4223160eb293eeea2992c0dcaf60c73632ce196441692c1f5131526818f5bff1cd83a75e414e595e13a082a47985bd505cf256f5ec516ae79c1117f9f7820594d696a7714d2151450127dee286f8d18de13517b24eda31f18547032200489505d2f53101c962641b7275b8a48296f339402f7f48ac2674f669841615752eaf495a6dac6bd4d6cae58be8c779890d201904221ef4a1a0f87c523b027fa43f52f37ea06f8a19070fe04f7533dfb529d802a22d7d299f3abb6974611456060915173bcca396febe1bc943150131d94a2406565957950fea0f11421c7ae2b7e900f46a8c947b7e5e724bd981c00a1c93070a6dd51bf406b876c5141f7fb5bd84cb788dda0b822bdc4263e500c85c079aee409cc00e411628ef38f9c948a3ca7989efe4592d7702aa846b3a1cfbfc5e4a1801ad6a63386bdaa004af3bd3182a082ae5f17d30862c5a0de12078aba63b51ad2ad9c695257e90cf997464da236ace2f90020547003d30e8f63998ab705aa56fd4031397d36526e09faa72c4789380a4df227104cba21542a16f64b19ba96a84103513d94336e5077c76eb3a301e3bcdf507c9a0ab6b7f98d6533d6cdfe04f72d24042472cd55b68ff9d9e1d8155c21e7df17066751a99ccc5147d0a828053f77b059879160522a71a95f14e058a51bf443264700870541f074c182b1ca3d1250b6e03377dea966eeba2bd820d680f35dc7226e4a8faebc88da83b49da2b5e7dc897dfc7998961a5fb82372fc03ddcbb6043284c80358ec0f2bf9cba7e3bbce8696dd16bc32819e8182b5528b3ac82df464b286b944a13704dc17befb80ed61d24a2b2407fd2bbf0fd1e547b1437db807e113c9e514048342558691bb18c8e2fb7318a5dd730f570722b73446476e28fe2c85f1fb908cf4898025d519f075bc16c0db7c580f8d5eab655c7fb355eff1463026b61b092594a971e573cbc71f2c6fd23782171dd7c5a41d980ac1830093a2ba8be6de21cfe8a07463deb4428bcf1382326c02edf5e1cc806779da45ac34d5c0db4f2382f5e08be2c31fcdfd93be10f4f6c0842938f9080674ac1ce67acacb5727ac6bfc7995b29f02e324e59f291a5bb4751f66dd6200ca2c9c8af94a1d6c5531e6d332d8a91c4e00c21fc1248cede72d88c8fa61f98513bddd06b910456ee60635081b1627a0e01c67e8312f0e0fed42145e9c514547cf5b5bc53634a11729b9eb6ddd217568a49772e18e020d0d5ddd2220d938c753a044fb4bd49d3b2002e951849e32ad1f37a7058750c627f4a94b03d048a524eee29bc8de8ea1f76255b402b63f9b4ff050aec4af6c6ff1ed7cdd62d5f9e38e5deb73068b2a49a460adc7b98f64e72729ffe7d63662e25ea421d73995c22633e12ba5892f6c2b1dfdbe1f85bf5c22208bf1399bcefb5f9357db7c84bca879fbddc966d7adad62121490505b5b95cbd545753fb900ddf50a6a6588828f8f95252b9c7aaf321303f19c496bfa2af90104416a10771ca02d7daf9204daa3c0d9c8eccc7fa80234995db0869947e7412e5443851495e89259f92bee8e5b33c51e08f3dcfdcfb2a3b989ba51c16e820ca2f3bbd34c913d8ffd75d36447bb2539e2f424297ac39ac5090463289f332a4555c6243315c3e2c241c5a2534c08c6397ee82dffd9b8b2dca1d64ca3cc29cbd07e67bf095ab47273a7a70b59cbe936cd018be9d0c3f8a224c405de0a58cc5bc2cac7b6e6f514089b036eee6b0904b1979e6077b6132d9fd669788f5031d3f88107a857e35117054f9196a197e97ce89507701e23077c93b98401ad75a8a30a77317952fca30ec28eef2179699f93dd1d07b998118930c481260cffe5d098a81048b0853bc957f61c376a392bc740d4bc083e9bc4b62ee04daee640c5339121174af2f2c3c78d716d97497c0518d0a7468d5ab25b59fab995c815f65550d52567ab22ce7215eadb2a5bf07ff8991007d381ad3b18f9af8307e97282b681c37a74cc1083bc079e49b79d08e8d15d602b65d2f153c27e54407d80a4dbda35acc29220996375f7819919ebd9ec6999a0aa1ee2b5a2f8cd95c1ed40ed310ec102b872a2f0ae40a383036398a467a8450032b8dc50fb3aead263d9004dee7f10e12aea154ab97f04f4a016d39b97742d00c837350002903c83f056c5d64316c7aafd83fe355a64802fbef14516b197dfa001695103d494c48359e58bbcd358b0eb20d1ef42e3184594fcf51443ad3cfb4047ced01f064c638698c690b9179b61aa68d369e35d5ac8bc65dae702cbc74890dcbedb029dd98055213866576a3d5e0617d063dc2470e8d8e5b83574b5687b20990a81ccea7787be7d38ba9f21b5ba3443049ddd82328d4e8fbe6b2817bf9380b46f9e643f6a593081f7f1d5d97c69b0fe58c9cc6074c373f8a71c90a12604b0884716acb458e7dd490b5f0057d917e586c69cafe9fbe47f69bd64c7e61b5b80a6825520c5327772c2ea8d7450c3d764c4d08cdaa1562a1d990eb0878134d35012482c2f5c4ec38fe5161e00eaf669187f2ec98584a6735a9341cf66b4827dc0bf512a1d4ead70ad08445b1a9af017ac6b375f9c5f0103d0755e1c13046d2c102a041f2c79577ed00c330e1a7c8e3a6d474dbf6d9ef9c4e3217cc3fe611967a0bc5b96d0815ad46b04d043dd39ade61805d12aff54f4f60ca89f2ad77433c390822c633e75cba3a385958fe1015c502315ce63e440baab882dbeb4ba118994103676b9e83e6f6d2d53db075f662469e733778dd8082c36cb246b03f9701c580015d55bf2ca24a02bd3888eb95597f654d04e84fcd79aefa00125091db32898f0545a6b7695838c01d0be144395d40af9d8e69b0ce2f1326bcc3d57a244be2400031cbf5ff387e2439cc3b492466343d35a6a7ae244c0abdcada6cd0e37fb8170ad2c7582aafefc50fe74f4f05bd1b1aa0a0391c18b2e54f7b8078517ce5072305e4a509c5ceb5165a148a78c67fc1de9d97d4a7c0a9e6d152b47e2320721c200c830a015279a3d84acd32f7070af60f82db8fe6f035973f4250dba240999a5005f96f27a773fc9e7b68558f5f98699aeee07c645097111ec1aaabdefea774690482af54c967848b2cd65ebd7f9bd4657292864f46e4620210ae41c1642357eb0985f8b1dd6e9d87fcca833964873224c9128e899acd85f74ee625809e9f045907e717b4e15183bf2fc55a1cae983526e833255b1a38a532c3319ae67428e6b604f1aa90a29afe47935795d9b629abe0654a9375268d11f91a7f0e13a74c9a380ae66988a69672f086bb939df9590f0e495364a72c8cb298e98bf04a0709722f00449422874e793b6029a95e26724609dc06fe9086f6ae690b34dc1b29427b430350a43678dfb0ddde1db147da17c2e6b3fd44dbe186dabc204f47ab241c2f560d4c103099635d550200103c05232988f48838d4b56b401242852c4be6ef991203fa32db1bac264e7663b866f21314c05d3eb006a4fc2e00fabf30b4197b5edc06b8777c5560b10549622a28ef19132a9e3960f54f3c449c3039985b24be69e80f17df718ef65afb8fbd4b1d6eb3fa982590962d8863720a6f7080f6d73251360a62b8674d157eb1a2d313631aa446635ec453090c75cb7470cbe60044f329130b829cf76313e13a5a58dd5378ba3d1bd7c7a6b931e63f233204c7caf9d4fb8d0b036910d5fd088dff93e7f6a43888096b38b5e0d39bb94b2c570e94258ba8f60143f516aef8442b816bcf88ce21eb62bf58874644dfed54d13ecaec398846950a01b76ab69ed1324cdda07cf14831bf6554f1ad9952defc2186c66da67a6265049e01bb01a69c67b81bc0977ce81b13cd3058ca82bc212d17514ac19cc40c9703cd8a3c8253d0163d494a9bce334b798aaeddece4c546509ed61953f8ed357c0e861c7bd6ec632e7831b57a9d796b0513a715ab7348db38f3dcfc8474a82b81076e69287de84cc3bb1e8326f467b961f0dddcd70a9d104cfad472d3661bada40b1aafdc9b6fcaf21ba61e8e5367aba0374fb927c375f90b87350bb0e0dbd8e105bbc62c5fc32f99f4bed49baa2f3db5a4705c4d2feaf3cd244d4d28d6f6a1400b5384e8f695e1449db4ef23f7b77618ed7a366eef3ae38d0b36aa19541a5f1d0b26c006bdaca798027322f8294933ad9d877fb79cce3cd756b94ed2cd311cb40db8cd46406725c9e7fd92893cdba0d1001796a286d9b18707f4ee433f90665607f60f5caadfa08817e1ab0f440b26dfb5163469c871bd7e0bfda5597597a2d40265d270c8303c48231c97b1807ddd444105aa6e195df4c208a0d07ab7721e49096e2b8edcaab76fc8729fd3bab94895518e3549dfcfe5238ef3f92e912e23bc0f032af4ad5abedd0c10613e50c26c9522902074202bd2c93ca60a7fdf4c5dff06e27594188bd5c6fed2874df4acf8b6d2f412959c2f274ffba18d040909985f09038124541070db29455f8d58c33323798cb03cfa2dc0298a856e64d4d7eb190f9c47bbbf7c075e8cc3ac5f980cad86287d1940dd8dc0ace92241ddb49d454605cd9229fe4080ca8e4b34bf3c35c3b95436d64f61d321a30f402f2695b6452d04f4aa87a45504333b39c487f05fab5ac541893bcccaccc27313293c20da1df10a1fe1edcb7962bd4c9b211472947196e01ee99d2d3f518710241dfc5f06a3ea0f53b36bc3696808aeba745a58c8c8b27f5bfeda5676a95596cfc24e7aebec110f851e464baf54513501af9262618d44cb81eff6d451d2ef90868a0b76fee1ae0f2392eca80014d625cf976c763f86186cf984943a0324bb38b8d23d97a2dae7002590aa56836825bee4770d74ded76b9187ed26ce8795d96e4c211056b7d8380f3e2ee2b7f4b37342968ebb1706e85233ad8fa15a39cda0417f86dacd35829a0053eec03966ac62876503efabf4407da1fd5eb13b542cbfa9ee755725cfe9c30f1235645468cbe11d875c5ebf96363b25abefa920867af543bcbc4148d5f1e50ac520452ced483e258ffb7ace14531c5c7de112f4827c594cbdaa373e52f54506833713e2d2fab34ecb5228c7c1c1cf402be69654c5ace0376d34ef7339383f0a2243fba2d420eb964593f871e18f5764824976f2f4c28f8624d9921237d451068ed944427dd822c8789064dbcdadca5c14b766706c7c3ac034875d44e94bf6047e1e989bc3506445c88c08b7805bf5a9ff3bf1636b93e98f039cb762d667ae02eca0fa2e30835f037dc359ce12edaafd46f064e0aa513d1e3239e0b63af879090a56c3d72748837b84bbd59a55a15c15b4e44d110532a020b9da108bfcf9530c3e1a49bf0c90143ecdd1324ad2beb6a4e1d033f46ceb918f4259d5b17bad3304e40fa0c77edf3afee63799c94b470a4dc3c89d18a43dc8dcd31aac6d33a23801a3548cd48aae4fcc39d0c2775ea1a26290f6d7c6d49506ad8772b1a356a8170bd0be3d0d451aa618d6312d6be5a86a2a398ad051a7e5660a454a4c45da41630fc9e3af071abd46b85a07749d1ee6287a4afb898f7c49655763e34a9ab47b9d0d750e5c0b8526960a576d55862c3e75f97716f211903d808ec860f41361129c0fc8cf440475e64a92338a37ccdef482ad7d9ad75a0a94bfb9007db701548dad0097312ecdb25219b47f5f44a707846ff2c180198747102dad0e37f3bdda0cba060aa8fafcabd48672879641167142c0024458aa118c2b6189ff95812c56515c06adf80b43718fe494419e883abe0f33e36d4e18f6cdfeb8edb121b81d0db1770f2705face099e6bbdae0cd485d486a8c2018d49bfcd956704f669edb352807c086c9edd1d0fac0eefed63077d6022aff763c8d34e51d69b26016740638b477606f68a07a3089aca2204035f772b06945823375fff78fd21fa554b16688ecb210b7291f1617493c6913bae6c90eda5935cc53a50c933e7b3b453b68f3871885b073c7a25563a66104e4dc28007990565ec9e2a3382f9924c6a63953af4929d4d0cd3282c0bb4c5cb3f2aa4c1d88751f15a5cb7ae8687034b6ab38d46b76b0bcf0967ba42db29eb1d7c458e923e6c6474e79ef469e92805b30a3a6e6e9101c53e0ce747701d014c338171df6ba79f8aa6234a49e78a8039e2ea9b671feddffa7d03cba531d055be1808d6fc2a6e229087b77a3be35e40ab4a6f378c216ea05d1900cd1aa21bf3bf0afe40e95c406000b49a429bdeeb563f32b87bedc1adc6e5e501c051a69901925053f064ae1e8054e34500b97eff9f974292eafbbd85715c2500e45a20f16556e09b7bb530764582a59ec40a4af28d95e6fdfea3f10ce35e2b0416e209c92a5045b8d7af4762cebf2d6d97b8960d9e9565e0549a2488a9e8b50c985f5395774fb807d49a0f84e0983fec0e32fb5342a3704c5d198dddc945d806c3262dc93f96a1b1a09c2bd461f324c068aae59d5473deba8ba6036ad96c670f42667ada96da901567e544157b12f56159bb7c3f83b90abd1b9e7eb948f2e702c9eca93744830f0dff8b7bc73a1458b0f8d40e679afd1e22b09c93f2c9729602c6c81026f8f02991c094a38ed0e6062508974a8bc69e8d6e6741a45eabc7db0665f8a2d879de17fdaf7ad1b210f337b90aec1c1c3676f244aa3852a77c05db0c05b9936bc10f0a327602aa2109bdd115f8a3e1081c1b0c56649638207d2dd9086c6ab885512fec9650ba288df5133c9dc87cb7fe81919fafca783f1252c2930332559bbafcdc830223837a7b78a7ce137e9f4a4e3e7d1195a494fa6d0ca9a10659c0df4ee0c9db91dba17acad08e5f6ccae71012e2c6e4b37894520fffe9620d66a72a1e4f04e5fa2abe93ef7f4b2e9e837c9e0f9d962b5c80f5e8340874610358576c23403b2d742c587e3827d17c6f41b31f9da4030207aaa2f4c32059f4017bfa4c324be2ac00a0aeabc7756ff2e16bdf39ed6b0c313f01b1acce8bc2b408a0cb3eaf9af9b68dd17f258c0bf0c536b19040d6bebc0a9dac7fbc59398b520891a741c4183a851c1cbcf3f78f30924d85ea2d907a6ac8db9a1455e4c672800a88629853fefb86800fec6631d77edf591d56199d5980bf3c86d9254773e02508be6a488b032686134049415bb194ad11ab8e4b1e6304274091beccc795e7b604649f106b395bef6f30a1dca330561709cdf7295557410d040543c279e169a908dc3d94c4809f0a7dd92ad8cefb014f5d47681b21d6b955fa894eff5b57cc5d0d1b08c01c4e892af363e6560a02212edc1fa645cdbce380b3e010d3c7f473960ec86e8ab8d2c235f594cbb2c2fa62f27b4ea4ab9796b8534688fd9819ea529c023ab370c6e482633801e3d588f3a4433cef5aaeb633547e922dbb35410af8a803156b64daf9633636063a37c4adc43e492023e09137099a715a690a480119f60d9c2f1a110a6590ff1b7774e0fe3e4a7419226448634c129aa1213e560814cf0b28d28fbd4029688f0d920a5f45c2a06bdea6c38f731a8bf741ba106bb8a3ac0e35fd8c015995d55c0733a18e66705dc84dab552aff7d2ae654b903f6fc9f1305da5a5bafbfbb22df241e211416fa902eaec589c7a22d6bf160a884e5534c5e0a685c312527c037f3196d4122f4d42c5a683d0e555af2a271d8d68faea80dfa04bc27bdbca4d4b3c14ae0745ea453174a6e6e3b3014dbd23ac3c8a413b5e29d08524227a74d9da9b38d2ddeb8649eb9d46bafca1453fb6ab89112544597bf2dd855511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e8104840abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739edc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e3106117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f0471ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee094a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c7272a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8ebec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e505e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9efbe1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f25d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189ac688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f7698bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda88b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0deefbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505eabaa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284bdf8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc8558fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa68a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b4918598a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999dc40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9bec704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f732b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0ca7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916de7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee449da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3bf7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7afd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043faba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7aaf0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0ade13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae5046b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0af06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea2615070aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a5074932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40ab801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d076ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec840810b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d25880483bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b008187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908", + "hash": "4da46a03139ab26295dba21cabcb5f6efc74b165f1df183e83d55169b6902f0b" +} + + +send_coins.js?2:303 13192 bytes <= 13 KB (current fee: 0.002230540000) +send_coins.js?2:313 Successful tx generation, submitting tx +send_coins.js?2:314 Tx hash: 4da46a03139ab26295dba21cabcb5f6efc74b165f1df183e83d55169b6902f0b +send_coins.js?2:323 Successfully submitted tx diff --git a/__test__/fixtures/mymonero.com-1530837258185.log b/__test__/fixtures/mymonero.com-1530837258185.log new file mode 100644 index 0000000..10d8d9a --- /dev/null +++ b/__test__/fixtures/mymonero.com-1530837258185.log @@ -0,0 +1,100 @@ +mymonero.com/:9 X-Frame-Options may only be set via an HTTP header sent along with a document. It may not be set inside . +crypto.js?1:1 pre-main prep time: 0 ms +modal.js?1:36 Showing modal: login +modal.js?1:41 Hiding modal: login +modal.js?1:36 Showing modal: idle-warning +modal.js?1:36 Showing modal: idle-timeout +cn_util.js?3:694 Uncaught TypeError: Cannot read property 'length' of undefined + at cnUtil.generate_key_image (cn_util.js?3:694) + at Object.accountService.cachedKeyImage (account.js?5:181) + at account.js?1:95 +cnUtil.generate_key_image @ cn_util.js?3:694 +accountService.cachedKeyImage @ account.js?5:181 +(anonymous) @ account.js?1:95 +setTimeout (async) +(anonymous) @ account.js?1:94 +(anonymous) @ account.js?1:105 +(anonymous) @ angular.js:9433 +(anonymous) @ angular.js:13318 +$eval @ angular.js:14570 +$digest @ angular.js:14386 +$apply @ angular.js:14675 +l @ angular.js:9725 +F @ angular.js:9915 +C.onload @ angular.js:9856 +load (async) +(anonymous) @ angular.js:9839 +n @ angular.js:9694 +f @ angular.js:9406 +(anonymous) @ angular.js:13318 +$eval @ angular.js:14570 +$digest @ angular.js:14386 +$apply @ angular.js:14675 +(anonymous) @ angular.js:10455 +setInterval (async) +e @ angular.js:10446 +(anonymous) @ account.js?1:181 +e @ angular.js:4219 +w.instance @ angular.js:8516 +(anonymous) @ angular.js:7762 +r @ angular.js:334 +I @ angular.js:7761 +g @ angular.js:7140 +g @ angular.js:7143 +(anonymous) @ angular.js:7019 +(anonymous) @ angular.js:1460 +$eval @ angular.js:14570 +$apply @ angular.js:14669 +(anonymous) @ angular.js:1458 +e @ angular.js:4219 +d @ angular.js:1456 +tc @ angular.js:1476 +Jd @ angular.js:1370 +(anonymous) @ angular.js:26446 +j @ jquery.js:3099 +fireWith @ jquery.js:3211 +ready @ jquery.js:3417 +I @ jquery.js:3433 +modal.js?1:41 Hiding modal: idle-warning +modal.js?1:41 Hiding modal: idle-timeout +modal.js?1:36 Showing modal: login +modal.js?1:41 Hiding modal: login +send_coins.js?2:200 Parsed destinations: [{"address":"46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA","amount":{"_d":[0,1000],"_s":1}}] +send_coins.js?2:201 Total before fee: 0.01 +send_coins.js?2:229 Destinations: +send_coins.js?2:232 46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA: 0.010000000000 +webflow.js:1171 Uncaught TypeError: Cannot read property 'handler' of undefined + at HTMLFormElement. (webflow.js:1171) + at HTMLDocument.dispatch (jquery.js:4435) + at HTMLDocument.r.handle (jquery.js:4121) +(anonymous) @ webflow.js:1171 +dispatch @ jquery.js:4435 +r.handle @ jquery.js:4121 +send_coins.js?2:285 Output used as mixin (c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b/4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7) +send_coins.js?2:289 Unspent outs: [{"amount":"296850150000","public_key":"af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d","index":0,"global_index":6251022,"rct":"35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06","tx_id":4751309,"tx_hash":"3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9","tx_pub_key":"5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf","tx_prefix_hash":"1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58","spend_key_images":["4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7"],"timestamp":"2018-07-06T00:25:48Z","height":1600105}] +send_coins.js?2:389 Balance required: 0.01223054 XMR +send_coins.js?2:367 Selecting outputs to use. Current total: 0 target: 0.01223054 +send_coins.js?2:373 Using output: 0.29685015 - {"amount":"296850150000","public_key":"af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d","index":0,"global_index":6251022,"rct":"35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06","tx_id":4751309,"tx_hash":"3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9","tx_pub_key":"5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf","tx_prefix_hash":"1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58","spend_key_images":["4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7"],"timestamp":"2018-07-06T00:25:48Z","height":1600105} +send_coins.js?2:433 Sending change of 0.28461961 XMR to 47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX +send_coins.js?2:458 Fetching mix outs... +send_coins.js?2:481 Destinations: +cn_util.js?3:1851 46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA: 0.010000000000 +cn_util.js?3:1851 47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX: 0.284619610000 +send_coins.js?2:488 Decomposed destinations: +cn_util.js?3:1851 46WhbYQYsJzdp3BVigsbFpFTG3ahq7QbcWJX8w9m48HUitAdQkqfJWDPfZUTWnqTv9jafzXwzbdWpWjh6HQeoBFUTTWVtuA: 0.010000000000 +cn_util.js?3:1851 47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX: 0.284619610000 +cn_util.js?3:1741 Selected transfers: [{…}]0: amount: "296850150000"global_index: 6251022height: 1600105index: 0public_key: "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d"rct: "35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06"spend_key_images: Array(1)0: "4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7"length: 1__proto__: Array(0)timestamp: "2018-07-06T00:25:48Z"tx_hash: "3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9"tx_id: 4751309tx_prefix_hash: "1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58"tx_pub_key: "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf"__proto__: Objectlength: 1__proto__: Array(0) +cn_util.js?3:1812 sources: [{…}]0: amount: "296850150000"in_ephemeral: mask: "61b1c6138c261599e8e40f3e437212b36d3d48fee73daeb502b141a26f777008"pub: "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d"sec: "0b645a7041b8709a977fba1c34e70b5ca3eb8511a023142442f4cd0516064f07"__proto__: Objectkey_image: "c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b"mask: "70273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05"outputs: Array(7)0: {index: "2080088", key: "e3b02dc808097301664c989a4faf7c5f2ec8976777b74dbe4b8cfecf8c44ca11", commit: "bdeba6a579f63f9dfe344ae4754c8db083e1f87a75956bb2c90f93210b3d02a1"}commit: "bdeba6a579f63f9dfe344ae4754c8db083e1f87a75956bb2c90f93210b3d02a1"index: "2080088"key: "e3b02dc808097301664c989a4faf7c5f2ec8976777b74dbe4b8cfecf8c44ca11"__proto__: Object1: {index: "2799561", key: "69d3eb381e3a43fd41b19cc41004866c00274e9daeb07de3303bf7a949719d50", commit: "733270f6b344c77dc8e463a95524fb90626874c2d8ae15b02578fe8404be8491"}2: {index: "4203500", key: "4d9eed420a1844a32e588317c7a713fbe59d9281b45b7f3504b9bda307c2c3c1", commit: "dc1afdf383ca54068da77efcd29ae628882db2fc93adf8c1e4a96da7d066d1a1"}3: {index: "4281075", key: "7c82f043d142a221f9f0a5f9bc4337270a58e3f2d8d4dfb6f8619842bfe0d1c0", commit: "636a54cdae73d0517334a02cdc9b6c6081c261e250f510284c1289ca3ed5e26c"}4: {index: "5980172", key: "3fb2a5f3016c21721349e27f98ba734bf3d0cb577ba4640fe5db5904a2bb146e", commit: "df525ea20e203f3d0c6ed5caf9e083e679ce6daee56c992216ec39849b12aaac"}5: {index: "6002676", key: "bcfd234d10151e10eff22c7b84bcf7064f9a35c3e0c53dd315394cd66d1ddc8e", commit: "5206721c74d96343283bb82ad0abde561f10913e1217b3826ed5cf33fe7c5355"}6: {index: "6251022", key: "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", commit: "35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f9"}length: 7__proto__: Array(0)real_out: 6real_out_in_tx: 0real_out_tx_key: "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf"__proto__: Objectlength: 1__proto__: Array(0) +cn_util.js?3:1560 {sec: "b9a57d04249992a0faca9a7da08109f31c5955bb8bd60a3dc2872abb1bd49b05", pub: "c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3"}pub: "c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3"sec: "b9a57d04249992a0faca9a7da08109f31c5955bb8bd60a3dc2872abb1bd49b05"__proto__: Object +cn_util.js?3:1593 Sources: +cn_util.js?3:1597 0: 0.296850150000 +cn_util.js?3:1234 Time take for range proof 0: 922 +cn_util.js?3:1234 Time take for range proof 1: 904 +cn_util.js?3:1700 {unlock_time: 0, version: 2, extra: "01c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3", vin: Array(1), vout: Array(2), …}extra: "01c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3"rct_signatures: ecdhInfo: Array(2)0: amount: "64294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b03"mask: "417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f7808"__proto__: Object1: amount: "0db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301"mask: "67826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c01"__proto__: Objectlength: 2__proto__: Array(0)message: "6ffddc62bc7c37b8b2c5dfca7fe2c8be1fc8099d86a4e779946c58dc8f5f2128"outPk: Array(2)0: "200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b"1: "6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e"length: 2__proto__: Array(0)p: MGs: Array(1)0: cc: "8187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908"ss: Array(7)0: (2) ["02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0a", "de13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05"]0: "02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0a"1: "de13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05"length: 2__proto__: Array(0)1: (2) ["738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae504", "6b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0a"]2: (2) ["f06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f", "5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea261507"]3: (2) ["0aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a507", "4932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40a"]4: (2) ["b801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d", "6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d07"]5: (2) ["6ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101", "c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec8408"]6: (2) ["10b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d258804", "83bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b00"]length: 7__proto__: Array(0)__proto__: Objectlength: 1__proto__: Array(0)rangeSigs: Array(2)0: Ci: Array(64)0: "02836fed5f531a64dd41aad00a1dc39ec13b49d1ebd7a2a6b5867d7d71db0e50"1: "a393ec33f28531624bdfa3ddab00ab9239b2bd54ea858920f34c26430045898f"2: "52b419f7f04f7b3b8e604bf11b20ed058e8ecdeae5be4845aa5f14f16c1f953d"3: "8e653029b087bcd4ab9953c1937cee84f81c393285c8bc3341a5eb5bae580b5d"4: "55cf6f0ee2afbea795764bf5d4ed0735367a0bf65b6f69732449e3dfec547d93"5: "d22056ada1eb1c35c2ca1924e07c0e6120b818d6941755efc4dae89d0cc03976"6: "2bf5e003763a01910d434d3f0c3e71af286c55d30c9aadb2a480b8d0938ce43c"7: "202968df06659dc14e713dd6b2899be1501718e3003be5220b7e90034244ab3e"8: "9db020600bdb9b2a17b9d87e150356cb3f3a7150bf305d03efb0aab95cf43e5c"9: "f124a8f5db6c37b81f7841b81a7cc5c2ed6d3b8124ff95f9981fc93d732dbf47"10: "0403a44e0f46ffff9bbef09e2fedbf9b42600918567617ea28d8f42e6b1d764a"11: "340ea317a4462b1083062b29560d25f2b35a82bfdf000033f8a0db28e7314d4d"12: "784f6b9d0df70928cc30b94119cf02085884a237b2ff7470e299726d06f430f7"13: "2c73a1727446f060a3d98ee0c867ea162c197bacce848ebc423f3dc6c4835e8b"14: "bce299084ecf73139b024c33f215bc132fe77cf326e2da2cd18b3008bf16447f"15: "f4b2a4a5679987564d369e8936270b4adb3128e6e88eb75205f9de36088fde3a"16: "7d294c7c38cf0d0bb02c1160a6695550423737668f1646ca65b93d95547097a2"17: "fa8bd70f74a623e5a3c9c3c6b70097bde54853b7517c76c0d36e59cabce4bb69"18: "1bff14524d2a0eac2155a9af73a4e87f0d6ec67d4a27840ed0b7134fa93d7515"19: "88a77416daca2b68d054a7e74dc752a9506090e79596c9b558f085853055d878"20: "52c251baa10a9607b86c009ba3ecba6ed2457d499128f26fe632c39e5ccc46fb"21: "4b7e225ee282d9883abaed6e531b4f844df3d5cb547152742d0a0744e40fb928"22: "4637a34dbd3a4ff4dae9a07904b36e539f56eec6ce7baf51ed8e8189b242effc"23: "3bc60908d6b62739b6d6e8206e4223160eb293eeea2992c0dcaf60c73632ce19"24: "6441692c1f5131526818f5bff1cd83a75e414e595e13a082a47985bd505cf256"25: "f5ec516ae79c1117f9f7820594d696a7714d2151450127dee286f8d18de13517"26: "b24eda31f18547032200489505d2f53101c962641b7275b8a48296f339402f7f"27: "48ac2674f669841615752eaf495a6dac6bd4d6cae58be8c779890d201904221e"28: "f4a1a0f87c523b027fa43f52f37ea06f8a19070fe04f7533dfb529d802a22d7d"29: "299f3abb6974611456060915173bcca396febe1bc943150131d94a2406565957"30: "950fea0f11421c7ae2b7e900f46a8c947b7e5e724bd981c00a1c93070a6dd51b"31: "f406b876c5141f7fb5bd84cb788dda0b822bdc4263e500c85c079aee409cc00e"32: "411628ef38f9c948a3ca7989efe4592d7702aa846b3a1cfbfc5e4a1801ad6a63"33: "386bdaa004af3bd3182a082ae5f17d30862c5a0de12078aba63b51ad2ad9c695"34: "257e90cf997464da236ace2f90020547003d30e8f63998ab705aa56fd4031397"35: "d36526e09faa72c4789380a4df227104cba21542a16f64b19ba96a84103513d9"36: "4336e5077c76eb3a301e3bcdf507c9a0ab6b7f98d6533d6cdfe04f72d2404247"37: "2cd55b68ff9d9e1d8155c21e7df17066751a99ccc5147d0a828053f77b059879"38: "160522a71a95f14e058a51bf443264700870541f074c182b1ca3d1250b6e0337"39: "7dea966eeba2bd820d680f35dc7226e4a8faebc88da83b49da2b5e7dc897dfc7"40: "998961a5fb82372fc03ddcbb6043284c80358ec0f2bf9cba7e3bbce8696dd16b"41: "c32819e8182b5528b3ac82df464b286b944a13704dc17befb80ed61d24a2b240"42: "7fd2bbf0fd1e547b1437db807e113c9e514048342558691bb18c8e2fb7318a5d"43: "d730f570722b73446476e28fe2c85f1fb908cf4898025d519f075bc16c0db7c5"44: "80f8d5eab655c7fb355eff1463026b61b092594a971e573cbc71f2c6fd237821"45: "71dd7c5a41d980ac1830093a2ba8be6de21cfe8a07463deb4428bcf1382326c0"46: "2edf5e1cc806779da45ac34d5c0db4f2382f5e08be2c31fcdfd93be10f4f6c08"47: "42938f9080674ac1ce67acacb5727ac6bfc7995b29f02e324e59f291a5bb4751"48: "f66dd6200ca2c9c8af94a1d6c5531e6d332d8a91c4e00c21fc1248cede72d88c"49: "8fa61f98513bddd06b910456ee60635081b1627a0e01c67e8312f0e0fed42145"50: "e9c514547cf5b5bc53634a11729b9eb6ddd217568a49772e18e020d0d5ddd222"51: "0d938c753a044fb4bd49d3b2002e951849e32ad1f37a7058750c627f4a94b03d"52: "048a524eee29bc8de8ea1f76255b402b63f9b4ff050aec4af6c6ff1ed7cdd62d"53: "5f9e38e5deb73068b2a49a460adc7b98f64e72729ffe7d63662e25ea421d7399"54: "5c22633e12ba5892f6c2b1dfdbe1f85bf5c22208bf1399bcefb5f9357db7c84b"55: "ca879fbddc966d7adad62121490505b5b95cbd545753fb900ddf50a6a6588828"56: "f8f95252b9c7aaf321303f19c496bfa2af90104416a10771ca02d7daf9204daa"57: "3c0d9c8eccc7fa80234995db0869947e7412e5443851495e89259f92bee8e5b3"58: "3c51e08f3dcfdcfb2a3b989ba51c16e820ca2f3bbd34c913d8ffd75d36447bb2"59: "539e2f424297ac39ac5090463289f332a4555c6243315c3e2c241c5a2534c08c"60: "6397ee82dffd9b8b2dca1d64ca3cc29cbd07e67bf095ab47273a7a70b59cbe93"61: "6cd018be9d0c3f8a224c405de0a58cc5bc2cac7b6e6f514089b036eee6b0904b"62: "1979e6077b6132d9fd669788f5031d3f88107a857e35117054f9196a197e97ce"63: "89507701e23077c93b98401ad75a8a30a77317952fca30ec28eef2179699f93d"length: 64__proto__: Array(0)bsig: ee: "46793313125f55f30bbabb52e61db65e5ae507cef64f08f501417edef3da5e02"s: Array(2)0: Array(64)0: "094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08"1: "b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c"2: "2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60a"3: "e93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810e"4: "b57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630c"5: "ad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff02"6: "0f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003"7: "bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e"8: "92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01"9: "adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a906"10: "7d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e"11: "0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405"12: "d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b"13: "9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a994004"14: "0331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b05"15: "40d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705"16: "e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb01"17: "06374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d14600"18: "1d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a0208"19: "7c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0e"20: "df3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c"21: "6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50b"22: "bb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e"23: "8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302"24: "bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605"25: "d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a25950908"26: "32aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f608"27: "73f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca4609"28: "9248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc1205"29: "2a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50c"30: "a90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd100"31: "18ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e"32: "4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05"33: "d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03"34: "296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d10184108"35: "63d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508"36: "645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0a"37: "f1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300"38: "a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b05"39: "9b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001"40: "fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0c"41: "e21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804"42: "824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0c"43: "fc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f"44: "94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa702"45: "0850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601"46: "a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700"47: "dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf16507"48: "4813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c"49: "1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01"50: "c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f05"51: "56be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780f"52: "a7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb04"53: "2dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d"54: "0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501"55: "e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b"56: "241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced06"57: "8d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202"58: "270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a"59: "72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302"60: "b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a"61: "578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403"62: "823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e"63: "0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70b"length: 64__proto__: Array(0)1: Array(64)0: "b3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801"1: "e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0a"2: "cc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da069afb8b06"3: "e63c93d5e36e33e4ba022fe2352061f55f4e5115d048e4c36ab56d9d558daf00"4: "15a8ec96436dd72e4ee610e0806adbb382a7ef9d3dd9774ed070abeec25d460e"5: "304ebc4b2d097bfa0f6982abe3f7490f0374a278f1464505e0233a1195e7af01"6: "ae7e06f814cb7266ca60722c53e2d3eb66a5a1f8cff91bad0da26b5716ab7106"7: "fceb4b4a1a613014804c5284e7c8d785512730c7f109d4c225f9fa00cff4d10c"8: "f7f120e75d1c37c5e5b4e9117f30fb30c3cbcf86a33225fb4f63f2a207168204"9: "5dfb733111c50730556e62bd49a471b71c9421b312e8805358ba53af071b6103"10: "99e917fa1b4687093313e330b453dd98b3f7866bcbe737f5e11f4bd1c0043b09"11: "75b84a92c4cfef856688345ebf4ba84b95aaad4ef573774e448d7ea93f6ba109"12: "37ec41a174bc0965d667918f608b6358d424628859d0933cccdf51ec7d55f906"13: "438342b45be0e9796bb57225e7bb184602fb97dfb48260bcfa427f787a759c0f"14: "5a2a8929697f47c854bebca692175efb36276a5f9fb92437836e65d87bb3b70a"15: "09663579a35c801c1aa654eb28517013b5e877a003be057b875b35df2fe22709"16: "0c58358156115c37d72c290912ee288600cdabb492312227fe659dc238df6e03"17: "def5668fd22573e63b245586cd10bc66d68942e01bc9dfb9d152a1a7898ce002"18: "daef4fbe574991fe37e0b8b69ff269e098b7b1640a47b370fb38f410eb0d5703"19: "536bbe4bcc900df2a526d80a413b9c9ed3ab75124ff7198d3a44e2ca0cce410f"20: "edf5f2ef7b33f93b43a26f4d30751f2bfb4b13ca42a3105196bb17e5c2653e0c"21: "00adac72a44baa60b2d2411fb239d0d0dfd68251d362bb04ab0a3cc169cf6104"22: "9bdc73cf11aee0b90f7b26cbcdab987873ab69542cdef0bb8c125da7034e320d"23: "c9bc4f84a9827cd927165c3455ae9fd8f5325e606b58e3cb6db67bc46b434c0b"24: "f78b1ef5530c0996205b419dbde1a8b1a9ed21abf38e3c5a16d34d6091a1e406"25: "5c79fe8d78b95184e779434993a52c23e6e9c6db90e9db5b90e97a29def79302"26: "922934dd5413a93d183cd83ad88ab3d439ef39753c84588a8269a74f0d197208"27: "0fbb35971f624d31041842ed430c70d09171c20f0f00e0d0c4e8ef8149b9af03"28: "8846db4d5b2d8a586e5f1b34803e52cab395311ac5bac25f2ce3cb2203d80703"29: "54da89b48c24c0da18b4db9f87d210388ec2655f48fcc42bcb78c367bef2aa0c"30: "543f7e562adc23a6b22445ca1389156587c0af0fa16aeaa1aa5e6508228e9c04"31: "620aafcb6408e66ad86f0258cdd04a376ca1f41a623e7cf36a800d5da9158302"32: "9bb46e73b038d27c370d948571c442f65302d09c1562ba3ebde7e7e803f32602"33: "799726043b23a4eca27e62986009cc9e4419fe95cece9504cabdf6d92f01f105"34: "beee7e9ece3fb49b495922bfc23337e53baafebcad53a250b66d168a87b3a406"35: "4440985fe7becf778fadbbf8eb4d01c024039a4cdfd5f27edab16f4702b4c205"36: "90dce3157b8c727316ba8c25de3cab3112c385f8ca0895e5c775e5f34c17520c"37: "c8cf80d28be593f186ad7f6f4666f702df2566c16a5d5e1aad21694774f41f00"38: "03fbcb4502fcb5e1deff0fa2a458f6ff88bbc91cfa2e21cb42b0077da5d43007"39: "d68610e05172ac51036ab6a52140e14e1707dc325fe8efcc39c74883780e9404"40: "bcb78fdf38b2763ddd7458db79ffaf28305aef1534d7ec4112316a8acfbe3504"41: "277ef2e7dd36cc72869f1d53689d904cabd23085165d2933ea15fa491da5ed01"42: "f00468c47ba2185b241405d89952922a74c02ceb8617543b70f353a62f017b02"43: "3dc53beb7b858019493aa1d2bdcf2d6eb7683d453d62dfd391b189c296bee006"44: "d7c85bd22dd02bf6448cb7af0460cc319913f9fbc357875bf5396f9e33fab505"45: "0aeb928f76e42175bb300f1e254f981d8bf22079c66b03169e4d04879495e80a"46: "395187afeb4d7e73edf5d4320ce49cc4774560697ed4c58e8b4214fd03d3e808"47: "d666a22ab6104dbafa95c6c0db8af94ea49f24fbd5c36d00fc009c451ce09708"48: "3f84b72f2e5b9b1dec327c4f14d43da764ad36c5e8e8389453755fb77157dd03"49: "f9e97ed405828144904fb6a2f578e1d39f7f30621d9a483291ce50391c53690a"50: "76b217cdc7d7ac7a1ca3add04d303a920713db149ca022ad49a83e72ea7fba0c"51: "6c29a44633d1c056ddbffcfd43cd1ad4b810cddf796ba994ddda1d63cebf8705"52: "e743ff022e0ecd99912346a9c2b892e5e4ebc3e52b98e60262d96f3367717703"53: "49b69d7560d297a73658c22441c5ce1b294065ff4f3da35227f7684e3a445001"54: "910975c311f461abd40ee2b56ab71f105770a4bc1a581b1221495023353aaa01"55: "d4bd115f5a8d9ba37b3b41dab0cb181f07e60624b755e79e1abfc7da826a6d08"56: "cab5edf981c43ec78bdcbf74313af449bbfe3b4806a205de6adbd2249015aa0b"57: "0efe4e92b667f66f2b8899084941fce413edc2e4a5ab44c066236c6b2a527707"58: "fe52311427d13f55a87cb3a361b196e0faecc106ca6bf40e968b057f64cde106"59: "b2b446d99da75974b26266573f1a641b4c7b477c61b370116d63267471132806"60: "f9bdb81ab2a81f03165e2a5b2d32f1f2b2cbe86d95e3c6f8bf8ac6a2296ccd0c"61: "2e6412ed8f74082f073b6bc2dd63e59ead35112c5c9362610a6561531b931b09"62: "f21e5bef71a01433b29402e008ecebaadda2bd8bed28d0cdf910ffd573434c0a"63: "c5781c5eee63cd99e97b5b319cd3925607dbb371a360e2d5e070f161a063f109"length: 64__proto__: Array(0)length: 2__proto__: Array(0)__proto__: Object__proto__: Object1: Ci: Array(64)0: "524227a74d9da9b38d2ddeb8649eb9d46bafca1453fb6ab89112544597bf2dd8"1: "55511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e81048"2: "40abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739e"3: "dc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be"4: "46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34"5: "893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e310"6: "6117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1"7: "e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b"8: "0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b"9: "165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479"10: "e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e"11: "302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b"12: "6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f047"13: "1ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee0"14: "94a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c72"15: "72a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8"16: "b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8e"17: "bec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125"18: "d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e"19: "8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939"20: "f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462"21: "c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e50"22: "5e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9ef"23: "be1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c"24: "8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f2"25: "5d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189a"26: "c688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61"27: "459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f76"28: "98bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4"29: "030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda8"30: "8b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0dee"31: "fbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505ea"32: "baa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8"33: "609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284b"34: "df8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150"35: "d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc855"36: "8fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65"37: "a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa6"38: "8a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b49185"39: "98a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999d"40: "c40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9be"41: "c704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f73"42: "2b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0c"43: "a7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046"44: "a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04"45: "100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce"46: "64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb"47: "6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916d"48: "e7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c"49: "99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb"50: "256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee4"51: "49da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd"52: "37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3b"53: "f7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da"54: "3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae"55: "0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda"56: "2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7a"57: "fd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c"58: "3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0"59: "127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693"60: "bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043f"61: "aba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7a"62: "af0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c"63: "7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a"length: 64__proto__: Array(0)bsig: ee: "bc27bdbca4d4b3c14ae0745ea453174a6e6e3b3014dbd23ac3c8a413b5e29d08"s: Array(2)0: Array(64)0: "d1d07b998118930c481260cffe5d098a81048b0853bc957f61c376a392bc740d"1: "4bc083e9bc4b62ee04daee640c5339121174af2f2c3c78d716d97497c0518d0a"2: "7468d5ab25b59fab995c815f65550d52567ab22ce7215eadb2a5bf07ff899100"3: "7d381ad3b18f9af8307e97282b681c37a74cc1083bc079e49b79d08e8d15d602"4: "b65d2f153c27e54407d80a4dbda35acc29220996375f7819919ebd9ec6999a0a"5: "a1ee2b5a2f8cd95c1ed40ed310ec102b872a2f0ae40a383036398a467a845003"6: "2b8dc50fb3aead263d9004dee7f10e12aea154ab97f04f4a016d39b97742d00c"7: "837350002903c83f056c5d64316c7aafd83fe355a64802fbef14516b197dfa00"8: "1695103d494c48359e58bbcd358b0eb20d1ef42e3184594fcf51443ad3cfb404"9: "7ced01f064c638698c690b9179b61aa68d369e35d5ac8bc65dae702cbc74890d"10: "cbedb029dd98055213866576a3d5e0617d063dc2470e8d8e5b83574b5687b209"11: "90a81ccea7787be7d38ba9f21b5ba3443049ddd82328d4e8fbe6b2817bf9380b"12: "46f9e643f6a593081f7f1d5d97c69b0fe58c9cc6074c373f8a71c90a12604b08"13: "84716acb458e7dd490b5f0057d917e586c69cafe9fbe47f69bd64c7e61b5b80a"14: "6825520c5327772c2ea8d7450c3d764c4d08cdaa1562a1d990eb0878134d3501"15: "2482c2f5c4ec38fe5161e00eaf669187f2ec98584a6735a9341cf66b4827dc0b"16: "f512a1d4ead70ad08445b1a9af017ac6b375f9c5f0103d0755e1c13046d2c102"17: "a041f2c79577ed00c330e1a7c8e3a6d474dbf6d9ef9c4e3217cc3fe611967a0b"18: "c5b96d0815ad46b04d043dd39ade61805d12aff54f4f60ca89f2ad77433c3908"19: "22c633e75cba3a385958fe1015c502315ce63e440baab882dbeb4ba118994103"20: "676b9e83e6f6d2d53db075f662469e733778dd8082c36cb246b03f9701c58001"21: "5d55bf2ca24a02bd3888eb95597f654d04e84fcd79aefa00125091db32898f05"22: "45a6b7695838c01d0be144395d40af9d8e69b0ce2f1326bcc3d57a244be24000"23: "31cbf5ff387e2439cc3b492466343d35a6a7ae244c0abdcada6cd0e37fb8170a"24: "d2c7582aafefc50fe74f4f05bd1b1aa0a0391c18b2e54f7b8078517ce5072305"25: "e4a509c5ceb5165a148a78c67fc1de9d97d4a7c0a9e6d152b47e2320721c200c"26: "830a015279a3d84acd32f7070af60f82db8fe6f035973f4250dba240999a5005"27: "f96f27a773fc9e7b68558f5f98699aeee07c645097111ec1aaabdefea7746904"28: "82af54c967848b2cd65ebd7f9bd4657292864f46e4620210ae41c1642357eb09"29: "85f8b1dd6e9d87fcca833964873224c9128e899acd85f74ee625809e9f045907"30: "e717b4e15183bf2fc55a1cae983526e833255b1a38a532c3319ae67428e6b604"31: "f1aa90a29afe47935795d9b629abe0654a9375268d11f91a7f0e13a74c9a380a"32: "e66988a69672f086bb939df9590f0e495364a72c8cb298e98bf04a0709722f00"33: "449422874e793b6029a95e26724609dc06fe9086f6ae690b34dc1b29427b4303"34: "50a43678dfb0ddde1db147da17c2e6b3fd44dbe186dabc204f47ab241c2f560d"35: "4c103099635d550200103c05232988f48838d4b56b401242852c4be6ef991203"36: "fa32db1bac264e7663b866f21314c05d3eb006a4fc2e00fabf30b4197b5edc06"37: "b8777c5560b10549622a28ef19132a9e3960f54f3c449c3039985b24be69e80f"38: "17df718ef65afb8fbd4b1d6eb3fa982590962d8863720a6f7080f6d73251360a"39: "62b8674d157eb1a2d313631aa446635ec453090c75cb7470cbe60044f329130b"40: "829cf76313e13a5a58dd5378ba3d1bd7c7a6b931e63f233204c7caf9d4fb8d0b"41: "036910d5fd088dff93e7f6a43888096b38b5e0d39bb94b2c570e94258ba8f601"42: "43f516aef8442b816bcf88ce21eb62bf58874644dfed54d13ecaec398846950a"43: "01b76ab69ed1324cdda07cf14831bf6554f1ad9952defc2186c66da67a626504"44: "9e01bb01a69c67b81bc0977ce81b13cd3058ca82bc212d17514ac19cc40c9703"45: "cd8a3c8253d0163d494a9bce334b798aaeddece4c546509ed61953f8ed357c0e"46: "861c7bd6ec632e7831b57a9d796b0513a715ab7348db38f3dcfc8474a82b8107"47: "6e69287de84cc3bb1e8326f467b961f0dddcd70a9d104cfad472d3661bada40b"48: "1aafdc9b6fcaf21ba61e8e5367aba0374fb927c375f90b87350bb0e0dbd8e105"49: "bbc62c5fc32f99f4bed49baa2f3db5a4705c4d2feaf3cd244d4d28d6f6a1400b"50: "5384e8f695e1449db4ef23f7b77618ed7a366eef3ae38d0b36aa19541a5f1d0b"51: "26c006bdaca798027322f8294933ad9d877fb79cce3cd756b94ed2cd311cb40d"52: "b8cd46406725c9e7fd92893cdba0d1001796a286d9b18707f4ee433f90665607"53: "f60f5caadfa08817e1ab0f440b26dfb5163469c871bd7e0bfda5597597a2d402"54: "65d270c8303c48231c97b1807ddd444105aa6e195df4c208a0d07ab7721e4909"55: "6e2b8edcaab76fc8729fd3bab94895518e3549dfcfe5238ef3f92e912e23bc0f"56: "032af4ad5abedd0c10613e50c26c9522902074202bd2c93ca60a7fdf4c5dff06"57: "e27594188bd5c6fed2874df4acf8b6d2f412959c2f274ffba18d040909985f09"58: "038124541070db29455f8d58c33323798cb03cfa2dc0298a856e64d4d7eb190f"59: "9c47bbbf7c075e8cc3ac5f980cad86287d1940dd8dc0ace92241ddb49d454605"60: "cd9229fe4080ca8e4b34bf3c35c3b95436d64f61d321a30f402f2695b6452d04"61: "f4aa87a45504333b39c487f05fab5ac541893bcccaccc27313293c20da1df10a"62: "1fe1edcb7962bd4c9b211472947196e01ee99d2d3f518710241dfc5f06a3ea0f"63: "53b36bc3696808aeba745a58c8c8b27f5bfeda5676a95596cfc24e7aebec110f"length: 64__proto__: Array(0)1: Array(64)0: "851e464baf54513501af9262618d44cb81eff6d451d2ef90868a0b76fee1ae0f"1: "2392eca80014d625cf976c763f86186cf984943a0324bb38b8d23d97a2dae700"2: "2590aa56836825bee4770d74ded76b9187ed26ce8795d96e4c211056b7d8380f"3: "3e2ee2b7f4b37342968ebb1706e85233ad8fa15a39cda0417f86dacd35829a00"4: "53eec03966ac62876503efabf4407da1fd5eb13b542cbfa9ee755725cfe9c30f"5: "1235645468cbe11d875c5ebf96363b25abefa920867af543bcbc4148d5f1e50a"6: "c520452ced483e258ffb7ace14531c5c7de112f4827c594cbdaa373e52f54506"7: "833713e2d2fab34ecb5228c7c1c1cf402be69654c5ace0376d34ef7339383f0a"8: "2243fba2d420eb964593f871e18f5764824976f2f4c28f8624d9921237d45106"9: "8ed944427dd822c8789064dbcdadca5c14b766706c7c3ac034875d44e94bf604"10: "7e1e989bc3506445c88c08b7805bf5a9ff3bf1636b93e98f039cb762d667ae02"11: "eca0fa2e30835f037dc359ce12edaafd46f064e0aa513d1e3239e0b63af87909"12: "0a56c3d72748837b84bbd59a55a15c15b4e44d110532a020b9da108bfcf9530c"13: "3e1a49bf0c90143ecdd1324ad2beb6a4e1d033f46ceb918f4259d5b17bad3304"14: "e40fa0c77edf3afee63799c94b470a4dc3c89d18a43dc8dcd31aac6d33a23801"15: "a3548cd48aae4fcc39d0c2775ea1a26290f6d7c6d49506ad8772b1a356a8170b"16: "d0be3d0d451aa618d6312d6be5a86a2a398ad051a7e5660a454a4c45da41630f"17: "c9e3af071abd46b85a07749d1ee6287a4afb898f7c49655763e34a9ab47b9d0d"18: "750e5c0b8526960a576d55862c3e75f97716f211903d808ec860f41361129c0f"19: "c8cf440475e64a92338a37ccdef482ad7d9ad75a0a94bfb9007db701548dad00"20: "97312ecdb25219b47f5f44a707846ff2c180198747102dad0e37f3bdda0cba06"21: "0aa8fafcabd48672879641167142c0024458aa118c2b6189ff95812c56515c06"22: "adf80b43718fe494419e883abe0f33e36d4e18f6cdfeb8edb121b81d0db1770f"23: "2705face099e6bbdae0cd485d486a8c2018d49bfcd956704f669edb352807c08"24: "6c9edd1d0fac0eefed63077d6022aff763c8d34e51d69b26016740638b477606"25: "f68a07a3089aca2204035f772b06945823375fff78fd21fa554b16688ecb210b"26: "7291f1617493c6913bae6c90eda5935cc53a50c933e7b3b453b68f3871885b07"27: "3c7a25563a66104e4dc28007990565ec9e2a3382f9924c6a63953af4929d4d0c"28: "d3282c0bb4c5cb3f2aa4c1d88751f15a5cb7ae8687034b6ab38d46b76b0bcf09"29: "67ba42db29eb1d7c458e923e6c6474e79ef469e92805b30a3a6e6e9101c53e0c"30: "e747701d014c338171df6ba79f8aa6234a49e78a8039e2ea9b671feddffa7d03"31: "cba531d055be1808d6fc2a6e229087b77a3be35e40ab4a6f378c216ea05d1900"32: "cd1aa21bf3bf0afe40e95c406000b49a429bdeeb563f32b87bedc1adc6e5e501"33: "c051a69901925053f064ae1e8054e34500b97eff9f974292eafbbd85715c2500"34: "e45a20f16556e09b7bb530764582a59ec40a4af28d95e6fdfea3f10ce35e2b04"35: "16e209c92a5045b8d7af4762cebf2d6d97b8960d9e9565e0549a2488a9e8b50c"36: "985f5395774fb807d49a0f84e0983fec0e32fb5342a3704c5d198dddc945d806"37: "c3262dc93f96a1b1a09c2bd461f324c068aae59d5473deba8ba6036ad96c670f"38: "42667ada96da901567e544157b12f56159bb7c3f83b90abd1b9e7eb948f2e702"39: "c9eca93744830f0dff8b7bc73a1458b0f8d40e679afd1e22b09c93f2c9729602"40: "c6c81026f8f02991c094a38ed0e6062508974a8bc69e8d6e6741a45eabc7db06"41: "65f8a2d879de17fdaf7ad1b210f337b90aec1c1c3676f244aa3852a77c05db0c"42: "05b9936bc10f0a327602aa2109bdd115f8a3e1081c1b0c56649638207d2dd908"43: "6c6ab885512fec9650ba288df5133c9dc87cb7fe81919fafca783f1252c29303"44: "32559bbafcdc830223837a7b78a7ce137e9f4a4e3e7d1195a494fa6d0ca9a106"45: "59c0df4ee0c9db91dba17acad08e5f6ccae71012e2c6e4b37894520fffe9620d"46: "66a72a1e4f04e5fa2abe93ef7f4b2e9e837c9e0f9d962b5c80f5e83408746103"47: "58576c23403b2d742c587e3827d17c6f41b31f9da4030207aaa2f4c32059f401"48: "7bfa4c324be2ac00a0aeabc7756ff2e16bdf39ed6b0c313f01b1acce8bc2b408"49: "a0cb3eaf9af9b68dd17f258c0bf0c536b19040d6bebc0a9dac7fbc59398b5208"50: "91a741c4183a851c1cbcf3f78f30924d85ea2d907a6ac8db9a1455e4c672800a"51: "88629853fefb86800fec6631d77edf591d56199d5980bf3c86d9254773e02508"52: "be6a488b032686134049415bb194ad11ab8e4b1e6304274091beccc795e7b604"53: "649f106b395bef6f30a1dca330561709cdf7295557410d040543c279e169a908"54: "dc3d94c4809f0a7dd92ad8cefb014f5d47681b21d6b955fa894eff5b57cc5d0d"55: "1b08c01c4e892af363e6560a02212edc1fa645cdbce380b3e010d3c7f473960e"56: "c86e8ab8d2c235f594cbb2c2fa62f27b4ea4ab9796b8534688fd9819ea529c02"57: "3ab370c6e482633801e3d588f3a4433cef5aaeb633547e922dbb35410af8a803"58: "156b64daf9633636063a37c4adc43e492023e09137099a715a690a480119f60d"59: "9c2f1a110a6590ff1b7774e0fe3e4a7419226448634c129aa1213e560814cf0b"60: "28d28fbd4029688f0d920a5f45c2a06bdea6c38f731a8bf741ba106bb8a3ac0e"61: "35fd8c015995d55c0733a18e66705dc84dab552aff7d2ae654b903f6fc9f1305"62: "da5a5bafbfbb22df241e211416fa902eaec589c7a22d6bf160a884e5534c5e0a"63: "685c312527c037f3196d4122f4d42c5a683d0e555af2a271d8d68faea80dfa04"length: 64__proto__: Array(0)length: 2__proto__: Array(0)__proto__: Object__proto__: Objectlength: 2__proto__: Array(0)__proto__: ObjectpseudoOuts: Array(0)length: 0__proto__: Array(0)txnFee: "2230540000"type: 1__proto__: Objectunlock_time: 0version: 2vin: Array(1)0: amount: "0"k_image: "c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b"key_offsets: Array(7)0: "2080088"1: "719473"2: "1403939"3: "77575"4: "1699097"5: "22504"6: "248346"length: 7__proto__: Array(0)type: "input_to_key"__proto__: Objectlength: 1__proto__: Array(0)vout: Array(2)0: amount: "0"target: key: "105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b1"type: "txout_to_key"__proto__: Object__proto__: Object1: amount: "0"target: key: "d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d31"type: "txout_to_key"__proto__: Object__proto__: Objectlength: 2__proto__: Array(0)__proto__: Object +send_coins.js?2:495 signed tx: {"unlock_time":0,"version":2,"extra":"01c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df3","vin":[{"type":"input_to_key","amount":"0","k_image":"c0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b","key_offsets":["2080088","719473","1403939","77575","1699097","22504","248346"]}],"vout":[{"amount":"0","target":{"type":"txout_to_key","key":"105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b1"}},{"amount":"0","target":{"type":"txout_to_key","key":"d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d31"}}],"rct_signatures":{"type":1,"message":"6ffddc62bc7c37b8b2c5dfca7fe2c8be1fc8099d86a4e779946c58dc8f5f2128","outPk":["200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b","6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e"],"p":{"rangeSigs":[{"Ci":["02836fed5f531a64dd41aad00a1dc39ec13b49d1ebd7a2a6b5867d7d71db0e50","a393ec33f28531624bdfa3ddab00ab9239b2bd54ea858920f34c26430045898f","52b419f7f04f7b3b8e604bf11b20ed058e8ecdeae5be4845aa5f14f16c1f953d","8e653029b087bcd4ab9953c1937cee84f81c393285c8bc3341a5eb5bae580b5d","55cf6f0ee2afbea795764bf5d4ed0735367a0bf65b6f69732449e3dfec547d93","d22056ada1eb1c35c2ca1924e07c0e6120b818d6941755efc4dae89d0cc03976","2bf5e003763a01910d434d3f0c3e71af286c55d30c9aadb2a480b8d0938ce43c","202968df06659dc14e713dd6b2899be1501718e3003be5220b7e90034244ab3e","9db020600bdb9b2a17b9d87e150356cb3f3a7150bf305d03efb0aab95cf43e5c","f124a8f5db6c37b81f7841b81a7cc5c2ed6d3b8124ff95f9981fc93d732dbf47","0403a44e0f46ffff9bbef09e2fedbf9b42600918567617ea28d8f42e6b1d764a","340ea317a4462b1083062b29560d25f2b35a82bfdf000033f8a0db28e7314d4d","784f6b9d0df70928cc30b94119cf02085884a237b2ff7470e299726d06f430f7","2c73a1727446f060a3d98ee0c867ea162c197bacce848ebc423f3dc6c4835e8b","bce299084ecf73139b024c33f215bc132fe77cf326e2da2cd18b3008bf16447f","f4b2a4a5679987564d369e8936270b4adb3128e6e88eb75205f9de36088fde3a","7d294c7c38cf0d0bb02c1160a6695550423737668f1646ca65b93d95547097a2","fa8bd70f74a623e5a3c9c3c6b70097bde54853b7517c76c0d36e59cabce4bb69","1bff14524d2a0eac2155a9af73a4e87f0d6ec67d4a27840ed0b7134fa93d7515","88a77416daca2b68d054a7e74dc752a9506090e79596c9b558f085853055d878","52c251baa10a9607b86c009ba3ecba6ed2457d499128f26fe632c39e5ccc46fb","4b7e225ee282d9883abaed6e531b4f844df3d5cb547152742d0a0744e40fb928","4637a34dbd3a4ff4dae9a07904b36e539f56eec6ce7baf51ed8e8189b242effc","3bc60908d6b62739b6d6e8206e4223160eb293eeea2992c0dcaf60c73632ce19","6441692c1f5131526818f5bff1cd83a75e414e595e13a082a47985bd505cf256","f5ec516ae79c1117f9f7820594d696a7714d2151450127dee286f8d18de13517","b24eda31f18547032200489505d2f53101c962641b7275b8a48296f339402f7f","48ac2674f669841615752eaf495a6dac6bd4d6cae58be8c779890d201904221e","f4a1a0f87c523b027fa43f52f37ea06f8a19070fe04f7533dfb529d802a22d7d","299f3abb6974611456060915173bcca396febe1bc943150131d94a2406565957","950fea0f11421c7ae2b7e900f46a8c947b7e5e724bd981c00a1c93070a6dd51b","f406b876c5141f7fb5bd84cb788dda0b822bdc4263e500c85c079aee409cc00e","411628ef38f9c948a3ca7989efe4592d7702aa846b3a1cfbfc5e4a1801ad6a63","386bdaa004af3bd3182a082ae5f17d30862c5a0de12078aba63b51ad2ad9c695","257e90cf997464da236ace2f90020547003d30e8f63998ab705aa56fd4031397","d36526e09faa72c4789380a4df227104cba21542a16f64b19ba96a84103513d9","4336e5077c76eb3a301e3bcdf507c9a0ab6b7f98d6533d6cdfe04f72d2404247","2cd55b68ff9d9e1d8155c21e7df17066751a99ccc5147d0a828053f77b059879","160522a71a95f14e058a51bf443264700870541f074c182b1ca3d1250b6e0337","7dea966eeba2bd820d680f35dc7226e4a8faebc88da83b49da2b5e7dc897dfc7","998961a5fb82372fc03ddcbb6043284c80358ec0f2bf9cba7e3bbce8696dd16b","c32819e8182b5528b3ac82df464b286b944a13704dc17befb80ed61d24a2b240","7fd2bbf0fd1e547b1437db807e113c9e514048342558691bb18c8e2fb7318a5d","d730f570722b73446476e28fe2c85f1fb908cf4898025d519f075bc16c0db7c5","80f8d5eab655c7fb355eff1463026b61b092594a971e573cbc71f2c6fd237821","71dd7c5a41d980ac1830093a2ba8be6de21cfe8a07463deb4428bcf1382326c0","2edf5e1cc806779da45ac34d5c0db4f2382f5e08be2c31fcdfd93be10f4f6c08","42938f9080674ac1ce67acacb5727ac6bfc7995b29f02e324e59f291a5bb4751","f66dd6200ca2c9c8af94a1d6c5531e6d332d8a91c4e00c21fc1248cede72d88c","8fa61f98513bddd06b910456ee60635081b1627a0e01c67e8312f0e0fed42145","e9c514547cf5b5bc53634a11729b9eb6ddd217568a49772e18e020d0d5ddd222","0d938c753a044fb4bd49d3b2002e951849e32ad1f37a7058750c627f4a94b03d","048a524eee29bc8de8ea1f76255b402b63f9b4ff050aec4af6c6ff1ed7cdd62d","5f9e38e5deb73068b2a49a460adc7b98f64e72729ffe7d63662e25ea421d7399","5c22633e12ba5892f6c2b1dfdbe1f85bf5c22208bf1399bcefb5f9357db7c84b","ca879fbddc966d7adad62121490505b5b95cbd545753fb900ddf50a6a6588828","f8f95252b9c7aaf321303f19c496bfa2af90104416a10771ca02d7daf9204daa","3c0d9c8eccc7fa80234995db0869947e7412e5443851495e89259f92bee8e5b3","3c51e08f3dcfdcfb2a3b989ba51c16e820ca2f3bbd34c913d8ffd75d36447bb2","539e2f424297ac39ac5090463289f332a4555c6243315c3e2c241c5a2534c08c","6397ee82dffd9b8b2dca1d64ca3cc29cbd07e67bf095ab47273a7a70b59cbe93","6cd018be9d0c3f8a224c405de0a58cc5bc2cac7b6e6f514089b036eee6b0904b","1979e6077b6132d9fd669788f5031d3f88107a857e35117054f9196a197e97ce","89507701e23077c93b98401ad75a8a30a77317952fca30ec28eef2179699f93d"],"bsig":{"s":[["094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08","b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c","2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60a","e93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810e","b57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630c","ad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff02","0f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003","bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e","92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01","adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a906","7d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e","0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405","d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b","9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a994004","0331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b05","40d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705","e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb01","06374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d14600","1d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a0208","7c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0e","df3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c","6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50b","bb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e","8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302","bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605","d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a25950908","32aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f608","73f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca4609","9248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc1205","2a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50c","a90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd100","18ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e","4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05","d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03","296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d10184108","63d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508","645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0a","f1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300","a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b05","9b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001","fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0c","e21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804","824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0c","fc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f","94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa702","0850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601","a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700","dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf16507","4813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c","1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01","c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f05","56be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780f","a7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb04","2dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d","0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501","e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b","241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced06","8d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202","270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a","72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302","b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a","578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403","823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e","0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70b"],["b3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801","e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0a","cc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da069afb8b06","e63c93d5e36e33e4ba022fe2352061f55f4e5115d048e4c36ab56d9d558daf00","15a8ec96436dd72e4ee610e0806adbb382a7ef9d3dd9774ed070abeec25d460e","304ebc4b2d097bfa0f6982abe3f7490f0374a278f1464505e0233a1195e7af01","ae7e06f814cb7266ca60722c53e2d3eb66a5a1f8cff91bad0da26b5716ab7106","fceb4b4a1a613014804c5284e7c8d785512730c7f109d4c225f9fa00cff4d10c","f7f120e75d1c37c5e5b4e9117f30fb30c3cbcf86a33225fb4f63f2a207168204","5dfb733111c50730556e62bd49a471b71c9421b312e8805358ba53af071b6103","99e917fa1b4687093313e330b453dd98b3f7866bcbe737f5e11f4bd1c0043b09","75b84a92c4cfef856688345ebf4ba84b95aaad4ef573774e448d7ea93f6ba109","37ec41a174bc0965d667918f608b6358d424628859d0933cccdf51ec7d55f906","438342b45be0e9796bb57225e7bb184602fb97dfb48260bcfa427f787a759c0f","5a2a8929697f47c854bebca692175efb36276a5f9fb92437836e65d87bb3b70a","09663579a35c801c1aa654eb28517013b5e877a003be057b875b35df2fe22709","0c58358156115c37d72c290912ee288600cdabb492312227fe659dc238df6e03","def5668fd22573e63b245586cd10bc66d68942e01bc9dfb9d152a1a7898ce002","daef4fbe574991fe37e0b8b69ff269e098b7b1640a47b370fb38f410eb0d5703","536bbe4bcc900df2a526d80a413b9c9ed3ab75124ff7198d3a44e2ca0cce410f","edf5f2ef7b33f93b43a26f4d30751f2bfb4b13ca42a3105196bb17e5c2653e0c","00adac72a44baa60b2d2411fb239d0d0dfd68251d362bb04ab0a3cc169cf6104","9bdc73cf11aee0b90f7b26cbcdab987873ab69542cdef0bb8c125da7034e320d","c9bc4f84a9827cd927165c3455ae9fd8f5325e606b58e3cb6db67bc46b434c0b","f78b1ef5530c0996205b419dbde1a8b1a9ed21abf38e3c5a16d34d6091a1e406","5c79fe8d78b95184e779434993a52c23e6e9c6db90e9db5b90e97a29def79302","922934dd5413a93d183cd83ad88ab3d439ef39753c84588a8269a74f0d197208","0fbb35971f624d31041842ed430c70d09171c20f0f00e0d0c4e8ef8149b9af03","8846db4d5b2d8a586e5f1b34803e52cab395311ac5bac25f2ce3cb2203d80703","54da89b48c24c0da18b4db9f87d210388ec2655f48fcc42bcb78c367bef2aa0c","543f7e562adc23a6b22445ca1389156587c0af0fa16aeaa1aa5e6508228e9c04","620aafcb6408e66ad86f0258cdd04a376ca1f41a623e7cf36a800d5da9158302","9bb46e73b038d27c370d948571c442f65302d09c1562ba3ebde7e7e803f32602","799726043b23a4eca27e62986009cc9e4419fe95cece9504cabdf6d92f01f105","beee7e9ece3fb49b495922bfc23337e53baafebcad53a250b66d168a87b3a406","4440985fe7becf778fadbbf8eb4d01c024039a4cdfd5f27edab16f4702b4c205","90dce3157b8c727316ba8c25de3cab3112c385f8ca0895e5c775e5f34c17520c","c8cf80d28be593f186ad7f6f4666f702df2566c16a5d5e1aad21694774f41f00","03fbcb4502fcb5e1deff0fa2a458f6ff88bbc91cfa2e21cb42b0077da5d43007","d68610e05172ac51036ab6a52140e14e1707dc325fe8efcc39c74883780e9404","bcb78fdf38b2763ddd7458db79ffaf28305aef1534d7ec4112316a8acfbe3504","277ef2e7dd36cc72869f1d53689d904cabd23085165d2933ea15fa491da5ed01","f00468c47ba2185b241405d89952922a74c02ceb8617543b70f353a62f017b02","3dc53beb7b858019493aa1d2bdcf2d6eb7683d453d62dfd391b189c296bee006","d7c85bd22dd02bf6448cb7af0460cc319913f9fbc357875bf5396f9e33fab505","0aeb928f76e42175bb300f1e254f981d8bf22079c66b03169e4d04879495e80a","395187afeb4d7e73edf5d4320ce49cc4774560697ed4c58e8b4214fd03d3e808","d666a22ab6104dbafa95c6c0db8af94ea49f24fbd5c36d00fc009c451ce09708","3f84b72f2e5b9b1dec327c4f14d43da764ad36c5e8e8389453755fb77157dd03","f9e97ed405828144904fb6a2f578e1d39f7f30621d9a483291ce50391c53690a","76b217cdc7d7ac7a1ca3add04d303a920713db149ca022ad49a83e72ea7fba0c","6c29a44633d1c056ddbffcfd43cd1ad4b810cddf796ba994ddda1d63cebf8705","e743ff022e0ecd99912346a9c2b892e5e4ebc3e52b98e60262d96f3367717703","49b69d7560d297a73658c22441c5ce1b294065ff4f3da35227f7684e3a445001","910975c311f461abd40ee2b56ab71f105770a4bc1a581b1221495023353aaa01","d4bd115f5a8d9ba37b3b41dab0cb181f07e60624b755e79e1abfc7da826a6d08","cab5edf981c43ec78bdcbf74313af449bbfe3b4806a205de6adbd2249015aa0b","0efe4e92b667f66f2b8899084941fce413edc2e4a5ab44c066236c6b2a527707","fe52311427d13f55a87cb3a361b196e0faecc106ca6bf40e968b057f64cde106","b2b446d99da75974b26266573f1a641b4c7b477c61b370116d63267471132806","f9bdb81ab2a81f03165e2a5b2d32f1f2b2cbe86d95e3c6f8bf8ac6a2296ccd0c","2e6412ed8f74082f073b6bc2dd63e59ead35112c5c9362610a6561531b931b09","f21e5bef71a01433b29402e008ecebaadda2bd8bed28d0cdf910ffd573434c0a","c5781c5eee63cd99e97b5b319cd3925607dbb371a360e2d5e070f161a063f109"]],"ee":"46793313125f55f30bbabb52e61db65e5ae507cef64f08f501417edef3da5e02"}},{"Ci":["524227a74d9da9b38d2ddeb8649eb9d46bafca1453fb6ab89112544597bf2dd8","55511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e81048","40abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739e","dc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be","46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34","893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e310","6117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1","e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b","0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b","165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479","e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e","302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b","6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f047","1ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee0","94a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c72","72a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8","b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8e","bec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125","d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e","8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939","f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462","c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e50","5e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9ef","be1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c","8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f2","5d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189a","c688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61","459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f76","98bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4","030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda8","8b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0dee","fbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505ea","baa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8","609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284b","df8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150","d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc855","8fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65","a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa6","8a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b49185","98a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999d","c40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9be","c704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f73","2b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0c","a7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046","a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04","100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce","64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb","6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916d","e7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c","99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb","256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee4","49da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd","37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3b","f7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da","3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae","0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda","2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7a","fd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c","3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0","127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693","bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043f","aba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7a","af0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c","7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a"],"bsig":{"s":[["d1d07b998118930c481260cffe5d098a81048b0853bc957f61c376a392bc740d","4bc083e9bc4b62ee04daee640c5339121174af2f2c3c78d716d97497c0518d0a","7468d5ab25b59fab995c815f65550d52567ab22ce7215eadb2a5bf07ff899100","7d381ad3b18f9af8307e97282b681c37a74cc1083bc079e49b79d08e8d15d602","b65d2f153c27e54407d80a4dbda35acc29220996375f7819919ebd9ec6999a0a","a1ee2b5a2f8cd95c1ed40ed310ec102b872a2f0ae40a383036398a467a845003","2b8dc50fb3aead263d9004dee7f10e12aea154ab97f04f4a016d39b97742d00c","837350002903c83f056c5d64316c7aafd83fe355a64802fbef14516b197dfa00","1695103d494c48359e58bbcd358b0eb20d1ef42e3184594fcf51443ad3cfb404","7ced01f064c638698c690b9179b61aa68d369e35d5ac8bc65dae702cbc74890d","cbedb029dd98055213866576a3d5e0617d063dc2470e8d8e5b83574b5687b209","90a81ccea7787be7d38ba9f21b5ba3443049ddd82328d4e8fbe6b2817bf9380b","46f9e643f6a593081f7f1d5d97c69b0fe58c9cc6074c373f8a71c90a12604b08","84716acb458e7dd490b5f0057d917e586c69cafe9fbe47f69bd64c7e61b5b80a","6825520c5327772c2ea8d7450c3d764c4d08cdaa1562a1d990eb0878134d3501","2482c2f5c4ec38fe5161e00eaf669187f2ec98584a6735a9341cf66b4827dc0b","f512a1d4ead70ad08445b1a9af017ac6b375f9c5f0103d0755e1c13046d2c102","a041f2c79577ed00c330e1a7c8e3a6d474dbf6d9ef9c4e3217cc3fe611967a0b","c5b96d0815ad46b04d043dd39ade61805d12aff54f4f60ca89f2ad77433c3908","22c633e75cba3a385958fe1015c502315ce63e440baab882dbeb4ba118994103","676b9e83e6f6d2d53db075f662469e733778dd8082c36cb246b03f9701c58001","5d55bf2ca24a02bd3888eb95597f654d04e84fcd79aefa00125091db32898f05","45a6b7695838c01d0be144395d40af9d8e69b0ce2f1326bcc3d57a244be24000","31cbf5ff387e2439cc3b492466343d35a6a7ae244c0abdcada6cd0e37fb8170a","d2c7582aafefc50fe74f4f05bd1b1aa0a0391c18b2e54f7b8078517ce5072305","e4a509c5ceb5165a148a78c67fc1de9d97d4a7c0a9e6d152b47e2320721c200c","830a015279a3d84acd32f7070af60f82db8fe6f035973f4250dba240999a5005","f96f27a773fc9e7b68558f5f98699aeee07c645097111ec1aaabdefea7746904","82af54c967848b2cd65ebd7f9bd4657292864f46e4620210ae41c1642357eb09","85f8b1dd6e9d87fcca833964873224c9128e899acd85f74ee625809e9f045907","e717b4e15183bf2fc55a1cae983526e833255b1a38a532c3319ae67428e6b604","f1aa90a29afe47935795d9b629abe0654a9375268d11f91a7f0e13a74c9a380a","e66988a69672f086bb939df9590f0e495364a72c8cb298e98bf04a0709722f00","449422874e793b6029a95e26724609dc06fe9086f6ae690b34dc1b29427b4303","50a43678dfb0ddde1db147da17c2e6b3fd44dbe186dabc204f47ab241c2f560d","4c103099635d550200103c05232988f48838d4b56b401242852c4be6ef991203","fa32db1bac264e7663b866f21314c05d3eb006a4fc2e00fabf30b4197b5edc06","b8777c5560b10549622a28ef19132a9e3960f54f3c449c3039985b24be69e80f","17df718ef65afb8fbd4b1d6eb3fa982590962d8863720a6f7080f6d73251360a","62b8674d157eb1a2d313631aa446635ec453090c75cb7470cbe60044f329130b","829cf76313e13a5a58dd5378ba3d1bd7c7a6b931e63f233204c7caf9d4fb8d0b","036910d5fd088dff93e7f6a43888096b38b5e0d39bb94b2c570e94258ba8f601","43f516aef8442b816bcf88ce21eb62bf58874644dfed54d13ecaec398846950a","01b76ab69ed1324cdda07cf14831bf6554f1ad9952defc2186c66da67a626504","9e01bb01a69c67b81bc0977ce81b13cd3058ca82bc212d17514ac19cc40c9703","cd8a3c8253d0163d494a9bce334b798aaeddece4c546509ed61953f8ed357c0e","861c7bd6ec632e7831b57a9d796b0513a715ab7348db38f3dcfc8474a82b8107","6e69287de84cc3bb1e8326f467b961f0dddcd70a9d104cfad472d3661bada40b","1aafdc9b6fcaf21ba61e8e5367aba0374fb927c375f90b87350bb0e0dbd8e105","bbc62c5fc32f99f4bed49baa2f3db5a4705c4d2feaf3cd244d4d28d6f6a1400b","5384e8f695e1449db4ef23f7b77618ed7a366eef3ae38d0b36aa19541a5f1d0b","26c006bdaca798027322f8294933ad9d877fb79cce3cd756b94ed2cd311cb40d","b8cd46406725c9e7fd92893cdba0d1001796a286d9b18707f4ee433f90665607","f60f5caadfa08817e1ab0f440b26dfb5163469c871bd7e0bfda5597597a2d402","65d270c8303c48231c97b1807ddd444105aa6e195df4c208a0d07ab7721e4909","6e2b8edcaab76fc8729fd3bab94895518e3549dfcfe5238ef3f92e912e23bc0f","032af4ad5abedd0c10613e50c26c9522902074202bd2c93ca60a7fdf4c5dff06","e27594188bd5c6fed2874df4acf8b6d2f412959c2f274ffba18d040909985f09","038124541070db29455f8d58c33323798cb03cfa2dc0298a856e64d4d7eb190f","9c47bbbf7c075e8cc3ac5f980cad86287d1940dd8dc0ace92241ddb49d454605","cd9229fe4080ca8e4b34bf3c35c3b95436d64f61d321a30f402f2695b6452d04","f4aa87a45504333b39c487f05fab5ac541893bcccaccc27313293c20da1df10a","1fe1edcb7962bd4c9b211472947196e01ee99d2d3f518710241dfc5f06a3ea0f","53b36bc3696808aeba745a58c8c8b27f5bfeda5676a95596cfc24e7aebec110f"],["851e464baf54513501af9262618d44cb81eff6d451d2ef90868a0b76fee1ae0f","2392eca80014d625cf976c763f86186cf984943a0324bb38b8d23d97a2dae700","2590aa56836825bee4770d74ded76b9187ed26ce8795d96e4c211056b7d8380f","3e2ee2b7f4b37342968ebb1706e85233ad8fa15a39cda0417f86dacd35829a00","53eec03966ac62876503efabf4407da1fd5eb13b542cbfa9ee755725cfe9c30f","1235645468cbe11d875c5ebf96363b25abefa920867af543bcbc4148d5f1e50a","c520452ced483e258ffb7ace14531c5c7de112f4827c594cbdaa373e52f54506","833713e2d2fab34ecb5228c7c1c1cf402be69654c5ace0376d34ef7339383f0a","2243fba2d420eb964593f871e18f5764824976f2f4c28f8624d9921237d45106","8ed944427dd822c8789064dbcdadca5c14b766706c7c3ac034875d44e94bf604","7e1e989bc3506445c88c08b7805bf5a9ff3bf1636b93e98f039cb762d667ae02","eca0fa2e30835f037dc359ce12edaafd46f064e0aa513d1e3239e0b63af87909","0a56c3d72748837b84bbd59a55a15c15b4e44d110532a020b9da108bfcf9530c","3e1a49bf0c90143ecdd1324ad2beb6a4e1d033f46ceb918f4259d5b17bad3304","e40fa0c77edf3afee63799c94b470a4dc3c89d18a43dc8dcd31aac6d33a23801","a3548cd48aae4fcc39d0c2775ea1a26290f6d7c6d49506ad8772b1a356a8170b","d0be3d0d451aa618d6312d6be5a86a2a398ad051a7e5660a454a4c45da41630f","c9e3af071abd46b85a07749d1ee6287a4afb898f7c49655763e34a9ab47b9d0d","750e5c0b8526960a576d55862c3e75f97716f211903d808ec860f41361129c0f","c8cf440475e64a92338a37ccdef482ad7d9ad75a0a94bfb9007db701548dad00","97312ecdb25219b47f5f44a707846ff2c180198747102dad0e37f3bdda0cba06","0aa8fafcabd48672879641167142c0024458aa118c2b6189ff95812c56515c06","adf80b43718fe494419e883abe0f33e36d4e18f6cdfeb8edb121b81d0db1770f","2705face099e6bbdae0cd485d486a8c2018d49bfcd956704f669edb352807c08","6c9edd1d0fac0eefed63077d6022aff763c8d34e51d69b26016740638b477606","f68a07a3089aca2204035f772b06945823375fff78fd21fa554b16688ecb210b","7291f1617493c6913bae6c90eda5935cc53a50c933e7b3b453b68f3871885b07","3c7a25563a66104e4dc28007990565ec9e2a3382f9924c6a63953af4929d4d0c","d3282c0bb4c5cb3f2aa4c1d88751f15a5cb7ae8687034b6ab38d46b76b0bcf09","67ba42db29eb1d7c458e923e6c6474e79ef469e92805b30a3a6e6e9101c53e0c","e747701d014c338171df6ba79f8aa6234a49e78a8039e2ea9b671feddffa7d03","cba531d055be1808d6fc2a6e229087b77a3be35e40ab4a6f378c216ea05d1900","cd1aa21bf3bf0afe40e95c406000b49a429bdeeb563f32b87bedc1adc6e5e501","c051a69901925053f064ae1e8054e34500b97eff9f974292eafbbd85715c2500","e45a20f16556e09b7bb530764582a59ec40a4af28d95e6fdfea3f10ce35e2b04","16e209c92a5045b8d7af4762cebf2d6d97b8960d9e9565e0549a2488a9e8b50c","985f5395774fb807d49a0f84e0983fec0e32fb5342a3704c5d198dddc945d806","c3262dc93f96a1b1a09c2bd461f324c068aae59d5473deba8ba6036ad96c670f","42667ada96da901567e544157b12f56159bb7c3f83b90abd1b9e7eb948f2e702","c9eca93744830f0dff8b7bc73a1458b0f8d40e679afd1e22b09c93f2c9729602","c6c81026f8f02991c094a38ed0e6062508974a8bc69e8d6e6741a45eabc7db06","65f8a2d879de17fdaf7ad1b210f337b90aec1c1c3676f244aa3852a77c05db0c","05b9936bc10f0a327602aa2109bdd115f8a3e1081c1b0c56649638207d2dd908","6c6ab885512fec9650ba288df5133c9dc87cb7fe81919fafca783f1252c29303","32559bbafcdc830223837a7b78a7ce137e9f4a4e3e7d1195a494fa6d0ca9a106","59c0df4ee0c9db91dba17acad08e5f6ccae71012e2c6e4b37894520fffe9620d","66a72a1e4f04e5fa2abe93ef7f4b2e9e837c9e0f9d962b5c80f5e83408746103","58576c23403b2d742c587e3827d17c6f41b31f9da4030207aaa2f4c32059f401","7bfa4c324be2ac00a0aeabc7756ff2e16bdf39ed6b0c313f01b1acce8bc2b408","a0cb3eaf9af9b68dd17f258c0bf0c536b19040d6bebc0a9dac7fbc59398b5208","91a741c4183a851c1cbcf3f78f30924d85ea2d907a6ac8db9a1455e4c672800a","88629853fefb86800fec6631d77edf591d56199d5980bf3c86d9254773e02508","be6a488b032686134049415bb194ad11ab8e4b1e6304274091beccc795e7b604","649f106b395bef6f30a1dca330561709cdf7295557410d040543c279e169a908","dc3d94c4809f0a7dd92ad8cefb014f5d47681b21d6b955fa894eff5b57cc5d0d","1b08c01c4e892af363e6560a02212edc1fa645cdbce380b3e010d3c7f473960e","c86e8ab8d2c235f594cbb2c2fa62f27b4ea4ab9796b8534688fd9819ea529c02","3ab370c6e482633801e3d588f3a4433cef5aaeb633547e922dbb35410af8a803","156b64daf9633636063a37c4adc43e492023e09137099a715a690a480119f60d","9c2f1a110a6590ff1b7774e0fe3e4a7419226448634c129aa1213e560814cf0b","28d28fbd4029688f0d920a5f45c2a06bdea6c38f731a8bf741ba106bb8a3ac0e","35fd8c015995d55c0733a18e66705dc84dab552aff7d2ae654b903f6fc9f1305","da5a5bafbfbb22df241e211416fa902eaec589c7a22d6bf160a884e5534c5e0a","685c312527c037f3196d4122f4d42c5a683d0e555af2a271d8d68faea80dfa04"]],"ee":"bc27bdbca4d4b3c14ae0745ea453174a6e6e3b3014dbd23ac3c8a413b5e29d08"}}],"MGs":[{"ss":[["02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0a","de13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05"],["738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae504","6b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0a"],["f06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f","5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea261507"],["0aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a507","4932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40a"],["b801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d","6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d07"],["6ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101","c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec8408"],["10b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d258804","83bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b00"]],"cc":"8187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908"}]},"ecdhInfo":[{"mask":"417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f7808","amount":"64294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b03"},{"mask":"67826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c01","amount":"0db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301"}],"txnFee":"2230540000","pseudoOuts":[]}} +send_coins.js?2:504 raw_tx and hash: +send_coins.js?2:505 {raw: "020001020007d8fa7ef1f42ba3d85587de0499da67e8af019a…5f9d6214afd896b20a15edba78f7a087c23a30d894f688908", hash: "4da46a03139ab26295dba21cabcb5f6efc74b165f1df183e83d55169b6902f0b"}hash: "4da46a03139ab26295dba21cabcb5f6efc74b165f1df183e83d55169b6902f0b"raw: "020001020007d8fa7ef1f42ba3d85587de0499da67e8af019a940fc0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b020002105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b10002d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d312101c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df301e0adcda708417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f780864294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b0367826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c010db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60ae93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810eb57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630cad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff020f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a9067d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a9940040331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b0540d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb0106374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d146001d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a02087c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0edf3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50bbb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a2595090832aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f60873f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca46099248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc12052a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50ca90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd10018ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d1018410863d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0af1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b059b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0ce21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0cfc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa7020850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf165074813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f0556be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780fa7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb042dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced068d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70bb3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0acc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da06…7bf2dd855511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e8104840abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739edc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e3106117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f0471ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee094a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c7272a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8ebec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e505e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9efbe1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f25d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189ac688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f7698bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda88b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0deefbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505eabaa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284bdf8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc8558fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa68a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b4918598a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999dc40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9bec704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f732b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0ca7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916de7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee449da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3bf7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7afd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043faba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7aaf0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0ade13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae5046b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0af06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea2615070aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a5074932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40ab801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d076ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec840810b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d25880483bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b008187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908"__proto__: Object +send_coins.js?2:303 13192 bytes <= 13 KB (current fee: 0.002230540000) +send_coins.js?2:313 Successful tx generation, submitting tx +send_coins.js?2:314 Tx hash: 4da46a03139ab26295dba21cabcb5f6efc74b165f1df183e83d55169b6902f0b +send_coins.js?2:323 Successfully submitted tx diff --git a/__test__/fixtures/requests-modified.json b/__test__/fixtures/requests-modified.json new file mode 100644 index 0000000..935d6bd --- /dev/null +++ b/__test__/fixtures/requests-modified.json @@ -0,0 +1,499 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "WebInspector", + "version": "537.36" + }, + "pages": [], + "entries": [ + { + "startedDateTime": "2018-07-06T00:25:11.160Z", + "time": 159.51527599937253, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/login", + "httpVersion": "http/2.0", + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"withCredentials\":true,\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\",\"create_account\":true}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 26, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 361 + }, + "cache": {}, + + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + + { + "startedDateTime": "2018-07-06T00:25:11.487Z", + "time": 159.68929200007187, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_txs", + "httpVersion": "http/2.0", + + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 1218, + "mimeType": "application/json", + "data": { + "total_received": "296850150000", + "scanned_height": 4819440, + "scanned_block_height": 1610359, + "start_height": 4751265, + "transaction_height": 4819440, + "transactions": [ + { + "id": 4751309, + "hash": + "3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9", + "timestamp": "2018-06-21T21:33:42Z", + "total_received": "296850150000", + "total_sent": "0", + "unlock_time": 0, + "height": 1600105, + "coinbase": false, + "mempool": false, + "mixin": 6 + }, + { + "id": 4754424, + "hash": + "b047570a0e8192204a7f47e8025f6a2593f47d7fa5def75ab2a927d9c1d0f2b9", + "timestamp": "2018-06-22T12:19:09Z", + "total_received": "0", + "total_sent": "296850150000", + "unlock_time": 0, + "height": 1600561, + "spent_outputs": [ + { + "amount": "296850150000", + "key_image": + "4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7", + "tx_pub_key": + "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "out_index": 0, + "mixin": 25 + } + ], + "coinbase": false, + "mempool": false, + "mixin": 25 + } + ], + "blockchain_height": 1610359 + } + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1555 + }, + "cache": {}, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:17.450Z", + "time": 165.1763129986066, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 958, + "mimeType": "application/json", + "data": { + "locked_funds": "0", + "total_received": "296850150000", + "total_sent": "296850150000", + "scanned_height": 4819440, + "scanned_block_height": 1610359, + "start_height": 4751265, + "transaction_height": 4819440, + "blockchain_height": 1610359, + "spent_outputs": [ + { + "amount": "296850150000", + "key_image": + "4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7", + "tx_pub_key": + "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "out_index": 0, + "mixin": 25 + } + ], + "rates": { + "AUD": 184.86, + "BRL": 545.54, + "BTC": 0.02091, + "CAD": 182.49, + "CHF": 136.67, + "CNY": 938.86, + "EUR": 116.82, + "GBP": 105.13, + "HKD": 1107.27, + "INR": 9757.02, + "JPY": 15489.53, + "KRW": 151000, + "MXN": 2640.93, + "NOK": 1148.19, + "NZD": 219.2, + "SEK": 1217.07, + "SGD": 187.42, + "TRY": 634.73, + "USD": 136.94, + "RUB": 8588.71, + "ZAR": 1925.7 + } + } + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1294 + }, + "cache": {}, + + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + + { + "startedDateTime": "2018-07-06T00:25:48.523Z", + "time": 155.7823879979951, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_unspent_outs", + "httpVersion": "http/2.0", + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\",\"amount\":\"0\",\"mixin\":6,\"use_dust\":false,\"dust_threshold\":\"10000000000\"}" + } + }, + + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 935, + "mimeType": "application/json", + "data": { + "per_kb_fee": 171580000, + "amount": "296850150000", + "outputs": [ + { + "amount": "296850150000", + "public_key": + "af40d936c933909f7bdd795e70129ce7a5779bbf9b44c4ae603ed826a397ca9d", + "index": 0, + "global_index": 6251022, + "rct": + "35ca97e4550d966247076aa1ca015e8ebcdc0d63594f48ad18cdd806c789b2f970273a47742ff06fa3549a004f97927dd89b0b126655bc2680ce3ed3bbb88e05f444932916512b22d51042a876337e9014f120a46cd103915b812779bda41e06", + "tx_id": 4751309, + "tx_hash": + "3153c385e55f1577cad35ab8327f1acf179a82a3c33691beac2d3dfb6c5189f9", + "tx_pub_key": + "5998bb39ed954dd90a3bde725b4e283160832aa668fcde400976759e84e84fcf", + "tx_prefix_hash": + "1c1ae56c6a558eb759c2382f3ffca3a557a6399d6133113aa5d8488cfa517c58", + "spend_key_images": [ + "4e6f85b47b282a388c15075746d2a9e3fdab4e0dbfbcb4251ae50cbbb91363b7" + ], + "timestamp": "2018-07-06T00:25:48Z", + "height": 1600105 + } + ] + } + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1271 + }, + "cache": {}, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:48.837Z", + "time": 158.4292709991605, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_random_outs", + "httpVersion": "http/2.0", + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": "{\"amounts\":[\"0\"],\"count\":7}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 1723, + "mimeType": "application/json", + "data": { + "amount_outs": [ + { + "amount": "0", + "outputs": [ + { + "global_index": "2080088", + "public_key": + "e3b02dc808097301664c989a4faf7c5f2ec8976777b74dbe4b8cfecf8c44ca11", + "rct": + "bdeba6a579f63f9dfe344ae4754c8db083e1f87a75956bb2c90f93210b3d02a1" + }, + { + "global_index": "6002676", + "public_key": + "bcfd234d10151e10eff22c7b84bcf7064f9a35c3e0c53dd315394cd66d1ddc8e", + "rct": + "5206721c74d96343283bb82ad0abde561f10913e1217b3826ed5cf33fe7c5355" + }, + { + "global_index": "5980172", + "public_key": + "3fb2a5f3016c21721349e27f98ba734bf3d0cb577ba4640fe5db5904a2bb146e", + "rct": + "df525ea20e203f3d0c6ed5caf9e083e679ce6daee56c992216ec39849b12aaac" + }, + { + "global_index": "6349826", + "public_key": + "85290b8d45e551067fdcb0ec89942e2aafe5b702dee8a03ce3670a2243775d31", + "rct": + "c85b7fe5e0fe219f9585d8808f6a32ed71eaeb4b663a525034a79b4d662237d6" + }, + { + "global_index": "4281075", + "public_key": + "7c82f043d142a221f9f0a5f9bc4337270a58e3f2d8d4dfb6f8619842bfe0d1c0", + "rct": + "636a54cdae73d0517334a02cdc9b6c6081c261e250f510284c1289ca3ed5e26c" + }, + { + "global_index": "2799561", + "public_key": + "69d3eb381e3a43fd41b19cc41004866c00274e9daeb07de3303bf7a949719d50", + "rct": + "733270f6b344c77dc8e463a95524fb90626874c2d8ae15b02578fe8404be8491" + }, + { + "global_index": "4203500", + "public_key": + "4d9eed420a1844a32e588317c7a713fbe59d9281b45b7f3504b9bda307c2c3c1", + "rct": + "dc1afdf383ca54068da77efcd29ae628882db2fc93adf8c1e4a96da7d066d1a1" + } + ] + } + ] + } + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 2069 + }, + "cache": {}, + "timings": { + "blocked": 1.1862710000007937, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.17300000000000004, + "wait": 156.43599999916879, + "receive": 0.6339999999909196, + "_blocked_queueing": 0.27100000079371966 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + + { + "startedDateTime": "2018-07-06T00:25:51.274Z", + "time": 401.30272299839635, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/submit_raw_tx", + "httpVersion": "http/2.0", + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\",\"tx\":\"020001020007d8fa7ef1f42ba3d85587de0499da67e8af019a940fc0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b020002105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b10002d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d312101c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df301e0adcda708417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f780864294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b0367826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c010db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60ae93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810eb57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630cad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff020f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a9067d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a9940040331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b0540d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb0106374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d146001d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a02087c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0edf3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50bbb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a2595090832aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f60873f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca46099248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc12052a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50ca90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd10018ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d1018410863d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0af1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b059b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0ce21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0cfc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa7020850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf165074813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f0556be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780fa7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb042dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced068d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70bb3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0acc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da069afb8b06e63c93d5e36e33e4ba022fe2352061f55f4e5115d048e4c36ab56d9d558daf0015a8ec96436dd72e4ee610e0806adbb382a7ef9d3dd9774ed070abeec25d460e304ebc4b2d097bfa0f6982abe3f7490f0374a278f1464505e0233a1195e7af01ae7e06f814cb7266ca60722c53e2d3eb66a5a1f8cff91bad0da26b5716ab7106fceb4b4a1a613014804c5284e7c8d785512730c7f109d4c225f9fa00cff4d10cf7f120e75d1c37c5e5b4e9117f30fb30c3cbcf86a33225fb4f63f2a2071682045dfb733111c50730556e62bd49a471b71c9421b312e8805358ba53af071b610399e917fa1b4687093313e330b453dd98b3f7866bcbe737f5e11f4bd1c0043b0975b84a92c4cfef856688345ebf4ba84b95aaad4ef573774e448d7ea93f6ba10937ec41a174bc0965d667918f608b6358d424628859d0933cccdf51ec7d55f906438342b45be0e9796bb57225e7bb184602fb97dfb48260bcfa427f787a759c0f5a2a8929697f47c854bebca692175efb36276a5f9fb92437836e65d87bb3b70a09663579a35c801c1aa654eb28517013b5e877a003be057b875b35df2fe227090c58358156115c37d72c290912ee288600cdabb492312227fe659dc238df6e03def5668fd22573e63b245586cd10bc66d68942e01bc9dfb9d152a1a7898ce002daef4fbe574991fe37e0b8b69ff269e098b7b1640a47b370fb38f410eb0d5703536bbe4bcc900df2a526d80a413b9c9ed3ab75124ff7198d3a44e2ca0cce410fedf5f2ef7b33f93b43a26f4d30751f2bfb4b13ca42a3105196bb17e5c2653e0c00adac72a44baa60b2d2411fb239d0d0dfd68251d362bb04ab0a3cc169cf61049bdc73cf11aee0b90f7b26cbcdab987873ab69542cdef0bb8c125da7034e320dc9bc4f84a9827cd927165c3455ae9fd8f5325e606b58e3cb6db67bc46b434c0bf78b1ef5530c0996205b419dbde1a8b1a9ed21abf38e3c5a16d34d6091a1e4065c79fe8d78b95184e779434993a52c23e6e9c6db90e9db5b90e97a29def79302922934dd5413a93d183cd83ad88ab3d439ef39753c84588a8269a74f0d1972080fbb35971f624d31041842ed430c70d09171c20f0f00e0d0c4e8ef8149b9af038846db4d5b2d8a586e5f1b34803e52cab395311ac5bac25f2ce3cb2203d8070354da89b48c24c0da18b4db9f87d210388ec2655f48fcc42bcb78c367bef2aa0c543f7e562adc23a6b22445ca1389156587c0af0fa16aeaa1aa5e6508228e9c04620aafcb6408e66ad86f0258cdd04a376ca1f41a623e7cf36a800d5da91583029bb46e73b038d27c370d948571c442f65302d09c1562ba3ebde7e7e803f32602799726043b23a4eca27e62986009cc9e4419fe95cece9504cabdf6d92f01f105beee7e9ece3fb49b495922bfc23337e53baafebcad53a250b66d168a87b3a4064440985fe7becf778fadbbf8eb4d01c024039a4cdfd5f27edab16f4702b4c20590dce3157b8c727316ba8c25de3cab3112c385f8ca0895e5c775e5f34c17520cc8cf80d28be593f186ad7f6f4666f702df2566c16a5d5e1aad21694774f41f0003fbcb4502fcb5e1deff0fa2a458f6ff88bbc91cfa2e21cb42b0077da5d43007d68610e05172ac51036ab6a52140e14e1707dc325fe8efcc39c74883780e9404bcb78fdf38b2763ddd7458db79ffaf28305aef1534d7ec4112316a8acfbe3504277ef2e7dd36cc72869f1d53689d904cabd23085165d2933ea15fa491da5ed01f00468c47ba2185b241405d89952922a74c02ceb8617543b70f353a62f017b023dc53beb7b858019493aa1d2bdcf2d6eb7683d453d62dfd391b189c296bee006d7c85bd22dd02bf6448cb7af0460cc319913f9fbc357875bf5396f9e33fab5050aeb928f76e42175bb300f1e254f981d8bf22079c66b03169e4d04879495e80a395187afeb4d7e73edf5d4320ce49cc4774560697ed4c58e8b4214fd03d3e808d666a22ab6104dbafa95c6c0db8af94ea49f24fbd5c36d00fc009c451ce097083f84b72f2e5b9b1dec327c4f14d43da764ad36c5e8e8389453755fb77157dd03f9e97ed405828144904fb6a2f578e1d39f7f30621d9a483291ce50391c53690a76b217cdc7d7ac7a1ca3add04d303a920713db149ca022ad49a83e72ea7fba0c6c29a44633d1c056ddbffcfd43cd1ad4b810cddf796ba994ddda1d63cebf8705e743ff022e0ecd99912346a9c2b892e5e4ebc3e52b98e60262d96f336771770349b69d7560d297a73658c22441c5ce1b294065ff4f3da35227f7684e3a445001910975c311f461abd40ee2b56ab71f105770a4bc1a581b1221495023353aaa01d4bd115f5a8d9ba37b3b41dab0cb181f07e60624b755e79e1abfc7da826a6d08cab5edf981c43ec78bdcbf74313af449bbfe3b4806a205de6adbd2249015aa0b0efe4e92b667f66f2b8899084941fce413edc2e4a5ab44c066236c6b2a527707fe52311427d13f55a87cb3a361b196e0faecc106ca6bf40e968b057f64cde106b2b446d99da75974b26266573f1a641b4c7b477c61b370116d63267471132806f9bdb81ab2a81f03165e2a5b2d32f1f2b2cbe86d95e3c6f8bf8ac6a2296ccd0c2e6412ed8f74082f073b6bc2dd63e59ead35112c5c9362610a6561531b931b09f21e5bef71a01433b29402e008ecebaadda2bd8bed28d0cdf910ffd573434c0ac5781c5eee63cd99e97b5b319cd3925607dbb371a360e2d5e070f161a063f10946793313125f55f30bbabb52e61db65e5ae507cef64f08f501417edef3da5e0202836fed5f531a64dd41aad00a1dc39ec13b49d1ebd7a2a6b5867d7d71db0e50a393ec33f28531624bdfa3ddab00ab9239b2bd54ea858920f34c26430045898f52b419f7f04f7b3b8e604bf11b20ed058e8ecdeae5be4845aa5f14f16c1f953d8e653029b087bcd4ab9953c1937cee84f81c393285c8bc3341a5eb5bae580b5d55cf6f0ee2afbea795764bf5d4ed0735367a0bf65b6f69732449e3dfec547d93d22056ada1eb1c35c2ca1924e07c0e6120b818d6941755efc4dae89d0cc039762bf5e003763a01910d434d3f0c3e71af286c55d30c9aadb2a480b8d0938ce43c202968df06659dc14e713dd6b2899be1501718e3003be5220b7e90034244ab3e9db020600bdb9b2a17b9d87e150356cb3f3a7150bf305d03efb0aab95cf43e5cf124a8f5db6c37b81f7841b81a7cc5c2ed6d3b8124ff95f9981fc93d732dbf470403a44e0f46ffff9bbef09e2fedbf9b42600918567617ea28d8f42e6b1d764a340ea317a4462b1083062b29560d25f2b35a82bfdf000033f8a0db28e7314d4d784f6b9d0df70928cc30b94119cf02085884a237b2ff7470e299726d06f430f72c73a1727446f060a3d98ee0c867ea162c197bacce848ebc423f3dc6c4835e8bbce299084ecf73139b024c33f215bc132fe77cf326e2da2cd18b3008bf16447ff4b2a4a5679987564d369e8936270b4adb3128e6e88eb75205f9de36088fde3a7d294c7c38cf0d0bb02c1160a6695550423737668f1646ca65b93d95547097a2fa8bd70f74a623e5a3c9c3c6b70097bde54853b7517c76c0d36e59cabce4bb691bff14524d2a0eac2155a9af73a4e87f0d6ec67d4a27840ed0b7134fa93d751588a77416daca2b68d054a7e74dc752a9506090e79596c9b558f085853055d87852c251baa10a9607b86c009ba3ecba6ed2457d499128f26fe632c39e5ccc46fb4b7e225ee282d9883abaed6e531b4f844df3d5cb547152742d0a0744e40fb9284637a34dbd3a4ff4dae9a07904b36e539f56eec6ce7baf51ed8e8189b242effc3bc60908d6b62739b6d6e8206e4223160eb293eeea2992c0dcaf60c73632ce196441692c1f5131526818f5bff1cd83a75e414e595e13a082a47985bd505cf256f5ec516ae79c1117f9f7820594d696a7714d2151450127dee286f8d18de13517b24eda31f18547032200489505d2f53101c962641b7275b8a48296f339402f7f48ac2674f669841615752eaf495a6dac6bd4d6cae58be8c779890d201904221ef4a1a0f87c523b027fa43f52f37ea06f8a19070fe04f7533dfb529d802a22d7d299f3abb6974611456060915173bcca396febe1bc943150131d94a2406565957950fea0f11421c7ae2b7e900f46a8c947b7e5e724bd981c00a1c93070a6dd51bf406b876c5141f7fb5bd84cb788dda0b822bdc4263e500c85c079aee409cc00e411628ef38f9c948a3ca7989efe4592d7702aa846b3a1cfbfc5e4a1801ad6a63386bdaa004af3bd3182a082ae5f17d30862c5a0de12078aba63b51ad2ad9c695257e90cf997464da236ace2f90020547003d30e8f63998ab705aa56fd4031397d36526e09faa72c4789380a4df227104cba21542a16f64b19ba96a84103513d94336e5077c76eb3a301e3bcdf507c9a0ab6b7f98d6533d6cdfe04f72d24042472cd55b68ff9d9e1d8155c21e7df17066751a99ccc5147d0a828053f77b059879160522a71a95f14e058a51bf443264700870541f074c182b1ca3d1250b6e03377dea966eeba2bd820d680f35dc7226e4a8faebc88da83b49da2b5e7dc897dfc7998961a5fb82372fc03ddcbb6043284c80358ec0f2bf9cba7e3bbce8696dd16bc32819e8182b5528b3ac82df464b286b944a13704dc17befb80ed61d24a2b2407fd2bbf0fd1e547b1437db807e113c9e514048342558691bb18c8e2fb7318a5dd730f570722b73446476e28fe2c85f1fb908cf4898025d519f075bc16c0db7c580f8d5eab655c7fb355eff1463026b61b092594a971e573cbc71f2c6fd23782171dd7c5a41d980ac1830093a2ba8be6de21cfe8a07463deb4428bcf1382326c02edf5e1cc806779da45ac34d5c0db4f2382f5e08be2c31fcdfd93be10f4f6c0842938f9080674ac1ce67acacb5727ac6bfc7995b29f02e324e59f291a5bb4751f66dd6200ca2c9c8af94a1d6c5531e6d332d8a91c4e00c21fc1248cede72d88c8fa61f98513bddd06b910456ee60635081b1627a0e01c67e8312f0e0fed42145e9c514547cf5b5bc53634a11729b9eb6ddd217568a49772e18e020d0d5ddd2220d938c753a044fb4bd49d3b2002e951849e32ad1f37a7058750c627f4a94b03d048a524eee29bc8de8ea1f76255b402b63f9b4ff050aec4af6c6ff1ed7cdd62d5f9e38e5deb73068b2a49a460adc7b98f64e72729ffe7d63662e25ea421d73995c22633e12ba5892f6c2b1dfdbe1f85bf5c22208bf1399bcefb5f9357db7c84bca879fbddc966d7adad62121490505b5b95cbd545753fb900ddf50a6a6588828f8f95252b9c7aaf321303f19c496bfa2af90104416a10771ca02d7daf9204daa3c0d9c8eccc7fa80234995db0869947e7412e5443851495e89259f92bee8e5b33c51e08f3dcfdcfb2a3b989ba51c16e820ca2f3bbd34c913d8ffd75d36447bb2539e2f424297ac39ac5090463289f332a4555c6243315c3e2c241c5a2534c08c6397ee82dffd9b8b2dca1d64ca3cc29cbd07e67bf095ab47273a7a70b59cbe936cd018be9d0c3f8a224c405de0a58cc5bc2cac7b6e6f514089b036eee6b0904b1979e6077b6132d9fd669788f5031d3f88107a857e35117054f9196a197e97ce89507701e23077c93b98401ad75a8a30a77317952fca30ec28eef2179699f93dd1d07b998118930c481260cffe5d098a81048b0853bc957f61c376a392bc740d4bc083e9bc4b62ee04daee640c5339121174af2f2c3c78d716d97497c0518d0a7468d5ab25b59fab995c815f65550d52567ab22ce7215eadb2a5bf07ff8991007d381ad3b18f9af8307e97282b681c37a74cc1083bc079e49b79d08e8d15d602b65d2f153c27e54407d80a4dbda35acc29220996375f7819919ebd9ec6999a0aa1ee2b5a2f8cd95c1ed40ed310ec102b872a2f0ae40a383036398a467a8450032b8dc50fb3aead263d9004dee7f10e12aea154ab97f04f4a016d39b97742d00c837350002903c83f056c5d64316c7aafd83fe355a64802fbef14516b197dfa001695103d494c48359e58bbcd358b0eb20d1ef42e3184594fcf51443ad3cfb4047ced01f064c638698c690b9179b61aa68d369e35d5ac8bc65dae702cbc74890dcbedb029dd98055213866576a3d5e0617d063dc2470e8d8e5b83574b5687b20990a81ccea7787be7d38ba9f21b5ba3443049ddd82328d4e8fbe6b2817bf9380b46f9e643f6a593081f7f1d5d97c69b0fe58c9cc6074c373f8a71c90a12604b0884716acb458e7dd490b5f0057d917e586c69cafe9fbe47f69bd64c7e61b5b80a6825520c5327772c2ea8d7450c3d764c4d08cdaa1562a1d990eb0878134d35012482c2f5c4ec38fe5161e00eaf669187f2ec98584a6735a9341cf66b4827dc0bf512a1d4ead70ad08445b1a9af017ac6b375f9c5f0103d0755e1c13046d2c102a041f2c79577ed00c330e1a7c8e3a6d474dbf6d9ef9c4e3217cc3fe611967a0bc5b96d0815ad46b04d043dd39ade61805d12aff54f4f60ca89f2ad77433c390822c633e75cba3a385958fe1015c502315ce63e440baab882dbeb4ba118994103676b9e83e6f6d2d53db075f662469e733778dd8082c36cb246b03f9701c580015d55bf2ca24a02bd3888eb95597f654d04e84fcd79aefa00125091db32898f0545a6b7695838c01d0be144395d40af9d8e69b0ce2f1326bcc3d57a244be2400031cbf5ff387e2439cc3b492466343d35a6a7ae244c0abdcada6cd0e37fb8170ad2c7582aafefc50fe74f4f05bd1b1aa0a0391c18b2e54f7b8078517ce5072305e4a509c5ceb5165a148a78c67fc1de9d97d4a7c0a9e6d152b47e2320721c200c830a015279a3d84acd32f7070af60f82db8fe6f035973f4250dba240999a5005f96f27a773fc9e7b68558f5f98699aeee07c645097111ec1aaabdefea774690482af54c967848b2cd65ebd7f9bd4657292864f46e4620210ae41c1642357eb0985f8b1dd6e9d87fcca833964873224c9128e899acd85f74ee625809e9f045907e717b4e15183bf2fc55a1cae983526e833255b1a38a532c3319ae67428e6b604f1aa90a29afe47935795d9b629abe0654a9375268d11f91a7f0e13a74c9a380ae66988a69672f086bb939df9590f0e495364a72c8cb298e98bf04a0709722f00449422874e793b6029a95e26724609dc06fe9086f6ae690b34dc1b29427b430350a43678dfb0ddde1db147da17c2e6b3fd44dbe186dabc204f47ab241c2f560d4c103099635d550200103c05232988f48838d4b56b401242852c4be6ef991203fa32db1bac264e7663b866f21314c05d3eb006a4fc2e00fabf30b4197b5edc06b8777c5560b10549622a28ef19132a9e3960f54f3c449c3039985b24be69e80f17df718ef65afb8fbd4b1d6eb3fa982590962d8863720a6f7080f6d73251360a62b8674d157eb1a2d313631aa446635ec453090c75cb7470cbe60044f329130b829cf76313e13a5a58dd5378ba3d1bd7c7a6b931e63f233204c7caf9d4fb8d0b036910d5fd088dff93e7f6a43888096b38b5e0d39bb94b2c570e94258ba8f60143f516aef8442b816bcf88ce21eb62bf58874644dfed54d13ecaec398846950a01b76ab69ed1324cdda07cf14831bf6554f1ad9952defc2186c66da67a6265049e01bb01a69c67b81bc0977ce81b13cd3058ca82bc212d17514ac19cc40c9703cd8a3c8253d0163d494a9bce334b798aaeddece4c546509ed61953f8ed357c0e861c7bd6ec632e7831b57a9d796b0513a715ab7348db38f3dcfc8474a82b81076e69287de84cc3bb1e8326f467b961f0dddcd70a9d104cfad472d3661bada40b1aafdc9b6fcaf21ba61e8e5367aba0374fb927c375f90b87350bb0e0dbd8e105bbc62c5fc32f99f4bed49baa2f3db5a4705c4d2feaf3cd244d4d28d6f6a1400b5384e8f695e1449db4ef23f7b77618ed7a366eef3ae38d0b36aa19541a5f1d0b26c006bdaca798027322f8294933ad9d877fb79cce3cd756b94ed2cd311cb40db8cd46406725c9e7fd92893cdba0d1001796a286d9b18707f4ee433f90665607f60f5caadfa08817e1ab0f440b26dfb5163469c871bd7e0bfda5597597a2d40265d270c8303c48231c97b1807ddd444105aa6e195df4c208a0d07ab7721e49096e2b8edcaab76fc8729fd3bab94895518e3549dfcfe5238ef3f92e912e23bc0f032af4ad5abedd0c10613e50c26c9522902074202bd2c93ca60a7fdf4c5dff06e27594188bd5c6fed2874df4acf8b6d2f412959c2f274ffba18d040909985f09038124541070db29455f8d58c33323798cb03cfa2dc0298a856e64d4d7eb190f9c47bbbf7c075e8cc3ac5f980cad86287d1940dd8dc0ace92241ddb49d454605cd9229fe4080ca8e4b34bf3c35c3b95436d64f61d321a30f402f2695b6452d04f4aa87a45504333b39c487f05fab5ac541893bcccaccc27313293c20da1df10a1fe1edcb7962bd4c9b211472947196e01ee99d2d3f518710241dfc5f06a3ea0f53b36bc3696808aeba745a58c8c8b27f5bfeda5676a95596cfc24e7aebec110f851e464baf54513501af9262618d44cb81eff6d451d2ef90868a0b76fee1ae0f2392eca80014d625cf976c763f86186cf984943a0324bb38b8d23d97a2dae7002590aa56836825bee4770d74ded76b9187ed26ce8795d96e4c211056b7d8380f3e2ee2b7f4b37342968ebb1706e85233ad8fa15a39cda0417f86dacd35829a0053eec03966ac62876503efabf4407da1fd5eb13b542cbfa9ee755725cfe9c30f1235645468cbe11d875c5ebf96363b25abefa920867af543bcbc4148d5f1e50ac520452ced483e258ffb7ace14531c5c7de112f4827c594cbdaa373e52f54506833713e2d2fab34ecb5228c7c1c1cf402be69654c5ace0376d34ef7339383f0a2243fba2d420eb964593f871e18f5764824976f2f4c28f8624d9921237d451068ed944427dd822c8789064dbcdadca5c14b766706c7c3ac034875d44e94bf6047e1e989bc3506445c88c08b7805bf5a9ff3bf1636b93e98f039cb762d667ae02eca0fa2e30835f037dc359ce12edaafd46f064e0aa513d1e3239e0b63af879090a56c3d72748837b84bbd59a55a15c15b4e44d110532a020b9da108bfcf9530c3e1a49bf0c90143ecdd1324ad2beb6a4e1d033f46ceb918f4259d5b17bad3304e40fa0c77edf3afee63799c94b470a4dc3c89d18a43dc8dcd31aac6d33a23801a3548cd48aae4fcc39d0c2775ea1a26290f6d7c6d49506ad8772b1a356a8170bd0be3d0d451aa618d6312d6be5a86a2a398ad051a7e5660a454a4c45da41630fc9e3af071abd46b85a07749d1ee6287a4afb898f7c49655763e34a9ab47b9d0d750e5c0b8526960a576d55862c3e75f97716f211903d808ec860f41361129c0fc8cf440475e64a92338a37ccdef482ad7d9ad75a0a94bfb9007db701548dad0097312ecdb25219b47f5f44a707846ff2c180198747102dad0e37f3bdda0cba060aa8fafcabd48672879641167142c0024458aa118c2b6189ff95812c56515c06adf80b43718fe494419e883abe0f33e36d4e18f6cdfeb8edb121b81d0db1770f2705face099e6bbdae0cd485d486a8c2018d49bfcd956704f669edb352807c086c9edd1d0fac0eefed63077d6022aff763c8d34e51d69b26016740638b477606f68a07a3089aca2204035f772b06945823375fff78fd21fa554b16688ecb210b7291f1617493c6913bae6c90eda5935cc53a50c933e7b3b453b68f3871885b073c7a25563a66104e4dc28007990565ec9e2a3382f9924c6a63953af4929d4d0cd3282c0bb4c5cb3f2aa4c1d88751f15a5cb7ae8687034b6ab38d46b76b0bcf0967ba42db29eb1d7c458e923e6c6474e79ef469e92805b30a3a6e6e9101c53e0ce747701d014c338171df6ba79f8aa6234a49e78a8039e2ea9b671feddffa7d03cba531d055be1808d6fc2a6e229087b77a3be35e40ab4a6f378c216ea05d1900cd1aa21bf3bf0afe40e95c406000b49a429bdeeb563f32b87bedc1adc6e5e501c051a69901925053f064ae1e8054e34500b97eff9f974292eafbbd85715c2500e45a20f16556e09b7bb530764582a59ec40a4af28d95e6fdfea3f10ce35e2b0416e209c92a5045b8d7af4762cebf2d6d97b8960d9e9565e0549a2488a9e8b50c985f5395774fb807d49a0f84e0983fec0e32fb5342a3704c5d198dddc945d806c3262dc93f96a1b1a09c2bd461f324c068aae59d5473deba8ba6036ad96c670f42667ada96da901567e544157b12f56159bb7c3f83b90abd1b9e7eb948f2e702c9eca93744830f0dff8b7bc73a1458b0f8d40e679afd1e22b09c93f2c9729602c6c81026f8f02991c094a38ed0e6062508974a8bc69e8d6e6741a45eabc7db0665f8a2d879de17fdaf7ad1b210f337b90aec1c1c3676f244aa3852a77c05db0c05b9936bc10f0a327602aa2109bdd115f8a3e1081c1b0c56649638207d2dd9086c6ab885512fec9650ba288df5133c9dc87cb7fe81919fafca783f1252c2930332559bbafcdc830223837a7b78a7ce137e9f4a4e3e7d1195a494fa6d0ca9a10659c0df4ee0c9db91dba17acad08e5f6ccae71012e2c6e4b37894520fffe9620d66a72a1e4f04e5fa2abe93ef7f4b2e9e837c9e0f9d962b5c80f5e8340874610358576c23403b2d742c587e3827d17c6f41b31f9da4030207aaa2f4c32059f4017bfa4c324be2ac00a0aeabc7756ff2e16bdf39ed6b0c313f01b1acce8bc2b408a0cb3eaf9af9b68dd17f258c0bf0c536b19040d6bebc0a9dac7fbc59398b520891a741c4183a851c1cbcf3f78f30924d85ea2d907a6ac8db9a1455e4c672800a88629853fefb86800fec6631d77edf591d56199d5980bf3c86d9254773e02508be6a488b032686134049415bb194ad11ab8e4b1e6304274091beccc795e7b604649f106b395bef6f30a1dca330561709cdf7295557410d040543c279e169a908dc3d94c4809f0a7dd92ad8cefb014f5d47681b21d6b955fa894eff5b57cc5d0d1b08c01c4e892af363e6560a02212edc1fa645cdbce380b3e010d3c7f473960ec86e8ab8d2c235f594cbb2c2fa62f27b4ea4ab9796b8534688fd9819ea529c023ab370c6e482633801e3d588f3a4433cef5aaeb633547e922dbb35410af8a803156b64daf9633636063a37c4adc43e492023e09137099a715a690a480119f60d9c2f1a110a6590ff1b7774e0fe3e4a7419226448634c129aa1213e560814cf0b28d28fbd4029688f0d920a5f45c2a06bdea6c38f731a8bf741ba106bb8a3ac0e35fd8c015995d55c0733a18e66705dc84dab552aff7d2ae654b903f6fc9f1305da5a5bafbfbb22df241e211416fa902eaec589c7a22d6bf160a884e5534c5e0a685c312527c037f3196d4122f4d42c5a683d0e555af2a271d8d68faea80dfa04bc27bdbca4d4b3c14ae0745ea453174a6e6e3b3014dbd23ac3c8a413b5e29d08524227a74d9da9b38d2ddeb8649eb9d46bafca1453fb6ab89112544597bf2dd855511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e8104840abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739edc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e3106117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f0471ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee094a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c7272a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8ebec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e505e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9efbe1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f25d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189ac688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f7698bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda88b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0deefbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505eabaa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284bdf8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc8558fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa68a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b4918598a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999dc40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9bec704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f732b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0ca7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916de7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee449da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3bf7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7afd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043faba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7aaf0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0ade13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae5046b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0af06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea2615070aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a5074932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40ab801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d076ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec840810b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d25880483bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b008187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 20, + "mimeType": "application/json", + "data": { + "status": "OK" + } + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 355 + }, + "cache": {}, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + + { + "startedDateTime": "2018-07-06T00:25:57.451Z", + "time": 165.87528899877725, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 969, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1305 + }, + "cache": {}, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:26:07.441Z", + "time": 172.03327899891883, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "cookies": [], + "content": { + "size": 969, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1305 + }, + "cache": {}, + "timings": { + "blocked": 0.830278999999864, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.09000000000000008, + "wait": 170.11299999871525, + "receive": 1.0000000002037268, + "_blocked_queueing": 0.2789999998640269 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + } + ] + } +} diff --git a/__test__/fixtures/requests.json b/__test__/fixtures/requests.json new file mode 100644 index 0000000..2e7e9ad --- /dev/null +++ b/__test__/fixtures/requests.json @@ -0,0 +1,4403 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "WebInspector", + "version": "537.36" + }, + "pages": [], + "entries": [ + { + "startedDateTime": "2018-07-06T00:25:10.474Z", + "time": 863.6693799996762, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/login", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/login" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:11 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.3603799999976764, + "dns": 178.158, + "ssl": 175.32299999999998, + "connect": 523.645, + "send": 0.18500000000005912, + "wait": 159.4730000000004, + "receive": 0.8479999996779952, + "_blocked_queueing": 0.3799999976763502 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:11.160Z", + "time": 159.51527599937253, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/login", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/login" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "232" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"withCredentials\":true,\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\",\"create_account\":true}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:11 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "26" + } + ], + "cookies": [], + "content": { + "size": 26, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 361 + }, + "cache": {}, + "timings": { + "blocked": 0.9902759999988484, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.13400000000000012, + "wait": 157.55199999910454, + "receive": 0.8390000002691522, + "_blocked_queueing": 0.27599999884841964 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:11.334Z", + "time": 153.21448900175528, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_txs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_txs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:11 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.5474890000018349, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.18300000000000005, + "wait": 150.6389999994531, + "receive": 0.8450000023003668, + "_blocked_queueing": 0.4890000018349383 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:11.335Z", + "time": 159.11257000118712, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_txs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_txs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:11 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 0.8495700000001525, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.10899999999999999, + "wait": 157.00100000011898, + "receive": 1.1530000010679942, + "_blocked_queueing": 0.5700000001525041 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:11.487Z", + "time": 159.68929200007187, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_txs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_txs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:11 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "1218" + } + ], + "cookies": [], + "content": { + "size": 1218, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1555 + }, + "cache": {}, + "timings": { + "blocked": 1.279292000000627, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.1100000000000001, + "wait": 157.4239999981902, + "receive": 0.8760000018810388, + "_blocked_queueing": 0.2920000006270129 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:11.495Z", + "time": 156.81636000243816, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_txs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_txs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:11 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "1218" + } + ], + "cookies": [], + "content": { + "size": 1218, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1555 + }, + "cache": {}, + "timings": { + "blocked": 1.0663600000018196, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.14500000000000002, + "wait": 154.6599999990242, + "receive": 0.9450000034121331, + "_blocked_queueing": 0.3600000018195715 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:17.285Z", + "time": 164.4336129980984, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:17 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 0.9136130000001577, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.09399999999999986, + "wait": 162.2699999996526, + "receive": 1.1559999984456226, + "_blocked_queueing": 0.6130000001576263 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:17.450Z", + "time": 165.1763129986066, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:17 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "958" + } + ], + "cookies": [], + "content": { + "size": 958, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1294 + }, + "cache": {}, + "timings": { + "blocked": 1.0823129999968224, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.10699999999999998, + "wait": 163.0099999989164, + "receive": 0.976999999693362, + "_blocked_queueing": 0.3129999968223274 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:18.319Z", + "time": 777.64030099907, + "request": { + "method": "GET", + "url": "https://mymonero.com/partials/send-coins.html?1", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/partials/send-coins.html?1" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "cookie", + "value": + "__cfduid=db9e89f80dfcf8d7ff1f8a18823c27ea31511991035" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "mymonero.com" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": ":method", + "value": "GET" + } + ], + "queryString": [ + { + "name": "1", + "value": "" + } + ], + "cookies": [ + { + "name": "__cfduid", + "value": + "db9e89f80dfcf8d7ff1f8a18823c27ea31511991035", + "expires": null, + "httpOnly": false, + "secure": false + } + ], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:18 GMT" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "last-modified", + "value": "Fri, 06 Apr 2018 05:57:24 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "x-frame-options", + "value": "Deny" + }, + { + "name": "etag", + "value": "W/\"5ac70c44-18dd\"" + }, + { + "name": "vary", + "value": "Accept-Encoding" + }, + { + "name": "content-type", + "value": "text/html" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubdomains;" + }, + { + "name": "x-xss-protection", + "value": "1" + } + ], + "cookies": [], + "content": { + "size": 6365, + "mimeType": "text/html" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1672 + }, + "cache": {}, + "timings": { + "blocked": 1.544301000000036, + "dns": 118.545, + "ssl": 180.40699999999998, + "connect": 472.44, + "send": 0.27600000000001046, + "wait": 183.55399999832503, + "receive": 1.2810000007448252, + "_blocked_queueing": 0.3010000000358559 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56402" + }, + { + "startedDateTime": "2018-07-06T00:25:18.999Z", + "time": 166.6423920001398, + "request": { + "method": "GET", + "url": "https://mymonero.com/partials/account-nav.html", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/partials/account-nav.html" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "cookie", + "value": + "__cfduid=db9e89f80dfcf8d7ff1f8a18823c27ea31511991035" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "mymonero.com" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": ":method", + "value": "GET" + } + ], + "queryString": [], + "cookies": [ + { + "name": "__cfduid", + "value": + "db9e89f80dfcf8d7ff1f8a18823c27ea31511991035", + "expires": null, + "httpOnly": false, + "secure": false + } + ], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:19 GMT" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "last-modified", + "value": "Thu, 21 Jun 2018 15:54:41 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "etag", + "value": "\"5b2bca41-474\"" + }, + { + "name": "x-frame-options", + "value": "Deny" + }, + { + "name": "content-type", + "value": "text/html" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubdomains;" + }, + { + "name": "accept-ranges", + "value": "bytes" + }, + { + "name": "content-length", + "value": "1140" + }, + { + "name": "x-xss-protection", + "value": "1" + } + ], + "cookies": [], + "content": { + "size": 1140, + "mimeType": "text/html" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1365 + }, + "cache": {}, + "timings": { + "blocked": 1.7473920000017389, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.1589999999999998, + "wait": 163.71500000010104, + "receive": 1.02100000003702, + "_blocked_queueing": 0.39200000173877925 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56402" + }, + { + "startedDateTime": "2018-07-06T00:25:19.000Z", + "time": 166.5593500011455, + "request": { + "method": "GET", + "url": "https://mymonero.com/modals/openalias-confirm.html", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/modals/openalias-confirm.html" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "cookie", + "value": + "__cfduid=db9e89f80dfcf8d7ff1f8a18823c27ea31511991035" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "mymonero.com" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": ":method", + "value": "GET" + } + ], + "queryString": [], + "cookies": [ + { + "name": "__cfduid", + "value": + "db9e89f80dfcf8d7ff1f8a18823c27ea31511991035", + "expires": null, + "httpOnly": false, + "secure": false + } + ], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:19 GMT" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "last-modified", + "value": "Mon, 06 Feb 2017 19:19:53 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "x-frame-options", + "value": "Deny" + }, + { + "name": "etag", + "value": "W/\"5898cc59-a21\"" + }, + { + "name": "vary", + "value": "Accept-Encoding" + }, + { + "name": "content-type", + "value": "text/html" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubdomains;" + }, + { + "name": "x-xss-protection", + "value": "1" + } + ], + "cookies": [], + "content": { + "size": 2593, + "mimeType": "text/html" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1016 + }, + "cache": {}, + "timings": { + "blocked": 1.1963500000020721, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.09000000000000008, + "wait": 164.43600000155135, + "receive": 0.8369999995920807, + "_blocked_queueing": 0.35000000207219273 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56402" + }, + { + "startedDateTime": "2018-07-06T00:25:19.002Z", + "time": 329.04037800053266, + "request": { + "method": "GET", + "url": "https://mymonero.com/images/aero.png", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/images/aero.png" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "cookie", + "value": + "__cfduid=db9e89f80dfcf8d7ff1f8a18823c27ea31511991035" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "image/webp,image/apng,image/*,*/*;q=0.8" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "mymonero.com" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": ":method", + "value": "GET" + } + ], + "queryString": [], + "cookies": [ + { + "name": "__cfduid", + "value": + "db9e89f80dfcf8d7ff1f8a18823c27ea31511991035", + "expires": null, + "httpOnly": false, + "secure": false + } + ], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "pragma", + "value": "public" + }, + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:19 GMT" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "last-modified", + "value": "Mon, 06 Feb 2017 19:19:53 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "etag", + "value": "W/\"5898cc59-4098\"" + }, + { + "name": "vary", + "value": "Accept-Encoding" + }, + { + "name": "content-type", + "value": "image/png" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "cache-control", + "value": "max-age=31536000" + }, + { + "name": "cache-control", + "value": "max-age=31536000, public" + }, + { + "name": "expires", + "value": "Sat, 06 Jul 2019 00:25:19 GMT" + } + ], + "cookies": [], + "content": { + "size": 16536, + "mimeType": "image/png" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 15018 + }, + "cache": {}, + "timings": { + "blocked": 1.2853780000006372, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.25, + "wait": 325.6869999998924, + "receive": 1.8180000006395858, + "_blocked_queueing": 0.37800000063725747 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56402" + }, + { + "startedDateTime": "2018-07-06T00:25:19.002Z", + "time": 336.9524740014749, + "request": { + "method": "GET", + "url": "https://mymonero.com/images/check-icon.png", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/images/check-icon.png" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "cookie", + "value": + "__cfduid=db9e89f80dfcf8d7ff1f8a18823c27ea31511991035" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "image/webp,image/apng,image/*,*/*;q=0.8" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "mymonero.com" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": ":method", + "value": "GET" + } + ], + "queryString": [], + "cookies": [ + { + "name": "__cfduid", + "value": + "db9e89f80dfcf8d7ff1f8a18823c27ea31511991035", + "expires": null, + "httpOnly": false, + "secure": false + } + ], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "pragma", + "value": "public" + }, + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:19 GMT" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "last-modified", + "value": "Mon, 06 Feb 2017 19:19:53 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "etag", + "value": "W/\"5898cc59-1c64\"" + }, + { + "name": "vary", + "value": "Accept-Encoding" + }, + { + "name": "content-type", + "value": "image/png" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "cache-control", + "value": "max-age=31536000" + }, + { + "name": "cache-control", + "value": "max-age=31536000, public" + }, + { + "name": "expires", + "value": "Sat, 06 Jul 2019 00:25:19 GMT" + } + ], + "cookies": [], + "content": { + "size": 7268, + "mimeType": "image/png" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 7293 + }, + "cache": {}, + "timings": { + "blocked": 1.342474000000395, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.125, + "wait": 333.75500000152226, + "receive": 1.7299999999522697, + "_blocked_queueing": 0.47400000039488077 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56402" + }, + { + "startedDateTime": "2018-07-06T00:25:27.285Z", + "time": 155.90357000258155, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:27 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.2885700000001525, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.14700000000000002, + "wait": 153.2940000016801, + "receive": 1.1740000009012874, + "_blocked_queueing": 0.5700000001525041 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:27.441Z", + "time": 162.4842840004021, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:27 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "958" + } + ], + "cookies": [], + "content": { + "size": 958, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1294 + }, + "cache": {}, + "timings": { + "blocked": 1.0612839999979187, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.16300000000000003, + "wait": 160.59400000049408, + "receive": 0.6659999999101274, + "_blocked_queueing": 0.2839999979187269 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:37.285Z", + "time": 154.2695690023902, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:37 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.1425690000034519, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.1080000000000001, + "wait": 151.90199999895412, + "receive": 1.1170000034326222, + "_blocked_queueing": 0.5690000034519471 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:37.440Z", + "time": 175.2733090007423, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:37 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "958" + } + ], + "cookies": [], + "content": { + "size": 958, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1294 + }, + "cache": {}, + "timings": { + "blocked": 1.007308999999106, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.14400000000000013, + "wait": 173.38100000090873, + "receive": 0.7409999998344574, + "_blocked_queueing": 0.30899999910616316 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:47.285Z", + "time": 161.12051199702546, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:47 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 0.9255119999987074, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.10799999999999987, + "wait": 159.08199999876834, + "receive": 1.0049999982584268, + "_blocked_queueing": 0.5119999987073243 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:47.447Z", + "time": 178.44339600069725, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:47 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "958" + } + ], + "cookies": [], + "content": { + "size": 958, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1294 + }, + "cache": {}, + "timings": { + "blocked": 0.8653959999994549, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.134, + "wait": 176.48199999880651, + "receive": 0.9620000018912833, + "_blocked_queueing": 0.39599999945494346 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:48.370Z", + "time": 151.84338099964225, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_unspent_outs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_unspent_outs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:48 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.2163809999980149, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.125, + "wait": 149.6289999987788, + "receive": 0.8730000008654315, + "_blocked_queueing": 0.38099999801488593 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:48.523Z", + "time": 155.7823879979951, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_unspent_outs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_unspent_outs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "258" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\",\"amount\":\"0\",\"mixin\":6,\"use_dust\":false,\"dust_threshold\":\"10000000000\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:48 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "935" + } + ], + "cookies": [], + "content": { + "size": 935, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1271 + }, + "cache": {}, + "timings": { + "blocked": 1.2723879999967467, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.11299999999999999, + "wait": 153.3750000012608, + "receive": 1.021999996737577, + "_blocked_queueing": 0.3879999967466574 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:48.684Z", + "time": 151.69544100047278, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_random_outs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_random_outs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:48 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.053441000000137, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.08600000000000008, + "wait": 149.6330000008703, + "receive": 0.9229999996023253, + "_blocked_queueing": 0.44100000013713725 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:48.837Z", + "time": 158.4292709991605, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_random_outs", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_random_outs" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "27" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": "{\"amounts\":[\"0\"],\"count\":7}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:48 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "1723" + } + ], + "cookies": [], + "content": { + "size": 1723, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 2069 + }, + "cache": {}, + "timings": { + "blocked": 1.1862710000007937, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.17300000000000004, + "wait": 156.43599999916879, + "receive": 0.6339999999909196, + "_blocked_queueing": 0.27100000079371966 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:51.117Z", + "time": 156.7322979994642, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/submit_raw_tx", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/submit_raw_tx" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:51 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.3272979999990202, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.14400000000000013, + "wait": 154.47100000123237, + "receive": 0.7899999982328154, + "_blocked_queueing": 0.29799999902024865 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:51.274Z", + "time": 401.30272299839635, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/submit_raw_tx", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/submit_raw_tx" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "26579" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\",\"tx\":\"020001020007d8fa7ef1f42ba3d85587de0499da67e8af019a940fc0ea7df439a9a9375151b7eb67df439be337bda577c711238c06e1284ad5e69b020002105f078f9332cf8fb2390be2f6be39abf40992dcd33daf0e3fbd9b02a23755b10002d4b94e2516134889b321d6f06792322debb0d311c761c6fc1fc4e699fbb87d312101c72f9ab780d28ac0c1d5df199c976792d5317fba9873694973b4e66565e54df301e0adcda708417e2c0f2fe4bfe629ad37a477089a98f91c98e59c54931dd53adc358a7f780864294cb7a3af9d977519ba3b9af993613e9c5f95c7ad3926fef81e603a1f3b0367826fabd56cd9596b110c32aff6a41a4eefc4309a43264609f1b69f52331c010db6b4fb75210d33417fc5700eaf5ae67f4f5930b9e4148824deafaf82cb8301200554164113b97acd5bb67b91ee69296bf15fff6001d7ce77374afffa68fb6b6464fb83555790fac27de59e3fd2cb8d27384ee923ad99de0dfe2e498104b31e094164e2b93cb85abd82ed7afc408fad29b1ae74fe2383a08cc4a154686e2e08b59d3a7a56337281c7de9edb43dc6cb3eb90afe399b23d65c9ca1c542e542b0c2b976ab45e2e11e27816623ce4349206c2a4c8b7540f394c868685267eefa60ae93f2fda2b1fd5835506acf9d7c77b0ca08e1cc89569df1ddf8a5688a630810eb57b4a9cc83bcc39e7c4ea8069afc2c5f97cf41bf4894d7cd3ad9fee9dcb630cad06176bcb8d5cbed6a09a16451ea8ed80fd21e16aa136a720b76b8a6b03ff020f92ec55b78668ac41c5423de75eb4bfbc6c2c20abb74e7a82ed9d2d53510003bdc18588724e2ecc6afcbfb3d8765f15b00a006306778c04d305dd99b90ac10e92ac7473a379ba999316ba893d0ff320788ecc754632bc2bd565e0838ffc9a01adab99709eb702deb1c350954857856de88d5b5d007919c225f6d70e1b89a9067d40bb9918e657d8dd8741453877ce22b13a5442ebf963757bd48488f15b7b0e0c96c3fa4118c6beaf72693c64308b9343472784d965e04f8b304ee3ebdb4405d04d70208389b5200b771cae428b88be6fe91970e7ec0c2b9409f16a582c6a0b9b9278f65bb6401b18e5fe1031f52c8b036bd5afd0e984cfd3cf4c650a9940040331067730c9e58688286fa2533a4815eb5289f8860664d47b55f4507a7e5b0540d8b2d3a98a02caf4109de34289f04ca6b316f7a682328f3cef057bab3b7705e25fdbf9b361023602c9fe45c9d47615cd388b251d4cc13c667e2c8235e1bb0106374aa1ff0e61f1121d47abeaf0ac767d1bb1a0de2afbe706c1014ec8d146001d727248b4db192c5da6699d3a32b09652922908900267fd2e1982e1f05a02087c6ade39bab5b1bcdc2f6cf03d455f8221f9d937fadcb87c4070262bde23ca0edf3422c0787a2135b741d68f5b2dedf689f455f200378e57603ab0704c9ffc0c6f0c0d13b284111698cf0bae7b0faa9ae341a25eacbd9adc502df4a12705c50bbb3d708c72e496bb9913852765c676ae683a04479b93e117e9cc0cacb403720e8e685a2a6a8cba27590a0c1dd0f1c08022ea3590acf423121a3f1c8abfcb7302bc40bcff2cbceb474f0bbb400d74f17e76e9e665a7a0ad2aff650fb8ee41c605d45ab263f78d60a67567e2bdd2085cb321f348b27f92c2e570e0396a2595090832aa144fa375474f3ef8d95295f46f0c7cfa8058a4ff11bb0dfdec1c6ba9f60873f1a08aa38b6417ecd09cf92b1547eb6a49a694f0f6ed57fa157b6eedca46099248eccd40954c5bebc6f2bbd5de96ae3e2efe842a251a8651231bac54bc12052a1962e4dbdc3ff1797e1ddf401d070a9b9dd2ea2ff3ae2e207d10c02c13e50ca90ae0d17418051108f06a77fcbafa80fbbf563e334da38624525e1d203bd10018ce2730e92157810ef2c092a51dce81cdcab79dc271a47749b8dc1fd6d84d0e4e0e97ee23d09c70c2d2882858bc7a318ab516a1c9f3291824c5ed79baff2c05d9d93f18348c5fbffd2337c3ae6973eacbf67aeeda71ea745fd635da419fac03296b62aa4b3f4c554b7452db9ca1887250bc8c507d042db2b9b7c81d1018410863d23ad52e74b68b13a1d0b17fec9ac5043cf22452b98f2c05328707d3b51508645918528f4712d553db4e03325e11ac0a3b3794e74884ebc3e2d63a8c2a2a0af1dab421831e8336538d8441a5145eac4830ac0a7fe5cfab808e6fc23c7c4300a33fd2bcbfe92dc9c47d173af9be7f1e50da97ce5d88c36724e4dff79f6d9b059b47fbf1fa33e259b339f1f74c9831473b86e7767f4cef199c7b53d260f00001fe22ce255569940f6b460c6aa9eccb1afc191f786f00256aca97d7b465ad8e0ce21b01b36099a8326edd3d509bcc49b37c69fdd04d8716df564c42d01f589804824fab7c5a13b31e41ec62321f42ace7e7c301abd04a29c4692eb526a9f5bc0cfc47845963028fe9bcac2619255251235ff7be950066e41933d86114c4799c0f94394c9b48e34a39908cd2c676fe6ee42d21a09b2c148506e9cf72bba00aa7020850b7d693ef97ce9074adc966a258c87841aa76468e10cc2474f58690b08601a7d7620a3f6c826422ebd339b47e0566a4ef9d74c91f8733e68da260ff052700dd03dd70ff28bb938ac6c420eb8942d31e0222a2c4c5e07339503f33baf165074813beabf4ae379d0d093c16438e5c9e08c06d2909221278c398698a2a45710c1ac2954ffb0064229d43b1686dff63189c9def06fbc84d52c6dd1b45785bee01c43ba993220032fb7530872ebb42cd1b8d1b56cc98784ac01b242a0859ad5f0556be67160b9b6a676b3f5b36040947b4c8d97cb2125ee429a785f1798052780fa7acb882cfff3842d52df35bd957f462c1b493cb64852f8d3479b1d930e6cb042dacc0b2112af2fa9be170d164bef97f69cf0c7bb428f3677f8431aeb300b10d0b89d7dc92f2ee2f1f60a7e93cd7ac88b6f71b4c83f2df1c4f1df217795af501e274a6b4ba98db41785a1268a5c26093cd7799e2fc97fca3832465aa0ca8a80b241240171cc310cbe3835eb71edab3ca918b62a883f27be003be1984c9fced068d3cfa28d895556eb48d6f6f21fea79f6818297df8a030202ddf87c4b38d5202270555a24fa46c4b124af481e0a791336efc5d87d16191f7a73686dc330cd00a72431571eaa9a6866d114c2abf5a9599dbd8c6b8c6ab41ad155329b20bd9d302b896f7ef3859598c1f7312149d49c85fc32734aec69311a1f7241713c902ca0a578f78aaecc9f9783095216d583312116983c97da812058ba197a73b7b00a403823f17e7d12a20d8d2af791623984d8a5d42e8b81cc33d2075706f188b96240e0c70da64295b99aba8a1ca9a692f908224766dd4ab85b21c2e4a3561d92da70bb3fcb96c7067379745497875654d4135f2a367f9760e1101e1b7c8e45bd78801e7166d6480bcd5628467993ee7c11b651a3e97afffcb302fe91d6cf55036bc0acc531d9d6c3dc509c2878ed7378a82ecb182da633ab8d7e08241da069afb8b06e63c93d5e36e33e4ba022fe2352061f55f4e5115d048e4c36ab56d9d558daf0015a8ec96436dd72e4ee610e0806adbb382a7ef9d3dd9774ed070abeec25d460e304ebc4b2d097bfa0f6982abe3f7490f0374a278f1464505e0233a1195e7af01ae7e06f814cb7266ca60722c53e2d3eb66a5a1f8cff91bad0da26b5716ab7106fceb4b4a1a613014804c5284e7c8d785512730c7f109d4c225f9fa00cff4d10cf7f120e75d1c37c5e5b4e9117f30fb30c3cbcf86a33225fb4f63f2a2071682045dfb733111c50730556e62bd49a471b71c9421b312e8805358ba53af071b610399e917fa1b4687093313e330b453dd98b3f7866bcbe737f5e11f4bd1c0043b0975b84a92c4cfef856688345ebf4ba84b95aaad4ef573774e448d7ea93f6ba10937ec41a174bc0965d667918f608b6358d424628859d0933cccdf51ec7d55f906438342b45be0e9796bb57225e7bb184602fb97dfb48260bcfa427f787a759c0f5a2a8929697f47c854bebca692175efb36276a5f9fb92437836e65d87bb3b70a09663579a35c801c1aa654eb28517013b5e877a003be057b875b35df2fe227090c58358156115c37d72c290912ee288600cdabb492312227fe659dc238df6e03def5668fd22573e63b245586cd10bc66d68942e01bc9dfb9d152a1a7898ce002daef4fbe574991fe37e0b8b69ff269e098b7b1640a47b370fb38f410eb0d5703536bbe4bcc900df2a526d80a413b9c9ed3ab75124ff7198d3a44e2ca0cce410fedf5f2ef7b33f93b43a26f4d30751f2bfb4b13ca42a3105196bb17e5c2653e0c00adac72a44baa60b2d2411fb239d0d0dfd68251d362bb04ab0a3cc169cf61049bdc73cf11aee0b90f7b26cbcdab987873ab69542cdef0bb8c125da7034e320dc9bc4f84a9827cd927165c3455ae9fd8f5325e606b58e3cb6db67bc46b434c0bf78b1ef5530c0996205b419dbde1a8b1a9ed21abf38e3c5a16d34d6091a1e4065c79fe8d78b95184e779434993a52c23e6e9c6db90e9db5b90e97a29def79302922934dd5413a93d183cd83ad88ab3d439ef39753c84588a8269a74f0d1972080fbb35971f624d31041842ed430c70d09171c20f0f00e0d0c4e8ef8149b9af038846db4d5b2d8a586e5f1b34803e52cab395311ac5bac25f2ce3cb2203d8070354da89b48c24c0da18b4db9f87d210388ec2655f48fcc42bcb78c367bef2aa0c543f7e562adc23a6b22445ca1389156587c0af0fa16aeaa1aa5e6508228e9c04620aafcb6408e66ad86f0258cdd04a376ca1f41a623e7cf36a800d5da91583029bb46e73b038d27c370d948571c442f65302d09c1562ba3ebde7e7e803f32602799726043b23a4eca27e62986009cc9e4419fe95cece9504cabdf6d92f01f105beee7e9ece3fb49b495922bfc23337e53baafebcad53a250b66d168a87b3a4064440985fe7becf778fadbbf8eb4d01c024039a4cdfd5f27edab16f4702b4c20590dce3157b8c727316ba8c25de3cab3112c385f8ca0895e5c775e5f34c17520cc8cf80d28be593f186ad7f6f4666f702df2566c16a5d5e1aad21694774f41f0003fbcb4502fcb5e1deff0fa2a458f6ff88bbc91cfa2e21cb42b0077da5d43007d68610e05172ac51036ab6a52140e14e1707dc325fe8efcc39c74883780e9404bcb78fdf38b2763ddd7458db79ffaf28305aef1534d7ec4112316a8acfbe3504277ef2e7dd36cc72869f1d53689d904cabd23085165d2933ea15fa491da5ed01f00468c47ba2185b241405d89952922a74c02ceb8617543b70f353a62f017b023dc53beb7b858019493aa1d2bdcf2d6eb7683d453d62dfd391b189c296bee006d7c85bd22dd02bf6448cb7af0460cc319913f9fbc357875bf5396f9e33fab5050aeb928f76e42175bb300f1e254f981d8bf22079c66b03169e4d04879495e80a395187afeb4d7e73edf5d4320ce49cc4774560697ed4c58e8b4214fd03d3e808d666a22ab6104dbafa95c6c0db8af94ea49f24fbd5c36d00fc009c451ce097083f84b72f2e5b9b1dec327c4f14d43da764ad36c5e8e8389453755fb77157dd03f9e97ed405828144904fb6a2f578e1d39f7f30621d9a483291ce50391c53690a76b217cdc7d7ac7a1ca3add04d303a920713db149ca022ad49a83e72ea7fba0c6c29a44633d1c056ddbffcfd43cd1ad4b810cddf796ba994ddda1d63cebf8705e743ff022e0ecd99912346a9c2b892e5e4ebc3e52b98e60262d96f336771770349b69d7560d297a73658c22441c5ce1b294065ff4f3da35227f7684e3a445001910975c311f461abd40ee2b56ab71f105770a4bc1a581b1221495023353aaa01d4bd115f5a8d9ba37b3b41dab0cb181f07e60624b755e79e1abfc7da826a6d08cab5edf981c43ec78bdcbf74313af449bbfe3b4806a205de6adbd2249015aa0b0efe4e92b667f66f2b8899084941fce413edc2e4a5ab44c066236c6b2a527707fe52311427d13f55a87cb3a361b196e0faecc106ca6bf40e968b057f64cde106b2b446d99da75974b26266573f1a641b4c7b477c61b370116d63267471132806f9bdb81ab2a81f03165e2a5b2d32f1f2b2cbe86d95e3c6f8bf8ac6a2296ccd0c2e6412ed8f74082f073b6bc2dd63e59ead35112c5c9362610a6561531b931b09f21e5bef71a01433b29402e008ecebaadda2bd8bed28d0cdf910ffd573434c0ac5781c5eee63cd99e97b5b319cd3925607dbb371a360e2d5e070f161a063f10946793313125f55f30bbabb52e61db65e5ae507cef64f08f501417edef3da5e0202836fed5f531a64dd41aad00a1dc39ec13b49d1ebd7a2a6b5867d7d71db0e50a393ec33f28531624bdfa3ddab00ab9239b2bd54ea858920f34c26430045898f52b419f7f04f7b3b8e604bf11b20ed058e8ecdeae5be4845aa5f14f16c1f953d8e653029b087bcd4ab9953c1937cee84f81c393285c8bc3341a5eb5bae580b5d55cf6f0ee2afbea795764bf5d4ed0735367a0bf65b6f69732449e3dfec547d93d22056ada1eb1c35c2ca1924e07c0e6120b818d6941755efc4dae89d0cc039762bf5e003763a01910d434d3f0c3e71af286c55d30c9aadb2a480b8d0938ce43c202968df06659dc14e713dd6b2899be1501718e3003be5220b7e90034244ab3e9db020600bdb9b2a17b9d87e150356cb3f3a7150bf305d03efb0aab95cf43e5cf124a8f5db6c37b81f7841b81a7cc5c2ed6d3b8124ff95f9981fc93d732dbf470403a44e0f46ffff9bbef09e2fedbf9b42600918567617ea28d8f42e6b1d764a340ea317a4462b1083062b29560d25f2b35a82bfdf000033f8a0db28e7314d4d784f6b9d0df70928cc30b94119cf02085884a237b2ff7470e299726d06f430f72c73a1727446f060a3d98ee0c867ea162c197bacce848ebc423f3dc6c4835e8bbce299084ecf73139b024c33f215bc132fe77cf326e2da2cd18b3008bf16447ff4b2a4a5679987564d369e8936270b4adb3128e6e88eb75205f9de36088fde3a7d294c7c38cf0d0bb02c1160a6695550423737668f1646ca65b93d95547097a2fa8bd70f74a623e5a3c9c3c6b70097bde54853b7517c76c0d36e59cabce4bb691bff14524d2a0eac2155a9af73a4e87f0d6ec67d4a27840ed0b7134fa93d751588a77416daca2b68d054a7e74dc752a9506090e79596c9b558f085853055d87852c251baa10a9607b86c009ba3ecba6ed2457d499128f26fe632c39e5ccc46fb4b7e225ee282d9883abaed6e531b4f844df3d5cb547152742d0a0744e40fb9284637a34dbd3a4ff4dae9a07904b36e539f56eec6ce7baf51ed8e8189b242effc3bc60908d6b62739b6d6e8206e4223160eb293eeea2992c0dcaf60c73632ce196441692c1f5131526818f5bff1cd83a75e414e595e13a082a47985bd505cf256f5ec516ae79c1117f9f7820594d696a7714d2151450127dee286f8d18de13517b24eda31f18547032200489505d2f53101c962641b7275b8a48296f339402f7f48ac2674f669841615752eaf495a6dac6bd4d6cae58be8c779890d201904221ef4a1a0f87c523b027fa43f52f37ea06f8a19070fe04f7533dfb529d802a22d7d299f3abb6974611456060915173bcca396febe1bc943150131d94a2406565957950fea0f11421c7ae2b7e900f46a8c947b7e5e724bd981c00a1c93070a6dd51bf406b876c5141f7fb5bd84cb788dda0b822bdc4263e500c85c079aee409cc00e411628ef38f9c948a3ca7989efe4592d7702aa846b3a1cfbfc5e4a1801ad6a63386bdaa004af3bd3182a082ae5f17d30862c5a0de12078aba63b51ad2ad9c695257e90cf997464da236ace2f90020547003d30e8f63998ab705aa56fd4031397d36526e09faa72c4789380a4df227104cba21542a16f64b19ba96a84103513d94336e5077c76eb3a301e3bcdf507c9a0ab6b7f98d6533d6cdfe04f72d24042472cd55b68ff9d9e1d8155c21e7df17066751a99ccc5147d0a828053f77b059879160522a71a95f14e058a51bf443264700870541f074c182b1ca3d1250b6e03377dea966eeba2bd820d680f35dc7226e4a8faebc88da83b49da2b5e7dc897dfc7998961a5fb82372fc03ddcbb6043284c80358ec0f2bf9cba7e3bbce8696dd16bc32819e8182b5528b3ac82df464b286b944a13704dc17befb80ed61d24a2b2407fd2bbf0fd1e547b1437db807e113c9e514048342558691bb18c8e2fb7318a5dd730f570722b73446476e28fe2c85f1fb908cf4898025d519f075bc16c0db7c580f8d5eab655c7fb355eff1463026b61b092594a971e573cbc71f2c6fd23782171dd7c5a41d980ac1830093a2ba8be6de21cfe8a07463deb4428bcf1382326c02edf5e1cc806779da45ac34d5c0db4f2382f5e08be2c31fcdfd93be10f4f6c0842938f9080674ac1ce67acacb5727ac6bfc7995b29f02e324e59f291a5bb4751f66dd6200ca2c9c8af94a1d6c5531e6d332d8a91c4e00c21fc1248cede72d88c8fa61f98513bddd06b910456ee60635081b1627a0e01c67e8312f0e0fed42145e9c514547cf5b5bc53634a11729b9eb6ddd217568a49772e18e020d0d5ddd2220d938c753a044fb4bd49d3b2002e951849e32ad1f37a7058750c627f4a94b03d048a524eee29bc8de8ea1f76255b402b63f9b4ff050aec4af6c6ff1ed7cdd62d5f9e38e5deb73068b2a49a460adc7b98f64e72729ffe7d63662e25ea421d73995c22633e12ba5892f6c2b1dfdbe1f85bf5c22208bf1399bcefb5f9357db7c84bca879fbddc966d7adad62121490505b5b95cbd545753fb900ddf50a6a6588828f8f95252b9c7aaf321303f19c496bfa2af90104416a10771ca02d7daf9204daa3c0d9c8eccc7fa80234995db0869947e7412e5443851495e89259f92bee8e5b33c51e08f3dcfdcfb2a3b989ba51c16e820ca2f3bbd34c913d8ffd75d36447bb2539e2f424297ac39ac5090463289f332a4555c6243315c3e2c241c5a2534c08c6397ee82dffd9b8b2dca1d64ca3cc29cbd07e67bf095ab47273a7a70b59cbe936cd018be9d0c3f8a224c405de0a58cc5bc2cac7b6e6f514089b036eee6b0904b1979e6077b6132d9fd669788f5031d3f88107a857e35117054f9196a197e97ce89507701e23077c93b98401ad75a8a30a77317952fca30ec28eef2179699f93dd1d07b998118930c481260cffe5d098a81048b0853bc957f61c376a392bc740d4bc083e9bc4b62ee04daee640c5339121174af2f2c3c78d716d97497c0518d0a7468d5ab25b59fab995c815f65550d52567ab22ce7215eadb2a5bf07ff8991007d381ad3b18f9af8307e97282b681c37a74cc1083bc079e49b79d08e8d15d602b65d2f153c27e54407d80a4dbda35acc29220996375f7819919ebd9ec6999a0aa1ee2b5a2f8cd95c1ed40ed310ec102b872a2f0ae40a383036398a467a8450032b8dc50fb3aead263d9004dee7f10e12aea154ab97f04f4a016d39b97742d00c837350002903c83f056c5d64316c7aafd83fe355a64802fbef14516b197dfa001695103d494c48359e58bbcd358b0eb20d1ef42e3184594fcf51443ad3cfb4047ced01f064c638698c690b9179b61aa68d369e35d5ac8bc65dae702cbc74890dcbedb029dd98055213866576a3d5e0617d063dc2470e8d8e5b83574b5687b20990a81ccea7787be7d38ba9f21b5ba3443049ddd82328d4e8fbe6b2817bf9380b46f9e643f6a593081f7f1d5d97c69b0fe58c9cc6074c373f8a71c90a12604b0884716acb458e7dd490b5f0057d917e586c69cafe9fbe47f69bd64c7e61b5b80a6825520c5327772c2ea8d7450c3d764c4d08cdaa1562a1d990eb0878134d35012482c2f5c4ec38fe5161e00eaf669187f2ec98584a6735a9341cf66b4827dc0bf512a1d4ead70ad08445b1a9af017ac6b375f9c5f0103d0755e1c13046d2c102a041f2c79577ed00c330e1a7c8e3a6d474dbf6d9ef9c4e3217cc3fe611967a0bc5b96d0815ad46b04d043dd39ade61805d12aff54f4f60ca89f2ad77433c390822c633e75cba3a385958fe1015c502315ce63e440baab882dbeb4ba118994103676b9e83e6f6d2d53db075f662469e733778dd8082c36cb246b03f9701c580015d55bf2ca24a02bd3888eb95597f654d04e84fcd79aefa00125091db32898f0545a6b7695838c01d0be144395d40af9d8e69b0ce2f1326bcc3d57a244be2400031cbf5ff387e2439cc3b492466343d35a6a7ae244c0abdcada6cd0e37fb8170ad2c7582aafefc50fe74f4f05bd1b1aa0a0391c18b2e54f7b8078517ce5072305e4a509c5ceb5165a148a78c67fc1de9d97d4a7c0a9e6d152b47e2320721c200c830a015279a3d84acd32f7070af60f82db8fe6f035973f4250dba240999a5005f96f27a773fc9e7b68558f5f98699aeee07c645097111ec1aaabdefea774690482af54c967848b2cd65ebd7f9bd4657292864f46e4620210ae41c1642357eb0985f8b1dd6e9d87fcca833964873224c9128e899acd85f74ee625809e9f045907e717b4e15183bf2fc55a1cae983526e833255b1a38a532c3319ae67428e6b604f1aa90a29afe47935795d9b629abe0654a9375268d11f91a7f0e13a74c9a380ae66988a69672f086bb939df9590f0e495364a72c8cb298e98bf04a0709722f00449422874e793b6029a95e26724609dc06fe9086f6ae690b34dc1b29427b430350a43678dfb0ddde1db147da17c2e6b3fd44dbe186dabc204f47ab241c2f560d4c103099635d550200103c05232988f48838d4b56b401242852c4be6ef991203fa32db1bac264e7663b866f21314c05d3eb006a4fc2e00fabf30b4197b5edc06b8777c5560b10549622a28ef19132a9e3960f54f3c449c3039985b24be69e80f17df718ef65afb8fbd4b1d6eb3fa982590962d8863720a6f7080f6d73251360a62b8674d157eb1a2d313631aa446635ec453090c75cb7470cbe60044f329130b829cf76313e13a5a58dd5378ba3d1bd7c7a6b931e63f233204c7caf9d4fb8d0b036910d5fd088dff93e7f6a43888096b38b5e0d39bb94b2c570e94258ba8f60143f516aef8442b816bcf88ce21eb62bf58874644dfed54d13ecaec398846950a01b76ab69ed1324cdda07cf14831bf6554f1ad9952defc2186c66da67a6265049e01bb01a69c67b81bc0977ce81b13cd3058ca82bc212d17514ac19cc40c9703cd8a3c8253d0163d494a9bce334b798aaeddece4c546509ed61953f8ed357c0e861c7bd6ec632e7831b57a9d796b0513a715ab7348db38f3dcfc8474a82b81076e69287de84cc3bb1e8326f467b961f0dddcd70a9d104cfad472d3661bada40b1aafdc9b6fcaf21ba61e8e5367aba0374fb927c375f90b87350bb0e0dbd8e105bbc62c5fc32f99f4bed49baa2f3db5a4705c4d2feaf3cd244d4d28d6f6a1400b5384e8f695e1449db4ef23f7b77618ed7a366eef3ae38d0b36aa19541a5f1d0b26c006bdaca798027322f8294933ad9d877fb79cce3cd756b94ed2cd311cb40db8cd46406725c9e7fd92893cdba0d1001796a286d9b18707f4ee433f90665607f60f5caadfa08817e1ab0f440b26dfb5163469c871bd7e0bfda5597597a2d40265d270c8303c48231c97b1807ddd444105aa6e195df4c208a0d07ab7721e49096e2b8edcaab76fc8729fd3bab94895518e3549dfcfe5238ef3f92e912e23bc0f032af4ad5abedd0c10613e50c26c9522902074202bd2c93ca60a7fdf4c5dff06e27594188bd5c6fed2874df4acf8b6d2f412959c2f274ffba18d040909985f09038124541070db29455f8d58c33323798cb03cfa2dc0298a856e64d4d7eb190f9c47bbbf7c075e8cc3ac5f980cad86287d1940dd8dc0ace92241ddb49d454605cd9229fe4080ca8e4b34bf3c35c3b95436d64f61d321a30f402f2695b6452d04f4aa87a45504333b39c487f05fab5ac541893bcccaccc27313293c20da1df10a1fe1edcb7962bd4c9b211472947196e01ee99d2d3f518710241dfc5f06a3ea0f53b36bc3696808aeba745a58c8c8b27f5bfeda5676a95596cfc24e7aebec110f851e464baf54513501af9262618d44cb81eff6d451d2ef90868a0b76fee1ae0f2392eca80014d625cf976c763f86186cf984943a0324bb38b8d23d97a2dae7002590aa56836825bee4770d74ded76b9187ed26ce8795d96e4c211056b7d8380f3e2ee2b7f4b37342968ebb1706e85233ad8fa15a39cda0417f86dacd35829a0053eec03966ac62876503efabf4407da1fd5eb13b542cbfa9ee755725cfe9c30f1235645468cbe11d875c5ebf96363b25abefa920867af543bcbc4148d5f1e50ac520452ced483e258ffb7ace14531c5c7de112f4827c594cbdaa373e52f54506833713e2d2fab34ecb5228c7c1c1cf402be69654c5ace0376d34ef7339383f0a2243fba2d420eb964593f871e18f5764824976f2f4c28f8624d9921237d451068ed944427dd822c8789064dbcdadca5c14b766706c7c3ac034875d44e94bf6047e1e989bc3506445c88c08b7805bf5a9ff3bf1636b93e98f039cb762d667ae02eca0fa2e30835f037dc359ce12edaafd46f064e0aa513d1e3239e0b63af879090a56c3d72748837b84bbd59a55a15c15b4e44d110532a020b9da108bfcf9530c3e1a49bf0c90143ecdd1324ad2beb6a4e1d033f46ceb918f4259d5b17bad3304e40fa0c77edf3afee63799c94b470a4dc3c89d18a43dc8dcd31aac6d33a23801a3548cd48aae4fcc39d0c2775ea1a26290f6d7c6d49506ad8772b1a356a8170bd0be3d0d451aa618d6312d6be5a86a2a398ad051a7e5660a454a4c45da41630fc9e3af071abd46b85a07749d1ee6287a4afb898f7c49655763e34a9ab47b9d0d750e5c0b8526960a576d55862c3e75f97716f211903d808ec860f41361129c0fc8cf440475e64a92338a37ccdef482ad7d9ad75a0a94bfb9007db701548dad0097312ecdb25219b47f5f44a707846ff2c180198747102dad0e37f3bdda0cba060aa8fafcabd48672879641167142c0024458aa118c2b6189ff95812c56515c06adf80b43718fe494419e883abe0f33e36d4e18f6cdfeb8edb121b81d0db1770f2705face099e6bbdae0cd485d486a8c2018d49bfcd956704f669edb352807c086c9edd1d0fac0eefed63077d6022aff763c8d34e51d69b26016740638b477606f68a07a3089aca2204035f772b06945823375fff78fd21fa554b16688ecb210b7291f1617493c6913bae6c90eda5935cc53a50c933e7b3b453b68f3871885b073c7a25563a66104e4dc28007990565ec9e2a3382f9924c6a63953af4929d4d0cd3282c0bb4c5cb3f2aa4c1d88751f15a5cb7ae8687034b6ab38d46b76b0bcf0967ba42db29eb1d7c458e923e6c6474e79ef469e92805b30a3a6e6e9101c53e0ce747701d014c338171df6ba79f8aa6234a49e78a8039e2ea9b671feddffa7d03cba531d055be1808d6fc2a6e229087b77a3be35e40ab4a6f378c216ea05d1900cd1aa21bf3bf0afe40e95c406000b49a429bdeeb563f32b87bedc1adc6e5e501c051a69901925053f064ae1e8054e34500b97eff9f974292eafbbd85715c2500e45a20f16556e09b7bb530764582a59ec40a4af28d95e6fdfea3f10ce35e2b0416e209c92a5045b8d7af4762cebf2d6d97b8960d9e9565e0549a2488a9e8b50c985f5395774fb807d49a0f84e0983fec0e32fb5342a3704c5d198dddc945d806c3262dc93f96a1b1a09c2bd461f324c068aae59d5473deba8ba6036ad96c670f42667ada96da901567e544157b12f56159bb7c3f83b90abd1b9e7eb948f2e702c9eca93744830f0dff8b7bc73a1458b0f8d40e679afd1e22b09c93f2c9729602c6c81026f8f02991c094a38ed0e6062508974a8bc69e8d6e6741a45eabc7db0665f8a2d879de17fdaf7ad1b210f337b90aec1c1c3676f244aa3852a77c05db0c05b9936bc10f0a327602aa2109bdd115f8a3e1081c1b0c56649638207d2dd9086c6ab885512fec9650ba288df5133c9dc87cb7fe81919fafca783f1252c2930332559bbafcdc830223837a7b78a7ce137e9f4a4e3e7d1195a494fa6d0ca9a10659c0df4ee0c9db91dba17acad08e5f6ccae71012e2c6e4b37894520fffe9620d66a72a1e4f04e5fa2abe93ef7f4b2e9e837c9e0f9d962b5c80f5e8340874610358576c23403b2d742c587e3827d17c6f41b31f9da4030207aaa2f4c32059f4017bfa4c324be2ac00a0aeabc7756ff2e16bdf39ed6b0c313f01b1acce8bc2b408a0cb3eaf9af9b68dd17f258c0bf0c536b19040d6bebc0a9dac7fbc59398b520891a741c4183a851c1cbcf3f78f30924d85ea2d907a6ac8db9a1455e4c672800a88629853fefb86800fec6631d77edf591d56199d5980bf3c86d9254773e02508be6a488b032686134049415bb194ad11ab8e4b1e6304274091beccc795e7b604649f106b395bef6f30a1dca330561709cdf7295557410d040543c279e169a908dc3d94c4809f0a7dd92ad8cefb014f5d47681b21d6b955fa894eff5b57cc5d0d1b08c01c4e892af363e6560a02212edc1fa645cdbce380b3e010d3c7f473960ec86e8ab8d2c235f594cbb2c2fa62f27b4ea4ab9796b8534688fd9819ea529c023ab370c6e482633801e3d588f3a4433cef5aaeb633547e922dbb35410af8a803156b64daf9633636063a37c4adc43e492023e09137099a715a690a480119f60d9c2f1a110a6590ff1b7774e0fe3e4a7419226448634c129aa1213e560814cf0b28d28fbd4029688f0d920a5f45c2a06bdea6c38f731a8bf741ba106bb8a3ac0e35fd8c015995d55c0733a18e66705dc84dab552aff7d2ae654b903f6fc9f1305da5a5bafbfbb22df241e211416fa902eaec589c7a22d6bf160a884e5534c5e0a685c312527c037f3196d4122f4d42c5a683d0e555af2a271d8d68faea80dfa04bc27bdbca4d4b3c14ae0745ea453174a6e6e3b3014dbd23ac3c8a413b5e29d08524227a74d9da9b38d2ddeb8649eb9d46bafca1453fb6ab89112544597bf2dd855511f88a12a3000d7410572e9705c11b7fcfbeb9b1e2317d274526555e8104840abf75d25f845b7262d8b97d359ec39019b7e79750d3924564a757f61d0739edc3b52509ccd4822e0a009ff01e33619090961ffee25f22fb6132f3273d4a2be46de6f8ef4bbb8105fd7ca8329d986901e77ddc8364f56a841c66eccd7c29d34893077d396aa37f39e5157e56029141ed64a21ef75be7509c4787df1ad05e3106117d83208dffda5818ad4b9214f8d98525b01b283ac7b1e10342f546147dfc1e7f3cf1e0108070c30f760d0fa2a9681bf480b329ec479a7b70c6d90e6dd8d3b0292dade0859548bf22cf4c808691e59acb8948f14a5eafcb81e15a3f0af341b165712b33dcfa798c4fdc96d576a144d6186911e1efa3b39cea48adb7e325479e0a1d07e78c8eef802096af323acfe1f0dc5f09eddb6fdc484dd73b09ef8be8e302758bdcddd89c671361d9b56857efc8486e4d1639b0b0a70546f8323db524b6b95350a947a9f2b929badd89da73ccb4c46e36eb9f994030a6eb36a6615f0471ba343eb5b8eb80b7e07523cb99431029b36db074d8e8643c417b80ef6e2cee094a39a04be94c9d08d4632548574d71e758440fb5bc26a628d19aec66d263c7272a551228be488c132df8c56b961e203ea465354fff1931763bca9609b9392e8b3290e60f8df264746081f9400ed506e5ac9b15da69625fd774d3f3450ba9b8ebec6b6cca9822ed85d7fc1cf6dbdada1cb34970cf87cc80b4d0ce3e9b973a125d761f03d35508d209a5c37b17c8b4d6d5064d4c8df72763285cadda3b56e6c6e8b5b75b719c41974164fd7af9e2478cf0f8a8ee7f663b3e60570779ca891a939f7f8cd30d7115a1da50b6099d1817986ec9e16459563c26e6714b3bb4d169462c6b8469680ac8a833d1f5a7a3fb4a3320ea262c3dd7d0ba79a7bbde853287e505e1979a376b2a2d925f9dca00fdf29d3b76484e5928433e716b186badab0a9efbe1562bd221f7275e46520c3a70f931a45cf22bcf455d9a45f65d0b15f44631c8e79f4c6ff9365027db2d4b322a62dd9eb3fcda81e30721970730ae5732216f25d311cf39877fcf0a6e258f786b5a617b9cc2b00eec15ba2d3ca12bc6a59189ac688d1a7e14cafb9060b516438ff68ff6f6084664ca56787d2be64104a20ae61459c8df34f473beb2f99efd5a16c3cb008aaa79902e9d7a48e91365d8e411f7698bad64eb84c061d98954d4b98bc1c6f2de13c9dcd0a1354fb25081bce08fae4030628f061af5d731bb3b4bc9ff12e311daebb60005413b27dca8eec97eddda88b55fa193767f679fc012e6404f97ab5b7efc52dff2c0e523b8a8ab0f4ca0deefbadb9fb79bd57d49bf07d1f9f9d12b09931860df0444cadbfcce259f6e505eabaa4b7625ab3ae7d72c60b06ec95c07981b7552483d385a17d6179f6106e86c8609cbf2d948e16fb41e195b0a93f250b296ddcfe20a5bd50a46c5d7f5a0f284bdf8dae11f26821fdaaaeaa3a11fdce58240685ab2709caed3feab3cd5b38d150d2cf728a23339fdf6f5f501d08bac9ed7538a98610c933cada9b5307814fc8558fa44b2042edd8ea2777147a026260569cb893a9b302e4756ea008abdae8cc65a6962e9845d8ceba5e9f0e723d950f839c9975f10b60725d37361bd2ce557fa68a6e9a6069d9727a98fa47fda31deabecc5c6f48fa57884283166ab5d7b4918598a01bed5c3d178b07494d7ad6fd7fc2717f0703ea0ff1965e2c77c63f8e999dc40035a91123df458822509becbcc22386349dea118d70efd59843a4eb3dc9bec704626d93b8286d709c46a9171f02255ad28581d1a2e385f177e9ccf01b9f732b50ef827e812e09c819dc082beb168dfe07739f976023d208cbcc54b76e9f0ca7b7ec2ddf15fe23195241837d5bc0c690261b46f05b7020bbae7f59ef42c046a7aa498a245b11d04f6fea6c0bf565de3cc419d74f00ae2dc2c6311bab6c8f04100ab1f61412d7f8a5a615136ad7e8a4785d7717f34b32af22f0d677b019f6ce64bd0c94a6c6ef2f704df82d21c34f63ae6d6ff32a27e2205dfb077d2d8f5bbb6f2135f2e771f31dbb4e98be4d9fa86a204acc1bf7f8ad76c800f6ac2009916de7d3fe6a7201b2c0c260c9a24bc45516f2244f95e836f30e8ac041d46f0ed08c99de3d62779195c599a3ad158ff9a6efecacdb7924a2146c472f551b9160aebb256dce0236fc7d46be77cf93c9e80ab021bea750e0d9b4b4cda04ed408909ee449da45a63e06ffaf7e311b125f2fef63c2ba9b6d2f3bdb5b0d04c117908308cd37665cc6775ef4d49ce555d3286671bb1d150b16276e38233e425c3230a31a3bf7d5946e6e13327519880ab9ceca4259f966826dcdeb30eed9a993971d9961da3e98e0c60f7c3f415707ecabe41920b1a20723a7a99ce84a255ecf97d5b004ae0062f108a76c5df2cb9b4b5a548a8b82468bc1ddc3b62f0f98f60d8b5e933fda2f11ceae7340c8ca55ce5a0d0dcce3ed1a558456698350f818a035f4b9d43e7afd2dad4678a6029e68d99f1ec4bb68aba853cf408a84bac2d379b47a9a9fec4c3ed22207d08edc7feae97451b4257a876186bdb1095ff6a404b3dca3bf5170d0127b93b23a2b88651a6a546677536a6ff3cdddf5247c98290cdc09d5600c6693bd736f080cda67770a209827ebfecdf9c86ff131cd3a17b39188348db4ca043faba0bdf151be4bee2b0559ed43ea7d5c4bd5ca6287e5b82f896a2a561c915b7aaf0ab0c6a238fafa262aff20c9aaa6b47e25e7c811e12ac2e9ad879214804a9c7bbb5bcde50c7ed7fb094ae029f869b3a441e0750473f3faa1d614456adc475a02cdd7aaf9c48b45161eb2a105ea4b9f03d676fd34f69bac593777a74578ef0ade13db1564e4172576fe454edeae5bc523fd6048d4bcb5a5716f6da4bf01fd05738c182aed9dc6a0fe13584c520f2bde1ac54e3c10b34985fc9711f4b8dae5046b92929afb4c8ba3f7ca1c662b78abbfaf2febf4f4184c1f115d552ca0872c0af06b61516dbbe09c3b086712e3eef9a0a0f5c890417b4fcf656f1b4cdddeba0f5a63d3b2ce929f6c3032387b4d4bafef61fb5611f846da35d4e3e11fea2615070aa485e0828b38359cc30426796db346825191141a5b14211937b12924e0a5074932707d8f1e76c3c80d6a3c64518dd3691c5d5252ba28df354dbb231da4f40ab801a29f5769a3ad6866582966b40896d943f3b62f711988613b1ab59bc0cc0d6dd2ea50fd4afa1ffd250339cfc0484693d119961d7b4f17dc6116c62adc7d076ed6834db8886b1620aff0dcd5ddac2f0492f45a7c0b448a668688ad6cb1d101c451dd8cc0c762b544849ca04153936549ae3b39112dbe2ea96bf0b210ec840810b47be58637baa446509b07c58ffa1fb66585fd4cb01df05291fd4d7d25880483bb8ac4e0505a7cc5119ee45948309eae12ea41a8b83e0a612acc25338e4b008187156b0971c145f9d6214afd896b20a15edba78f7a087c23a30d894f688908\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:51 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "20" + } + ], + "cookies": [], + "content": { + "size": 20, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 355 + }, + "cache": {}, + "timings": { + "blocked": 1.2457230000010169, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.31499999999999995, + "wait": 398.4800000004447, + "receive": 1.2619999979506247, + "_blocked_queueing": 0.7230000010167714 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:57.285Z", + "time": 165.00839099994118, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:57 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.6373909999977623, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.123, + "wait": 162.38599999916391, + "receive": 0.862000000779517, + "_blocked_queueing": 0.3909999977622647 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:25:57.451Z", + "time": 165.87528899877725, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:25:57 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "969" + } + ], + "cookies": [], + "content": { + "size": 969, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1305 + }, + "cache": {}, + "timings": { + "blocked": 1.0402889999996114, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.137, + "wait": 163.8619999995241, + "receive": 0.835999999253545, + "_blocked_queueing": 0.28899999961140566 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:26:07.285Z", + "time": 155.2864420010701, + "request": { + "method": "OPTIONS", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "access-control-request-headers", + "value": "content-type" + }, + { + "name": "access-control-request-method", + "value": "POST" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "accept", + "value": "*/*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "OPTIONS" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0 + }, + "response": { + "status": 204, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:26:07 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "204" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-max-age", + "value": "1728000" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "text/plain charset=UTF-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "0" + } + ], + "cookies": [], + "content": { + "size": 0, + "mimeType": "text/plain" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 344 + }, + "cache": {}, + "timings": { + "blocked": 1.4484420000004756, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.133, + "wait": 152.7629999986731, + "receive": 0.9420000023965258, + "_blocked_queueing": 0.442000000475673 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + }, + { + "startedDateTime": "2018-07-06T00:26:07.441Z", + "time": 172.03327899891883, + "request": { + "method": "POST", + "url": "https://api.mymonero.com:8443/get_address_info", + "httpVersion": "http/2.0", + "headers": [ + { + "name": ":path", + "value": "/get_address_info" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "origin", + "value": "https://mymonero.com" + }, + { + "name": "accept-encoding", + "value": "gzip, deflate, br" + }, + { + "name": "accept-language", + "value": "en-CA,en-GB;q=0.9,en-US;q=0.8,en;q=0.7" + }, + { + "name": "user-agent", + "value": + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.33 Safari/537.36" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "cache-control", + "value": "no-cache" + }, + { + "name": ":authority", + "value": "api.mymonero.com:8443" + }, + { + "name": "referer", + "value": "https://mymonero.com/" + }, + { + "name": ":scheme", + "value": "https" + }, + { + "name": "content-length", + "value": "187" + }, + { + "name": "dnt", + "value": "1" + }, + { + "name": ":method", + "value": "POST" + } + ], + "queryString": [], + "cookies": [], + "headersSize": -1, + "bodySize": 0, + "postData": { + "mimeType": "application/json;charset=UTF-8", + "text": + "{\"address\":\"47VnPqnYvct5bDGSU1gsE35GiTtUQSAPmSGZ28wDU5EwDJCp9BVhtNb7H56CHmnvaGTZuAav89MyFLbDJE6z6oRp9ecnbTX\",\"view_key\":\"550eae3eb2cec618f8f726020276943dba03c189adf7ad2d8edb4de54482650c\"}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "http/2.0", + "headers": [ + { + "name": "date", + "value": "Fri, 06 Jul 2018 00:26:07 GMT" + }, + { + "name": "server", + "value": "nginx" + }, + { + "name": "status", + "value": "200" + }, + { + "name": "x-powered-by", + "value": "go-json-rest" + }, + { + "name": "access-control-max-age", + "value": "86400" + }, + { + "name": "access-control-allow-methods", + "value": "GET, POST, OPTIONS" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "access-control-allow-origin", + "value": "https://mymonero.com" + }, + { + "name": "access-control-allow-credentials", + "value": "true" + }, + { + "name": "access-control-allow-headers", + "value": + "*, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Set-Cookie" + }, + { + "name": "content-length", + "value": "969" + } + ], + "cookies": [], + "content": { + "size": 969, + "mimeType": "application/json" + }, + "redirectURL": "", + "headersSize": -1, + "bodySize": -1, + "_transferSize": 1305 + }, + "cache": {}, + "timings": { + "blocked": 0.830278999999864, + "dns": -1, + "ssl": -1, + "connect": -1, + "send": 0.09000000000000008, + "wait": 170.11299999871525, + "receive": 1.0000000002037268, + "_blocked_queueing": 0.2789999998640269 + }, + "serverIPAddress": "185.152.64.175", + "connection": "56354" + } + ] + } +} diff --git a/tests/range_proofs/range_proofs.spec.js b/__test__/range_proofs/range_proofs.spec.ts similarity index 67% rename from tests/range_proofs/range_proofs.spec.js rename to __test__/range_proofs/range_proofs.spec.ts index 4b11289..2c75ad3 100644 --- a/tests/range_proofs/range_proofs.spec.js +++ b/__test__/range_proofs/range_proofs.spec.ts @@ -1,3 +1,14 @@ +import { BigInt } from "biginteger"; +import { + hash_to_scalar, + Z, + generate_key_image_2, + genRct, + verRct, + decodeRct, +} from "cryptonote_utils"; +import { ctskpkGen, populateFromBlockchain } from "./test_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -25,12 +36,6 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const { - ctskpkGen, - populateFromBlockchain, - JSBigInt, - monero_utils, -} = require("./test_utils"); it("range_proofs", () => { //Ring CT Stuff @@ -55,29 +60,29 @@ it("range_proofs", () => { // key vector let amount_keys = []; - amounts.push(new JSBigInt(500)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(500)); + amount_keys.push(hash_to_scalar(Z)); - amounts.push(new JSBigInt(4500)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(4500)); + amount_keys.push(hash_to_scalar(Z)); - amounts.push(new JSBigInt(500)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(500)); + amount_keys.push(hash_to_scalar(Z)); - amounts.push(new JSBigInt(500)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(500)); + amount_keys.push(hash_to_scalar(Z)); //compute rct data with mixin 500 const { index, mixRing } = populateFromBlockchain(inPk, 3); // generate kimg - const kimg = [monero_utils.generate_key_image_2(inPk[0].dest, inSk[0].x)]; + const kimg = [generate_key_image_2(inPk[0].dest, inSk[0].x)]; - let s = monero_utils.genRct( - monero_utils.Z, + let s = genRct( + Z, inSk, kimg, - [[]], + [], amounts, mixRing, amount_keys, @@ -85,32 +90,22 @@ it("range_proofs", () => { "0", ); - expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true); - expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(true); + expect(verRct(s, true, mixRing, kimg[0])).toEqual(true); + expect(verRct(s, false, mixRing, kimg[0])).toEqual(true); //decode received amount - monero_utils.decodeRct(s, amount_keys[1], 1); + decodeRct(s, amount_keys[1], 1); // Ring CT with failing MG sig part should not verify! // Since sum of inputs != outputs - amounts[1] = new JSBigInt(12501); + amounts[1] = new BigInt(12501); - s = monero_utils.genRct( - monero_utils.Z, - inSk, - kimg, - [[]], - amounts, - mixRing, - amount_keys, - [index], - "0", - ); + s = genRct(Z, inSk, kimg, [], amounts, mixRing, amount_keys, [index], "0"); - expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true); - expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(false); + expect(verRct(s, true, mixRing, kimg[0])).toEqual(true); + expect(verRct(s, false, mixRing, kimg[0])).toEqual(false); //decode received amount - monero_utils.decodeRct(s, amount_keys[1], 1); + decodeRct(s, amount_keys[1], 1); }); diff --git a/tests/range_proofs/range_proofs_with_fee.spec.js b/__test__/range_proofs/range_proofs_with_fee.spec.ts similarity index 69% rename from tests/range_proofs/range_proofs_with_fee.spec.js rename to __test__/range_proofs/range_proofs_with_fee.spec.ts index a9bbe60..7f46ba9 100644 --- a/tests/range_proofs/range_proofs_with_fee.spec.js +++ b/__test__/range_proofs/range_proofs_with_fee.spec.ts @@ -1,3 +1,15 @@ +import { BigInt } from "biginteger"; +import { + hash_to_scalar, + Z, + generate_key_image_2, + genRct, + verRct, + decodeRct, +} from "cryptonote_utils"; +import { ctskpkGen, populateFromBlockchain } from "./test_utils"; +import { SecretCommitment, MixCommitment } from "types"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -25,19 +37,13 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const { - ctskpkGen, - populateFromBlockchain, - JSBigInt, - monero_utils, -} = require("./test_utils"); it("range_proofs", () => { //Ring CT Stuff //ct range proofs // ctkey vectors - let inSk = [], - inPk = []; + let inSk: SecretCommitment[] = [], + inPk: MixCommitment[] = []; // ctkeys // we test only a single input here since the current impl of @@ -55,29 +61,29 @@ it("range_proofs", () => { // key vector let amount_keys = []; - amounts.push(new JSBigInt(1000)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(1000)); + amount_keys.push(hash_to_scalar(Z)); - amounts.push(new JSBigInt(4000)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(4000)); + amount_keys.push(hash_to_scalar(Z)); - amounts.push(new JSBigInt(1000)); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + amounts.push(new BigInt(1000)); + amount_keys.push(hash_to_scalar(Z)); //compute rct data with mixin 500 const { index, mixRing } = populateFromBlockchain(inPk, 2); // generate kimg - const kimg = [monero_utils.generate_key_image_2(inPk[0].dest, inSk[0].x)]; + const kimg = [generate_key_image_2(inPk[0].dest, inSk[0].x)]; // add fee of 1 NOTE: fee is passed in with its endian not swapped, hence no usage of d2s const fee = "1"; - let s = monero_utils.genRct( - monero_utils.Z, + let s = genRct( + Z, inSk, kimg, - [[]], + [], amounts, mixRing, amount_keys, @@ -85,32 +91,22 @@ it("range_proofs", () => { fee, ); - expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true); - expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(true); + expect(verRct(s, true, mixRing, kimg[0])).toEqual(true); + expect(verRct(s, false, mixRing, kimg[0])).toEqual(true); //decode received amount - monero_utils.decodeRct(s, amount_keys[1], 1); + decodeRct(s, amount_keys[1], 1); // Ring CT with failing MG sig part should not verify! // Since sum of inputs != outputs - amounts[1] = new JSBigInt(4501); + amounts[1] = new BigInt(4501); - s = monero_utils.genRct( - monero_utils.Z, - inSk, - kimg, - [[]], - amounts, - mixRing, - amount_keys, - [index], - fee, - ); + s = genRct(Z, inSk, kimg, [], amounts, mixRing, amount_keys, [index], fee); - expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true); - expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(false); + expect(verRct(s, true, mixRing, kimg[0])).toEqual(true); + expect(verRct(s, false, mixRing, kimg[0])).toEqual(false); //decode received amount - monero_utils.decodeRct(s, amount_keys[1], 1); + decodeRct(s, amount_keys[1], 1); }); diff --git a/tests/range_proofs/simple.spec.js b/__test__/range_proofs/simple.spec.ts similarity index 76% rename from tests/range_proofs/simple.spec.js rename to __test__/range_proofs/simple.spec.ts index a92aa08..76aba3e 100644 --- a/tests/range_proofs/simple.spec.js +++ b/__test__/range_proofs/simple.spec.ts @@ -26,12 +26,17 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const { - ctskpkGen, - populateFromBlockchainSimple, - JSBigInt, - monero_utils, -} = require("./test_utils"); +import { ctskpkGen, populateFromBlockchainSimple } from "./test_utils"; +import { + hash_to_scalar, + Z, + random_scalar, + generate_key_image_2, + genRct, + verRctSimple, + decodeRctSimple, +} from "cryptonote_utils"; +import { BigInt } from "index"; it("should test ringct simple transactions", () => { //Ring CT Stuff @@ -50,7 +55,7 @@ it("should test ringct simple transactions", () => { let [sctmp, pctmp] = ctskpkGen(3000); inSk.push(sctmp); inPk.push(pctmp); - inamounts.push(3000); + inamounts.push(new BigInt(3000)); } //add fake input 3000 @@ -60,16 +65,16 @@ it("should test ringct simple transactions", () => { let [sctmp, pctmp] = ctskpkGen(3000); inSk.push(sctmp); inPk.push(pctmp); - inamounts.push(3000); + inamounts.push(new BigInt(3000)); } - outamounts.push(5000); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + outamounts.push(new BigInt(5000)); + amount_keys.push(hash_to_scalar(Z)); - outamounts.push(999); - amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z)); + outamounts.push(new BigInt(999)); + amount_keys.push(hash_to_scalar(Z)); - const message = monero_utils.random_scalar(); + const message = random_scalar(); const txnFee = "1"; // generate mixin and indices @@ -84,11 +89,11 @@ it("should test ringct simple transactions", () => { // generate kimg const kimg = [ - monero_utils.generate_key_image_2(inPk[0].dest, inSk[0].x), - monero_utils.generate_key_image_2(inPk[1].dest, inSk[1].x), + generate_key_image_2(inPk[0].dest, inSk[0].x), + generate_key_image_2(inPk[1].dest, inSk[1].x), ]; - const s = monero_utils.genRct( + const s = genRct( message, inSk, kimg, @@ -100,8 +105,8 @@ it("should test ringct simple transactions", () => { txnFee, ); - expect(monero_utils.verRctSimple(s, true, mixRings, kimg)).toEqual(true); - expect(monero_utils.verRctSimple(s, false, mixRings, kimg)).toEqual(true); + expect(verRctSimple(s, true, mixRings, kimg)).toEqual(true); + expect(verRctSimple(s, false, mixRings, kimg)).toEqual(true); - monero_utils.decodeRctSimple(s, amount_keys[1], 1); + decodeRctSimple(s, amount_keys[1], 1); }); diff --git a/tests/range_proofs/test_utils.js b/__test__/range_proofs/test_utils.ts similarity index 66% rename from tests/range_proofs/test_utils.js rename to __test__/range_proofs/test_utils.ts index 542cdd8..bb21ee2 100644 --- a/tests/range_proofs/test_utils.js +++ b/__test__/range_proofs/test_utils.ts @@ -25,60 +25,67 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("../../").monero_utils; -const JSBigInt = require("../../cryptonote_utils/biginteger").BigInteger; -const { randomBytes } = require("crypto"); +import { randomBytes } from "crypto"; +import { + random_keypair, + d2s, + ge_scalarmult, + ge_add, + H, +} from "cryptonote_utils"; +import { SecretCommitment, MixCommitment } from "types"; //generates a / Pedersen commitment to the amount -function ctskpkGen(amount) { - let sk = {}, - pk = {}; - const key_pair1 = monero_utils.random_keypair(); - const key_pair2 = monero_utils.random_keypair(); + +export function ctskpkGen(amount: number): [SecretCommitment, MixCommitment] { + let sk = { x: "", a: "" }, + pk = { dest: "", mask: "" }; + const key_pair1 = random_keypair(); + const key_pair2 = random_keypair(); sk.x = key_pair1.sec; pk.dest = key_pair1.pub; sk.a = key_pair2.sec; pk.mask = key_pair2.pub; - const am = monero_utils.d2s(amount.toString()); - const bH = monero_utils.ge_scalarmult(monero_utils.H, am); + const am = d2s(amount.toString()); + const bH = ge_scalarmult(H, am); - pk.mask = monero_utils.ge_add(pk.mask, bH); + pk.mask = ge_add(pk.mask, bH); return [sk, pk]; } -function randomNum(upperLimit) { +export function randomNum(upperLimit: number) { return parseInt(randomBytes(1).toString("hex"), 16) % upperLimit; } -//These functions get keys from blockchain -//replace these when connecting blockchain -//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with -function getKeyFromBlockchain(reference_index) { - let a = {}; - a.dest = monero_utils.random_keypair().pub; - a.mask = monero_utils.random_keypair().pub; +// These functions get keys from blockchain +// replace these when connecting blockchain +// getKeyFromBlockchain grabs a key from the blockchain at "reference_index" (unused param) to mix with +export function getKeyFromBlockchain() { + let a = { dest: "", mask: "" }; + a.dest = random_keypair().pub; + a.mask = random_keypair().pub; return a; } // populateFromBlockchain creates a keymatrix with "mixin" + 1 columns and one of the columns is inPk // the return values are the key matrix, and the index where inPk was put (random). -function populateFromBlockchain(inPk, mixin) { +export function populateFromBlockchain(inPk: MixCommitment[], mixin: number) { const rows = inPk.length; const inPkCpy = [...inPk]; // ctkeyMatrix - const mixRing = []; + const mixRing: MixCommitment[][] = []; const index = randomNum(mixin); for (let i = 0; i < rows; i++) { mixRing[i] = []; for (let j = 0; j <= mixin; j++) { if (j !== index) { - mixRing[i][j] = getKeyFromBlockchain(index); /*?*/ + mixRing[i][j] = getKeyFromBlockchain(); /*?*/ } else { - mixRing[i][j] = inPkCpy.pop(); + mixRing[i][j] = inPkCpy.pop() as MixCommitment; } } } @@ -86,13 +93,16 @@ function populateFromBlockchain(inPk, mixin) { return { mixRing, index }; } -function populateFromBlockchainSimple(inPk, mixin) { +export function populateFromBlockchainSimple( + inPk: MixCommitment, + mixin: number, +) { const index = randomNum(mixin); const mixRing = []; for (let i = 0; i <= mixin; i++) { if (i !== index) { - mixRing[i] = getKeyFromBlockchain(index); + mixRing[i] = getKeyFromBlockchain(); } else { mixRing[i] = inPk; } @@ -100,12 +110,3 @@ function populateFromBlockchainSimple(inPk, mixin) { return { mixRing, index }; } - -module.exports = { - ctskpkGen, - populateFromBlockchain, - populateFromBlockchainSimple, - getKeyFromBlockchain, - monero_utils, - JSBigInt, -}; diff --git a/cryptonote_utils/cryptonote_utils.js b/cryptonote_utils/cryptonote_utils.js deleted file mode 100644 index e18bc1b..0000000 --- a/cryptonote_utils/cryptonote_utils.js +++ /dev/null @@ -1,2890 +0,0 @@ -// Copyright (c) 2014-2018, MyMonero.com -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// Original Author: Lucas Jones -// Modified to remove jQuery dep and support modular inclusion of deps by Paul Shapiro (2016) -// Modified to add RingCT support by luigi1111 (2017) -// -// v--- These should maybe be injected into a context and supplied to currencyConfig for future platforms -const JSBigInt = require("./biginteger").BigInteger; -const cnBase58 = require("./internal_libs/bs58"); -const CNCrypto = require("./internal_libs/cn_crypto"); -const nacl = require("./internal_libs/fast_cn"); -const SHA3 = require("keccakjs"); -const nettype_utils = require("./nettype"); -const { randomBytes } = require("crypto"); - -var cnUtil = function(currencyConfig) { - var config = {}; // shallow copy of initConfig - for (var key in currencyConfig) { - config[key] = currencyConfig[key]; - } - - config.coinUnits = new JSBigInt(10).pow(config.coinUnitPlaces); - - var HASH_SIZE = 32; - var ADDRESS_CHECKSUM_SIZE = 4; - var INTEGRATED_ID_SIZE = 8; - var ENCRYPTED_PAYMENT_ID_TAIL = 141; - // - var UINT64_MAX = new JSBigInt(2).pow(64); - var CURRENT_TX_VERSION = 2; - var OLD_TX_VERSION = 1; - var RCTTypeFull = 1; - var RCTTypeSimple = 2; - var TX_EXTRA_NONCE_MAX_COUNT = 255; - var TX_EXTRA_TAGS = { - PADDING: "00", - PUBKEY: "01", - NONCE: "02", - MERGE_MINING: "03", - }; - var TX_EXTRA_NONCE_TAGS = { - PAYMENT_ID: "00", - ENCRYPTED_PAYMENT_ID: "01", - }; - var KEY_SIZE = 32; - var STRUCT_SIZES = { - GE_P3: 160, - GE_P2: 120, - GE_P1P1: 160, - GE_CACHED: 160, - EC_SCALAR: 32, - EC_POINT: 32, - KEY_IMAGE: 32, - GE_DSMP: 160 * 8, // ge_cached * 8 - SIGNATURE: 64, // ec_scalar * 2 - }; - - //RCT vars - var H = "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94"; //base H for amounts - this.H = H; - var l = JSBigInt( - "7237005577332262213973186563042994240857116359379907606001950938285454250989", - ); //curve order (not RCT specific) - - var I = "0100000000000000000000000000000000000000000000000000000000000000"; //identity element - this.I = I; - this.identity = function() { - return I; - }; - - var Z = "0000000000000000000000000000000000000000000000000000000000000000"; //zero scalar - this.Z = Z; - - //H2 object to speed up some operations - var H2 = [ - "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94", - "8faa448ae4b3e2bb3d4d130909f55fcd79711c1c83cdbccadd42cbe1515e8712", - "12a7d62c7791654a57f3e67694ed50b49a7d9e3fc1e4c7a0bde29d187e9cc71d", - "789ab9934b49c4f9e6785c6d57a498b3ead443f04f13df110c5427b4f214c739", - "771e9299d94f02ac72e38e44de568ac1dcb2edc6edb61f83ca418e1077ce3de8", - "73b96db43039819bdaf5680e5c32d741488884d18d93866d4074a849182a8a64", - "8d458e1c2f68ebebccd2fd5d379f5e58f8134df3e0e88cad3d46701063a8d412", - "09551edbe494418e81284455d64b35ee8ac093068a5f161fa6637559177ef404", - "d05a8866f4df8cee1e268b1d23a4c58c92e760309786cdac0feda1d247a9c9a7", - "55cdaad518bd871dd1eb7bc7023e1dc0fdf3339864f88fdd2de269fe9ee1832d", - "e7697e951a98cfd5712b84bbe5f34ed733e9473fcb68eda66e3788df1958c306", - "f92a970bae72782989bfc83adfaa92a4f49c7e95918b3bba3cdc7fe88acc8d47", - "1f66c2d491d75af915c8db6a6d1cb0cd4f7ddcd5e63d3ba9b83c866c39ef3a2b", - "3eec9884b43f58e93ef8deea260004efea2a46344fc5965b1a7dd5d18997efa7", - "b29f8f0ccb96977fe777d489d6be9e7ebc19c409b5103568f277611d7ea84894", - "56b1f51265b9559876d58d249d0c146d69a103636699874d3f90473550fe3f2c", - "1d7a36575e22f5d139ff9cc510fa138505576b63815a94e4b012bfd457caaada", - "d0ac507a864ecd0593fa67be7d23134392d00e4007e2534878d9b242e10d7620", - "f6c6840b9cf145bb2dccf86e940be0fc098e32e31099d56f7fe087bd5deb5094", - "28831a3340070eb1db87c12e05980d5f33e9ef90f83a4817c9f4a0a33227e197", - "87632273d629ccb7e1ed1a768fa2ebd51760f32e1c0b867a5d368d5271055c6e", - "5c7b29424347964d04275517c5ae14b6b5ea2798b573fc94e6e44a5321600cfb", - "e6945042d78bc2c3bd6ec58c511a9fe859c0ad63fde494f5039e0e8232612bd5", - "36d56907e2ec745db6e54f0b2e1b2300abcb422e712da588a40d3f1ebbbe02f6", - "34db6ee4d0608e5f783650495a3b2f5273c5134e5284e4fdf96627bb16e31e6b", - "8e7659fb45a3787d674ae86731faa2538ec0fdf442ab26e9c791fada089467e9", - "3006cf198b24f31bb4c7e6346000abc701e827cfbb5df52dcfa42e9ca9ff0802", - "f5fd403cb6e8be21472e377ffd805a8c6083ea4803b8485389cc3ebc215f002a", - "3731b260eb3f9482e45f1c3f3b9dcf834b75e6eef8c40f461ea27e8b6ed9473d", - "9f9dab09c3f5e42855c2de971b659328a2dbc454845f396ffc053f0bb192f8c3", - "5e055d25f85fdb98f273e4afe08464c003b70f1ef0677bb5e25706400be620a5", - "868bcf3679cb6b500b94418c0b8925f9865530303ae4e4b262591865666a4590", - "b3db6bd3897afbd1df3f9644ab21c8050e1f0038a52f7ca95ac0c3de7558cb7a", - "8119b3a059ff2cac483e69bcd41d6d27149447914288bbeaee3413e6dcc6d1eb", - "10fc58f35fc7fe7ae875524bb5850003005b7f978c0c65e2a965464b6d00819c", - "5acd94eb3c578379c1ea58a343ec4fcff962776fe35521e475a0e06d887b2db9", - "33daf3a214d6e0d42d2300a7b44b39290db8989b427974cd865db011055a2901", - "cfc6572f29afd164a494e64e6f1aeb820c3e7da355144e5124a391d06e9f95ea", - "d5312a4b0ef615a331f6352c2ed21dac9e7c36398b939aec901c257f6cbc9e8e", - "551d67fefc7b5b9f9fdbf6af57c96c8a74d7e45a002078a7b5ba45c6fde93e33", - "d50ac7bd5ca593c656928f38428017fc7ba502854c43d8414950e96ecb405dc3", - "0773e18ea1be44fe1a97e239573cfae3e4e95ef9aa9faabeac1274d3ad261604", - "e9af0e7ca89330d2b8615d1b4137ca617e21297f2f0ded8e31b7d2ead8714660", - "7b124583097f1029a0c74191fe7378c9105acc706695ed1493bb76034226a57b", - "ec40057b995476650b3db98e9db75738a8cd2f94d863b906150c56aac19caa6b", - "01d9ff729efd39d83784c0fe59c4ae81a67034cb53c943fb818b9d8ae7fc33e5", - "00dfb3c696328c76424519a7befe8e0f6c76f947b52767916d24823f735baf2e", - "461b799b4d9ceea8d580dcb76d11150d535e1639d16003c3fb7e9d1fd13083a8", - "ee03039479e5228fdc551cbde7079d3412ea186a517ccc63e46e9fcce4fe3a6c", - "a8cfb543524e7f02b9f045acd543c21c373b4c9b98ac20cec417a6ddb5744e94", - "932b794bf89c6edaf5d0650c7c4bad9242b25626e37ead5aa75ec8c64e09dd4f", - "16b10c779ce5cfef59c7710d2e68441ea6facb68e9b5f7d533ae0bb78e28bf57", - "0f77c76743e7396f9910139f4937d837ae54e21038ac5c0b3fd6ef171a28a7e4", - "d7e574b7b952f293e80dde905eb509373f3f6cd109a02208b3c1e924080a20ca", - "45666f8c381e3da675563ff8ba23f83bfac30c34abdde6e5c0975ef9fd700cb9", - "b24612e454607eb1aba447f816d1a4551ef95fa7247fb7c1f503020a7177f0dd", - "7e208861856da42c8bb46a7567f8121362d9fb2496f131a4aa9017cf366cdfce", - "5b646bff6ad1100165037a055601ea02358c0f41050f9dfe3c95dccbd3087be0", - "746d1dccfed2f0ff1e13c51e2d50d5324375fbd5bf7ca82a8931828d801d43ab", - "cb98110d4a6bb97d22feadbc6c0d8930c5f8fc508b2fc5b35328d26b88db19ae", - "60b626a033b55f27d7676c4095eababc7a2c7ede2624b472e97f64f96b8cfc0e", - "e5b52bc927468df71893eb8197ef820cf76cb0aaf6e8e4fe93ad62d803983104", - "056541ae5da9961be2b0a5e895e5c5ba153cbb62dd561a427bad0ffd41923199", - "f8fef05a3fa5c9f3eba41638b247b711a99f960fe73aa2f90136aeb20329b888", - ]; - - this.H2 = H2; - - //begin rct new functions - //creates a Pedersen commitment from an amount (in scalar form) and a mask - //C = bG + aH where b = mask, a = amount - function commit(amount, mask) { - if ( - !valid_hex(mask) || - mask.length !== 64 || - !valid_hex(amount) || - amount.length !== 64 - ) { - throw "invalid amount or mask!"; - } - var C = this.ge_double_scalarmult_base_vartime(amount, H, mask); - return C; - } - - function zeroCommit(amount) { - if (!valid_hex(amount) || amount.length !== 64) { - throw "invalid amount!"; - } - var C = this.ge_double_scalarmult_base_vartime(amount, H, I); - return C; - } - - this.decode_rct_ecdh = function(ecdh, key) { - var first = this.hash_to_scalar(key); - var second = this.hash_to_scalar(first); - return { - mask: this.sc_sub(ecdh.mask, first), - amount: this.sc_sub(ecdh.amount, second), - }; - }; - - this.encode_rct_ecdh = function(ecdh, key) { - var first = this.hash_to_scalar(key); - var second = this.hash_to_scalar(first); - return { - mask: this.sc_add(ecdh.mask, first), - amount: this.sc_add(ecdh.amount, second), - }; - }; - - //switch byte order for hex string - function swapEndian(hex) { - if (hex.length % 2 !== 0) { - return "length must be a multiple of 2!"; - } - var data = ""; - for (var i = 1; i <= hex.length / 2; i++) { - data += hex.substr(0 - 2 * i, 2); - } - return data; - } - - //switch byte order charwise - function swapEndianC(string) { - var data = ""; - for (var i = 1; i <= string.length; i++) { - data += string.substr(0 - i, 1); - } - return data; - } - - //for most uses you'll also want to swapEndian after conversion - //mainly to convert integer "scalars" to usable hexadecimal strings - //uint long long to 32 byte key - function d2h(integer) { - if (typeof integer !== "string" && integer.toString().length > 15) { - throw "integer should be entered as a string for precision"; - } - var padding = ""; - for (var i = 0; i < 63; i++) { - padding += "0"; - } - return ( - padding + - JSBigInt(integer) - .toString(16) - .toLowerCase() - ).slice(-64); - } - this.d2h = d2h; - - //integer (string) to scalar - function d2s(integer) { - return swapEndian(d2h(integer)); - } - - this.d2s = d2s; - //scalar to integer (string) - function s2d(scalar) { - return JSBigInt.parse(swapEndian(scalar), 16).toString(); - } - - //convert integer string to 64bit "binary" little-endian string - function d2b(integer) { - if (typeof integer !== "string" && integer.toString().length > 15) { - throw "integer should be entered as a string for precision"; - } - var padding = ""; - for (var i = 0; i < 63; i++) { - padding += "0"; - } - var a = new JSBigInt(integer); - if (a.toString(2).length > 64) { - throw "amount overflows uint64!"; - } - return swapEndianC((padding + a.toString(2)).slice(-64)); - } - - //end rct new functions - - this.valid_hex = function(hex) { - var exp = new RegExp("[0-9a-fA-F]{" + hex.length + "}"); - return exp.test(hex); - }; - - //simple exclusive or function for two hex inputs - this.hex_xor = function(hex1, hex2) { - if ( - !hex1 || - !hex2 || - hex1.length !== hex2.length || - hex1.length % 2 !== 0 || - hex2.length % 2 !== 0 - ) { - throw "Hex string(s) is/are invalid!"; - } - var bin1 = hextobin(hex1); - var bin2 = hextobin(hex2); - var xor = new Uint8Array(bin1.length); - for (var i = 0; i < xor.length; i++) { - xor[i] = bin1[i] ^ bin2[i]; - } - return bintohex(xor); - }; - - function hextobin(hex) { - if (hex.length % 2 !== 0) throw "Hex string has invalid length!"; - var res = new Uint8Array(hex.length / 2); - for (var i = 0; i < hex.length / 2; ++i) { - res[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); - } - return res; - } - this.hextobin = hextobin; - - function bintohex(bin) { - var out = []; - for (var i = 0; i < bin.length; ++i) { - out.push(("0" + bin[i].toString(16)).slice(-2)); - } - return out.join(""); - } - - // Generate a 256-bit / 64-char / 32-byte crypto random - this.rand_32 = function() { - return randomBytes(32).toString("hex"); - }; - - // Generate a 64-bit / 16-char / 8-byte crypto random - this.rand_8 = function() { - return randomBytes(8).toString("hex"); - }; - - this.encode_varint = function(i) { - i = new JSBigInt(i); - var out = ""; - // While i >= b10000000 - while (i.compare(0x80) >= 0) { - // out.append i & b01111111 | b10000000 - out += ("0" + ((i.lowVal() & 0x7f) | 0x80).toString(16)).slice(-2); - i = i.divide(new JSBigInt(2).pow(7)); - } - out += ("0" + i.toJSValue().toString(16)).slice(-2); - return out; - }; - - this.sc_reduce = function(hex) { - var input = hextobin(hex); - if (input.length !== 64) { - throw "Invalid input length"; - } - var mem = CNCrypto._malloc(64); - CNCrypto.HEAPU8.set(input, mem); - CNCrypto.ccall("sc_reduce", "void", ["number"], [mem]); - var output = CNCrypto.HEAPU8.subarray(mem, mem + 64); - CNCrypto._free(mem); - return bintohex(output); - }; - - this.sc_reduce32 = function(hex) { - var input = hextobin(hex); - if (input.length !== 32) { - throw "Invalid input length"; - } - var mem = CNCrypto._malloc(32); - CNCrypto.HEAPU8.set(input, mem); - CNCrypto.ccall("sc_reduce32", "void", ["number"], [mem]); - var output = CNCrypto.HEAPU8.subarray(mem, mem + 32); - CNCrypto._free(mem); - return bintohex(output); - }; - - this.cn_fast_hash = function(input) { - if (input.length % 2 !== 0 || !this.valid_hex(input)) { - throw "Input invalid"; - } - - const hasher = new SHA3(256); - hasher.update(hextobin(input)); - return hasher.digest("hex"); - }; - - this.sec_key_to_pub = function(sec) { - if (sec.length !== 64) { - throw "Invalid sec length"; - } - return bintohex(nacl.ge_scalarmult_base(hextobin(sec))); - }; - - //alias - this.ge_scalarmult_base = function(sec) { - return this.sec_key_to_pub(sec); - }; - - this.ge_scalarmult = function(pub, sec) { - if (pub.length !== 64 || sec.length !== 64) { - throw "Invalid input length"; - } - return bintohex(nacl.ge_scalarmult(hextobin(pub), hextobin(sec))); - }; - - this.pubkeys_to_string = function(spend, view, nettype) { - var prefix = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForStandardAddressOn(nettype), - ); - var data = prefix + spend + view; - var checksum = this.cn_fast_hash(data); - return cnBase58.encode( - data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2), - ); - }; - - this.new__int_addr_from_addr_and_short_pid = function( - address, - short_pid, - nettype, - ) { - // throws - let decoded_address = this.decode_address( - address, // TODO/FIXME: not super happy about having to decode just to re-encode… this was a quick hack - nettype, - ); // throws - if (!short_pid || short_pid.length != 16) { - throw "expected valid short_pid"; - } - var prefix = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForIntegratedAddressOn(nettype), - ); - var data = - prefix + decoded_address.spend + decoded_address.view + short_pid; - var checksum = this.cn_fast_hash(data); - var encodable__data = - data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2); - // - return cnBase58.encode(encodable__data); - }; - - // Generate keypair from seed - this.generate_keys = function(seed) { - if (seed.length !== 64) throw "Invalid input length!"; - var sec = this.sc_reduce32(seed); - var pub = this.sec_key_to_pub(sec); - return { - sec: sec, - pub: pub, - }; - }; - - this.random_keypair = function() { - return this.generate_keys(this.rand_32()); - }; - - // Random 32-byte ec scalar - this.random_scalar = function() { - return this.sc_reduce32(this.rand_32()); - }; - - // alias - this.skGen = random_scalar; - - this.create_address = function(seed, nettype) { - var keys = {}; - // updated by Luigi and PS to support reduced and non-reduced seeds - var first; - if (seed.length !== 64) { - first = this.cn_fast_hash(seed); - } else { - first = this.sc_reduce32(seed); - } - keys.spend = this.generate_keys(first); - var second = this.cn_fast_hash(first); - keys.view = this.generate_keys(second); - keys.public_addr = this.pubkeys_to_string( - keys.spend.pub, - keys.view.pub, - nettype, - ); - return keys; - }; - - this.create_addr_prefix = function(seed, nettype) { - var first; - if (seed.length !== 64) { - first = this.cn_fast_hash(seed); - } else { - first = seed; - } - var spend = this.generate_keys(first); - var prefix = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForStandardAddressOn(nettype), - ); - return cnBase58.encode(prefix + spend.pub).slice(0, 44); - }; - - this.decode_address = function(address, nettype) { - var dec = cnBase58.decode(address); - var expectedPrefix = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForStandardAddressOn(nettype), - ); - var expectedPrefixInt = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForIntegratedAddressOn(nettype), - ); - var expectedPrefixSub = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForSubAddressOn(nettype), - ); - var prefix = dec.slice(0, expectedPrefix.length); - if ( - prefix !== expectedPrefix && - prefix !== expectedPrefixInt && - prefix !== expectedPrefixSub - ) { - throw "Invalid address prefix"; - } - dec = dec.slice(expectedPrefix.length); - var spend = dec.slice(0, 64); - var view = dec.slice(64, 128); - if (prefix === expectedPrefixInt) { - var intPaymentId = dec.slice(128, 128 + INTEGRATED_ID_SIZE * 2); - var checksum = dec.slice( - 128 + INTEGRATED_ID_SIZE * 2, - 128 + INTEGRATED_ID_SIZE * 2 + ADDRESS_CHECKSUM_SIZE * 2, - ); - var expectedChecksum = this.cn_fast_hash( - prefix + spend + view + intPaymentId, - ).slice(0, ADDRESS_CHECKSUM_SIZE * 2); - } else { - var checksum = dec.slice(128, 128 + ADDRESS_CHECKSUM_SIZE * 2); - var expectedChecksum = this.cn_fast_hash( - prefix + spend + view, - ).slice(0, ADDRESS_CHECKSUM_SIZE * 2); - } - if (checksum !== expectedChecksum) { - throw "Invalid checksum"; - } - if (intPaymentId) { - return { - spend: spend, - view: view, - intPaymentId: intPaymentId, - }; - } else { - return { - spend: spend, - view: view, - }; - } - }; - - this.is_subaddress = function(addr, nettype) { - var decoded = cnBase58.decode(addr); - var subaddressPrefix = this.encode_varint( - nettype_utils.cryptonoteBase58PrefixForSubAddressOn(nettype), - ); - var prefix = decoded.slice(0, subaddressPrefix.length); - return prefix === subaddressPrefix; - }; - - this.valid_keys = function(view_pub, view_sec, spend_pub, spend_sec) { - var expected_view_pub = this.sec_key_to_pub(view_sec); - var expected_spend_pub = this.sec_key_to_pub(spend_sec); - return ( - expected_spend_pub === spend_pub && expected_view_pub === view_pub - ); - }; - - this.hash_to_scalar = function(buf) { - var hash = this.cn_fast_hash(buf); - var scalar = this.sc_reduce32(hash); - return scalar; - }; - - this.generate_key_derivation = function(pub, sec) { - if (pub.length !== 64 || sec.length !== 64) { - throw "Invalid input length"; - } - var P = this.ge_scalarmult(pub, sec); - return this.ge_scalarmult(P, d2s(8)); //mul8 to ensure group - }; - - this.derivation_to_scalar = function(derivation, output_index) { - var buf = ""; - if (derivation.length !== STRUCT_SIZES.EC_POINT * 2) { - throw "Invalid derivation length!"; - } - buf += derivation; - var enc = encode_varint(output_index); - if (enc.length > 10 * 2) { - throw "output_index didn't fit in 64-bit varint"; - } - buf += enc; - return this.hash_to_scalar(buf); - }; - - this.derive_secret_key = function(derivation, out_index, sec) { - if (derivation.length !== 64 || sec.length !== 64) { - throw "Invalid input length!"; - } - var scalar_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - var scalar_b = hextobin( - this.derivation_to_scalar(derivation, out_index), - ); - CNCrypto.HEAPU8.set(scalar_b, scalar_m); - var base_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hextobin(sec), base_m); - var derived_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - CNCrypto.ccall( - "sc_add", - "void", - ["number", "number", "number"], - [derived_m, base_m, scalar_m], - ); - var res = CNCrypto.HEAPU8.subarray( - derived_m, - derived_m + STRUCT_SIZES.EC_SCALAR, - ); - CNCrypto._free(scalar_m); - CNCrypto._free(base_m); - CNCrypto._free(derived_m); - return bintohex(res); - }; - - this.derive_public_key = function(derivation, out_index, pub) { - if (derivation.length !== 64 || pub.length !== 64) { - throw "Invalid input length!"; - } - var s = this.derivation_to_scalar(derivation, out_index); - return bintohex( - nacl.ge_add(hextobin(pub), hextobin(this.ge_scalarmult_base(s))), - ); - }; - - // D' = P - Hs(aR|i)G - this.derive_subaddress_public_key = function( - output_key, - derivation, - out_index, - ) { - if (output_key.length !== 64 || derivation.length !== 64) { - throw "Invalid input length!"; - } - var scalar = this.derivation_to_scalar(derivation, out_index); - var point = this.ge_scalarmult_base(scalar); - return this.ge_sub(output_key, point); - }; - - this.hash_to_ec = function(key) { - if (key.length !== KEY_SIZE * 2) { - throw "Invalid input length"; - } - var h_m = CNCrypto._malloc(HASH_SIZE); - var point_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); - var point2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P1P1); - var res_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); - var hash = hextobin(this.cn_fast_hash(key, KEY_SIZE)); - CNCrypto.HEAPU8.set(hash, h_m); - CNCrypto.ccall( - "ge_fromfe_frombytes_vartime", - "void", - ["number", "number"], - [point_m, h_m], - ); - CNCrypto.ccall( - "ge_mul8", - "void", - ["number", "number"], - [point2_m, point_m], - ); - CNCrypto.ccall( - "ge_p1p1_to_p3", - "void", - ["number", "number"], - [res_m, point2_m], - ); - var res = CNCrypto.HEAPU8.subarray(res_m, res_m + STRUCT_SIZES.GE_P3); - CNCrypto._free(h_m); - CNCrypto._free(point_m); - CNCrypto._free(point2_m); - CNCrypto._free(res_m); - return bintohex(res); - }; - - //returns a 32 byte point via "ge_p3_tobytes" rather than a 160 byte "p3", otherwise same as above; - this.hash_to_ec_2 = function(key) { - if (key.length !== KEY_SIZE * 2) { - throw "Invalid input length"; - } - var h_m = CNCrypto._malloc(HASH_SIZE); - var point_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); - var point2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P1P1); - var res_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); - var hash = hextobin(this.cn_fast_hash(key, KEY_SIZE)); - var res2_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hash, h_m); - CNCrypto.ccall( - "ge_fromfe_frombytes_vartime", - "void", - ["number", "number"], - [point_m, h_m], - ); - CNCrypto.ccall( - "ge_mul8", - "void", - ["number", "number"], - [point2_m, point_m], - ); - CNCrypto.ccall( - "ge_p1p1_to_p3", - "void", - ["number", "number"], - [res_m, point2_m], - ); - CNCrypto.ccall( - "ge_p3_tobytes", - "void", - ["number", "number"], - [res2_m, res_m], - ); - var res = CNCrypto.HEAPU8.subarray(res2_m, res2_m + KEY_SIZE); - CNCrypto._free(h_m); - CNCrypto._free(point_m); - CNCrypto._free(point2_m); - CNCrypto._free(res_m); - CNCrypto._free(res2_m); - return bintohex(res); - }; - this.hashToPoint = hash_to_ec_2; - - this.generate_key_image_2 = function(pub, sec) { - if (!pub || !sec || pub.length !== 64 || sec.length !== 64) { - throw "Invalid input length"; - } - var pub_m = CNCrypto._malloc(KEY_SIZE); - var sec_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hextobin(pub), pub_m); - CNCrypto.HEAPU8.set(hextobin(sec), sec_m); - if (CNCrypto.ccall("sc_check", "number", ["number"], [sec_m]) !== 0) { - throw "sc_check(sec) != 0"; - } - var point_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); - var point2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); - var point_b = hextobin(this.hash_to_ec(pub)); - CNCrypto.HEAPU8.set(point_b, point_m); - var image_m = CNCrypto._malloc(STRUCT_SIZES.KEY_IMAGE); - CNCrypto.ccall( - "ge_scalarmult", - "void", - ["number", "number", "number"], - [point2_m, sec_m, point_m], - ); - CNCrypto.ccall( - "ge_tobytes", - "void", - ["number", "number"], - [image_m, point2_m], - ); - var res = CNCrypto.HEAPU8.subarray( - image_m, - image_m + STRUCT_SIZES.KEY_IMAGE, - ); - CNCrypto._free(pub_m); - CNCrypto._free(sec_m); - CNCrypto._free(point_m); - CNCrypto._free(point2_m); - CNCrypto._free(image_m); - return bintohex(res); - }; - - this.generate_key_image = function( - tx_pub, - view_sec, - spend_pub, - spend_sec, - output_index, - ) { - if (tx_pub.length !== 64) { - throw "Invalid tx_pub length"; - } - if (view_sec.length !== 64) { - throw "Invalid view_sec length"; - } - if (spend_pub.length !== 64) { - throw "Invalid spend_pub length"; - } - if (spend_sec.length !== 64) { - throw "Invalid spend_sec length"; - } - var recv_derivation = this.generate_key_derivation(tx_pub, view_sec); - var ephemeral_pub = this.derive_public_key( - recv_derivation, - output_index, - spend_pub, - ); - var ephemeral_sec = this.derive_secret_key( - recv_derivation, - output_index, - spend_sec, - ); - var k_image = this.generate_key_image_2(ephemeral_pub, ephemeral_sec); - return { - ephemeral_pub: ephemeral_pub, - key_image: k_image, - }; - }; - - this.generate_key_image_helper_rct = function( - keys, - tx_pub_key, - out_index, - enc_mask, - ) { - var recv_derivation = this.generate_key_derivation( - tx_pub_key, - keys.view.sec, - ); - if (!recv_derivation) throw "Failed to generate key image"; - var mask = enc_mask - ? sc_sub( - enc_mask, - hash_to_scalar( - derivation_to_scalar(recv_derivation, out_index), - ), - ) - : I; //decode mask, or d2s(1) if no mask - var ephemeral_pub = this.derive_public_key( - recv_derivation, - out_index, - keys.spend.pub, - ); - if (!ephemeral_pub) throw "Failed to generate key image"; - var ephemeral_sec = this.derive_secret_key( - recv_derivation, - out_index, - keys.spend.sec, - ); - var image = this.generate_key_image_2(ephemeral_pub, ephemeral_sec); - return { - in_ephemeral: { - pub: ephemeral_pub, - sec: ephemeral_sec, - mask: mask, - }, - image: image, - }; - }; - - //curve and scalar functions; split out to make their host functions cleaner and more readable - //inverts X coordinate -- this seems correct ^_^ -luigi1111 - this.ge_neg = function(point) { - if (point.length !== 64) { - throw "expected 64 char hex string"; - } - return ( - point.slice(0, 62) + - ((parseInt(point.slice(62, 63), 16) + 8) % 16).toString(16) + - point.slice(63, 64) - ); - }; - - this.ge_add = function(p1, p2) { - if (p1.length !== 64 || p2.length !== 64) { - throw "Invalid input length!"; - } - return bintohex(nacl.ge_add(hextobin(p1), hextobin(p2))); - }; - - //order matters - this.ge_sub = function(point1, point2) { - point2n = ge_neg(point2); - return ge_add(point1, point2n); - }; - - //adds two scalars together - this.sc_add = function(scalar1, scalar2) { - if (scalar1.length !== 64 || scalar2.length !== 64) { - throw "Invalid input length!"; - } - var scalar1_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - var scalar2_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - CNCrypto.HEAPU8.set(hextobin(scalar1), scalar1_m); - CNCrypto.HEAPU8.set(hextobin(scalar2), scalar2_m); - var derived_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - CNCrypto.ccall( - "sc_add", - "void", - ["number", "number", "number"], - [derived_m, scalar1_m, scalar2_m], - ); - var res = CNCrypto.HEAPU8.subarray( - derived_m, - derived_m + STRUCT_SIZES.EC_SCALAR, - ); - CNCrypto._free(scalar1_m); - CNCrypto._free(scalar2_m); - CNCrypto._free(derived_m); - return bintohex(res); - }; - - //subtracts one scalar from another - this.sc_sub = function(scalar1, scalar2) { - if (scalar1.length !== 64 || scalar2.length !== 64) { - throw "Invalid input length!"; - } - var scalar1_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - var scalar2_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - CNCrypto.HEAPU8.set(hextobin(scalar1), scalar1_m); - CNCrypto.HEAPU8.set(hextobin(scalar2), scalar2_m); - var derived_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - CNCrypto.ccall( - "sc_sub", - "void", - ["number", "number", "number"], - [derived_m, scalar1_m, scalar2_m], - ); - var res = CNCrypto.HEAPU8.subarray( - derived_m, - derived_m + STRUCT_SIZES.EC_SCALAR, - ); - CNCrypto._free(scalar1_m); - CNCrypto._free(scalar2_m); - CNCrypto._free(derived_m); - return bintohex(res); - }; - - //fun mul function - this.sc_mul = function(scalar1, scalar2) { - if (scalar1.length !== 64 || scalar2.length !== 64) { - throw "Invalid input length!"; - } - return d2s( - JSBigInt(s2d(scalar1)) - .multiply(JSBigInt(s2d(scalar2))) - .remainder(l) - .toString(), - ); - }; - - //res = c - (ab) mod l; argument names copied from the signature implementation - this.sc_mulsub = function(sigc, sec, k) { - if ( - k.length !== KEY_SIZE * 2 || - sigc.length !== KEY_SIZE * 2 || - sec.length !== KEY_SIZE * 2 || - !this.valid_hex(k) || - !this.valid_hex(sigc) || - !this.valid_hex(sec) - ) { - throw "bad scalar"; - } - var sec_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hextobin(sec), sec_m); - var sigc_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hextobin(sigc), sigc_m); - var k_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hextobin(k), k_m); - var res_m = CNCrypto._malloc(KEY_SIZE); - - CNCrypto.ccall( - "sc_mulsub", - "void", - ["number", "number", "number", "number"], - [res_m, sigc_m, sec_m, k_m], - ); - res = CNCrypto.HEAPU8.subarray(res_m, res_m + KEY_SIZE); - CNCrypto._free(k_m); - CNCrypto._free(sec_m); - CNCrypto._free(sigc_m); - CNCrypto._free(res_m); - return bintohex(res); - }; - - this.ge_double_scalarmult_base_vartime = function(c, P, r) { - if (c.length !== 64 || P.length !== 64 || r.length !== 64) { - throw "Invalid input length!"; - } - return bintohex( - nacl.ge_double_scalarmult_base_vartime( - hextobin(c), - hextobin(P), - hextobin(r), - ), - ); - }; - - this.ge_double_scalarmult_postcomp_vartime = function(r, P, c, I) { - if ( - c.length !== 64 || - P.length !== 64 || - r.length !== 64 || - I.length !== 64 - ) { - throw "Invalid input length!"; - } - var Pb = this.hash_to_ec_2(P); - return bintohex( - nacl.ge_double_scalarmult_postcomp_vartime( - hextobin(r), - hextobin(Pb), - hextobin(c), - hextobin(I), - ), - ); - }; - - //begin RCT functions - - //xv: vector of secret keys, 1 per ring (nrings) - //pm: matrix of pubkeys, indexed by size first - //iv: vector of indexes, 1 per ring (nrings), can be a string - //size: ring size, default 2 - //nrings: number of rings, default 64 - //extensible borromean signatures - this.genBorromean = function(xv, pm, iv, size, nrings) { - if (xv.length !== nrings) { - throw "wrong xv length " + xv.length; - } - if (pm.length !== size) { - throw "wrong pm size " + pm.length; - } - for (var i = 0; i < pm.length; i++) { - if (pm[i].length !== nrings) { - throw "wrong pm[" + i + "] length " + pm[i].length; - } - } - if (iv.length !== nrings) { - throw "wrong iv length " + iv.length; - } - for (var i = 0; i < iv.length; i++) { - if (iv[i] >= size) { - throw "bad indices value at: " + i + ": " + iv[i]; - } - } - //signature struct - // in the case of size 2 and nrings 64 - // bb.s = [[64], [64]] - var bb = { - s: [], - ee: "", - }; - //signature pubkey matrix - var L = []; - //add needed sub vectors (1 per ring size) - for (var i = 0; i < size; i++) { - bb.s[i] = []; - L[i] = []; - } - //compute starting at the secret index to the last row - var index; - var alpha = []; - for (var i = 0; i < nrings; i++) { - index = parseInt(iv[i]); - alpha[i] = random_scalar(); - L[index][i] = ge_scalarmult_base(alpha[i]); - for (var j = index + 1; j < size; j++) { - bb.s[j][i] = random_scalar(); - var c = hash_to_scalar(L[j - 1][i]); - L[j][i] = ge_double_scalarmult_base_vartime( - c, - pm[j][i], - bb.s[j][i], - ); - } - } - //hash last row to create ee - var ltemp = ""; - for (var i = 0; i < nrings; i++) { - ltemp += L[size - 1][i]; - } - bb.ee = hash_to_scalar(ltemp); - //compute the rest from 0 to secret index - for (var i = 0; i < nrings; i++) { - var cc = bb.ee; - for (var j = 0; j < iv[i]; j++) { - bb.s[j][i] = random_scalar(); - var LL = ge_double_scalarmult_base_vartime( - cc, - pm[j][i], - bb.s[j][i], - ); - cc = hash_to_scalar(LL); - } - bb.s[j][i] = sc_mulsub(xv[i], cc, alpha[i]); - } - return bb; - }; - - this.verifyBorromean = function(bb, P1, P2) { - let Lv1 = []; - let chash; - let LL; - - let p2 = ""; - for (let ii = 0; ii < 64; ii++) { - p2 = this.ge_double_scalarmult_base_vartime( - bb.ee, - P1[ii], - bb.s[0][ii], - ); - LL = p2; - chash = this.hash_to_scalar(LL); - - p2 = this.ge_double_scalarmult_base_vartime( - chash, - P2[ii], - bb.s[1][ii], - ); - Lv1[ii] = p2; - } - const eeComputed = this.array_hash_to_scalar(Lv1); - const equalKeys = eeComputed === bb.ee; - console.log(`[verifyBorromean] Keys equal? ${equalKeys} - ${eeComputed} - ${bb.ee}`); - - return equalKeys; - }; - - //proveRange - //proveRange gives C, and mask such that \sumCi = C - // c.f. http://eprint.iacr.org/2015/1098 section 5.1 - // and Ci is a commitment to either 0 or s^i, i=0,...,n - // thus this proves that "amount" is in [0, s^n] (we assume s to be 4) (2 for now with v2 txes) - // mask is a such that C = aG + bH, and b = amount - //commitMaskObj = {C: commit, mask: mask} - this.proveRange = function(commitMaskObj, amount, nrings) { - var size = 2; - var C = I; //identity - var mask = Z; //zero scalar - var indices = d2b(amount); //base 2 for now - var sig = { - Ci: [], - //exp: exponent //doesn't exist for now - }; - - var ai = []; - var PM = []; - for (var i = 0; i < size; i++) { - PM[i] = []; - } - var j; - //start at index and fill PM left and right -- PM[0] holds Ci - for (var i = 0; i < nrings; i++) { - ai[i] = random_scalar(); - j = indices[i]; - PM[j][i] = ge_scalarmult_base(ai[i]); - while (j > 0) { - j--; - PM[j][i] = ge_add(PM[j + 1][i], H2[i]); //will need to use i*2 for base 4 (or different object) - } - j = indices[i]; - while (j < size - 1) { - j++; - PM[j][i] = ge_sub(PM[j - 1][i], H2[i]); //will need to use i*2 for base 4 (or different object) - } - mask = sc_add(mask, ai[i]); - } - /* - * some more payload stuff here - */ - //copy commitments to sig and sum them to commitment - for (var i = 0; i < nrings; i++) { - //if (i < nrings - 1) //for later version - sig.Ci[i] = PM[0][i]; - C = ge_add(C, PM[0][i]); - } - - sig.bsig = this.genBorromean(ai, PM, indices, size, nrings); - commitMaskObj.C = C; - commitMaskObj.mask = mask; - return sig; - }; - - //proveRange and verRange - //proveRange gives C, and mask such that \sumCi = C - // c.f. http://eprint.iacr.org/2015/1098 section 5.1 - // and Ci is a commitment to either 0 or 2^i, i=0,...,63 - // thus this proves that "amount" is in [0, 2^64] - // mask is a such that C = aG + bH, and b = amount - //verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i - - this.verRange = function(C, as, nrings = 64) { - try { - let CiH = []; // len 64 - let asCi = []; // len 64 - let Ctmp = this.identity(); - for (let i = 0; i < nrings; i++) { - CiH[i] = this.ge_sub(as.Ci[i], this.H2[i]); - asCi[i] = as.Ci[i]; - Ctmp = this.ge_add(Ctmp, as.Ci[i]); - } - const equalKeys = Ctmp === C; - console.log(`[verRange] Equal keys? ${equalKeys} - C: ${C} - Ctmp: ${Ctmp}`); - if (!equalKeys) { - return false; - } - - if (!this.verifyBorromean(as.bsig, asCi, CiH)) { - return false; - } - - return true; - } catch (e) { - console.error(`[verRange]`, e); - return false; - } - }; - - function array_hash_to_scalar(array) { - var buf = ""; - for (var i = 0; i < array.length; i++) { - if (typeof array[i] !== "string") { - throw "unexpected array element"; - } - buf += array[i]; - } - return hash_to_scalar(buf); - } - this.array_hash_to_scalar = array_hash_to_scalar; - - // Gen creates a signature which proves that for some column in the keymatrix "pk" - // the signer knows a secret key for each row in that column - // we presently only support matrices of 2 rows (pubkey, commitment) - // this is a simplied MLSAG_Gen function to reflect that - // because we don't want to force same secret column for all inputs - this.MLSAG_Gen = function(message, pk, xx, kimg, index) { - var cols = pk.length; //ring size - var i; - - // secret index - if (index >= cols) { - throw "index out of range"; - } - var rows = pk[0].length; //number of signature rows (always 2) - // [pub, com] = 2 - if (rows !== 2) { - throw "wrong row count"; - } - // check all are len 2 - for (i = 0; i < cols; i++) { - if (pk[i].length !== rows) { - throw "pk is not rectangular"; - } - } - if (xx.length !== rows) { - throw "bad xx size"; - } - - var c_old = ""; - var alpha = []; - - var rv = { - ss: [], - cc: null, - }; - for (i = 0; i < cols; i++) { - rv.ss[i] = []; - } - var toHash = []; //holds 6 elements: message, pubkey, dsRow L, dsRow R, commitment, ndsRow L - toHash[0] = message; - - //secret index (pubkey section) - - alpha[0] = random_scalar(); //need to save alphas for later - toHash[1] = pk[index][0]; //secret index pubkey - - // this is the keyimg anyway const H1 = this.hashToPoint(pk[index][0]) // Hp(K_in) - // rv.II[0] = this.ge_scalarmult(H1, xx[0]) // k_in.Hp(K_in) - - toHash[2] = ge_scalarmult_base(alpha[0]); //dsRow L, a.G - toHash[3] = generate_key_image_2(pk[index][0], alpha[0]); //dsRow R (key image check) - //secret index (commitment section) - alpha[1] = random_scalar(); - toHash[4] = pk[index][1]; //secret index commitment - toHash[5] = ge_scalarmult_base(alpha[1]); //ndsRow L - - c_old = array_hash_to_scalar(toHash); - - i = (index + 1) % cols; - if (i === 0) { - rv.cc = c_old; - } - while (i != index) { - rv.ss[i][0] = random_scalar(); //dsRow ss - rv.ss[i][1] = random_scalar(); //ndsRow ss - - //!secret index (pubkey section) - toHash[1] = pk[i][0]; - toHash[2] = ge_double_scalarmult_base_vartime( - c_old, - pk[i][0], - rv.ss[i][0], - ); - toHash[3] = ge_double_scalarmult_postcomp_vartime( - rv.ss[i][0], - pk[i][0], - c_old, - kimg, - ); - //!secret index (commitment section) - toHash[4] = pk[i][1]; - toHash[5] = ge_double_scalarmult_base_vartime( - c_old, - pk[i][1], - rv.ss[i][1], - ); - c_old = array_hash_to_scalar(toHash); //hash to get next column c - i = (i + 1) % cols; - if (i === 0) { - rv.cc = c_old; - } - } - for (i = 0; i < rows; i++) { - rv.ss[index][i] = sc_mulsub(c_old, xx[i], alpha[i]); - } - return rv; - }; - - this.MLSAG_ver = function(message, pk, rv, kimg) { - // we assume that col, row, rectangular checks are already done correctly - // in MLSAG_gen - const cols = pk.length; - let c_old = rv.cc; - let i = 0; - let toHash = []; - toHash[0] = message; - while (i < cols) { - //!secret index (pubkey section) - toHash[1] = pk[i][0]; - toHash[2] = ge_double_scalarmult_base_vartime( - c_old, - pk[i][0], - rv.ss[i][0], - ); - toHash[3] = ge_double_scalarmult_postcomp_vartime( - rv.ss[i][0], - pk[i][0], - c_old, - kimg, - ); - - //!secret index (commitment section) - toHash[4] = pk[i][1]; - toHash[5] = ge_double_scalarmult_base_vartime( - c_old, - pk[i][1], - rv.ss[i][1], - ); - - c_old = array_hash_to_scalar(toHash); - - i = i + 1; - } - - const c = this.sc_sub(c_old, rv.cc); - console.log(`[MLSAG_ver] - c_old: ${c_old} - rc.cc: ${rv.cc} - c: ${c}`); - - return Number(c) === 0; - }; - - //Ring-ct MG sigs - //Prove: - // c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. - // This does the MG sig on the "dest" part of the given key matrix, and - // the last row is the sum of input commitments from that column - sum output commitments - // this shows that sum inputs = sum outputs - //Ver: - // verifies the above sig is created corretly - this.proveRctMG = function(message, pubs, inSk, kimg, mask, Cout, index) { - var cols = pubs.length; - if (cols < 3) { - throw "cols must be > 2 (mixin)"; - } - var xx = []; - var PK = []; - //fill pubkey matrix (copy destination, subtract commitments) - for (var i = 0; i < cols; i++) { - PK[i] = []; - PK[i][0] = pubs[i].dest; - PK[i][1] = ge_sub(pubs[i].mask, Cout); - } - xx[0] = inSk.x; - xx[1] = sc_sub(inSk.a, mask); - return this.MLSAG_Gen(message, PK, xx, kimg, index); - }; - - //Ring-ct MG sigs - //Prove: - // c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. - // This does the MG sig on the "dest" part of the given key matrix, and - // the last row is the sum of input commitments from that column - sum output commitments - // this shows that sum inputs = sum outputs - //Ver: - // verifies the above sig is created corretly - - this.verRctMG = function(mg, pubs, outPk, txnFeeKey, message, kimg) { - const cols = pubs.length; - if (cols < 1) { - throw Error("Empty pubs"); - } - const rows = pubs[0].length; - if (rows < 1) { - throw Error("Empty pubs"); - } - for (let i = 0; i < cols.length; ++i) { - if (pubs[i].length !== rows) { - throw Error("Pubs is not rectangular"); - } - } - - // key matrix of (cols, tmp) - - let M = []; - console.log(pubs); - //create the matrix to mg sig - for (let i = 0; i < rows; i++) { - M[i] = []; - M[i][0] = pubs[0][i].dest; - M[i][1] = this.ge_add(M[i][1] || this.identity(), pubs[0][i].mask); // start with input commitment - for (let j = 0; j < outPk.length; j++) { - M[i][1] = this.ge_sub(M[i][1], outPk[j]); // subtract all output commitments - } - M[i][1] = this.ge_sub(M[i][1], txnFeeKey); // subtract txnfee - } - - console.log( - `[MLSAG_ver input]`, - JSON.stringify({ message, M, mg, kimg }, null, 1), - ); - return this.MLSAG_ver(message, M, mg, kimg); - }; - - // simple version, assuming only post Rct - - this.verRctMGSimple = function(message, mg, pubs, C, kimg) { - try { - const rows = 1; - const cols = pubs.len; - const M = []; - - for (let i = 0; i < cols; i++) { - M[i][0] = pubs[i].dest; - M[i][1] = this.ge_sub(pubs[i].mask, C); - } - - return MLSAG_ver(message, M, mg, kimg); - } catch (error) { - console.error("[verRctSimple]", error); - return false; - } - }; - - this.verBulletProof = function() { - throw Error("verBulletProof is not implemented"); - }; - - this.get_pre_mlsag_hash = function(rv) { - var hashes = ""; - hashes += rv.message; - hashes += this.cn_fast_hash(this.serialize_rct_base(rv)); - var buf = serialize_range_proofs(rv); - hashes += this.cn_fast_hash(buf); - return this.cn_fast_hash(hashes); - }; - - function serialize_range_proofs(rv) { - var buf = ""; - for (var i = 0; i < rv.p.rangeSigs.length; i++) { - for (var j = 0; j < rv.p.rangeSigs[i].bsig.s.length; j++) { - for (var l = 0; l < rv.p.rangeSigs[i].bsig.s[j].length; l++) { - buf += rv.p.rangeSigs[i].bsig.s[j][l]; - } - } - buf += rv.p.rangeSigs[i].bsig.ee; - for (j = 0; j < rv.p.rangeSigs[i].Ci.length; j++) { - buf += rv.p.rangeSigs[i].Ci[j]; - } - } - return buf; - } - - //message is normal prefix hash - //inSk is vector of x,a - //kimg is vector of kimg - //destinations is vector of pubkeys (we skip and proxy outAmounts instead) - //inAmounts is vector of strings - //outAmounts is vector of strings - //mixRing is matrix of pubkey, commit (dest, mask) - //amountKeys is vector of scalars - //indices is vector - //txnFee is string, with its endian not swapped (e.g d2s is not called before passing it in as an argument) - //to this function - this.genRct = function( - message, - inSk, - kimg, - inAmounts, - outAmounts, - mixRing, - amountKeys, - indices, - txnFee, - ) { - if (outAmounts.length !== amountKeys.length) { - throw "different number of amounts/amount_keys"; - } - for (var i = 0; i < mixRing.length; i++) { - if (mixRing[i].length <= indices[i]) { - throw "bad mixRing/index size"; - } - } - if (mixRing.length !== inSk.length) { - throw "mismatched mixRing/inSk"; - } - if (inAmounts.length !== inSk.length) { - throw "mismatched inAmounts/inSk"; - } - if (indices.length !== inSk.length) { - throw "mismatched indices/inSk"; - } - - rv = { - type: inSk.length === 1 ? RCTTypeFull : RCTTypeSimple, - message: message, - outPk: [], - p: { - rangeSigs: [], - MGs: [], - }, - ecdhInfo: [], - txnFee: txnFee.toString(), - pseudoOuts: [], - }; - - var sumout = Z; - var cmObj = { - C: null, - mask: null, - }; - var nrings = 64; //for base 2/current - var i; - //compute range proofs, etc - for (i = 0; i < outAmounts.length; i++) { - var teststart = new Date().getTime(); - rv.p.rangeSigs[i] = this.proveRange( - cmObj, - outAmounts[i], - nrings, - 0, - 0, - ); - var testfinish = new Date().getTime() - teststart; - console.log("Time take for range proof " + i + ": " + testfinish); - rv.outPk[i] = cmObj.C; - sumout = sc_add(sumout, cmObj.mask); - rv.ecdhInfo[i] = this.encode_rct_ecdh( - { mask: cmObj.mask, amount: d2s(outAmounts[i]) }, - amountKeys[i], - ); - } - - //simple - if (rv.type === 2) { - var ai = []; - var sumpouts = Z; - //create pseudoOuts - for (i = 0; i < inAmounts.length - 1; i++) { - ai[i] = random_scalar(); - sumpouts = sc_add(sumpouts, ai[i]); - rv.pseudoOuts[i] = commit(d2s(inAmounts[i]), ai[i]); - } - ai[i] = sc_sub(sumout, sumpouts); - rv.pseudoOuts[i] = commit(d2s(inAmounts[i]), ai[i]); - var full_message = this.get_pre_mlsag_hash(rv); - for (i = 0; i < inAmounts.length; i++) { - rv.p.MGs.push( - this.proveRctMG( - full_message, - mixRing[i], - inSk[i], - kimg[i], - ai[i], - rv.pseudoOuts[i], - indices[i], - ), - ); - } - } else { - var sumC = I; - //get sum of output commitments to use in MLSAG - for (i = 0; i < rv.outPk.length; i++) { - sumC = ge_add(sumC, rv.outPk[i]); - } - sumC = ge_add(sumC, ge_scalarmult(H, d2s(rv.txnFee))); - var full_message = this.get_pre_mlsag_hash(rv); - rv.p.MGs.push( - this.proveRctMG( - full_message, - mixRing[0], - inSk[0], - kimg[0], - sumout, - sumC, - indices[0], - ), - ); - } - return rv; - }; - - this.verRct = function(rv, semantics, mixRing, kimg) { - if (rv.type === 0x03) { - throw Error("Bulletproof validation not implemented"); - } - - // where RCTTypeFull is 0x01 and RCTTypeFullBulletproof is 0x03 - if (rv.type !== 0x01 && rv.type !== 0x03) { - throw Error("verRct called on non-full rctSig"); - } - if (semantics) { - //RCTTypeFullBulletproof checks not implemented - // RCTTypeFull checks - if (rv.outPk.length !== rv.p.rangeSigs.length) { - throw Error("Mismatched sizes of outPk and rv.p.rangeSigs"); - } - if (rv.outPk.length !== rv.ecdhInfo.length) { - throw Error("Mismatched sizes of outPk and rv.ecdhInfo"); - } - if (rv.p.MGs.length !== 1) { - throw Error("full rctSig has not one MG"); - } - } else { - // semantics check is early, we don't have the MGs resolved yet - } - try { - if (semantics) { - const results = []; - for (let i = 0; i < rv.outPk.length; i++) { - // might want to parallelize this like its done in the c++ codebase - // via some abstraction library to support browser + node - if (rv.p.rangeSigs.length === 0) { - results[i] = this.verBulletproof(rv.p.bulletproofs[i]); - } else { - // mask -> C if public - results[i] = this.verRange( - rv.outPk[i], - rv.p.rangeSigs[i], - ); - } - } - - for (let i = 0; i < rv.outPk.length; i++) { - if (!results[i]) { - console.error( - "Range proof verification failed for output", - i, - ); - return false; - } - } - } else { - // compute txn fee - const txnFeeKey = this.ge_scalarmult(H, this.d2s(rv.txnFee)); - const mgVerd = this.verRctMG( - rv.p.MGs[0], - mixRing, - rv.outPk, - txnFeeKey, - this.get_pre_mlsag_hash(rv), - kimg, - ); - console.log("mg sig verified?", mgVerd); - if (!mgVerd) { - console.error("MG Signature verification failed"); - return false; - } - } - return true; - } catch (e) { - console.error("Error in verRct: ", e); - return false; - } - }; - //ver RingCT simple - //assumes only post-rct style inputs (at least for max anonymity) - this.verRctSimple = function(rv, semantics, mixRing, kimgs) { - try { - if (rv.type === 0x04) { - throw Error("Simple Bulletproof validation not implemented"); - } - - if (rv.type !== 0x02 && rv.type !== 0x04) { - throw Error("verRctSimple called on non simple rctSig"); - } - - if (semantics) { - if (rv.type == 0x04) { - throw Error( - "Simple Bulletproof validation not implemented", - ); - } else { - if (rv.outPk.length !== rv.p.rangeSigs.length) { - throw Error( - "Mismatched sizes of outPk and rv.p.rangeSigs", - ); - } - if (rv.pseudoOuts.length !== rv.p.MGs.length) { - throw Error( - "Mismatched sizes of rv.pseudoOuts and rv.p.MGs", - ); - } - // originally the check is rv.p.pseudoOuts.length, but this'll throw - // until p.pseudoOuts is added as a property to the rv object - if (rv.p.pseudoOuts) { - throw Error("rv.p.pseudoOuts must be empty"); - } - } - } else { - if (rv.type === 0x04) { - throw Error( - "Simple Bulletproof validation not implemented", - ); - } else { - // semantics check is early, and mixRing/MGs aren't resolved yet - if (rv.pseudoOuts.length !== mixRing.length) { - throw Error( - "Mismatched sizes of rv.pseudoOuts and mixRing", - ); - } - } - } - - // if bulletproof, then use rv.p.pseudoOuts, otherwise use rv.pseudoOuts - const pseudoOuts = - rv.type === 0x04 ? rv.p.pseudoOuts : rv.pseudoOuts; - - if (semantics) { - let sumOutpks = this.identity(); - for (let i = 0; i < rv.outPk.length; i++) { - sumOutpks = this.ge_add(sumOutpks, rv.outPk[i]); // add all of the output commitments - } - - const txnFeeKey = this.ge_scalarmult( - this.H, - this.d2s(rv.txnFee), - ); - sumOutpks = this.ge_add(txnFeeKey, sumOutpks); // add txnfeekey - - let sumPseudoOuts = this.identity(); - for (let i = 0; i < pseudoOuts.length; i++) { - sumPseudoOuts = this.ge_add(sumPseudoOuts, pseudoOuts[i]); // sum up all of the pseudoOuts - } - - if (sumOutpks !== sumPseudoOuts) { - console.error("Sum check failed"); - return false; - } - - const results = []; - for (let i = 0; i < rv.outPk.length; i++) { - // might want to parallelize this like its done in the c++ codebase - // via some abstraction library to support browser + node - if (rv.p.rangeSigs.length === 0) { - results[i] = this.verBulletproof(rv.p.bulletproofs[i]); - } else { - // mask -> C if public - results[i] = this.verRange( - rv.outPk[i], - rv.p.rangeSigs[i], - ); - } - } - - for (let i = 0; i < results.length; i++) { - if (!results[i]) { - console.error( - "Range proof verification failed for output", - i, - ); - return false; - } - } - } else { - const message = this.get_pre_mlsag_hash(rv); - const results = []; - for (let i = 0; i < mixRing.length; i++) { - results[i] = this.verRctMGSimple( - message, - rv.p.MGs[i], - mixRing[i], - pseudoOuts[i], - kimgs[i], - ); - } - - for (let i = 0; i < results.length; i++) { - if (!results[i]) { - console.error( - "Range proof verification failed for output", - i, - ); - return false; - } - } - } - - return true; - } catch (error) { - console.log("[verRctSimple]", error); - return false; - } - }; - - //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) - // uses the attached ecdh info to find the amounts represented by each output commitment - // must know the destination private key to find the correct amount, else will return a random number - - this.decodeRct = function(rv, sk, i) { - // where RCTTypeFull is 0x01 and RCTTypeFullBulletproof is 0x03 - if (rv.type !== 0x01 && rv.type !== 0x03) { - throw Error("verRct called on non-full rctSig"); - } - if (i >= rv.ecdhInfo.length) { - throw Error("Bad index"); - } - if (rv.outPk.length !== rv.ecdhInfo.length) { - throw Error("Mismatched sizes of rv.outPk and rv.ecdhInfo"); - } - - // mask amount and mask - const ecdh_info = rv.ecdhInfo[i]; - const { mask, amount } = this.decode_rct_ecdh(ecdh_info, sk); - - const C = rv.outPk[i]; - const Ctmp = this.ge_double_scalarmult_base_vartime( - amount, - this.H, - mask, - ); - - console.log("[decodeRct]", C, Ctmp); - if (C !== Ctmp) { - throw Error( - "warning, amount decoded incorrectly, will be unable to spend", - ); - } - return { amount, mask }; - }; - - this.decodeRctSimple = function(rv, sk, i) { - if (rv.type !== 0x02 && rv.type !== 0x04) { - throw Error("verRct called on full rctSig"); - } - if (i >= rv.ecdhInfo.length) { - throw Error("Bad index"); - } - if (rv.outPk.length !== rv.ecdhInfo.length) { - throw Error("Mismatched sizes of rv.outPk and rv.ecdhInfo"); - } - - // mask amount and mask - const ecdh_info = rv.ecdhInfo[i]; - const { mask, amount } = this.decode_rct_ecdh(ecdh_info, sk); - - const C = rv.outPk[i]; - const Ctmp = this.ge_double_scalarmult_base_vartime( - amount, - this.H, - mask, - ); - - console.log("[decodeRctSimple]", C, Ctmp); - if (C !== Ctmp) { - throw Error( - "warning, amount decoded incorrectly, will be unable to spend", - ); - } - return { amount, mask }; - }; - - this.verBulletProof = function() { - throw Error("verBulletProof is not implemented"); - }; - //end RCT functions - - this.add_pub_key_to_extra = function(extra, pubkey) { - if (pubkey.length !== 64) throw "Invalid pubkey length"; - // Append pubkey tag and pubkey - extra += TX_EXTRA_TAGS.PUBKEY + pubkey; - return extra; - }; - - this.add_nonce_to_extra = function(extra, nonce) { - // Append extra nonce - if (nonce.length % 2 !== 0) { - throw "Invalid extra nonce"; - } - if (nonce.length / 2 > TX_EXTRA_NONCE_MAX_COUNT) { - throw "Extra nonce must be at most " + - TX_EXTRA_NONCE_MAX_COUNT + - " bytes"; - } - // Add nonce tag - extra += TX_EXTRA_TAGS.NONCE; - // Encode length of nonce - extra += ("0" + (nonce.length / 2).toString(16)).slice(-2); - // Write nonce - extra += nonce; - return extra; - }; - - this.get_payment_id_nonce = function(payment_id, pid_encrypt) { - if (payment_id.length !== 64 && payment_id.length !== 16) { - throw "Invalid payment id"; - } - var res = ""; - if (pid_encrypt) { - res += TX_EXTRA_NONCE_TAGS.ENCRYPTED_PAYMENT_ID; - } else { - res += TX_EXTRA_NONCE_TAGS.PAYMENT_ID; - } - res += payment_id; - return res; - }; - - this.abs_to_rel_offsets = function(offsets) { - if (offsets.length === 0) return offsets; - for (var i = offsets.length - 1; i >= 1; --i) { - offsets[i] = new JSBigInt(offsets[i]) - .subtract(offsets[i - 1]) - .toString(); - } - return offsets; - }; - - this.get_tx_prefix_hash = function(tx) { - var prefix = this.serialize_tx(tx, true); - return this.cn_fast_hash(prefix); - }; - - this.get_tx_hash = function(tx) { - if (typeof tx === "string") { - return this.cn_fast_hash(tx); - } else { - return this.cn_fast_hash(this.serialize_tx(tx)); - } - }; - - this.serialize_tx = function(tx, headeronly) { - //tx: { - // version: uint64, - // unlock_time: uint64, - // extra: hex, - // vin: [{amount: uint64, k_image: hex, key_offsets: [uint64,..]},...], - // vout: [{amount: uint64, target: {key: hex}},...], - // signatures: [[s,s,...],...] - //} - if (headeronly === undefined) { - headeronly = false; - } - var buf = ""; - buf += this.encode_varint(tx.version); - buf += this.encode_varint(tx.unlock_time); - buf += this.encode_varint(tx.vin.length); - var i, j; - for (i = 0; i < tx.vin.length; i++) { - var vin = tx.vin[i]; - switch (vin.type) { - case "input_to_key": - buf += "02"; - buf += this.encode_varint(vin.amount); - buf += this.encode_varint(vin.key_offsets.length); - for (j = 0; j < vin.key_offsets.length; j++) { - buf += this.encode_varint(vin.key_offsets[j]); - } - buf += vin.k_image; - break; - default: - throw "Unhandled vin type: " + vin.type; - } - } - buf += this.encode_varint(tx.vout.length); - for (i = 0; i < tx.vout.length; i++) { - var vout = tx.vout[i]; - buf += this.encode_varint(vout.amount); - switch (vout.target.type) { - case "txout_to_key": - buf += "02"; - buf += vout.target.key; - break; - default: - throw "Unhandled txout target type: " + vout.target.type; - } - } - if (!this.valid_hex(tx.extra)) { - throw "Tx extra has invalid hex"; - } - buf += this.encode_varint(tx.extra.length / 2); - buf += tx.extra; - if (!headeronly) { - if (tx.vin.length !== tx.signatures.length) { - throw "Signatures length != vin length"; - } - for (i = 0; i < tx.vin.length; i++) { - for (j = 0; j < tx.signatures[i].length; j++) { - buf += tx.signatures[i][j]; - } - } - } - return buf; - }; - - this.serialize_rct_tx_with_hash = function(tx) { - var hashes = ""; - var buf = ""; - buf += this.serialize_tx(tx, true); - hashes += this.cn_fast_hash(buf); - var buf2 = this.serialize_rct_base(tx.rct_signatures); - hashes += this.cn_fast_hash(buf2); - buf += buf2; - var buf3 = serialize_range_proofs(tx.rct_signatures); - //add MGs - for (var i = 0; i < tx.rct_signatures.p.MGs.length; i++) { - for (var j = 0; j < tx.rct_signatures.p.MGs[i].ss.length; j++) { - buf3 += tx.rct_signatures.p.MGs[i].ss[j][0]; - buf3 += tx.rct_signatures.p.MGs[i].ss[j][1]; - } - buf3 += tx.rct_signatures.p.MGs[i].cc; - } - hashes += this.cn_fast_hash(buf3); - buf += buf3; - var hash = this.cn_fast_hash(hashes); - return { - raw: buf, - hash: hash, - }; - }; - - this.serialize_rct_base = function(rv) { - var buf = ""; - buf += this.encode_varint(rv.type); - buf += this.encode_varint(rv.txnFee); - var i; - if (rv.type === 2) { - for (var i = 0; i < rv.pseudoOuts.length; i++) { - buf += rv.pseudoOuts[i]; - } - } - if (rv.ecdhInfo.length !== rv.outPk.length) { - throw "mismatched outPk/ecdhInfo!"; - } - for (i = 0; i < rv.ecdhInfo.length; i++) { - buf += rv.ecdhInfo[i].mask; - buf += rv.ecdhInfo[i].amount; - } - for (i = 0; i < rv.outPk.length; i++) { - buf += rv.outPk[i]; - } - return buf; - }; - - this.generate_ring_signature = function( - prefix_hash, - k_image, - keys, - sec, - real_index, - ) { - if (k_image.length !== STRUCT_SIZES.KEY_IMAGE * 2) { - throw "invalid key image length"; - } - if (sec.length !== KEY_SIZE * 2) { - throw "Invalid secret key length"; - } - if ( - prefix_hash.length !== HASH_SIZE * 2 || - !this.valid_hex(prefix_hash) - ) { - throw "Invalid prefix hash"; - } - if (real_index >= keys.length || real_index < 0) { - throw "real_index is invalid"; - } - var _ge_tobytes = CNCrypto.cwrap("ge_tobytes", "void", [ - "number", - "number", - ]); - var _ge_p3_tobytes = CNCrypto.cwrap("ge_p3_tobytes", "void", [ - "number", - "number", - ]); - var _ge_scalarmult_base = CNCrypto.cwrap("ge_scalarmult_base", "void", [ - "number", - "number", - ]); - var _ge_scalarmult = CNCrypto.cwrap("ge_scalarmult", "void", [ - "number", - "number", - "number", - ]); - var _sc_add = CNCrypto.cwrap("sc_add", "void", [ - "number", - "number", - "number", - ]); - var _sc_sub = CNCrypto.cwrap("sc_sub", "void", [ - "number", - "number", - "number", - ]); - var _sc_mulsub = CNCrypto.cwrap("sc_mulsub", "void", [ - "number", - "number", - "number", - "number", - ]); - var _sc_0 = CNCrypto.cwrap("sc_0", "void", ["number"]); - var _ge_double_scalarmult_base_vartime = CNCrypto.cwrap( - "ge_double_scalarmult_base_vartime", - "void", - ["number", "number", "number", "number"], - ); - var _ge_double_scalarmult_precomp_vartime = CNCrypto.cwrap( - "ge_double_scalarmult_precomp_vartime", - "void", - ["number", "number", "number", "number", "number"], - ); - var _ge_frombytes_vartime = CNCrypto.cwrap( - "ge_frombytes_vartime", - "number", - ["number", "number"], - ); - var _ge_dsm_precomp = CNCrypto.cwrap("ge_dsm_precomp", "void", [ - "number", - "number", - ]); - - var buf_size = STRUCT_SIZES.EC_POINT * 2 * keys.length; - var buf_m = CNCrypto._malloc(buf_size); - var sig_size = STRUCT_SIZES.SIGNATURE * keys.length; - var sig_m = CNCrypto._malloc(sig_size); - - // Struct pointer helper functions - function buf_a(i) { - return buf_m + STRUCT_SIZES.EC_POINT * (2 * i); - } - function buf_b(i) { - return buf_m + STRUCT_SIZES.EC_POINT * (2 * i + 1); - } - function sig_c(i) { - return sig_m + STRUCT_SIZES.EC_SCALAR * (2 * i); - } - function sig_r(i) { - return sig_m + STRUCT_SIZES.EC_SCALAR * (2 * i + 1); - } - var image_m = CNCrypto._malloc(STRUCT_SIZES.KEY_IMAGE); - CNCrypto.HEAPU8.set(hextobin(k_image), image_m); - var i; - var image_unp_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); - var image_pre_m = CNCrypto._malloc(STRUCT_SIZES.GE_DSMP); - var sum_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - var k_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - var h_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); - var tmp2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); - var tmp3_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); - var pub_m = CNCrypto._malloc(KEY_SIZE); - var sec_m = CNCrypto._malloc(KEY_SIZE); - CNCrypto.HEAPU8.set(hextobin(sec), sec_m); - if (_ge_frombytes_vartime(image_unp_m, image_m) != 0) { - throw "failed to call ge_frombytes_vartime"; - } - _ge_dsm_precomp(image_pre_m, image_unp_m); - _sc_0(sum_m); - for (i = 0; i < keys.length; i++) { - if (i === real_index) { - // Real key - var rand = this.random_scalar(); - CNCrypto.HEAPU8.set(hextobin(rand), k_m); - _ge_scalarmult_base(tmp3_m, k_m); - _ge_p3_tobytes(buf_a(i), tmp3_m); - var ec = this.hash_to_ec(keys[i]); - CNCrypto.HEAPU8.set(hextobin(ec), tmp3_m); - _ge_scalarmult(tmp2_m, k_m, tmp3_m); - _ge_tobytes(buf_b(i), tmp2_m); - } else { - CNCrypto.HEAPU8.set(hextobin(this.random_scalar()), sig_c(i)); - CNCrypto.HEAPU8.set(hextobin(this.random_scalar()), sig_r(i)); - CNCrypto.HEAPU8.set(hextobin(keys[i]), pub_m); - if ( - CNCrypto.ccall( - "ge_frombytes_vartime", - "void", - ["number", "number"], - [tmp3_m, pub_m], - ) !== 0 - ) { - throw "Failed to call ge_frombytes_vartime"; - } - _ge_double_scalarmult_base_vartime( - tmp2_m, - sig_c(i), - tmp3_m, - sig_r(i), - ); - _ge_tobytes(buf_a(i), tmp2_m); - var ec = this.hash_to_ec(keys[i]); - CNCrypto.HEAPU8.set(hextobin(ec), tmp3_m); - _ge_double_scalarmult_precomp_vartime( - tmp2_m, - sig_r(i), - tmp3_m, - sig_c(i), - image_pre_m, - ); - _ge_tobytes(buf_b(i), tmp2_m); - _sc_add(sum_m, sum_m, sig_c(i)); - } - } - var buf_bin = CNCrypto.HEAPU8.subarray(buf_m, buf_m + buf_size); - var scalar = this.hash_to_scalar(prefix_hash + bintohex(buf_bin)); - CNCrypto.HEAPU8.set(hextobin(scalar), h_m); - _sc_sub(sig_c(real_index), h_m, sum_m); - _sc_mulsub(sig_r(real_index), sig_c(real_index), sec_m, k_m); - var sig_data = bintohex( - CNCrypto.HEAPU8.subarray(sig_m, sig_m + sig_size), - ); - var sigs = []; - for (var k = 0; k < keys.length; k++) { - sigs.push( - sig_data.slice( - STRUCT_SIZES.SIGNATURE * 2 * k, - STRUCT_SIZES.SIGNATURE * 2 * (k + 1), - ), - ); - } - CNCrypto._free(image_m); - CNCrypto._free(image_unp_m); - CNCrypto._free(image_pre_m); - CNCrypto._free(sum_m); - CNCrypto._free(k_m); - CNCrypto._free(h_m); - CNCrypto._free(tmp2_m); - CNCrypto._free(tmp3_m); - CNCrypto._free(buf_m); - CNCrypto._free(sig_m); - CNCrypto._free(pub_m); - CNCrypto._free(sec_m); - return sigs; - }; - - this.construct_tx = function( - keys, - sources, - dsts, - fee_amount, - payment_id, - pid_encrypt, - realDestViewKey, - unlock_time, - rct, - nettype, - ) { - //we move payment ID stuff here, because we need txkey to encrypt - var txkey = this.random_keypair(); - console.log(txkey); - var extra = ""; - if (payment_id) { - if (pid_encrypt && payment_id.length !== INTEGRATED_ID_SIZE * 2) { - throw "payment ID must be " + - INTEGRATED_ID_SIZE + - " bytes to be encrypted!"; - } - console.log("Adding payment id: " + payment_id); - if (pid_encrypt) { - //get the derivation from our passed viewkey, then hash that + tail to get encryption key - var pid_key = this.cn_fast_hash( - this.generate_key_derivation(realDestViewKey, txkey.sec) + - ENCRYPTED_PAYMENT_ID_TAIL.toString(16), - ).slice(0, INTEGRATED_ID_SIZE * 2); - console.log("Txkeys:", txkey, "Payment ID key:", pid_key); - payment_id = this.hex_xor(payment_id, pid_key); - } - var nonce = this.get_payment_id_nonce(payment_id, pid_encrypt); - console.log("Extra nonce: " + nonce); - extra = this.add_nonce_to_extra(extra, nonce); - } - var tx = { - unlock_time: unlock_time, - version: rct ? CURRENT_TX_VERSION : OLD_TX_VERSION, - extra: extra, - vin: [], - vout: [], - }; - if (rct) { - tx.rct_signatures = {}; - } else { - tx.signatures = []; - } - - var in_contexts = []; - var inputs_money = JSBigInt.ZERO; - var i, j; - console.log("Sources: "); - //run the for loop twice to sort ins by key image - //first generate key image and other construction data to sort it all in one go - for (i = 0; i < sources.length; i++) { - console.log(i + ": " + this.formatMoneyFull(sources[i].amount)); - if (sources[i].real_out >= sources[i].outputs.length) { - throw "real index >= outputs.length"; - } - var res = this.generate_key_image_helper_rct( - keys, - sources[i].real_out_tx_key, - sources[i].real_out_in_tx, - sources[i].mask, - ); //mask will be undefined for non-rct - if ( - res.in_ephemeral.pub !== - sources[i].outputs[sources[i].real_out].key - ) { - throw "in_ephemeral.pub != source.real_out.key"; - } - sources[i].key_image = res.image; - sources[i].in_ephemeral = res.in_ephemeral; - } - //sort ins - sources.sort(function(a, b) { - return ( - JSBigInt.parse(a.key_image, 16).compare( - JSBigInt.parse(b.key_image, 16), - ) * -1 - ); - }); - //copy the sorted sources data to tx - for (i = 0; i < sources.length; i++) { - inputs_money = inputs_money.add(sources[i].amount); - in_contexts.push(sources[i].in_ephemeral); - var input_to_key = {}; - input_to_key.type = "input_to_key"; - input_to_key.amount = sources[i].amount; - input_to_key.k_image = sources[i].key_image; - input_to_key.key_offsets = []; - for (j = 0; j < sources[i].outputs.length; ++j) { - input_to_key.key_offsets.push(sources[i].outputs[j].index); - } - input_to_key.key_offsets = this.abs_to_rel_offsets( - input_to_key.key_offsets, - ); - tx.vin.push(input_to_key); - } - var outputs_money = JSBigInt.ZERO; - var out_index = 0; - var amountKeys = []; //rct only - for (i = 0; i < dsts.length; ++i) { - if (new JSBigInt(dsts[i].amount).compare(0) < 0) { - throw "dst.amount < 0"; //amount can be zero if no change - } - dsts[i].keys = this.decode_address(dsts[i].address, nettype); - - // R = rD for subaddresses - if (this.is_subaddress(dsts[i].address, nettype)) { - if (typeof payment_id !== "undefined" && payment_id) { - // this could stand to be placed earlier in the function but we save repeating a little algo time this way - throw "Payment ID must not be supplied when sending to a subaddress"; - } - txkey.pub = this.ge_scalarmult(dsts[i].keys.spend, txkey.sec); - } - - var out_derivation; - - // send change to ourselves - if (dsts[i].keys.view == keys.view.pub) { - out_derivation = this.generate_key_derivation( - txkey.pub, - keys.view.sec, - ); - } else { - out_derivation = this.generate_key_derivation( - dsts[i].keys.view, - txkey.sec, - ); - } - - if (rct) { - amountKeys.push( - this.derivation_to_scalar(out_derivation, out_index), - ); - } - var out_ephemeral_pub = this.derive_public_key( - out_derivation, - out_index, - dsts[i].keys.spend, - ); - var out = { - amount: dsts[i].amount.toString(), - }; - // txout_to_key - out.target = { - type: "txout_to_key", - key: out_ephemeral_pub, - }; - tx.vout.push(out); - ++out_index; - outputs_money = outputs_money.add(dsts[i].amount); - } - - // add pub key to extra after we know whether to use R = rG or R = rD - tx.extra = this.add_pub_key_to_extra(tx.extra, txkey.pub); - - if (outputs_money.add(fee_amount).compare(inputs_money) > 0) { - throw "outputs money (" + - this.formatMoneyFull(outputs_money) + - ") + fee (" + - this.formatMoneyFull(fee_amount) + - ") > inputs money (" + - this.formatMoneyFull(inputs_money) + - ")"; - } - if (!rct) { - for (i = 0; i < sources.length; ++i) { - var src_keys = []; - for (j = 0; j < sources[i].outputs.length; ++j) { - src_keys.push(sources[i].outputs[j].key); - } - var sigs = this.generate_ring_signature( - this.get_tx_prefix_hash(tx), - tx.vin[i].k_image, - src_keys, - in_contexts[i].sec, - sources[i].real_out, - ); - tx.signatures.push(sigs); - } - } else { - //rct - var txnFee = fee_amount; - var keyimages = []; - var inSk = []; - var inAmounts = []; - var mixRing = []; - var indices = []; - for (i = 0; i < tx.vin.length; i++) { - keyimages.push(tx.vin[i].k_image); - inSk.push({ - x: in_contexts[i].sec, - a: in_contexts[i].mask, - }); - inAmounts.push(tx.vin[i].amount); - if (in_contexts[i].mask !== I) { - //if input is rct (has a valid mask), 0 out amount - tx.vin[i].amount = "0"; - } - mixRing[i] = []; - for (j = 0; j < sources[i].outputs.length; j++) { - mixRing[i].push({ - dest: sources[i].outputs[j].key, - mask: sources[i].outputs[j].commit, - }); - } - indices.push(sources[i].real_out); - } - var outAmounts = []; - for (i = 0; i < tx.vout.length; i++) { - outAmounts.push(tx.vout[i].amount); - tx.vout[i].amount = "0"; //zero out all rct outputs - } - var tx_prefix_hash = this.get_tx_prefix_hash(tx); - tx.rct_signatures = genRct( - tx_prefix_hash, - inSk, - keyimages, - /*destinations, */ inAmounts, - outAmounts, - mixRing, - amountKeys, - indices, - txnFee, - ); - } - console.log(tx); - return tx; - }; - - this.create_transaction = function( - pub_keys, - sec_keys, - dsts, - outputs, - mix_outs, - fake_outputs_count, - fee_amount, - payment_id, - pid_encrypt, - realDestViewKey, - unlock_time, - rct, - nettype, - ) { - unlock_time = unlock_time || 0; - mix_outs = mix_outs || []; - var i, j; - if (dsts.length === 0) { - throw "Destinations empty"; - } - if (mix_outs.length !== outputs.length && fake_outputs_count !== 0) { - throw "Wrong number of mix outs provided (" + - outputs.length + - " outputs, " + - mix_outs.length + - " mix outs)"; - } - for (i = 0; i < mix_outs.length; i++) { - if ((mix_outs[i].outputs || []).length < fake_outputs_count) { - throw "Not enough outputs to mix with"; - } - } - var keys = { - view: { - pub: pub_keys.view, - sec: sec_keys.view, - }, - spend: { - pub: pub_keys.spend, - sec: sec_keys.spend, - }, - }; - if ( - !this.valid_keys( - keys.view.pub, - keys.view.sec, - keys.spend.pub, - keys.spend.sec, - ) - ) { - throw "Invalid secret keys!"; - } - var needed_money = JSBigInt.ZERO; - for (i = 0; i < dsts.length; ++i) { - needed_money = needed_money.add(dsts[i].amount); - if (needed_money.compare(UINT64_MAX) !== -1) { - throw "Output overflow!"; - } - } - var found_money = JSBigInt.ZERO; - var sources = []; - console.log("Selected transfers: ", outputs); - for (i = 0; i < outputs.length; ++i) { - found_money = found_money.add(outputs[i].amount); - if (found_money.compare(UINT64_MAX) !== -1) { - throw "Input overflow!"; - } - var src = { - outputs: [], - }; - src.amount = new JSBigInt(outputs[i].amount).toString(); - if (mix_outs.length !== 0) { - // Sort fake outputs by global index - mix_outs[i].outputs.sort(function(a, b) { - return new JSBigInt(a.global_index).compare(b.global_index); - }); - j = 0; - while ( - src.outputs.length < fake_outputs_count && - j < mix_outs[i].outputs.length - ) { - var out = mix_outs[i].outputs[j]; - if (out.global_index === outputs[i].global_index) { - console.log("got mixin the same as output, skipping"); - j++; - continue; - } - var oe = {}; - oe.index = out.global_index.toString(); - oe.key = out.public_key; - if (rct) { - if (out.rct) { - oe.commit = out.rct.slice(0, 64); //add commitment from rct mix outs - } else { - if (outputs[i].rct) { - throw "mix rct outs missing commit"; - } - oe.commit = zeroCommit(d2s(src.amount)); //create identity-masked commitment for non-rct mix input - } - } - src.outputs.push(oe); - j++; - } - } - var real_oe = {}; - real_oe.index = new JSBigInt( - outputs[i].global_index || 0, - ).toString(); - real_oe.key = outputs[i].public_key; - if (rct) { - if (outputs[i].rct) { - real_oe.commit = outputs[i].rct.slice(0, 64); //add commitment for real input - } else { - real_oe.commit = zeroCommit(d2s(src.amount)); //create identity-masked commitment for non-rct input - } - } - var real_index = src.outputs.length; - for (j = 0; j < src.outputs.length; j++) { - if ( - new JSBigInt(real_oe.index).compare(src.outputs[j].index) < - 0 - ) { - real_index = j; - break; - } - } - // Add real_oe to outputs - src.outputs.splice(real_index, 0, real_oe); - src.real_out_tx_key = outputs[i].tx_pub_key; - // Real output entry index - src.real_out = real_index; - src.real_out_in_tx = outputs[i].index; - if (rct) { - if (outputs[i].rct) { - src.mask = outputs[i].rct.slice(64, 128); //encrypted - } else { - src.mask = null; //will be set by generate_key_image_helper_rct - } - } - sources.push(src); - } - console.log("sources: ", sources); - var change = { - amount: JSBigInt.ZERO, - }; - var cmp = needed_money.compare(found_money); - if (cmp < 0) { - change.amount = found_money.subtract(needed_money); - if (change.amount.compare(fee_amount) !== 0) { - throw "early fee calculation != later"; - } - } else if (cmp > 0) { - throw "Need more money than found! (have: " + - cnUtil.formatMoney(found_money) + - " need: " + - cnUtil.formatMoney(needed_money) + - ")"; - } - return this.construct_tx( - keys, - sources, - dsts, - fee_amount, - payment_id, - pid_encrypt, - realDestViewKey, - unlock_time, - rct, - nettype, - ); - }; - - this.estimateRctSize = function(inputs, mixin, outputs) { - var size = 0; - // tx prefix - // first few bytes - size += 1 + 6; - size += inputs * (1 + 6 + (mixin + 1) * 3 + 32); // original C implementation is *2+32 but author advised to change 2 to 3 as key offsets are variable size and this constitutes a best guess - // vout - size += outputs * (6 + 32); - // extra - size += 40; - // rct signatures - // type - size += 1; - // rangeSigs - size += (2 * 64 * 32 + 32 + 64 * 32) * outputs; - // MGs - size += inputs * (32 * (mixin + 1) + 32); - // mixRing - not serialized, can be reconstructed - /* size += 2 * 32 * (mixin+1) * inputs; */ - // pseudoOuts - size += 32 * inputs; - // ecdhInfo - size += 2 * 32 * outputs; - // outPk - only commitment is saved - size += 32 * outputs; - // txnFee - size += 4; - // const logStr = `estimated rct tx size for ${inputs} at mixin ${mixin} and ${outputs} : ${size} (${((32 * inputs/*+1*/) + 2 * 32 * (mixin+1) * inputs + 32 * outputs)}) saved)` - // console.log(logStr) - - return size; - }; - - function trimRight(str, char) { - while (str[str.length - 1] == char) str = str.slice(0, -1); - return str; - } - - function padLeft(str, len, char) { - while (str.length < len) { - str = char + str; - } - return str; - } - - this.padLeft = padLeft; - - this.printDsts = function(dsts) { - for (var i = 0; i < dsts.length; i++) { - console.log( - dsts[i].address + ": " + this.formatMoneyFull(dsts[i].amount), - ); - } - }; - - this.formatMoneyFull = function(units) { - units = units.toString(); - var symbol = units[0] === "-" ? "-" : ""; - if (symbol === "-") { - units = units.slice(1); - } - var decimal; - if (units.length >= config.coinUnitPlaces) { - decimal = units.substr( - units.length - config.coinUnitPlaces, - config.coinUnitPlaces, - ); - } else { - decimal = padLeft(units, config.coinUnitPlaces, "0"); - } - return ( - symbol + - (units.substr(0, units.length - config.coinUnitPlaces) || "0") + - "." + - decimal - ); - }; - - this.formatMoneyFullSymbol = function(units) { - return this.formatMoneyFull(units) + " " + config.coinSymbol; - }; - - this.formatMoney = function(units) { - var f = trimRight(this.formatMoneyFull(units), "0"); - if (f[f.length - 1] === ".") { - return f.slice(0, f.length - 1); - } - return f; - }; - - this.formatMoneySymbol = function(units) { - return this.formatMoney(units) + " " + config.coinSymbol; - }; - - /** - * - * @param {string} str - */ - this.parseMoney = function(str) { - if (!str) return JSBigInt.ZERO; - var negative = str[0] === "-"; - if (negative) { - str = str.slice(1); - } - var decimalIndex = str.indexOf("."); - if (decimalIndex == -1) { - if (negative) { - return JSBigInt.multiply(str, config.coinUnits).negate(); - } - return JSBigInt.multiply(str, config.coinUnits); - } - if (decimalIndex + config.coinUnitPlaces + 1 < str.length) { - str = str.substr(0, decimalIndex + config.coinUnitPlaces + 1); - } - if (negative) { - return new JSBigInt(str.substr(0, decimalIndex)) - .exp10(config.coinUnitPlaces) - .add( - new JSBigInt(str.substr(decimalIndex + 1)).exp10( - decimalIndex + config.coinUnitPlaces - str.length + 1, - ), - ).negate; - } - return new JSBigInt(str.substr(0, decimalIndex)) - .exp10(config.coinUnitPlaces) - .add( - new JSBigInt(str.substr(decimalIndex + 1)).exp10( - decimalIndex + config.coinUnitPlaces - str.length + 1, - ), - ); - }; - - this.decompose_amount_into_digits = function(amount) { - amount = amount.toString(); - var ret = []; - while (amount.length > 0) { - //check so we don't create 0s - if (amount[0] !== "0") { - var digit = amount[0]; - while (digit.length < amount.length) { - digit += "0"; - } - ret.push(new JSBigInt(digit)); - } - amount = amount.slice(1); - } - return ret; - }; - - this.decompose_tx_destinations = function(dsts, rct) { - var out = []; - if (rct) { - for (var i = 0; i < dsts.length; i++) { - out.push({ - address: dsts[i].address, - amount: dsts[i].amount, - }); - } - } else { - for (var i = 0; i < dsts.length; i++) { - var digits = this.decompose_amount_into_digits(dsts[i].amount); - for (var j = 0; j < digits.length; j++) { - if (digits[j].compare(0) > 0) { - out.push({ - address: dsts[i].address, - amount: digits[j], - }); - } - } - } - } - return out.sort(function(a, b) { - return a["amount"] - b["amount"]; - }); - }; - - this.is_tx_unlocked = function(unlock_time, blockchain_height) { - if (!config.maxBlockNumber) { - throw "Max block number is not set in config!"; - } - if (unlock_time < config.maxBlockNumber) { - // unlock time is block height - return blockchain_height >= unlock_time; - } else { - // unlock time is timestamp - var current_time = Math.round(new Date().getTime() / 1000); - return current_time >= unlock_time; - } - }; - - this.tx_locked_reason = function(unlock_time, blockchain_height) { - if (unlock_time < config.maxBlockNumber) { - // unlock time is block height - var numBlocks = unlock_time - blockchain_height; - if (numBlocks <= 0) { - return "Transaction is unlocked"; - } - var unlock_prediction = moment().add( - numBlocks * config.avgBlockTime, - "seconds", - ); - return ( - "Will be unlocked in " + - numBlocks + - " blocks, ~" + - unlock_prediction.fromNow(true) + - ", " + - unlock_prediction.calendar() + - "" - ); - } else { - // unlock time is timestamp - var current_time = Math.round(new Date().getTime() / 1000); - var time_difference = unlock_time - current_time; - if (time_difference <= 0) { - return "Transaction is unlocked"; - } - var unlock_moment = moment(unlock_time * 1000); - return ( - "Will be unlocked " + - unlock_moment.fromNow() + - ", " + - unlock_moment.calendar() - ); - } - }; - - return this; -}; -exports.cnUtil = cnUtil; diff --git a/cryptonote_utils/internal_libs/bs58/index.js b/cryptonote_utils/internal_libs/bs58/index.js deleted file mode 100644 index d898210..0000000 --- a/cryptonote_utils/internal_libs/bs58/index.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const cryptonote_base58_1 = require("./cryptonote_base58"); -const { decode, encode } = cryptonote_base58_1.cnBase58; -exports.decode = decode; -exports.encode = encode; diff --git a/cryptonote_utils/internal_libs/cn_crypto/index.js b/cryptonote_utils/internal_libs/cn_crypto/index.js deleted file mode 100644 index 4e247df..0000000 --- a/cryptonote_utils/internal_libs/cn_crypto/index.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict"; -const CNCrypto = require("./cryptonote_crypto_EMSCRIPTEN"); -module.exports = CNCrypto; diff --git a/cryptonote_utils/internal_libs/fast_cn/index.js b/cryptonote_utils/internal_libs/fast_cn/index.js deleted file mode 100644 index 5bb1179..0000000 --- a/cryptonote_utils/internal_libs/fast_cn/index.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const nacl_fast_cn_1 = require("./nacl-fast-cn"); -const { ge_add, ge_double_scalarmult_base_vartime, ge_double_scalarmult_postcomp_vartime, ge_scalarmult, ge_scalarmult_base, } = nacl_fast_cn_1.ll; -exports.ge_add = ge_add; -exports.ge_double_scalarmult_base_vartime = ge_double_scalarmult_base_vartime; -exports.ge_double_scalarmult_postcomp_vartime = ge_double_scalarmult_postcomp_vartime; -exports.ge_scalarmult = ge_scalarmult; -exports.ge_scalarmult_base = ge_scalarmult_base; diff --git a/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js b/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js deleted file mode 100644 index 536b667..0000000 --- a/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js +++ /dev/null @@ -1,571 +0,0 @@ -(function(nacl) { -'use strict'; - -// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. -// Public domain. -// -// Implementation derived from TweetNaCl version 20140427. -// See for details: http://tweetnacl.cr.yp.to/ - -// modified 2017 for some CN functions by luigi1111 - -var gf = function(init) { - var i, r = new Float64Array(16); - if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; - return r; -}; - -// Pluggable, initialized in high-level API below. -var randombytes = function(/* x, n */) { throw new Error('no PRNG'); }; - -var _0 = new Uint8Array(16); -var _9 = new Uint8Array(32); _9[0] = 9; - -var gf0 = gf(), - gf1 = gf([1]), - _121665 = gf([0xdb41, 1]), - D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), - D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]), - X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]), - Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]), - I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); - -function vn(x, xi, y, yi, n) { - var i,d = 0; - for (i = 0; i < n; i++) d |= x[xi+i]^y[yi+i]; - return (1 & ((d - 1) >>> 8)) - 1; -} - -function crypto_verify_16(x, xi, y, yi) { - return vn(x,xi,y,yi,16); -} - -function crypto_verify_32(x, xi, y, yi) { - return vn(x,xi,y,yi,32); -} - -function set25519(r, a) { - var i; - for (i = 0; i < 16; i++) r[i] = a[i]|0; -} - -function car25519(o) { - var i, v, c = 1; - for (i = 0; i < 16; i++) { - v = o[i] + c + 65535; - c = Math.floor(v / 65536); - o[i] = v - c * 65536; - } - o[0] += c-1 + 37 * (c-1); -} - -function sel25519(p, q, b) { - var t, c = ~(b-1); - for (var i = 0; i < 16; i++) { - t = c & (p[i] ^ q[i]); - p[i] ^= t; - q[i] ^= t; - } -} - -function pack25519(o, n) { - var i, j, b; - var m = gf(), t = gf(); - for (i = 0; i < 16; i++) t[i] = n[i]; - car25519(t); - car25519(t); - car25519(t); - for (j = 0; j < 2; j++) { - m[0] = t[0] - 0xffed; - for (i = 1; i < 15; i++) { - m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); - m[i-1] &= 0xffff; - } - m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); - b = (m[15]>>16) & 1; - m[14] &= 0xffff; - sel25519(t, m, 1-b); - } - for (i = 0; i < 16; i++) { - o[2*i] = t[i] & 0xff; - o[2*i+1] = t[i]>>8; - } -} - -function neq25519(a, b) { - var c = new Uint8Array(32), d = new Uint8Array(32); - pack25519(c, a); - pack25519(d, b); - return crypto_verify_32(c, 0, d, 0); -} - -function par25519(a) { - var d = new Uint8Array(32); - pack25519(d, a); - return d[0] & 1; -} - -function unpack25519(o, n) { - var i; - for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); - o[15] &= 0x7fff; -} - -function A(o, a, b) { - for (var i = 0; i < 16; i++) o[i] = a[i] + b[i]; -} - -function Z(o, a, b) { - for (var i = 0; i < 16; i++) o[i] = a[i] - b[i]; -} - -function M(o, a, b) { - var v, c, - t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, - t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, - t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, - t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0, - b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5], - b6 = b[6], b7 = b[7], b8 = b[8], b9 = b[9], b10 = b[10], - b11 = b[11], b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15]; - - v = a[0]; - t0 += v * b0; t1 += v * b1; t2 += v * b2; t3 += v * b3; - t4 += v * b4; t5 += v * b5; t6 += v * b6; t7 += v * b7; - t8 += v * b8; t9 += v * b9; t10 += v * b10; t11 += v * b11; - t12 += v * b12; t13 += v * b13; t14 += v * b14; t15 += v * b15; - v = a[1]; - t1 += v * b0; t2 += v * b1; t3 += v * b2; t4 += v * b3; - t5 += v * b4; t6 += v * b5; t7 += v * b6; t8 += v * b7; - t9 += v * b8; t10 += v * b9; t11 += v * b10; t12 += v * b11; - t13 += v * b12; t14 += v * b13; t15 += v * b14; t16 += v * b15; - v = a[2]; - t2 += v * b0; t3 += v * b1; t4 += v * b2; t5 += v * b3; - t6 += v * b4; t7 += v * b5; t8 += v * b6; t9 += v * b7; - t10 += v * b8; t11 += v * b9; t12 += v * b10; t13 += v * b11; - t14 += v * b12; t15 += v * b13; t16 += v * b14; t17 += v * b15; - v = a[3]; - t3 += v * b0; t4 += v * b1; t5 += v * b2; t6 += v * b3; - t7 += v * b4; t8 += v * b5; t9 += v * b6; t10 += v * b7; - t11 += v * b8; t12 += v * b9; t13 += v * b10; t14 += v * b11; - t15 += v * b12; t16 += v * b13; t17 += v * b14; t18 += v * b15; - v = a[4]; - t4 += v * b0; t5 += v * b1; t6 += v * b2; t7 += v * b3; - t8 += v * b4; t9 += v * b5; t10 += v * b6; t11 += v * b7; - t12 += v * b8; t13 += v * b9; t14 += v * b10; t15 += v * b11; - t16 += v * b12; t17 += v * b13; t18 += v * b14; t19 += v * b15; - v = a[5]; - t5 += v * b0; t6 += v * b1; t7 += v * b2; t8 += v * b3; - t9 += v * b4; t10 += v * b5; t11 += v * b6; t12 += v * b7; - t13 += v * b8; t14 += v * b9; t15 += v * b10; t16 += v * b11; - t17 += v * b12; t18 += v * b13; t19 += v * b14; t20 += v * b15; - v = a[6]; - t6 += v * b0; t7 += v * b1; t8 += v * b2; t9 += v * b3; - t10 += v * b4; t11 += v * b5; t12 += v * b6; t13 += v * b7; - t14 += v * b8; t15 += v * b9; t16 += v * b10; t17 += v * b11; - t18 += v * b12; t19 += v * b13; t20 += v * b14; t21 += v * b15; - v = a[7]; - t7 += v * b0; t8 += v * b1; t9 += v * b2; t10 += v * b3; - t11 += v * b4; t12 += v * b5; t13 += v * b6; t14 += v * b7; - t15 += v * b8; t16 += v * b9; t17 += v * b10; t18 += v * b11; - t19 += v * b12; t20 += v * b13; t21 += v * b14; t22 += v * b15; - v = a[8]; - t8 += v * b0; t9 += v * b1; t10 += v * b2; t11 += v * b3; - t12 += v * b4; t13 += v * b5; t14 += v * b6; t15 += v * b7; - t16 += v * b8; t17 += v * b9; t18 += v * b10; t19 += v * b11; - t20 += v * b12; t21 += v * b13; t22 += v * b14; t23 += v * b15; - v = a[9]; - t9 += v * b0; t10 += v * b1; t11 += v * b2; t12 += v * b3; - t13 += v * b4; t14 += v * b5; t15 += v * b6; t16 += v * b7; - t17 += v * b8; t18 += v * b9; t19 += v * b10; t20 += v * b11; - t21 += v * b12; t22 += v * b13; t23 += v * b14; t24 += v * b15; - v = a[10]; - t10 += v * b0; t11 += v * b1; t12 += v * b2; t13 += v * b3; - t14 += v * b4; t15 += v * b5; t16 += v * b6; t17 += v * b7; - t18 += v * b8; t19 += v * b9; t20 += v * b10; t21 += v * b11; - t22 += v * b12; t23 += v * b13; t24 += v * b14; t25 += v * b15; - v = a[11]; - t11 += v * b0; t12 += v * b1; t13 += v * b2; t14 += v * b3; - t15 += v * b4; t16 += v * b5; t17 += v * b6; t18 += v * b7; - t19 += v * b8; t20 += v * b9; t21 += v * b10; t22 += v * b11; - t23 += v * b12; t24 += v * b13; t25 += v * b14; t26 += v * b15; - v = a[12]; - t12 += v * b0; t13 += v * b1; t14 += v * b2; t15 += v * b3; - t16 += v * b4; t17 += v * b5; t18 += v * b6; t19 += v * b7; - t20 += v * b8; t21 += v * b9; t22 += v * b10; t23 += v * b11; - t24 += v * b12; t25 += v * b13; t26 += v * b14; t27 += v * b15; - v = a[13]; - t13 += v * b0; t14 += v * b1; t15 += v * b2; t16 += v * b3; - t17 += v * b4; t18 += v * b5; t19 += v * b6; t20 += v * b7; - t21 += v * b8; t22 += v * b9; t23 += v * b10; t24 += v * b11; - t25 += v * b12; t26 += v * b13; t27 += v * b14; t28 += v * b15; - v = a[14]; - t14 += v * b0; t15 += v * b1; t16 += v * b2; t17 += v * b3; - t18 += v * b4; t19 += v * b5; t20 += v * b6; t21 += v * b7; - t22 += v * b8; t23 += v * b9; t24 += v * b10; t25 += v * b11; - t26 += v * b12; t27 += v * b13; t28 += v * b14; t29 += v * b15; - v = a[15]; - t15 += v * b0; t16 += v * b1; t17 += v * b2; t18 += v * b3; - t19 += v * b4; t20 += v * b5; t21 += v * b6; t22 += v * b7; - t23 += v * b8; t24 += v * b9; t25 += v * b10; t26 += v * b11; - t27 += v * b12; t28 += v * b13; t29 += v * b14; t30 += v * b15; - - t0 += 38 * t16; t1 += 38 * t17; t2 += 38 * t18; t3 += 38 * t19; - t4 += 38 * t20; t5 += 38 * t21; t6 += 38 * t22; t7 += 38 * t23; - t8 += 38 * t24; t9 += 38 * t25; t10 += 38 * t26; t11 += 38 * t27; - t12 += 38 * t28; t13 += 38 * t29; t14 += 38 * t30; // t15 left as is - - // first car - c = 1; - v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; - v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; - v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; - v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; - v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; - v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; - v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; - v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; - v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; - v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; - v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; - v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; - v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; - v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; - v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; - v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; - t0 += c-1 + 37 * (c-1); - - // second car - c = 1; - v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; - v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; - v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; - v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; - v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; - v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; - v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; - v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; - v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; - v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; - v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; - v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; - v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; - v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; - v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; - v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; - t0 += c-1 + 37 * (c-1); - - o[ 0] = t0; - o[ 1] = t1; - o[ 2] = t2; - o[ 3] = t3; - o[ 4] = t4; - o[ 5] = t5; - o[ 6] = t6; - o[ 7] = t7; - o[ 8] = t8; - o[ 9] = t9; - o[10] = t10; - o[11] = t11; - o[12] = t12; - o[13] = t13; - o[14] = t14; - o[15] = t15; -} - -function S(o, a) { - M(o, a, a); -} - -function inv25519(o, i) { - var c = gf(); - var a; - for (a = 0; a < 16; a++) c[a] = i[a]; - for (a = 253; a >= 0; a--) { - S(c, c); - if(a !== 2 && a !== 4) M(c, c, i); - } - for (a = 0; a < 16; a++) o[a] = c[a]; -} - -function pow2523(o, i) { - var c = gf(); - var a; - for (a = 0; a < 16; a++) c[a] = i[a]; - for (a = 250; a >= 0; a--) { - S(c, c); - if(a !== 1) M(c, c, i); - } - for (a = 0; a < 16; a++) o[a] = c[a]; -} - -function add(p, q) { - var a = gf(), b = gf(), c = gf(), - d = gf(), e = gf(), f = gf(), - g = gf(), h = gf(), t = gf(); - - Z(a, p[1], p[0]); - Z(t, q[1], q[0]); - M(a, a, t); - A(b, p[0], p[1]); - A(t, q[0], q[1]); - M(b, b, t); - M(c, p[3], q[3]); - M(c, c, D2); - M(d, p[2], q[2]); - A(d, d, d); - Z(e, b, a); - Z(f, d, c); - A(g, d, c); - A(h, b, a); - - M(p[0], e, f); - M(p[1], h, g); - M(p[2], g, f); - M(p[3], e, h); -} - -function cswap(p, q, b) { - var i; - for (i = 0; i < 4; i++) { - sel25519(p[i], q[i], b); - } -} - -function pack(r, p) { - var tx = gf(), ty = gf(), zi = gf(); - inv25519(zi, p[2]); - M(tx, p[0], zi); - M(ty, p[1], zi); - pack25519(r, ty); - r[31] ^= par25519(tx) << 7; -} - -function scalarmult(p, q, s) { - var b, i; - set25519(p[0], gf0); - set25519(p[1], gf1); - set25519(p[2], gf1); - set25519(p[3], gf0); - for (i = 255; i >= 0; --i) { - b = (s[(i/8)|0] >> (i&7)) & 1; - cswap(p, q, b); - add(q, p); - add(p, p); - cswap(p, q, b); - } -} - -function scalarbase(p, s) { - var q = [gf(), gf(), gf(), gf()]; - set25519(q[0], X); - set25519(q[1], Y); - set25519(q[2], gf1); - M(q[3], X, Y); - scalarmult(p, q, s); -} - -//new functions for CN - scalar operations are handled externally -// this only handles curve operations, except for Hp() - -//why do we negate points when unpacking them??? -function ge_neg(pub) { - pub[31] ^= 0x80; -} - -//res = s*G -function ge_scalarmult_base(s) { - var p = [gf(), gf(), gf(), gf()]; - scalarbase(p, s); - var pk = new Uint8Array(32); - pack(pk, p); - return pk; -} - -//res = s*P -function ge_scalarmult(P, s) { - var p = [gf(), gf(), gf(), gf()], - upk = [gf(), gf(), gf(), gf()], - res = new Uint8Array(32); - ge_neg(P); - if (unpackneg(upk, P) !== 0) throw "non-0 error on point decode"; - scalarmult(p, upk, s); - pack(res, p); - return res; -} - -//res = c*P + r*G -function ge_double_scalarmult_base_vartime(c, P, r) { - var uP = [gf(), gf(), gf(), gf()], - cP = [gf(), gf(), gf(), gf()], - rG = [gf(), gf(), gf(), gf()], - res = new Uint8Array(32); - ge_neg(P); - if (unpackneg(uP, P) !== 0) throw "non-0 error on point decode"; - scalarmult(cP, uP, c); - scalarbase(rG, r); - add(rG, cP); - pack(res, rG); - return res; -} - -//name changed to reflect not using precomp; res = r*Pb + c*I -function ge_double_scalarmult_postcomp_vartime(r, Pb, c, I) { - var uPb = [gf(), gf(), gf(), gf()], - uI = [gf(), gf(), gf(), gf()], - cI = [gf(), gf(), gf(), gf()], - rPb = [gf(), gf(), gf(), gf()], - res = new Uint8Array(32); - ge_neg(Pb); - if (unpackneg(uPb, Pb) !== 0) throw "non-0 error on point decode"; - scalarmult(rPb, uPb, r); - ge_neg(I); - if (unpackneg(uI, I) !== 0) throw "non-0 error on point decode"; - scalarmult(cI, uI, c); - add(rPb, cI); - pack(res, rPb); - return res; -} - -//res = P + Q -function ge_add(P, Q) { - var uP = [gf(), gf(), gf(), gf()], - uQ = [gf(), gf(), gf(), gf()], - res = new Uint8Array(32); - ge_neg(P); - ge_neg(Q); - if (unpackneg(uP, P) !== 0) throw "non-0 error on point decode"; - if (unpackneg(uQ, Q) !== 0) throw "non-0 error on point decode"; - add(uP, uQ); - pack(res, uP); - return res; -} - -var L = new Float64Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10]); - -function modL(r, x) { - var carry, i, j, k; - for (i = 63; i >= 32; --i) { - carry = 0; - for (j = i - 32, k = i - 12; j < k; ++j) { - x[j] += carry - 16 * x[i] * L[j - (i - 32)]; - carry = (x[j] + 128) >> 8; - x[j] -= carry * 256; - } - x[j] += carry; - x[i] = 0; - } - carry = 0; - for (j = 0; j < 32; j++) { - x[j] += carry - (x[31] >> 4) * L[j]; - carry = x[j] >> 8; - x[j] &= 255; - } - for (j = 0; j < 32; j++) x[j] -= carry * L[j]; - for (i = 0; i < 32; i++) { - x[i+1] += x[i] >> 8; - r[i] = x[i] & 255; - } -} - -function reduce(r) { - var x = new Float64Array(64), i; - for (i = 0; i < 64; i++) x[i] = r[i]; - for (i = 0; i < 64; i++) r[i] = 0; - modL(r, x); -} - -function unpackneg(r, p) { - var t = gf(), chk = gf(), num = gf(), - den = gf(), den2 = gf(), den4 = gf(), - den6 = gf(); - - set25519(r[2], gf1); - unpack25519(r[1], p); - S(num, r[1]); - M(den, num, D); - Z(num, num, r[2]); - A(den, r[2], den); - - S(den2, den); - S(den4, den2); - M(den6, den4, den2); - M(t, den6, num); - M(t, t, den); - - pow2523(t, t); - M(t, t, num); - M(t, t, den); - M(t, t, den); - M(r[0], t, den); - - S(chk, r[0]); - M(chk, chk, den); - if (neq25519(chk, num)) M(r[0], r[0], I); - - S(chk, r[0]); - M(chk, chk, den); - if (neq25519(chk, num)) return -1; - - if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); - - M(r[3], r[0], r[1]); - return 0; -} - -nacl.ll = { - - ge_scalarmult_base: ge_scalarmult_base, - ge_scalarmult: ge_scalarmult, - ge_double_scalarmult_base_vartime: ge_double_scalarmult_base_vartime, - ge_add: ge_add, - ge_double_scalarmult_postcomp_vartime: ge_double_scalarmult_postcomp_vartime - -}; - - -/* High-level API */ - -function cleanup(arr) { - for (var i = 0; i < arr.length; i++) arr[i] = 0; -} - -nacl.randomBytes = function(n) { - var b = new Uint8Array(n); - randombytes(b, n); - return b; -}; - -nacl.setPRNG = function(fn) { - randombytes = fn; -}; - -(function() { - // Initialize PRNG if environment provides CSPRNG. - // If not, methods calling randombytes will throw. - var crypto = typeof self !== 'undefined' ? (self.crypto || self.msCrypto) : null; - if (crypto && crypto.getRandomValues) { - // Browsers. - var QUOTA = 65536; - nacl.setPRNG(function(x, n) { - var i, v = new Uint8Array(n); - for (i = 0; i < n; i += QUOTA) { - crypto.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA))); - } - for (i = 0; i < n; i++) x[i] = v[i]; - cleanup(v); - }); - } else if (typeof require !== 'undefined') { - // Node.js. - crypto = require('crypto'); - if (crypto && crypto.randomBytes) { - nacl.setPRNG(function(x, n) { - var i, v = crypto.randomBytes(n); - for (i = 0; i < n; i++) x[i] = v[i]; - cleanup(v); - }); - } - } -})(); - -})(typeof module !== 'undefined' && module.exports ? module.exports : (self.nacl = self.nacl || {})); diff --git a/index.js b/index.js deleted file mode 100644 index 3247530..0000000 --- a/index.js +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2014-2018, MyMonero.com -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -"use strict"; -// -// NOTE: The main downside to using an index.js file like this is that it will pull in all the code - rather than the consumer requiring code module-by-module -// It's of course possible to construct your own stripped-down index.[custom name].js file for, e.g., special webpack bundling usages. -const mymonero_core_js = {}; -mymonero_core_js.monero_utils = require("./monero_utils/monero_cryptonote_utils_instance"); -mymonero_core_js.monero_config = require("./monero_utils/monero_config"); -mymonero_core_js.monero_txParsing_utils = require("./monero_utils/monero_txParsing_utils"); -mymonero_core_js.monero_sendingFunds_utils = require("./monero_utils/monero_sendingFunds_utils"); -mymonero_core_js.request_funds_uri_utils = require("./monero_utils/request_funds_uri_utils"); -mymonero_core_js.key_image_utils = require("./monero_utils/key_image_utils"); -mymonero_core_js.monero_paymentID_utils = require("./monero_utils/monero_paymentID_utils"); -mymonero_core_js.api_response_parser_utils = require("./hostAPI/response_parser_utils"); -// -mymonero_core_js.nettype_utils = require("./cryptonote_utils/nettype"); -mymonero_core_js.JSBigInt = require("./cryptonote_utils/biginteger").BigInteger; // so that it is available to a hypothetical consumer's language-bridging web context for constructing string arguments to the above modules -// -module.exports = mymonero_core_js; diff --git a/package.json b/package.json index 6732530..8ad0225 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "url": "git+https://github.com/mymonero/mymonero-core-js.git" }, "scripts": { + "build": "tsc", "format": "find . -name '*.js*' | xargs prettier --write --config ./.prettierrc --config-precedence file-override", "test": "jest", "test:coverage": "jest --coverage" @@ -32,21 +33,32 @@ }, "homepage": "https://github.com/mymonero/mymonero-core-js#readme", "dependencies": { - "keccakjs": "^0.2.1" + "keccakjs": "^0.2.1", + "moment": "^2.22.2" }, "devDependencies": { "jest": "^23.1.0", + "ts-jest": "^23.0.1", "ts-node": "^7.0.0", - "tsconfig-paths": "^3.4.2" + "tsconfig-paths": "^3.4.2", + "typescript": "^2.9.2" }, "jest": { - "testEnvironment": "node", + "moduleFileExtensions": [ + "js", + "jsx", + "json", + "ts", + "tsx" + ], + "transform": { + "\\.(ts|tsx)$": "/node_modules/ts-jest/preprocessor.js" + }, + "modulePaths": ["/src" ], + "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", "coveragePathIgnorePatterns": [ - "node_modules", - "cryptonote_utils/biginteger.js", - "cryptonote_utils/nacl-fast-cn.js", - "cryptonote_utils/sha3.js", - "cryptonote_utils/cryptonote_crypto_EMSCRIPTEN.js" + "/node_modules/", + "__test__" ] } } diff --git a/cryptonote_utils/biginteger.d.ts b/src/biginteger/biginteger.d.ts similarity index 99% rename from cryptonote_utils/biginteger.d.ts rename to src/biginteger/biginteger.d.ts index 891843c..37e2db9 100644 --- a/cryptonote_utils/biginteger.d.ts +++ b/src/biginteger/biginteger.d.ts @@ -291,7 +291,7 @@ declare namespace BigInteger { * @returns {BigInteger} * @memberof BigInteger */ - parse(s: string, base?: number): BigInteger; + static parse(s: string, base?: number): BigInteger; /** * @description Add two . diff --git a/cryptonote_utils/biginteger.js b/src/biginteger/biginteger.js similarity index 100% rename from cryptonote_utils/biginteger.js rename to src/biginteger/biginteger.js diff --git a/src/biginteger/index.ts b/src/biginteger/index.ts new file mode 100644 index 0000000..7c1c190 --- /dev/null +++ b/src/biginteger/index.ts @@ -0,0 +1,4 @@ +import BigInteger = require("./biginteger"); + +export const BigInt = BigInteger.BigInteger; +export type BigInt = BigInteger.BigInteger; diff --git a/src/cryptonote_utils/cryptonote_utils.ts b/src/cryptonote_utils/cryptonote_utils.ts new file mode 100644 index 0000000..7069006 --- /dev/null +++ b/src/cryptonote_utils/cryptonote_utils.ts @@ -0,0 +1,2789 @@ +// Copyright (c) 2014-2018, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Original Author: Lucas Jones +// Modified to remove jQuery dep and support modular inclusion of deps by Paul Shapiro (2016) +// Modified to add RingCT support by luigi1111 (2017) +// +// v--- These should maybe be injected into a context and supplied to currencyConfig for future platforms + +import cnBase58 = require("./internal_libs/bs58"); +import CNCrypto = require("./internal_libs/cn_crypto"); +import nacl = require("./internal_libs/fast_cn"); +import SHA3 = require("keccakjs"); +import nettype_utils = require("./nettype"); +import { randomBytes } from "crypto"; +import { BigInt } from "biginteger"; +import { NetType } from "./nettype"; +import { formatMoney, formatMoneyFull } from "./formatters"; +import { SecretCommitment, MixCommitment } from "types"; +import { + ViewSendKeys, + ParsedTarget, + Output, + AmountOutput, + Pid, +} from "monero_utils/sending_funds/internal_libs/types"; + +const HASH_SIZE = 32; +const ADDRESS_CHECKSUM_SIZE = 4; +const INTEGRATED_ID_SIZE = 8; +const ENCRYPTED_PAYMENT_ID_TAIL = 141; + +const UINT64_MAX = new BigInt(2).pow(64); +const CURRENT_TX_VERSION = 2; +const OLD_TX_VERSION = 1; +const RCTTypeFull = 1; +const RCTTypeSimple = 2; +const TX_EXTRA_NONCE_MAX_COUNT = 255; +const TX_EXTRA_TAGS = { + PADDING: "00", + PUBKEY: "01", + NONCE: "02", + MERGE_MINING: "03", +}; +const TX_EXTRA_NONCE_TAGS = { + PAYMENT_ID: "00", + ENCRYPTED_PAYMENT_ID: "01", +}; +const KEY_SIZE = 32; +const STRUCT_SIZES = { + GE_P3: 160, + GE_P2: 120, + GE_P1P1: 160, + GE_CACHED: 160, + EC_SCALAR: 32, + EC_POINT: 32, + KEY_IMAGE: 32, + GE_DSMP: 160 * 8, // ge_cached * 8 + SIGNATURE: 64, // ec_scalar * 2 +}; + +//RCT vars +export const H = + "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94"; //base H for amounts + +export const l = new BigInt( + "7237005577332262213973186563042994240857116359379907606001950938285454250989", +); //curve order (not RCT specific) + +export const I = + "0100000000000000000000000000000000000000000000000000000000000000"; //identity element + +export function identity() { + return I; +} + +export const Z = + "0000000000000000000000000000000000000000000000000000000000000000"; //zero scalar + +//H2 object to speed up some operations +export const H2 = [ + "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94", + "8faa448ae4b3e2bb3d4d130909f55fcd79711c1c83cdbccadd42cbe1515e8712", + "12a7d62c7791654a57f3e67694ed50b49a7d9e3fc1e4c7a0bde29d187e9cc71d", + "789ab9934b49c4f9e6785c6d57a498b3ead443f04f13df110c5427b4f214c739", + "771e9299d94f02ac72e38e44de568ac1dcb2edc6edb61f83ca418e1077ce3de8", + "73b96db43039819bdaf5680e5c32d741488884d18d93866d4074a849182a8a64", + "8d458e1c2f68ebebccd2fd5d379f5e58f8134df3e0e88cad3d46701063a8d412", + "09551edbe494418e81284455d64b35ee8ac093068a5f161fa6637559177ef404", + "d05a8866f4df8cee1e268b1d23a4c58c92e760309786cdac0feda1d247a9c9a7", + "55cdaad518bd871dd1eb7bc7023e1dc0fdf3339864f88fdd2de269fe9ee1832d", + "e7697e951a98cfd5712b84bbe5f34ed733e9473fcb68eda66e3788df1958c306", + "f92a970bae72782989bfc83adfaa92a4f49c7e95918b3bba3cdc7fe88acc8d47", + "1f66c2d491d75af915c8db6a6d1cb0cd4f7ddcd5e63d3ba9b83c866c39ef3a2b", + "3eec9884b43f58e93ef8deea260004efea2a46344fc5965b1a7dd5d18997efa7", + "b29f8f0ccb96977fe777d489d6be9e7ebc19c409b5103568f277611d7ea84894", + "56b1f51265b9559876d58d249d0c146d69a103636699874d3f90473550fe3f2c", + "1d7a36575e22f5d139ff9cc510fa138505576b63815a94e4b012bfd457caaada", + "d0ac507a864ecd0593fa67be7d23134392d00e4007e2534878d9b242e10d7620", + "f6c6840b9cf145bb2dccf86e940be0fc098e32e31099d56f7fe087bd5deb5094", + "28831a3340070eb1db87c12e05980d5f33e9ef90f83a4817c9f4a0a33227e197", + "87632273d629ccb7e1ed1a768fa2ebd51760f32e1c0b867a5d368d5271055c6e", + "5c7b29424347964d04275517c5ae14b6b5ea2798b573fc94e6e44a5321600cfb", + "e6945042d78bc2c3bd6ec58c511a9fe859c0ad63fde494f5039e0e8232612bd5", + "36d56907e2ec745db6e54f0b2e1b2300abcb422e712da588a40d3f1ebbbe02f6", + "34db6ee4d0608e5f783650495a3b2f5273c5134e5284e4fdf96627bb16e31e6b", + "8e7659fb45a3787d674ae86731faa2538ec0fdf442ab26e9c791fada089467e9", + "3006cf198b24f31bb4c7e6346000abc701e827cfbb5df52dcfa42e9ca9ff0802", + "f5fd403cb6e8be21472e377ffd805a8c6083ea4803b8485389cc3ebc215f002a", + "3731b260eb3f9482e45f1c3f3b9dcf834b75e6eef8c40f461ea27e8b6ed9473d", + "9f9dab09c3f5e42855c2de971b659328a2dbc454845f396ffc053f0bb192f8c3", + "5e055d25f85fdb98f273e4afe08464c003b70f1ef0677bb5e25706400be620a5", + "868bcf3679cb6b500b94418c0b8925f9865530303ae4e4b262591865666a4590", + "b3db6bd3897afbd1df3f9644ab21c8050e1f0038a52f7ca95ac0c3de7558cb7a", + "8119b3a059ff2cac483e69bcd41d6d27149447914288bbeaee3413e6dcc6d1eb", + "10fc58f35fc7fe7ae875524bb5850003005b7f978c0c65e2a965464b6d00819c", + "5acd94eb3c578379c1ea58a343ec4fcff962776fe35521e475a0e06d887b2db9", + "33daf3a214d6e0d42d2300a7b44b39290db8989b427974cd865db011055a2901", + "cfc6572f29afd164a494e64e6f1aeb820c3e7da355144e5124a391d06e9f95ea", + "d5312a4b0ef615a331f6352c2ed21dac9e7c36398b939aec901c257f6cbc9e8e", + "551d67fefc7b5b9f9fdbf6af57c96c8a74d7e45a002078a7b5ba45c6fde93e33", + "d50ac7bd5ca593c656928f38428017fc7ba502854c43d8414950e96ecb405dc3", + "0773e18ea1be44fe1a97e239573cfae3e4e95ef9aa9faabeac1274d3ad261604", + "e9af0e7ca89330d2b8615d1b4137ca617e21297f2f0ded8e31b7d2ead8714660", + "7b124583097f1029a0c74191fe7378c9105acc706695ed1493bb76034226a57b", + "ec40057b995476650b3db98e9db75738a8cd2f94d863b906150c56aac19caa6b", + "01d9ff729efd39d83784c0fe59c4ae81a67034cb53c943fb818b9d8ae7fc33e5", + "00dfb3c696328c76424519a7befe8e0f6c76f947b52767916d24823f735baf2e", + "461b799b4d9ceea8d580dcb76d11150d535e1639d16003c3fb7e9d1fd13083a8", + "ee03039479e5228fdc551cbde7079d3412ea186a517ccc63e46e9fcce4fe3a6c", + "a8cfb543524e7f02b9f045acd543c21c373b4c9b98ac20cec417a6ddb5744e94", + "932b794bf89c6edaf5d0650c7c4bad9242b25626e37ead5aa75ec8c64e09dd4f", + "16b10c779ce5cfef59c7710d2e68441ea6facb68e9b5f7d533ae0bb78e28bf57", + "0f77c76743e7396f9910139f4937d837ae54e21038ac5c0b3fd6ef171a28a7e4", + "d7e574b7b952f293e80dde905eb509373f3f6cd109a02208b3c1e924080a20ca", + "45666f8c381e3da675563ff8ba23f83bfac30c34abdde6e5c0975ef9fd700cb9", + "b24612e454607eb1aba447f816d1a4551ef95fa7247fb7c1f503020a7177f0dd", + "7e208861856da42c8bb46a7567f8121362d9fb2496f131a4aa9017cf366cdfce", + "5b646bff6ad1100165037a055601ea02358c0f41050f9dfe3c95dccbd3087be0", + "746d1dccfed2f0ff1e13c51e2d50d5324375fbd5bf7ca82a8931828d801d43ab", + "cb98110d4a6bb97d22feadbc6c0d8930c5f8fc508b2fc5b35328d26b88db19ae", + "60b626a033b55f27d7676c4095eababc7a2c7ede2624b472e97f64f96b8cfc0e", + "e5b52bc927468df71893eb8197ef820cf76cb0aaf6e8e4fe93ad62d803983104", + "056541ae5da9961be2b0a5e895e5c5ba153cbb62dd561a427bad0ffd41923199", + "f8fef05a3fa5c9f3eba41638b247b711a99f960fe73aa2f90136aeb20329b888", +]; + +//begin rct new functions +//creates a Pedersen commitment from an amount (in scalar form) and a mask +//C = bG + aH where b = mask, a = amount +function commit(amount: string, mask: string) { + if ( + !valid_hex(mask) || + mask.length !== 64 || + !valid_hex(amount) || + amount.length !== 64 + ) { + throw Error("invalid amount or mask!"); + } + const C = ge_double_scalarmult_base_vartime(amount, H, mask); + return C; +} + +function zeroCommit(amount: string) { + if (!valid_hex(amount) || amount.length !== 64) { + throw Error("invalid amount!"); + } + const C = ge_double_scalarmult_base_vartime(amount, H, I); + return C; +} + +interface Commit { + mask: string; + amount: string; +} + +export function decode_rct_ecdh(ecdh: Commit, key: string): Commit { + const first = hash_to_scalar(key); + const second = hash_to_scalar(first); + return { + mask: sc_sub(ecdh.mask, first), + amount: sc_sub(ecdh.amount, second), + }; +} + +export function encode_rct_ecdh(ecdh: Commit, key: string): Commit { + const first = hash_to_scalar(key); + const second = hash_to_scalar(first); + return { + mask: sc_add(ecdh.mask, first), + amount: sc_add(ecdh.amount, second), + }; +} + +//switch byte order for hex string +function swapEndian(hex: string) { + if (hex.length % 2 !== 0) { + return "length must be a multiple of 2!"; + } + let data = ""; + for (let i = 1; i <= hex.length / 2; i++) { + data += hex.substr(0 - 2 * i, 2); + } + return data; +} + +//switch byte order charwise +function swapEndianC(str: string) { + let data = ""; + for (let i = 1; i <= str.length; i++) { + data += str.substr(0 - i, 1); + } + return data; +} + +//for most uses you'll also want to swapEndian after conversion +//mainly to convert integer "scalars" to usable hexadecimal strings +//uint long long to 32 byte key +function d2h(integer: string | BigInt) { + let padding = ""; + for (let i = 0; i < 63; i++) { + padding += "0"; + } + return (padding + new BigInt(integer).toString(16).toLowerCase()).slice( + -64, + ); +} + +//integer (string) to scalar +export function d2s(integer: string | BigInt) { + return swapEndian(d2h(integer)); +} + +//convert integer string to 64bit "binary" little-endian string +function d2b(integer: string | BigInt) { + let padding = ""; + for (let i = 0; i < 63; i++) { + padding += "0"; + } + const a = new BigInt(integer); + if (a.toString(2).length > 64) { + throw Error("amount overflows uint64!"); + } + return swapEndianC((padding + a.toString(2)).slice(-64)); +} + +//end rct new functions + +export function valid_hex(hex: string) { + const exp = new RegExp("[0-9a-fA-F]{" + hex.length + "}"); + return exp.test(hex); +} + +//simple exclusive or function for two hex inputs +function hex_xor(hex1: string, hex2: string) { + if ( + !hex1 || + !hex2 || + hex1.length !== hex2.length || + hex1.length % 2 !== 0 || + hex2.length % 2 !== 0 + ) { + throw Error("Hex string(s) is/are invalid!"); + } + const bin1 = hextobin(hex1); + const bin2 = hextobin(hex2); + const xor = new Uint8Array(bin1.length); + for (let i = 0; i < xor.length; i++) { + xor[i] = bin1[i] ^ bin2[i]; + } + return bintohex(xor); +} + +function hextobin(hex: string) { + if (hex.length % 2 !== 0) throw Error("Hex string has invalid length!"); + const res = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length / 2; ++i) { + res[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); + } + return res; +} + +function bintohex(bin: Uint8Array) { + const out = []; + for (let i = 0; i < bin.length; ++i) { + out.push(("0" + bin[i].toString(16)).slice(-2)); + } + return out.join(""); +} + +// Generate a 256-bit / 64-char / 32-byte crypto random +function rand_32() { + return randomBytes(32).toString("hex"); +} + +// Generate a 64-bit / 16-char / 8-byte crypto random +export function rand_8() { + return randomBytes(8).toString("hex"); +} + +function encode_varint(input: number | string) { + let i = new BigInt(input); + let out = ""; + // While i >= b10000000 + while (i.compare(0x80) >= 0) { + // out.append i & b01111111 | b10000000 + out += ("0" + ((i.lowVal() & 0x7f) | 0x80).toString(16)).slice(-2); + i = i.divide(new BigInt(2).pow(7)); + } + out += ("0" + i.toJSValue().toString(16)).slice(-2); + return out; +} + +function sc_reduce32(hex: string) { + const input = hextobin(hex); + if (input.length !== 32) { + throw Error("Invalid input length"); + } + const mem = CNCrypto._malloc(32); + CNCrypto.HEAPU8.set(input, mem); + CNCrypto.ccall("sc_reduce32", "void", ["number"], [mem]); + const output = CNCrypto.HEAPU8.subarray(mem, mem + 32); + CNCrypto._free(mem); + return bintohex(output); +} + +export function cn_fast_hash(input: string) { + if (input.length % 2 !== 0 || !valid_hex(input)) { + throw Error("Input invalid"); + } + + const hasher = new SHA3(256); + hasher.update(Buffer.from((hextobin(input).buffer as any) as Buffer)); + return hasher.digest("hex"); +} + +function sec_key_to_pub(sec: string) { + if (sec.length !== 64) { + throw Error("Invalid sec length"); + } + return bintohex(nacl.ge_scalarmult_base(hextobin(sec))); +} + +//alias +export function ge_scalarmult_base(sec: string) { + return sec_key_to_pub(sec); +} + +export function ge_scalarmult(pub: string, sec: string) { + if (pub.length !== 64 || sec.length !== 64) { + throw Error("Invalid input length"); + } + return bintohex(nacl.ge_scalarmult(hextobin(pub), hextobin(sec))); +} + +function pubkeys_to_string(spend: string, view: string, nettype: NetType) { + const prefix = encode_varint( + nettype_utils.cryptonoteBase58PrefixForStandardAddressOn(nettype), + ); + const data = prefix + spend + view; + const checksum = cn_fast_hash(data); + return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2)); +} + +export function makeIntegratedAddressFromAddressAndShortPid( + address: string, + short_pid: string, + nettype: NetType, +) { + // throws + let decoded_address = decode_address( + address, // TODO/FIXME: not super happy about having to decode just to re-encode… this was a quick hack + nettype, + ); // throws + if (!short_pid || short_pid.length != 16) { + throw Error("expected valid short_pid"); + } + const prefix = encode_varint( + nettype_utils.cryptonoteBase58PrefixForIntegratedAddressOn(nettype), + ); + const data = + prefix + decoded_address.spend + decoded_address.view + short_pid; + const checksum = cn_fast_hash(data); + const encodable__data = data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2); + // + return cnBase58.encode(encodable__data); +} + +// Generate keypair from seed +function generate_keys(seed: string): Key { + if (seed.length !== 64) throw Error("Invalid input length!"); + const sec = sc_reduce32(seed); + const pub = sec_key_to_pub(sec); + return { + sec: sec, + pub: pub, + }; +} + +export function random_keypair() { + return generate_keys(rand_32()); +} + +// Random 32-byte ec scalar +export function random_scalar() { + return sc_reduce32(rand_32()); +} + +// alias +export const skGen = random_scalar; + +interface Key { + pub: string; + sec: string; +} +interface Account { + spend: Key; + view: Key; + public_addr: string; +} + +export function create_address(seed: string, nettype: NetType): Account { + // updated by Luigi and PS to support reduced and non-reduced seeds + let first; + if (seed.length !== 64) { + first = cn_fast_hash(seed); + } else { + first = sc_reduce32(seed); + } + const spend = generate_keys(first); + const second = cn_fast_hash(first); + const view = generate_keys(second); + const public_addr = pubkeys_to_string(spend.pub, view.pub, nettype); + return { spend, view, public_addr }; +} + +export function decode_address(address: string, nettype: NetType) { + let dec = cnBase58.decode(address); + const expectedPrefix = encode_varint( + nettype_utils.cryptonoteBase58PrefixForStandardAddressOn(nettype), + ); + const expectedPrefixInt = encode_varint( + nettype_utils.cryptonoteBase58PrefixForIntegratedAddressOn(nettype), + ); + const expectedPrefixSub = encode_varint( + nettype_utils.cryptonoteBase58PrefixForSubAddressOn(nettype), + ); + const prefix = dec.slice(0, expectedPrefix.length); + if ( + prefix !== expectedPrefix && + prefix !== expectedPrefixInt && + prefix !== expectedPrefixSub + ) { + throw Error("Invalid address prefix"); + } + dec = dec.slice(expectedPrefix.length); + const spend = dec.slice(0, 64); + const view = dec.slice(64, 128); + let checksum; + let expectedChecksum; + let intPaymentId; + + if (prefix === expectedPrefixInt) { + intPaymentId = dec.slice(128, 128 + INTEGRATED_ID_SIZE * 2); + checksum = dec.slice( + 128 + INTEGRATED_ID_SIZE * 2, + 128 + INTEGRATED_ID_SIZE * 2 + ADDRESS_CHECKSUM_SIZE * 2, + ); + expectedChecksum = cn_fast_hash( + prefix + spend + view + intPaymentId, + ).slice(0, ADDRESS_CHECKSUM_SIZE * 2); + } else { + checksum = dec.slice(128, 128 + ADDRESS_CHECKSUM_SIZE * 2); + expectedChecksum = cn_fast_hash(prefix + spend + view).slice( + 0, + ADDRESS_CHECKSUM_SIZE * 2, + ); + } + if (checksum !== expectedChecksum) { + throw Error("Invalid checksum"); + } + if (intPaymentId) { + return { + spend: spend, + view: view, + intPaymentId: intPaymentId, + }; + } else { + return { + spend: spend, + view: view, + }; + } +} + +export function is_subaddress(addr: string, nettype: NetType) { + const decoded = cnBase58.decode(addr); + const subaddressPrefix = encode_varint( + nettype_utils.cryptonoteBase58PrefixForSubAddressOn(nettype), + ); + const prefix = decoded.slice(0, subaddressPrefix.length); + return prefix === subaddressPrefix; +} + +function valid_keys( + view_pub: string, + view_sec: string, + spend_pub: string, + spend_sec: string, +) { + const expected_view_pub = sec_key_to_pub(view_sec); + const expected_spend_pub = sec_key_to_pub(spend_sec); + return expected_spend_pub === spend_pub && expected_view_pub === view_pub; +} + +export function hash_to_scalar(buf: string) { + const hash = cn_fast_hash(buf); + const scalar = sc_reduce32(hash); + return scalar; +} + +export function generate_key_derivation(pub: string, sec: string) { + if (pub.length !== 64 || sec.length !== 64) { + throw Error("Invalid input length"); + } + const P = ge_scalarmult(pub, sec); + return ge_scalarmult(P, d2s("8")); //mul8 to ensure group +} + +export function derivation_to_scalar(derivation: string, output_index: number) { + let buf = ""; + if (derivation.length !== STRUCT_SIZES.EC_POINT * 2) { + throw Error("Invalid derivation length!"); + } + buf += derivation; + const enc = encode_varint(output_index); + if (enc.length > 10 * 2) { + throw Error("output_index didn't fit in 64-bit varint"); + } + buf += enc; + return hash_to_scalar(buf); +} + +function derive_secret_key(derivation: string, out_index: number, sec: string) { + if (derivation.length !== 64 || sec.length !== 64) { + throw Error("Invalid input length!"); + } + const scalar_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + const scalar_b = hextobin(derivation_to_scalar(derivation, out_index)); + CNCrypto.HEAPU8.set(scalar_b, scalar_m); + const base_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hextobin(sec), base_m); + const derived_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + CNCrypto.ccall( + "sc_add", + "void", + ["number", "number", "number"], + [derived_m, base_m, scalar_m], + ); + const res = CNCrypto.HEAPU8.subarray( + derived_m, + derived_m + STRUCT_SIZES.EC_SCALAR, + ); + CNCrypto._free(scalar_m); + CNCrypto._free(base_m); + CNCrypto._free(derived_m); + return bintohex(res); +} + +export function derive_public_key( + derivation: string, + out_index: number, + pub: string, +) { + if (derivation.length !== 64 || pub.length !== 64) { + throw Error("Invalid input length!"); + } + const s = derivation_to_scalar(derivation, out_index); + return bintohex( + nacl.ge_add(hextobin(pub), hextobin(ge_scalarmult_base(s))), + ); +} + +// D' = P - Hs(aR|i)G +export function derive_subaddress_public_key( + output_key: string, + derivation: string, + out_index: number, +) { + if (output_key.length !== 64 || derivation.length !== 64) { + throw Error("Invalid input length!"); + } + const scalar = derivation_to_scalar(derivation, out_index); + const point = ge_scalarmult_base(scalar); + return ge_sub(output_key, point); +} + +function hash_to_ec(key: string) { + if (key.length !== KEY_SIZE * 2) { + throw Error("Invalid input length"); + } + const h_m = CNCrypto._malloc(HASH_SIZE); + const point_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); + const point2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P1P1); + const res_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); + const hash = hextobin(cn_fast_hash(key)); + CNCrypto.HEAPU8.set(hash, h_m); + CNCrypto.ccall( + "ge_fromfe_frombytes_vartime", + "void", + ["number", "number"], + [point_m, h_m], + ); + CNCrypto.ccall( + "ge_mul8", + "void", + ["number", "number"], + [point2_m, point_m], + ); + CNCrypto.ccall( + "ge_p1p1_to_p3", + "void", + ["number", "number"], + [res_m, point2_m], + ); + const res = CNCrypto.HEAPU8.subarray(res_m, res_m + STRUCT_SIZES.GE_P3); + CNCrypto._free(h_m); + CNCrypto._free(point_m); + CNCrypto._free(point2_m); + CNCrypto._free(res_m); + return bintohex(res); +} + +//returns a 32 byte point via "ge_p3_tobytes" rather than a 160 byte "p3", otherwise same as above; +function hash_to_ec_2(key: string) { + if (key.length !== KEY_SIZE * 2) { + throw Error("Invalid input length"); + } + const h_m = CNCrypto._malloc(HASH_SIZE); + const point_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); + const point2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P1P1); + const res_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); + const hash = hextobin(cn_fast_hash(key)); + const res2_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hash, h_m); + CNCrypto.ccall( + "ge_fromfe_frombytes_vartime", + "void", + ["number", "number"], + [point_m, h_m], + ); + CNCrypto.ccall( + "ge_mul8", + "void", + ["number", "number"], + [point2_m, point_m], + ); + CNCrypto.ccall( + "ge_p1p1_to_p3", + "void", + ["number", "number"], + [res_m, point2_m], + ); + CNCrypto.ccall( + "ge_p3_tobytes", + "void", + ["number", "number"], + [res2_m, res_m], + ); + const res = CNCrypto.HEAPU8.subarray(res2_m, res2_m + KEY_SIZE); + CNCrypto._free(h_m); + CNCrypto._free(point_m); + CNCrypto._free(point2_m); + CNCrypto._free(res_m); + CNCrypto._free(res2_m); + return bintohex(res); +} +export const hashToPoint = hash_to_ec_2; + +export function generate_key_image_2(pub: string, sec: string) { + if (!pub || !sec || pub.length !== 64 || sec.length !== 64) { + throw Error("Invalid input length"); + } + const pub_m = CNCrypto._malloc(KEY_SIZE); + const sec_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hextobin(pub), pub_m); + CNCrypto.HEAPU8.set(hextobin(sec), sec_m); + if (CNCrypto.ccall("sc_check", "number", ["number"], [sec_m]) !== 0) { + throw Error("sc_check(sec) != 0"); + } + const point_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); + const point2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); + const point_b = hextobin(hash_to_ec(pub)); + CNCrypto.HEAPU8.set(point_b, point_m); + const image_m = CNCrypto._malloc(STRUCT_SIZES.KEY_IMAGE); + CNCrypto.ccall( + "ge_scalarmult", + "void", + ["number", "number", "number"], + [point2_m, sec_m, point_m], + ); + CNCrypto.ccall( + "ge_tobytes", + "void", + ["number", "number"], + [image_m, point2_m], + ); + const res = CNCrypto.HEAPU8.subarray( + image_m, + image_m + STRUCT_SIZES.KEY_IMAGE, + ); + CNCrypto._free(pub_m); + CNCrypto._free(sec_m); + CNCrypto._free(point_m); + CNCrypto._free(point2_m); + CNCrypto._free(image_m); + return bintohex(res); +} + +export function generate_key_image( + tx_pub: string, + view_sec: string, + spend_pub: string, + spend_sec: string, + output_index: number, +) { + if (tx_pub.length !== 64) { + throw Error("Invalid tx_pub length"); + } + if (view_sec.length !== 64) { + throw Error("Invalid view_sec length"); + } + if (spend_pub.length !== 64) { + throw Error("Invalid spend_pub length"); + } + if (spend_sec.length !== 64) { + throw Error("Invalid spend_sec length"); + } + const recv_derivation = generate_key_derivation(tx_pub, view_sec); + const ephemeral_pub = derive_public_key( + recv_derivation, + output_index, + spend_pub, + ); + const ephemeral_sec = derive_secret_key( + recv_derivation, + output_index, + spend_sec, + ); + const k_image = generate_key_image_2(ephemeral_pub, ephemeral_sec); + return { + ephemeral_pub: ephemeral_pub, + key_image: k_image, + }; +} + +function generate_key_image_helper_rct( + keys: Keys, + tx_pub_key: string, + out_index: number, + enc_mask?: string | null, +) { + const recv_derivation = generate_key_derivation(tx_pub_key, keys.view.sec); + if (!recv_derivation) throw Error("Failed to generate key image"); + const mask = enc_mask + ? sc_sub( + enc_mask, + hash_to_scalar( + derivation_to_scalar(recv_derivation, out_index), + ), + ) + : I; //decode mask, or d2s(1) if no mask + const ephemeral_pub = derive_public_key( + recv_derivation, + out_index, + keys.spend.pub, + ); + if (!ephemeral_pub) throw Error("Failed to generate key image"); + const ephemeral_sec = derive_secret_key( + recv_derivation, + out_index, + keys.spend.sec, + ); + const key_image = generate_key_image_2(ephemeral_pub, ephemeral_sec); + return { + in_ephemeral: { + pub: ephemeral_pub, + sec: ephemeral_sec, + mask: mask, + }, + key_image, + }; +} + +//curve and scalar functions; split out to make their host functions cleaner and more readable +//inverts X coordinate -- this seems correct ^_^ -luigi1111 +function ge_neg(point: string) { + if (point.length !== 64) { + throw Error("expected 64 char hex string"); + } + return ( + point.slice(0, 62) + + ((parseInt(point.slice(62, 63), 16) + 8) % 16).toString(16) + + point.slice(63, 64) + ); +} + +export function ge_add(p1: string, p2: string) { + if (p1.length !== 64 || p2.length !== 64) { + throw Error("Invalid input length!"); + } + return bintohex(nacl.ge_add(hextobin(p1), hextobin(p2))); +} + +//order matters +export function ge_sub(point1: string, point2: string) { + const point2n = ge_neg(point2); + return ge_add(point1, point2n); +} + +//adds two scalars together +function sc_add(scalar1: string, scalar2: string) { + if (scalar1.length !== 64 || scalar2.length !== 64) { + throw Error("Invalid input length!"); + } + const scalar1_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + const scalar2_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + CNCrypto.HEAPU8.set(hextobin(scalar1), scalar1_m); + CNCrypto.HEAPU8.set(hextobin(scalar2), scalar2_m); + const derived_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + CNCrypto.ccall( + "sc_add", + "void", + ["number", "number", "number"], + [derived_m, scalar1_m, scalar2_m], + ); + const res = CNCrypto.HEAPU8.subarray( + derived_m, + derived_m + STRUCT_SIZES.EC_SCALAR, + ); + CNCrypto._free(scalar1_m); + CNCrypto._free(scalar2_m); + CNCrypto._free(derived_m); + return bintohex(res); +} + +//subtracts one scalar from another +function sc_sub(scalar1: string, scalar2: string) { + if (scalar1.length !== 64 || scalar2.length !== 64) { + throw Error("Invalid input length!"); + } + const scalar1_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + const scalar2_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + CNCrypto.HEAPU8.set(hextobin(scalar1), scalar1_m); + CNCrypto.HEAPU8.set(hextobin(scalar2), scalar2_m); + const derived_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + CNCrypto.ccall( + "sc_sub", + "void", + ["number", "number", "number"], + [derived_m, scalar1_m, scalar2_m], + ); + const res = CNCrypto.HEAPU8.subarray( + derived_m, + derived_m + STRUCT_SIZES.EC_SCALAR, + ); + CNCrypto._free(scalar1_m); + CNCrypto._free(scalar2_m); + CNCrypto._free(derived_m); + return bintohex(res); +} + +//res = c - (ab) mod l; argument names copied from the signature implementation +function sc_mulsub(sigc: string, sec: string, k: string) { + if ( + k.length !== KEY_SIZE * 2 || + sigc.length !== KEY_SIZE * 2 || + sec.length !== KEY_SIZE * 2 || + !valid_hex(k) || + !valid_hex(sigc) || + !valid_hex(sec) + ) { + throw Error("bad scalar"); + } + const sec_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hextobin(sec), sec_m); + const sigc_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hextobin(sigc), sigc_m); + const k_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hextobin(k), k_m); + const res_m = CNCrypto._malloc(KEY_SIZE); + + CNCrypto.ccall( + "sc_mulsub", + "void", + ["number", "number", "number", "number"], + [res_m, sigc_m, sec_m, k_m], + ); + const res = CNCrypto.HEAPU8.subarray(res_m, res_m + KEY_SIZE); + CNCrypto._free(k_m); + CNCrypto._free(sec_m); + CNCrypto._free(sigc_m); + CNCrypto._free(res_m); + return bintohex(res); +} + +function ge_double_scalarmult_base_vartime(c: string, P: string, r: string) { + if (c.length !== 64 || P.length !== 64 || r.length !== 64) { + throw Error("Invalid input length!"); + } + return bintohex( + nacl.ge_double_scalarmult_base_vartime( + hextobin(c), + hextobin(P), + hextobin(r), + ), + ); +} + +function ge_double_scalarmult_postcomp_vartime( + r: string, + P: string, + c: string, + I: string, +) { + if ( + c.length !== 64 || + P.length !== 64 || + r.length !== 64 || + I.length !== 64 + ) { + throw Error("Invalid input length!"); + } + const Pb = hash_to_ec_2(P); + return bintohex( + nacl.ge_double_scalarmult_postcomp_vartime( + hextobin(r), + hextobin(Pb), + hextobin(c), + hextobin(I), + ), + ); +} + +//begin RCT functions + +//xv: vector of secret keys, 1 per ring (nrings) +//pm: matrix of pubkeys, indexed by size first +//iv: vector of indexes, 1 per ring (nrings), can be a string +//size: ring size, default 2 +//nrings: number of rings, default 64 +//extensible borromean signatures +interface BorromeanSignature { + s: string[][]; + ee: string; +} + +export function genBorromean( + xv: string[], + pm: string[][], + iv: string[] | string, + size: number, + nrings: number, +) { + if (xv.length !== nrings) { + throw Error("wrong xv length " + xv.length); + } + if (pm.length !== size) { + throw Error("wrong pm size " + pm.length); + } + for (let i = 0; i < pm.length; i++) { + if (pm[i].length !== nrings) { + throw Error("wrong pm[" + i + "] length " + pm[i].length); + } + } + if (iv.length !== nrings) { + throw Error("wrong iv length " + iv.length); + } + for (let i = 0; i < iv.length; i++) { + if (+iv[i] >= size) { + throw Error("bad indices value at: " + i + ": " + iv[i]); + } + } + //signature struct + // in the case of size 2 and nrings 64 + // bb.s = [[64], [64]] + + const bb: BorromeanSignature = { + s: [], + ee: "", + }; + //signature pubkey matrix + const L: string[][] = []; + //add needed sub vectors (1 per ring size) + for (let i = 0; i < size; i++) { + bb.s[i] = []; + L[i] = []; + } + //compute starting at the secret index to the last row + let index; + const alpha = []; + for (let i = 0; i < nrings; i++) { + index = parseInt(iv[i]); + alpha[i] = random_scalar(); + L[index][i] = ge_scalarmult_base(alpha[i]); + for (let j = index + 1; j < size; j++) { + bb.s[j][i] = random_scalar(); + const c = hash_to_scalar(L[j - 1][i]); + L[j][i] = ge_double_scalarmult_base_vartime( + c, + pm[j][i], + bb.s[j][i], + ); + } + } + //hash last row to create ee + let ltemp = ""; + for (let i = 0; i < nrings; i++) { + ltemp += L[size - 1][i]; + } + bb.ee = hash_to_scalar(ltemp); + //compute the rest from 0 to secret index + let j: number; + for (let i = 0; i < nrings; i++) { + let cc = bb.ee; + for (j = 0; j < +iv[i]; j++) { + bb.s[j][i] = random_scalar(); + const LL = ge_double_scalarmult_base_vartime( + cc, + pm[j][i], + bb.s[j][i], + ); + cc = hash_to_scalar(LL); + } + bb.s[j][i] = sc_mulsub(xv[i], cc, alpha[i]); + } + return bb; +} + +export function verifyBorromean( + bb: BorromeanSignature, + P1: string[], + P2: string[], +) { + let Lv1 = []; + let chash; + let LL; + + let p2 = ""; + for (let ii = 0; ii < 64; ii++) { + p2 = ge_double_scalarmult_base_vartime(bb.ee, P1[ii], bb.s[0][ii]); + LL = p2; + chash = hash_to_scalar(LL); + + p2 = ge_double_scalarmult_base_vartime(chash, P2[ii], bb.s[1][ii]); + Lv1[ii] = p2; + } + const eeComputed = array_hash_to_scalar(Lv1); + const equalKeys = eeComputed === bb.ee; + console.log(`[verifyBorromean] Keys equal? ${equalKeys} + ${eeComputed} + ${bb.ee}`); + + return equalKeys; +} + +//proveRange +//proveRange gives C, and mask such that \sumCi = C +// c.f. http://eprint.iacr.org/2015/1098 section 5.1 +// and Ci is a commitment to either 0 or s^i, i=0,...,n +// thus this proves that "amount" is in [0, s^n] (we assume s to be 4) (2 for now with v2 txes) +// mask is a such that C = aG + bH, and b = amount +//commitMaskObj = {C: commit, mask: mask} + +interface CommitMask { + C: string; + mask: string; +} + +interface RangeSignature { + Ci: string[]; + bsig: BorromeanSignature; +} + +function proveRange( + commitMaskObj: CommitMask, + amount: string | BigInt, + nrings: number, +) { + const size = 2; + let C = I; //identity + let mask = Z; //zero scalar + const indices = d2b(amount); //base 2 for now + const Ci: string[] = []; + + const ai: string[] = []; + const PM: string[][] = []; + for (let i = 0; i < size; i++) { + PM[i] = []; + } + let j; + //start at index and fill PM left and right -- PM[0] holds Ci + for (let i = 0; i < nrings; i++) { + ai[i] = random_scalar(); + + j = +indices[i]; + PM[j][i] = ge_scalarmult_base(ai[i]); + while (j > 0) { + j--; + PM[j][i] = ge_add(PM[j + 1][i], H2[i]); //will need to use i*2 for base 4 (or different object) + } + + j = +indices[i]; + while (j < size - 1) { + j++; + PM[j][i] = ge_sub(PM[j - 1][i], H2[i]); //will need to use i*2 for base 4 (or different object) + } + mask = sc_add(mask, ai[i]); + } + /* + * some more payload stuff here + */ + //copy commitments to sig and sum them to commitment + for (let i = 0; i < nrings; i++) { + //if (i < nrings - 1) //for later version + Ci[i] = PM[0][i]; + C = ge_add(C, PM[0][i]); + } + + const sig: RangeSignature = { + Ci, + bsig: genBorromean(ai, PM, indices, size, nrings), + }; + //exp: exponent //doesn't exist for now + + commitMaskObj.C = C; + commitMaskObj.mask = mask; + return sig; +} + +//proveRange and verRange +//proveRange gives C, and mask such that \sumCi = C +// c.f. http://eprint.iacr.org/2015/1098 section 5.1 +// and Ci is a commitment to either 0 or 2^i, i=0,...,63 +// thus this proves that "amount" is in [0, 2^64] +// mask is a such that C = aG + bH, and b = amount +//verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i + +function verRange(C: string, as: RangeSignature, nrings = 64) { + try { + let CiH = []; // len 64 + let asCi = []; // len 64 + let Ctmp = identity(); + for (let i = 0; i < nrings; i++) { + CiH[i] = ge_sub(as.Ci[i], H2[i]); + asCi[i] = as.Ci[i]; + Ctmp = ge_add(Ctmp, as.Ci[i]); + } + const equalKeys = Ctmp === C; + console.log(`[verRange] Equal keys? ${equalKeys} + C: ${C} + Ctmp: ${Ctmp}`); + if (!equalKeys) { + return false; + } + + if (!verifyBorromean(as.bsig, asCi, CiH)) { + return false; + } + + return true; + } catch (e) { + console.error(`[verRange]`, e); + return false; + } +} + +function array_hash_to_scalar(array: string[]) { + let buf = ""; + for (let i = 0; i < array.length; i++) { + buf += array[i]; + } + return hash_to_scalar(buf); +} + +// Gen creates a signature which proves that for some column in the keymatrix "pk" +// the signer knows a secret key for each row in that column +// we presently only support matrices of 2 rows (pubkey, commitment) +// this is a simplied MLSAG_Gen function to reflect that +// because we don't want to force same secret column for all inputs + +interface MGSig { + ss: string[][]; + cc: string; +} +export function MLSAG_Gen( + message: string, + pk: string[][], + xx: string[], + kimg: string, + index: number, +) { + const cols = pk.length; //ring size + let i; + + // secret index + if (index >= cols) { + throw Error("index out of range"); + } + const rows = pk[0].length; //number of signature rows (always 2) + // [pub, com] = 2 + if (rows !== 2) { + throw Error("wrong row count"); + } + // check all are len 2 + for (i = 0; i < cols; i++) { + if (pk[i].length !== rows) { + throw Error("pk is not rectangular"); + } + } + if (xx.length !== rows) { + throw Error("bad xx size"); + } + + let c_old = ""; + const alpha = []; + + const rv: MGSig = { + ss: [], + cc: "", + }; + for (i = 0; i < cols; i++) { + rv.ss[i] = []; + } + const toHash = []; //holds 6 elements: message, pubkey, dsRow L, dsRow R, commitment, ndsRow L + toHash[0] = message; + + //secret index (pubkey section) + + alpha[0] = random_scalar(); //need to save alphas for later + toHash[1] = pk[index][0]; //secret index pubkey + + // this is the keyimg anyway const H1 = hashToPoint(pk[index][0]) // Hp(K_in) + // rv.II[0] = ge_scalarmult(H1, xx[0]) // k_in.Hp(K_in) + + toHash[2] = ge_scalarmult_base(alpha[0]); //dsRow L, a.G + toHash[3] = generate_key_image_2(pk[index][0], alpha[0]); //dsRow R (key image check) + //secret index (commitment section) + alpha[1] = random_scalar(); + toHash[4] = pk[index][1]; //secret index commitment + toHash[5] = ge_scalarmult_base(alpha[1]); //ndsRow L + + c_old = array_hash_to_scalar(toHash); + + i = (index + 1) % cols; + if (i === 0) { + rv.cc = c_old; + } + while (i != index) { + rv.ss[i][0] = random_scalar(); //dsRow ss + rv.ss[i][1] = random_scalar(); //ndsRow ss + + //!secret index (pubkey section) + toHash[1] = pk[i][0]; + toHash[2] = ge_double_scalarmult_base_vartime( + c_old, + pk[i][0], + rv.ss[i][0], + ); + toHash[3] = ge_double_scalarmult_postcomp_vartime( + rv.ss[i][0], + pk[i][0], + c_old, + kimg, + ); + //!secret index (commitment section) + toHash[4] = pk[i][1]; + toHash[5] = ge_double_scalarmult_base_vartime( + c_old, + pk[i][1], + rv.ss[i][1], + ); + c_old = array_hash_to_scalar(toHash); //hash to get next column c + i = (i + 1) % cols; + if (i === 0) { + rv.cc = c_old; + } + } + for (i = 0; i < rows; i++) { + rv.ss[index][i] = sc_mulsub(c_old, xx[i], alpha[i]); + } + return rv; +} + +export function MLSAG_ver( + message: string, + pk: string[][], + rv: MGSig, + kimg: string, +) { + // we assume that col, row, rectangular checks are already done correctly + // in MLSAG_gen + const cols = pk.length; + let c_old = rv.cc; + let i = 0; + let toHash = []; + toHash[0] = message; + while (i < cols) { + //!secret index (pubkey section) + toHash[1] = pk[i][0]; + toHash[2] = ge_double_scalarmult_base_vartime( + c_old, + pk[i][0], + rv.ss[i][0], + ); + toHash[3] = ge_double_scalarmult_postcomp_vartime( + rv.ss[i][0], + pk[i][0], + c_old, + kimg, + ); + + //!secret index (commitment section) + toHash[4] = pk[i][1]; + toHash[5] = ge_double_scalarmult_base_vartime( + c_old, + pk[i][1], + rv.ss[i][1], + ); + + c_old = array_hash_to_scalar(toHash); + + i = i + 1; + } + + const c = sc_sub(c_old, rv.cc); + console.log(`[MLSAG_ver] + c_old: ${c_old} + rc.cc: ${rv.cc} + c: ${c}`); + + return Number(c) === 0; +} + +//Ring-ct MG sigs +//Prove: +// c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. +// This does the MG sig on the "dest" part of the given key matrix, and +// the last row is the sum of input commitments from that column - sum output commitments +// this shows that sum inputs = sum outputs +//Ver: +// verifies the above sig is created corretly +function proveRctMG( + message: string, + pubs: MixCommitment[], + inSk: SecretCommitment, + kimg: string, + mask: string, + Cout: string, + index: number, +) { + const cols = pubs.length; + if (cols < 3) { + throw Error("cols must be > 2 (mixin)"); + } + + const PK: string[][] = []; + //fill pubkey matrix (copy destination, subtract commitments) + for (let i = 0; i < cols; i++) { + PK[i] = []; + PK[i][0] = pubs[i].dest; + PK[i][1] = ge_sub(pubs[i].mask, Cout); + } + + const xx = [inSk.x, sc_sub(inSk.a, mask)]; + return MLSAG_Gen(message, PK, xx, kimg, index); +} + +//Ring-ct MG sigs +//Prove: +// c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. +// This does the MG sig on the "dest" part of the given key matrix, and +// the last row is the sum of input commitments from that column - sum output commitments +// this shows that sum inputs = sum outputs +//Ver: +// verifies the above sig is created corretly + +function verRctMG( + mg: MGSig, + pubs: MixCommitment[][], + outPk: string[], + txnFeeKey: string, + message: string, + kimg: string, +) { + const cols = pubs.length; + if (cols < 1) { + throw Error("Empty pubs"); + } + const rows = pubs[0].length; + + if (rows < 1) { + throw Error("Empty pubs"); + } + + for (let i = 0; i < cols; ++i) { + if (pubs[i].length !== rows) { + throw Error("Pubs is not rectangular"); + } + } + + // key matrix of (cols, tmp) + + let M: string[][] = []; + console.log(pubs); + //create the matrix to mg sig + for (let i = 0; i < rows; i++) { + M[i] = []; + M[i][0] = pubs[0][i].dest; + M[i][1] = ge_add(M[i][1] || identity(), pubs[0][i].mask); // start with input commitment + for (let j = 0; j < outPk.length; j++) { + M[i][1] = ge_sub(M[i][1], outPk[j]); // subtract all output commitments + } + M[i][1] = ge_sub(M[i][1], txnFeeKey); // subtract txnfee + } + + console.log( + `[MLSAG_ver input]`, + JSON.stringify({ message, M, mg, kimg }, null, 1), + ); + return MLSAG_ver(message, M, mg, kimg); +} + +// simple version, assuming only post Rct + +function verRctMGSimple( + message: string, + mg: MGSig, + pubs: MixCommitment[], + C: string, + kimg: string, +) { + try { + const M: string[][] = pubs.map(pub => [pub.dest, ge_sub(pub.mask, C)]); + + return MLSAG_ver(message, M, mg, kimg); + } catch (error) { + console.error("[verRctSimple]", error); + return false; + } +} + +function verBulletProof(..._: any[]) { + throw Error("verBulletProof is not implemented"); +} + +function get_pre_mlsag_hash(rv: RCTSignatures) { + let hashes = ""; + hashes += rv.message; + hashes += cn_fast_hash(serialize_rct_base(rv)); + const buf = serialize_range_proofs(rv); + hashes += cn_fast_hash(buf); + return cn_fast_hash(hashes); +} + +function serialize_range_proofs(rv: RCTSignatures) { + let buf = ""; + + for (let i = 0; i < rv.p.rangeSigs.length; i++) { + for (let j = 0; j < rv.p.rangeSigs[i].bsig.s.length; j++) { + for (let l = 0; l < rv.p.rangeSigs[i].bsig.s[j].length; l++) { + buf += rv.p.rangeSigs[i].bsig.s[j][l]; + } + } + buf += rv.p.rangeSigs[i].bsig.ee; + for (let j = 0; j < rv.p.rangeSigs[i].Ci.length; j++) { + buf += rv.p.rangeSigs[i].Ci[j]; + } + } + return buf; +} + +//message is normal prefix hash +//inSk is vector of x,a +//kimg is vector of kimg +//destinations is vector of pubkeys (we skip and proxy outAmounts instead) +//inAmounts is vector of strings +//outAmounts is vector of strings +//mixRing is matrix of pubkey, commit (dest, mask) +//amountKeys is vector of scalars +//indices is vector +//txnFee is string, with its endian not swapped (e.g d2s is not called before passing it in as an argument) +//to this function + +interface RCTSignatures { + type: number; + message: string; + outPk: string[]; + p: { + rangeSigs: RangeSignature[]; + MGs: MGSig[]; + }; + ecdhInfo: Commit[]; + txnFee: string; + pseudoOuts: string[]; +} +export function genRct( + message: string, + inSk: SecretCommitment[], + kimg: string[], + inAmounts: (BigInt | string)[], + outAmounts: (BigInt | string)[], + mixRing: MixCommitment[][], + amountKeys: string[], + indices: number[], + txnFee: string, +) { + if (outAmounts.length !== amountKeys.length) { + throw Error("different number of amounts/amount_keys"); + } + for (let i = 0; i < mixRing.length; i++) { + if (mixRing[i].length <= indices[i]) { + throw Error("bad mixRing/index size"); + } + } + if (mixRing.length !== inSk.length) { + throw Error("mismatched mixRing/inSk"); + } + + if (indices.length !== inSk.length) { + throw Error("mismatched indices/inSk"); + } + + const rv: RCTSignatures = { + type: inSk.length === 1 ? RCTTypeFull : RCTTypeSimple, + message, + outPk: [], + p: { + rangeSigs: [], + MGs: [], + }, + ecdhInfo: [], + txnFee, + pseudoOuts: [], + }; + + let sumout = Z; + const cmObj = { + C: "", + mask: "", + }; + + const nrings = 64; //for base 2/current + let i; + //compute range proofs, etc + for (i = 0; i < outAmounts.length; i++) { + const teststart = new Date().getTime(); + rv.p.rangeSigs[i] = proveRange(cmObj, outAmounts[i], nrings); + const testfinish = new Date().getTime() - teststart; + console.log("Time take for range proof " + i + ": " + testfinish); + rv.outPk[i] = cmObj.C; + sumout = sc_add(sumout, cmObj.mask); + rv.ecdhInfo[i] = encode_rct_ecdh( + { mask: cmObj.mask, amount: d2s(outAmounts[i]) }, + amountKeys[i], + ); + } + + //simple + if (rv.type === 2) { + if (inAmounts.length !== inSk.length) { + throw Error("mismatched inAmounts/inSk"); + } + + const ai = []; + let sumpouts = Z; + //create pseudoOuts + for (i = 0; i < inAmounts.length - 1; i++) { + ai[i] = random_scalar(); + sumpouts = sc_add(sumpouts, ai[i]); + rv.pseudoOuts[i] = commit(d2s(inAmounts[i]), ai[i]); + } + ai[i] = sc_sub(sumout, sumpouts); + rv.pseudoOuts[i] = commit(d2s(inAmounts[i]), ai[i]); + const full_message = get_pre_mlsag_hash(rv); + for (i = 0; i < inAmounts.length; i++) { + rv.p.MGs.push( + proveRctMG( + full_message, + mixRing[i], + inSk[i], + kimg[i], + ai[i], + rv.pseudoOuts[i], + indices[i], + ), + ); + } + } else { + let sumC = I; + //get sum of output commitments to use in MLSAG + for (i = 0; i < rv.outPk.length; i++) { + sumC = ge_add(sumC, rv.outPk[i]); + } + sumC = ge_add(sumC, ge_scalarmult(H, d2s(rv.txnFee))); + const full_message = get_pre_mlsag_hash(rv); + rv.p.MGs.push( + proveRctMG( + full_message, + mixRing[0], + inSk[0], + kimg[0], + sumout, + sumC, + indices[0], + ), + ); + } + return rv; +} + +export function verRct( + rv: RCTSignatures, + semantics: boolean, + mixRing: MixCommitment[][], + kimg: string, +) { + if (rv.type === 0x03) { + throw Error("Bulletproof validation not implemented"); + } + + // where RCTTypeFull is 0x01 and RCTTypeFullBulletproof is 0x03 + if (rv.type !== 0x01 && rv.type !== 0x03) { + throw Error("verRct called on non-full rctSig"); + } + if (semantics) { + //RCTTypeFullBulletproof checks not implemented + // RCTTypeFull checks + if (rv.outPk.length !== rv.p.rangeSigs.length) { + throw Error("Mismatched sizes of outPk and rv.p.rangeSigs"); + } + if (rv.outPk.length !== rv.ecdhInfo.length) { + throw Error("Mismatched sizes of outPk and rv.ecdhInfo"); + } + if (rv.p.MGs.length !== 1) { + throw Error("full rctSig has not one MG"); + } + } else { + // semantics check is early, we don't have the MGs resolved yet + } + try { + if (semantics) { + const results = []; + for (let i = 0; i < rv.outPk.length; i++) { + // might want to parallelize this like its done in the c++ codebase + // via some abstraction library to support browser + node + if (rv.p.rangeSigs.length === 0) { + results[i] = verBulletProof((rv.p as any).bulletproofs[i]); + } else { + // mask -> C if public + results[i] = verRange(rv.outPk[i], rv.p.rangeSigs[i]); + } + } + + for (let i = 0; i < rv.outPk.length; i++) { + if (!results[i]) { + console.error( + "Range proof verification failed for output", + i, + ); + return false; + } + } + } else { + // compute txn fee + const txnFeeKey = ge_scalarmult(H, d2s(rv.txnFee)); + const mgVerd = verRctMG( + rv.p.MGs[0], + mixRing, + rv.outPk, + txnFeeKey, + get_pre_mlsag_hash(rv), + kimg, + ); + console.log("mg sig verified?", mgVerd); + if (!mgVerd) { + console.error("MG Signature verification failed"); + return false; + } + } + return true; + } catch (e) { + console.error("Error in verRct: ", e); + return false; + } +} + +//ver RingCT simple +//assumes only post-rct style inputs (at least for max anonymity) +export function verRctSimple( + rv: RCTSignatures, + semantics: boolean, + mixRing: MixCommitment[][], + kimgs: string[], +) { + try { + if (rv.type === 0x04) { + throw Error("Simple Bulletproof validation not implemented"); + } + + if (rv.type !== 0x02 && rv.type !== 0x04) { + throw Error("verRctSimple called on non simple rctSig"); + } + + if (semantics) { + if (rv.type == 0x04) { + throw Error("Simple Bulletproof validation not implemented"); + } else { + if (rv.outPk.length !== rv.p.rangeSigs.length) { + throw Error("Mismatched sizes of outPk and rv.p.rangeSigs"); + } + if (rv.pseudoOuts.length !== rv.p.MGs.length) { + throw Error( + "Mismatched sizes of rv.pseudoOuts and rv.p.MGs", + ); + } + // originally the check is rv.p.pseudoOuts.length, but this'll throw + // until p.pseudoOuts is added as a property to the rv object + if ((rv.p as any).pseudoOuts) { + throw Error("rv.p.pseudoOuts must be empty"); + } + } + } else { + if (rv.type === 0x04) { + throw Error("Simple Bulletproof validation not implemented"); + } else { + // semantics check is early, and mixRing/MGs aren't resolved yet + if (rv.pseudoOuts.length !== mixRing.length) { + throw Error( + "Mismatched sizes of rv.pseudoOuts and mixRing", + ); + } + } + } + + // if bulletproof, then use rv.p.pseudoOuts, otherwise use rv.pseudoOuts + const pseudoOuts = + (rv.type as number) === 0x04 + ? (rv.p as any).pseudoOuts + : rv.pseudoOuts; + + if (semantics) { + let sumOutpks = identity(); + for (let i = 0; i < rv.outPk.length; i++) { + sumOutpks = ge_add(sumOutpks, rv.outPk[i]); // add all of the output commitments + } + + const txnFeeKey = ge_scalarmult(H, d2s(rv.txnFee)); + sumOutpks = ge_add(txnFeeKey, sumOutpks); // add txnfeekey + + let sumPseudoOuts = identity(); + for (let i = 0; i < pseudoOuts.length; i++) { + sumPseudoOuts = ge_add(sumPseudoOuts, pseudoOuts[i]); // sum up all of the pseudoOuts + } + + if (sumOutpks !== sumPseudoOuts) { + console.error("Sum check failed"); + return false; + } + + const results = []; + for (let i = 0; i < rv.outPk.length; i++) { + // might want to parallelize this like its done in the c++ codebase + // via some abstraction library to support browser + node + if (rv.p.rangeSigs.length === 0) { + results[i] = verBulletProof((rv.p as any).bulletproofs[i]); + } else { + // mask -> C if public + results[i] = verRange(rv.outPk[i], rv.p.rangeSigs[i]); + } + } + + for (let i = 0; i < results.length; i++) { + if (!results[i]) { + console.error( + "Range proof verification failed for output", + i, + ); + return false; + } + } + } else { + const message = get_pre_mlsag_hash(rv); + const results = []; + for (let i = 0; i < mixRing.length; i++) { + results[i] = verRctMGSimple( + message, + rv.p.MGs[i], + mixRing[i], + pseudoOuts[i], + kimgs[i], + ); + } + + for (let i = 0; i < results.length; i++) { + if (!results[i]) { + console.error( + "Range proof verification failed for output", + i, + ); + return false; + } + } + } + + return true; + } catch (error) { + console.log("[verRctSimple]", error); + return false; + } +} + +//decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) +// uses the attached ecdh info to find the amounts represented by each output commitment +// must know the destination private key to find the correct amount, else will return a random number + +export function decodeRct(rv: RCTSignatures, sk: string, i: number) { + // where RCTTypeFull is 0x01 and RCTTypeFullBulletproof is 0x03 + if (rv.type !== 0x01 && rv.type !== 0x03) { + throw Error("verRct called on non-full rctSig"); + } + if (i >= rv.ecdhInfo.length) { + throw Error("Bad index"); + } + if (rv.outPk.length !== rv.ecdhInfo.length) { + throw Error("Mismatched sizes of rv.outPk and rv.ecdhInfo"); + } + + // mask amount and mask + const ecdh_info = rv.ecdhInfo[i]; + const { mask, amount } = decode_rct_ecdh(ecdh_info, sk); + + const C = rv.outPk[i]; + const Ctmp = ge_double_scalarmult_base_vartime(amount, H, mask); + + console.log("[decodeRct]", C, Ctmp); + if (C !== Ctmp) { + throw Error( + "warning, amount decoded incorrectly, will be unable to spend", + ); + } + return { amount, mask }; +} + +export function decodeRctSimple(rv: RCTSignatures, sk: string, i: number) { + if (rv.type !== 0x02 && rv.type !== 0x04) { + throw Error("verRct called on full rctSig"); + } + if (i >= rv.ecdhInfo.length) { + throw Error("Bad index"); + } + if (rv.outPk.length !== rv.ecdhInfo.length) { + throw Error("Mismatched sizes of rv.outPk and rv.ecdhInfo"); + } + + // mask amount and mask + const ecdh_info = rv.ecdhInfo[i]; + const { mask, amount } = decode_rct_ecdh(ecdh_info, sk); + + const C = rv.outPk[i]; + const Ctmp = ge_double_scalarmult_base_vartime(amount, H, mask); + + console.log("[decodeRctSimple]", C, Ctmp); + if (C !== Ctmp) { + throw Error( + "warning, amount decoded incorrectly, will be unable to spend", + ); + } + return { amount, mask }; +} + +//end RCT functions + +function add_pub_key_to_extra(extra: string, pubkey: string) { + if (pubkey.length !== 64) throw Error("Invalid pubkey length"); + // Append pubkey tag and pubkey + extra += TX_EXTRA_TAGS.PUBKEY + pubkey; + return extra; +} + +function add_nonce_to_extra(extra: string, nonce: string) { + // Append extra nonce + if (nonce.length % 2 !== 0) { + throw Error("Invalid extra nonce"); + } + if (nonce.length / 2 > TX_EXTRA_NONCE_MAX_COUNT) { + throw Error( + "Extra nonce must be at most " + + TX_EXTRA_NONCE_MAX_COUNT + + " bytes", + ); + } + // Add nonce tag + extra += TX_EXTRA_TAGS.NONCE; + // Encode length of nonce + extra += ("0" + (nonce.length / 2).toString(16)).slice(-2); + // Write nonce + extra += nonce; + return extra; +} + +function get_payment_id_nonce(payment_id: string, pid_encrypt: boolean) { + if (payment_id.length !== 64 && payment_id.length !== 16) { + throw Error("Invalid payment id"); + } + let res = ""; + if (pid_encrypt) { + res += TX_EXTRA_NONCE_TAGS.ENCRYPTED_PAYMENT_ID; + } else { + res += TX_EXTRA_NONCE_TAGS.PAYMENT_ID; + } + res += payment_id; + return res; +} + +function abs_to_rel_offsets(offsets: string[]) { + if (offsets.length === 0) return offsets; + for (let i = offsets.length - 1; i >= 1; --i) { + offsets[i] = new BigInt(offsets[i]).subtract(offsets[i - 1]).toString(); + } + return offsets; +} + +function get_tx_prefix_hash(tx: SignedTransaction) { + const prefix = serialize_tx(tx, true); + return cn_fast_hash(prefix); +} + +export function serialize_tx(tx: SignedTransaction, headeronly?: boolean) { + //tx: { + // version: uint64, + // unlock_time: uint64, + // extra: hex, + // vin: [{amount: uint64, k_image: hex, key_offsets: [uint64,..]},...], + // vout: [{amount: uint64, target: {key: hex}},...], + // signatures: [[s,s,...],...] + //} + + if (!tx.signatures) { + throw Error("This transaction does not contain pre rct signatures"); + } + + let buf = ""; + buf += encode_varint(tx.version); + buf += encode_varint(tx.unlock_time); + buf += encode_varint(tx.vin.length); + let i, j; + for (i = 0; i < tx.vin.length; i++) { + const vin = tx.vin[i]; + switch (vin.type) { + case "input_to_key": + buf += "02"; + buf += encode_varint(vin.amount); + buf += encode_varint(vin.key_offsets.length); + for (j = 0; j < vin.key_offsets.length; j++) { + buf += encode_varint(vin.key_offsets[j]); + } + buf += vin.k_image; + break; + default: + throw Error("Unhandled vin type: " + vin.type); + } + } + buf += encode_varint(tx.vout.length); + for (i = 0; i < tx.vout.length; i++) { + const vout = tx.vout[i]; + buf += encode_varint(vout.amount); + switch (vout.target.type) { + case "txout_to_key": + buf += "02"; + buf += vout.target.key; + break; + default: + throw Error("Unhandled txout target type: " + vout.target.type); + } + } + if (!valid_hex(tx.extra)) { + throw Error("Tx extra has invalid hex"); + } + buf += encode_varint(tx.extra.length / 2); + buf += tx.extra; + if (!headeronly) { + if (tx.vin.length !== tx.signatures.length) { + throw Error("Signatures length != vin length"); + } + for (i = 0; i < tx.vin.length; i++) { + for (j = 0; j < tx.signatures[i].length; j++) { + buf += tx.signatures[i][j]; + } + } + } + return buf; +} + +// RCT only +export function serialize_rct_tx_with_hash(tx: SignedTransaction) { + if (!tx.rct_signatures) { + throw Error("This transaction does not contain rct_signatures"); + } + + let hashes = ""; + let buf = ""; + buf += serialize_tx(tx, true); + hashes += cn_fast_hash(buf); + const buf2 = serialize_rct_base(tx.rct_signatures); + hashes += cn_fast_hash(buf2); + buf += buf2; + let buf3 = serialize_range_proofs(tx.rct_signatures); + //add MGs + for (let i = 0; i < tx.rct_signatures.p.MGs.length; i++) { + for (let j = 0; j < tx.rct_signatures.p.MGs[i].ss.length; j++) { + buf3 += tx.rct_signatures.p.MGs[i].ss[j][0]; + buf3 += tx.rct_signatures.p.MGs[i].ss[j][1]; + } + buf3 += tx.rct_signatures.p.MGs[i].cc; + } + hashes += cn_fast_hash(buf3); + buf += buf3; + const hash = cn_fast_hash(hashes); + return { + raw: buf, + hash: hash, + }; +} + +function serialize_rct_base(rv: RCTSignatures) { + let buf = ""; + buf += encode_varint(rv.type); + buf += encode_varint(rv.txnFee); + if (rv.type === 2) { + for (let i = 0; i < rv.pseudoOuts.length; i++) { + buf += rv.pseudoOuts[i]; + } + } + if (rv.ecdhInfo.length !== rv.outPk.length) { + throw Error("mismatched outPk/ecdhInfo!"); + } + for (let i = 0; i < rv.ecdhInfo.length; i++) { + buf += rv.ecdhInfo[i].mask; + buf += rv.ecdhInfo[i].amount; + } + for (let i = 0; i < rv.outPk.length; i++) { + buf += rv.outPk[i]; + } + return buf; +} + +function generate_ring_signature( + prefix_hash: string, + k_image: string, + keys: string[], + sec: string, + real_index: number, +) { + if (k_image.length !== STRUCT_SIZES.KEY_IMAGE * 2) { + throw Error("invalid key image length"); + } + if (sec.length !== KEY_SIZE * 2) { + throw Error("Invalid secret key length"); + } + if (prefix_hash.length !== HASH_SIZE * 2 || !valid_hex(prefix_hash)) { + throw Error("Invalid prefix hash"); + } + if (real_index >= keys.length || real_index < 0) { + throw Error("real_index is invalid"); + } + const _ge_tobytes = CNCrypto.cwrap("ge_tobytes", "void", [ + "number", + "number", + ]); + const _ge_p3_tobytes = CNCrypto.cwrap("ge_p3_tobytes", "void", [ + "number", + "number", + ]); + const _ge_scalarmult_base = CNCrypto.cwrap("ge_scalarmult_base", "void", [ + "number", + "number", + ]); + const _ge_scalarmult = CNCrypto.cwrap("ge_scalarmult", "void", [ + "number", + "number", + "number", + ]); + const _sc_add = CNCrypto.cwrap("sc_add", "void", [ + "number", + "number", + "number", + ]); + const _sc_sub = CNCrypto.cwrap("sc_sub", "void", [ + "number", + "number", + "number", + ]); + const _sc_mulsub = CNCrypto.cwrap("sc_mulsub", "void", [ + "number", + "number", + "number", + "number", + ]); + const _sc_0 = CNCrypto.cwrap("sc_0", "void", ["number"]); + const _ge_double_scalarmult_base_vartime = CNCrypto.cwrap( + "ge_double_scalarmult_base_vartime", + "void", + ["number", "number", "number", "number"], + ); + const _ge_double_scalarmult_precomp_vartime = CNCrypto.cwrap( + "ge_double_scalarmult_precomp_vartime", + "void", + ["number", "number", "number", "number", "number"], + ); + const _ge_frombytes_vartime = CNCrypto.cwrap( + "ge_frombytes_vartime", + "number", + ["number", "number"], + ); + const _ge_dsm_precomp = CNCrypto.cwrap("ge_dsm_precomp", "void", [ + "number", + "number", + ]); + + const buf_size = STRUCT_SIZES.EC_POINT * 2 * keys.length; + const buf_m = CNCrypto._malloc(buf_size); + const sig_size = STRUCT_SIZES.SIGNATURE * keys.length; + const sig_m = CNCrypto._malloc(sig_size); + + // Struct pointer helper functions + function buf_a(i: number) { + return buf_m + STRUCT_SIZES.EC_POINT * (2 * i); + } + function buf_b(i: number) { + return buf_m + STRUCT_SIZES.EC_POINT * (2 * i + 1); + } + function sig_c(i: number) { + return sig_m + STRUCT_SIZES.EC_SCALAR * (2 * i); + } + function sig_r(i: number) { + return sig_m + STRUCT_SIZES.EC_SCALAR * (2 * i + 1); + } + const image_m = CNCrypto._malloc(STRUCT_SIZES.KEY_IMAGE); + CNCrypto.HEAPU8.set(hextobin(k_image), image_m); + let i; + const image_unp_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); + const image_pre_m = CNCrypto._malloc(STRUCT_SIZES.GE_DSMP); + const sum_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + const k_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + const h_m = CNCrypto._malloc(STRUCT_SIZES.EC_SCALAR); + const tmp2_m = CNCrypto._malloc(STRUCT_SIZES.GE_P2); + const tmp3_m = CNCrypto._malloc(STRUCT_SIZES.GE_P3); + const pub_m = CNCrypto._malloc(KEY_SIZE); + const sec_m = CNCrypto._malloc(KEY_SIZE); + CNCrypto.HEAPU8.set(hextobin(sec), sec_m); + if (_ge_frombytes_vartime(image_unp_m, image_m) != 0) { + throw Error("failed to call ge_frombytes_vartime"); + } + _ge_dsm_precomp(image_pre_m, image_unp_m); + _sc_0(sum_m); + for (i = 0; i < keys.length; i++) { + if (i === real_index) { + // Real key + const rand = random_scalar(); + CNCrypto.HEAPU8.set(hextobin(rand), k_m); + _ge_scalarmult_base(tmp3_m, k_m); + _ge_p3_tobytes(buf_a(i), tmp3_m); + const ec = hash_to_ec(keys[i]); + CNCrypto.HEAPU8.set(hextobin(ec), tmp3_m); + _ge_scalarmult(tmp2_m, k_m, tmp3_m); + _ge_tobytes(buf_b(i), tmp2_m); + } else { + CNCrypto.HEAPU8.set(hextobin(random_scalar()), sig_c(i)); + CNCrypto.HEAPU8.set(hextobin(random_scalar()), sig_r(i)); + CNCrypto.HEAPU8.set(hextobin(keys[i]), pub_m); + if ( + CNCrypto.ccall( + "ge_frombytes_vartime", + "void", + ["number", "number"], + [tmp3_m, pub_m], + ) !== 0 + ) { + throw Error("Failed to call ge_frombytes_vartime"); + } + _ge_double_scalarmult_base_vartime( + tmp2_m, + sig_c(i), + tmp3_m, + sig_r(i), + ); + _ge_tobytes(buf_a(i), tmp2_m); + const ec = hash_to_ec(keys[i]); + CNCrypto.HEAPU8.set(hextobin(ec), tmp3_m); + _ge_double_scalarmult_precomp_vartime( + tmp2_m, + sig_r(i), + tmp3_m, + sig_c(i), + image_pre_m, + ); + _ge_tobytes(buf_b(i), tmp2_m); + _sc_add(sum_m, sum_m, sig_c(i)); + } + } + const buf_bin = CNCrypto.HEAPU8.subarray(buf_m, buf_m + buf_size); + const scalar = hash_to_scalar(prefix_hash + bintohex(buf_bin)); + CNCrypto.HEAPU8.set(hextobin(scalar), h_m); + _sc_sub(sig_c(real_index), h_m, sum_m); + _sc_mulsub(sig_r(real_index), sig_c(real_index), sec_m, k_m); + const sig_data = bintohex( + CNCrypto.HEAPU8.subarray(sig_m, sig_m + sig_size), + ); + const sigs = []; + for (let k = 0; k < keys.length; k++) { + sigs.push( + sig_data.slice( + STRUCT_SIZES.SIGNATURE * 2 * k, + STRUCT_SIZES.SIGNATURE * 2 * (k + 1), + ), + ); + } + CNCrypto._free(image_m); + CNCrypto._free(image_unp_m); + CNCrypto._free(image_pre_m); + CNCrypto._free(sum_m); + CNCrypto._free(k_m); + CNCrypto._free(h_m); + CNCrypto._free(tmp2_m); + CNCrypto._free(tmp3_m); + CNCrypto._free(buf_m); + CNCrypto._free(sig_m); + CNCrypto._free(pub_m); + CNCrypto._free(sec_m); + return sigs; +} + +interface TransactionInput { + type: string; + amount: string; + k_image: string; + key_offsets: string[]; +} + +interface TransactionOutput { + amount: string; + target: { + type: string; + key: string; + }; +} + +export interface SignedTransaction { + unlock_time: number; + version: number; + extra: string; + vin: TransactionInput[]; + vout: TransactionOutput[]; + rct_signatures?: RCTSignatures; + signatures?: string[][]; +} + +function construct_tx( + keys: Keys, + sources: Source[], + dsts: ParsedTarget[], + fee_amount: BigInt, + payment_id: string | null, + pid_encrypt: boolean, + realDestViewKey: string | undefined, + unlock_time: number, + rct: boolean, + nettype: NetType, +) { + //we move payment ID stuff here, because we need txkey to encrypt + const txkey = random_keypair(); + console.log(txkey); + let extra = ""; + if (payment_id) { + if (pid_encrypt && payment_id.length !== INTEGRATED_ID_SIZE * 2) { + throw Error( + "payment ID must be " + + INTEGRATED_ID_SIZE + + " bytes to be encrypted!", + ); + } + console.log("Adding payment id: " + payment_id); + if (pid_encrypt) { + if (!realDestViewKey) { + throw Error("RealDestViewKey not found"); + } + //get the derivation from our passed viewkey, then hash that + tail to get encryption key + const pid_key = cn_fast_hash( + generate_key_derivation(realDestViewKey, txkey.sec) + + ENCRYPTED_PAYMENT_ID_TAIL.toString(16), + ).slice(0, INTEGRATED_ID_SIZE * 2); + console.log("Txkeys:", txkey, "Payment ID key:", pid_key); + payment_id = hex_xor(payment_id, pid_key); + } + const nonce = get_payment_id_nonce(payment_id, pid_encrypt); + console.log("Extra nonce: " + nonce); + extra = add_nonce_to_extra(extra, nonce); + } + + const tx: SignedTransaction = { + unlock_time, + version: rct ? CURRENT_TX_VERSION : OLD_TX_VERSION, + extra, + vin: [], + vout: [], + rct_signatures: undefined, + signatures: undefined, + }; + + const inputs_money = sources.reduce( + (totalAmount, { amount }) => totalAmount.add(amount), + BigInt.ZERO, + ); + + let i; + console.log("Sources: "); + + //run the for loop twice to sort ins by key image + //first generate key image and other construction data to sort it all in one go + const sourcesWithKeyImgAndKeys = sources.map((source, idx) => { + console.log(idx + ": " + formatMoneyFull(source.amount)); + if (source.real_out >= source.outputs.length) { + throw Error("real index >= outputs.length"); + } + const { key_image, in_ephemeral } = generate_key_image_helper_rct( + keys, + source.real_out_tx_key, + source.real_out_in_tx, + source.mask, + ); //mask will be undefined for non-rct + + if (in_ephemeral.pub !== source.outputs[source.real_out].key) { + throw Error("in_ephemeral.pub != source.real_out.key"); + } + + return { + ...source, + key_image, + in_ephemeral, + }; + }); + + //sort ins + sourcesWithKeyImgAndKeys.sort((a, b) => { + return ( + BigInt.parse(a.key_image, 16).compare( + BigInt.parse(b.key_image, 16), + ) * -1 + ); + }); + + const in_contexts = sourcesWithKeyImgAndKeys.map( + source => source.in_ephemeral, + ); + + //copy the sorted sourcesWithKeyImgAndKeys data to tx + const vin = sourcesWithKeyImgAndKeys.map(source => { + const input_to_key = { + type: "input_to_key", + amount: source.amount, + k_image: source.key_image, + key_offsets: source.outputs.map(s => s.index), + }; + + input_to_key.key_offsets = abs_to_rel_offsets(input_to_key.key_offsets); + return input_to_key; + }); + + tx.vin = vin; + + const dstsWithKeys = dsts.map(d => { + if (d.amount.compare(0) < 0) { + throw Error("dst.amount < 0"); //amount can be zero if no change + } + const keys = decode_address(d.address, nettype); + return { ...d, keys }; + }); + + const outputs_money = dstsWithKeys.reduce( + (outputs_money, { amount }) => outputs_money.add(amount), + BigInt.ZERO, + ); + + interface Ret { + amountKeys: string[]; + vout: TransactionOutput[]; + } + + const ret: Ret = { amountKeys: [], vout: [] }; + //amountKeys is rct only + const { amountKeys, vout } = dstsWithKeys.reduce( + ({ amountKeys, vout }, dstWKey, out_index) => { + // R = rD for subaddresses + if (is_subaddress(dstWKey.address, nettype)) { + if (payment_id) { + // this could stand to be placed earlier in the function but we save repeating a little algo time this way + throw Error( + "Payment ID must not be supplied when sending to a subaddress", + ); + } + txkey.pub = ge_scalarmult(dstWKey.keys.spend, txkey.sec); + } + // send change to ourselves + const out_derivation = + dstWKey.keys.view === keys.view.pub + ? generate_key_derivation(txkey.pub, keys.view.sec) + : generate_key_derivation(dstWKey.keys.view, txkey.sec); + + const out_ephemeral_pub = derive_public_key( + out_derivation, + out_index, + dstWKey.keys.spend, + ); + + const out = { + amount: dstWKey.amount.toString(), + // txout_to_key + target: { + type: "txout_to_key", + key: out_ephemeral_pub, + }, + }; + + const nextAmountKeys = rct + ? [ + ...amountKeys, + derivation_to_scalar(out_derivation, out_index), + ] + : amountKeys; + const nextVout = [...vout, out]; + const nextVal: Ret = { amountKeys: nextAmountKeys, vout: nextVout }; + return nextVal; + }, + ret, + ); + + tx.vout = vout; + + // add pub key to extra after we know whether to use R = rG or R = rD + tx.extra = add_pub_key_to_extra(tx.extra, txkey.pub); + + if (outputs_money.add(fee_amount).compare(inputs_money) > 0) { + throw Error( + `outputs money:${formatMoneyFull( + outputs_money, + )} + fee:${formatMoneyFull( + fee_amount, + )} > inputs money:${formatMoneyFull(inputs_money)}`, + ); + } + + if (!rct) { + const signatures = sourcesWithKeyImgAndKeys.map((src, i) => { + const src_keys = src.outputs.map(s => s.key); + const sigs = generate_ring_signature( + get_tx_prefix_hash(tx), + tx.vin[i].k_image, + src_keys, + in_contexts[i].sec, + sourcesWithKeyImgAndKeys[i].real_out, + ); + return sigs; + }); + tx.signatures = signatures; + } else { + //rct + const keyimages: string[] = []; + const inSk: SecretCommitment[] = []; + const inAmounts: string[] = []; + const mixRing: MixCommitment[][] = []; + const indices: number[] = []; + + tx.vin.forEach((input, i) => { + keyimages.push(input.k_image); + inSk.push({ + x: in_contexts[i].sec, + a: in_contexts[i].mask, + }); + inAmounts.push(input.amount); + + if (in_contexts[i].mask !== I) { + //if input is rct (has a valid mask), 0 out amount + input.amount = "0"; + } + + mixRing[i] = sourcesWithKeyImgAndKeys[i].outputs.map(o => { + if (!o.commit) { + throw Error("Commit not found"); + } + return { + dest: o.key, + mask: o.commit, + }; + }); + + indices.push(sourcesWithKeyImgAndKeys[i].real_out); + }); + + const outAmounts = []; + for (i = 0; i < tx.vout.length; i++) { + outAmounts.push(tx.vout[i].amount); + tx.vout[i].amount = "0"; //zero out all rct outputs + } + const tx_prefix_hash = get_tx_prefix_hash(tx); + tx.rct_signatures = genRct( + tx_prefix_hash, + inSk, + keyimages, + /*destinations, */ inAmounts, + outAmounts, + mixRing, + amountKeys, + indices, + fee_amount.toString(), + ); + } + console.log(tx); + return tx; +} + +interface Keys { + view: Key; + spend: Key; +} + +interface SourceOutput { + index: string; + key: string; + commit?: string; +} + +interface Source { + amount: string; + outputs: SourceOutput[]; + real_out_tx_key: string; + real_out: number; + real_out_in_tx: number; + mask?: string | null; +} +export function create_transaction( + pub_keys: ViewSendKeys, + sec_keys: ViewSendKeys, + dsts: ParsedTarget[], + outputs: Output[], + mix_outs: AmountOutput[] | undefined, + fake_outputs_count: number, + fee_amount: BigInt, + payment_id: Pid, + pid_encrypt: boolean, + realDestViewKey: string | undefined, + unlock_time: number, + rct: boolean, + nettype: NetType, +) { + unlock_time = unlock_time || 0; + mix_outs = mix_outs || []; + let i, j; + if (dsts.length === 0) { + throw Error("Destinations empty"); + } + if (mix_outs.length !== outputs.length && fake_outputs_count !== 0) { + throw Error( + "Wrong number of mix outs provided (" + + outputs.length + + " outputs, " + + mix_outs.length + + " mix outs)", + ); + } + for (i = 0; i < mix_outs.length; i++) { + if ((mix_outs[i].outputs || []).length < fake_outputs_count) { + throw Error("Not enough outputs to mix with"); + } + } + const keys: Keys = { + view: { + pub: pub_keys.view, + sec: sec_keys.view, + }, + spend: { + pub: pub_keys.spend, + sec: sec_keys.spend, + }, + }; + if ( + !valid_keys( + keys.view.pub, + keys.view.sec, + keys.spend.pub, + keys.spend.sec, + ) + ) { + throw Error("Invalid secret keys!"); + } + let needed_money = BigInt.ZERO; + for (i = 0; i < dsts.length; ++i) { + needed_money = needed_money.add(dsts[i].amount); + if (needed_money.compare(UINT64_MAX) !== -1) { + throw Error("Output overflow!"); + } + } + let found_money = BigInt.ZERO; + const sources = []; + console.log("Selected transfers: ", outputs); + for (i = 0; i < outputs.length; ++i) { + found_money = found_money.add(outputs[i].amount); + if (found_money.compare(UINT64_MAX) !== -1) { + throw Error("Input overflow!"); + } + + const src: Source = { + amount: outputs[i].amount, + outputs: [], + real_out: 0, + real_out_in_tx: 0, + real_out_tx_key: "", + }; + + if (mix_outs.length !== 0) { + // Sort fake outputs by global index + mix_outs[i].outputs.sort(function(a, b) { + return new BigInt(a.global_index).compare(b.global_index); + }); + j = 0; + while ( + src.outputs.length < fake_outputs_count && + j < mix_outs[i].outputs.length + ) { + const out = mix_outs[i].outputs[j]; + if (+out.global_index === outputs[i].global_index) { + console.log("got mixin the same as output, skipping"); + j++; + continue; + } + + const oe: SourceOutput = { + index: out.global_index.toString(), + key: out.public_key, + }; + + if (rct) { + if (out.rct) { + oe.commit = out.rct.slice(0, 64); //add commitment from rct mix outs + } else { + if (outputs[i].rct) { + throw Error("mix rct outs missing commit"); + } + oe.commit = zeroCommit(d2s(src.amount)); //create identity-masked commitment for non-rct mix input + } + } + src.outputs.push(oe); + j++; + } + } + const real_oe: SourceOutput = { + index: outputs[i].global_index.toString(), + key: outputs[i].public_key, + }; + + if (rct) { + if (outputs[i].rct) { + real_oe.commit = outputs[i].rct.slice(0, 64); //add commitment for real input + } else { + real_oe.commit = zeroCommit(d2s(src.amount)); //create identity-masked commitment for non-rct input + } + } + + let real_index = src.outputs.length; + for (j = 0; j < src.outputs.length; j++) { + if (new BigInt(real_oe.index).compare(src.outputs[j].index) < 0) { + real_index = j; + break; + } + } + // Add real_oe to outputs + src.outputs.splice(real_index, 0, real_oe); + src.real_out_tx_key = outputs[i].tx_pub_key; + // Real output entry index + src.real_out = real_index; + src.real_out_in_tx = outputs[i].index; + if (rct) { + // if rct, slice encrypted, otherwise will be set by generate_key_image_helper_rct + src.mask = outputs[i].rct ? outputs[i].rct.slice(64, 128) : null; + } + sources.push(src); + } + console.log("sources: ", sources); + const change = { + amount: BigInt.ZERO, + }; + const cmp = needed_money.compare(found_money); + if (cmp < 0) { + change.amount = found_money.subtract(needed_money); + if (change.amount.compare(fee_amount) !== 0) { + throw Error("early fee calculation != later"); + } + } else if (cmp > 0) { + throw Error("Need more money than found! (have: ") + + formatMoney(found_money) + + " need: " + + formatMoney(needed_money) + + ")"; + } + return construct_tx( + keys, + sources, + dsts, + fee_amount, + payment_id, + pid_encrypt, + realDestViewKey, + unlock_time, + rct, + nettype, + ); +} + +export function estimateRctSize( + inputs: number, + mixin: number, + outputs: number, +) { + let size = 0; + // tx prefix + // first few bytes + size += 1 + 6; + size += inputs * (1 + 6 + (mixin + 1) * 3 + 32); // original C implementation is *2+32 but author advised to change 2 to 3 as key offsets are variable size and this constitutes a best guess + // vout + size += outputs * (6 + 32); + // extra + size += 40; + // rct signatures + // type + size += 1; + // rangeSigs + size += (2 * 64 * 32 + 32 + 64 * 32) * outputs; + // MGs + size += inputs * (32 * (mixin + 1) + 32); + // mixRing - not serialized, can be reconstructed + /* size += 2 * 32 * (mixin+1) * inputs; */ + // pseudoOuts + size += 32 * inputs; + // ecdhInfo + size += 2 * 32 * outputs; + // outPk - only commitment is saved + size += 32 * outputs; + // txnFee + size += 4; + // const logStr = `estimated rct tx size for ${inputs} at mixin ${mixin} and ${outputs} : ${size} (${((32 * inputs/*+1*/) + 2 * 32 * (mixin+1) * inputs + 32 * outputs)}) saved)` + // console.log(logStr) + + return size; +} diff --git a/src/cryptonote_utils/formatters.ts b/src/cryptonote_utils/formatters.ts new file mode 100644 index 0000000..a00c051 --- /dev/null +++ b/src/cryptonote_utils/formatters.ts @@ -0,0 +1,141 @@ +import { config } from "monero_utils/monero_config"; +import { BigInt } from "biginteger"; +import { ParsedTarget } from "monero_utils/sending_funds/internal_libs/types"; + +export function formatMoneyFull(units: BigInt | string) { + let strUnits = units.toString(); + const symbol = strUnits[0] === "-" ? "-" : ""; + if (symbol === "-") { + strUnits = strUnits.slice(1); + } + let decimal; + if (strUnits.length >= config.coinUnitPlaces) { + decimal = strUnits.substr( + strUnits.length - config.coinUnitPlaces, + config.coinUnitPlaces, + ); + } else { + decimal = padLeft(strUnits, config.coinUnitPlaces, "0"); + } + return ( + symbol + + (strUnits.substr(0, strUnits.length - config.coinUnitPlaces) || "0") + + "." + + decimal + ); +} + +export function formatMoneyFullSymbol(units: BigInt | string) { + return formatMoneyFull(units) + " " + config.coinSymbol; +} + +export function formatMoney(units: BigInt | string) { + const f = trimRight(formatMoneyFull(units), "0"); + if (f[f.length - 1] === ".") { + return f.slice(0, f.length - 1); + } + return f; +} + +export function formatMoneySymbol(units: BigInt | string) { + return formatMoney(units) + " " + config.coinSymbol; +} + +/** + * + * @param {string} str + */ +export function parseMoney(str: string): BigInt { + if (!str) return BigInt.ZERO; + const negative = str[0] === "-"; + if (negative) { + str = str.slice(1); + } + const decimalIndex = str.indexOf("."); + if (decimalIndex == -1) { + if (negative) { + return config.coinUnits.multiply(str).negate(); + } + return config.coinUnits.multiply(str); + } + if (decimalIndex + config.coinUnitPlaces + 1 < str.length) { + str = str.substr(0, decimalIndex + config.coinUnitPlaces + 1); + } + if (negative) { + return new BigInt(str.substr(0, decimalIndex)) + .exp10(config.coinUnitPlaces) + .add( + new BigInt(str.substr(decimalIndex + 1)).exp10( + decimalIndex + config.coinUnitPlaces - str.length + 1, + ), + ) + .negate(); + } + return new BigInt(str.substr(0, decimalIndex)) + .exp10(config.coinUnitPlaces) + .add( + new BigInt(str.substr(decimalIndex + 1)).exp10( + decimalIndex + config.coinUnitPlaces - str.length + 1, + ), + ); +} + +export function printDsts(dsts: ParsedTarget[]) { + for (let i = 0; i < dsts.length; i++) { + console.log(dsts[i].address + ": " + formatMoneyFull(dsts[i].amount)); + } +} + +export function decompose_tx_destinations(dsts: ParsedTarget[], rct: boolean) { + const out = []; + if (rct) { + for (let i = 0; i < dsts.length; i++) { + out.push({ + address: dsts[i].address, + amount: dsts[i].amount, + }); + } + } else { + for (let i = 0; i < dsts.length; i++) { + const digits = decompose_amount_into_digits(dsts[i].amount); + for (let j = 0; j < digits.length; j++) { + if (digits[j].compare(0) > 0) { + out.push({ + address: dsts[i].address, + amount: digits[j], + }); + } + } + } + } + return out.sort((a, b) => a.amount.subtract(b.amount).toJSValue()); +} + +function trimRight(str: string, char: string) { + while (str[str.length - 1] == char) str = str.slice(0, -1); + return str; +} + +export function padLeft(str: string, len: number, char: string) { + while (str.length < len) { + str = char + str; + } + return str; +} + +function decompose_amount_into_digits(amount: BigInt) { + let amtStr = amount.toString(); + const ret = []; + while (amtStr.length > 0) { + //check so we don't create 0s + if (amtStr[0] !== "0") { + let digit = amtStr[0]; + while (digit.length < amtStr.length) { + digit += "0"; + } + ret.push(new BigInt(digit)); + } + amtStr = amtStr.slice(1); + } + return ret; +} diff --git a/src/cryptonote_utils/index.ts b/src/cryptonote_utils/index.ts new file mode 100644 index 0000000..87bf143 --- /dev/null +++ b/src/cryptonote_utils/index.ts @@ -0,0 +1,2 @@ +export * from "./cryptonote_utils"; +export * from "./nettype"; diff --git a/cryptonote_utils/internal_libs/bs58/cryptonote_base58.d.ts b/src/cryptonote_utils/internal_libs/bs58/cryptonote_base58.d.ts similarity index 100% rename from cryptonote_utils/internal_libs/bs58/cryptonote_base58.d.ts rename to src/cryptonote_utils/internal_libs/bs58/cryptonote_base58.d.ts diff --git a/cryptonote_utils/internal_libs/bs58/cryptonote_base58.js b/src/cryptonote_utils/internal_libs/bs58/cryptonote_base58.js similarity index 92% rename from cryptonote_utils/internal_libs/bs58/cryptonote_base58.js rename to src/cryptonote_utils/internal_libs/bs58/cryptonote_base58.js index a57b15e..66d59d5 100644 --- a/cryptonote_utils/internal_libs/bs58/cryptonote_base58.js +++ b/src/cryptonote_utils/internal_libs/bs58/cryptonote_base58.js @@ -27,7 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // v--- These should maybe be injected into context -const JSBigInt = require("../../biginteger").BigInteger; +const JSBigInt = require("../../../biginteger").BigInt; var cnBase58 = (function() { var b58 = {}; @@ -47,7 +47,7 @@ var cnBase58 = (function() { var UINT64_MAX = new JSBigInt(2).pow(64); function hextobin(hex) { - if (hex.length % 2 !== 0) throw "Hex string has invalid length!"; + if (hex.length % 2 !== 0) throw Error("Hex string has invalid length!"); var res = new Uint8Array(hex.length / 2); for (var i = 0; i < hex.length / 2; ++i) { res[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); @@ -81,7 +81,7 @@ var cnBase58 = (function() { function uint8_be_to_64(data) { if (data.length < 1 || data.length > 8) { - throw "Invalid input length"; + throw Error("Invalid input length"); } var res = JSBigInt.ZERO; var twopow8 = new JSBigInt(2).pow(8); @@ -105,7 +105,7 @@ var cnBase58 = (function() { res = res.multiply(twopow8).add(data[i++]); break; default: - throw "Impossible condition"; + throw Error("Impossible condition"); } return res; } @@ -113,7 +113,7 @@ var cnBase58 = (function() { function uint64_to_8be(num, size) { var res = new Uint8Array(size); if (size < 1 || size > 8) { - throw "Invalid input length"; + throw Error("Invalid input length"); } var twopow8 = new JSBigInt(2).pow(8); for (var i = size - 1; i >= 0; i--) { @@ -125,7 +125,7 @@ var cnBase58 = (function() { b58.encode_block = function(data, buf, index) { if (data.length < 1 || data.length > full_encoded_block_size) { - throw "Invalid block length: " + data.length; + throw Error("Invalid block length: " + data.length); } var num = uint8_be_to_64(data); var i = encoded_block_sizes[data.length] - 1; @@ -183,24 +183,24 @@ var cnBase58 = (function() { b58.decode_block = function(data, buf, index) { if (data.length < 1 || data.length > full_encoded_block_size) { - throw "Invalid block length: " + data.length; + throw Error("Invalid block length: " + data.length); } var res_size = encoded_block_sizes.indexOf(data.length); if (res_size <= 0) { - throw "Invalid block size"; + throw Error("Invalid block size"); } var res_num = new JSBigInt(0); var order = new JSBigInt(1); for (var i = data.length - 1; i >= 0; i--) { var digit = alphabet.indexOf(data[i]); if (digit < 0) { - throw "Invalid symbol"; + throw Error("Invalid symbol"); } var product = order.multiply(digit).add(res_num); // if product > UINT64_MAX if (product.compare(UINT64_MAX) === 1) { - throw "Overflow"; + throw Error("Overflow"); } res_num = product; order = order.multiply(alphabet_size); @@ -209,7 +209,7 @@ var cnBase58 = (function() { res_size < full_block_size && new JSBigInt(2).pow(8 * res_size).compare(res_num) <= 0 ) { - throw "Overflow 2"; + throw Error("Overflow 2"); } buf.set(uint64_to_8be(res_num, res_size), index); return buf; @@ -226,7 +226,7 @@ var cnBase58 = (function() { last_block_size, ); if (last_block_decoded_size < 0) { - throw "Invalid encoded length"; + throw Error("Invalid encoded length"); } var data_size = full_block_count * full_block_size + last_block_decoded_size; diff --git a/cryptonote_utils/internal_libs/bs58/index.ts b/src/cryptonote_utils/internal_libs/bs58/index.ts similarity index 100% rename from cryptonote_utils/internal_libs/bs58/index.ts rename to src/cryptonote_utils/internal_libs/bs58/index.ts diff --git a/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts b/src/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts similarity index 68% rename from cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts rename to src/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts index 363bc56..0899a8a 100644 --- a/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts +++ b/src/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.d.ts @@ -1,7 +1,6 @@ declare namespace CNCrypto { - type Memory = string & { _tag_: "memory" }; - function _malloc(bytes: number): Memory; - function _free(mem: Memory): mem is never; + function _malloc(bytes: number): number; + function _free(mem: number): mem is never; var HEAPU8: Uint8Array; diff --git a/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.js b/src/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.js similarity index 100% rename from cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.js rename to src/cryptonote_utils/internal_libs/cn_crypto/cryptonote_crypto_EMSCRIPTEN.js diff --git a/cryptonote_utils/internal_libs/cn_crypto/index.ts b/src/cryptonote_utils/internal_libs/cn_crypto/index.ts similarity index 100% rename from cryptonote_utils/internal_libs/cn_crypto/index.ts rename to src/cryptonote_utils/internal_libs/cn_crypto/index.ts diff --git a/cryptonote_utils/internal_libs/fast_cn/index.ts b/src/cryptonote_utils/internal_libs/fast_cn/index.ts similarity index 100% rename from cryptonote_utils/internal_libs/fast_cn/index.ts rename to src/cryptonote_utils/internal_libs/fast_cn/index.ts diff --git a/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.d.ts b/src/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.d.ts similarity index 100% rename from cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.d.ts rename to src/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.d.ts diff --git a/src/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js b/src/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js new file mode 100644 index 0000000..1029f7e --- /dev/null +++ b/src/cryptonote_utils/internal_libs/fast_cn/nacl-fast-cn.js @@ -0,0 +1,1026 @@ +(function(nacl) { + "use strict"; + + // Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. + // Public domain. + // + // Implementation derived from TweetNaCl version 20140427. + // See for details: http://tweetnacl.cr.yp.to/ + + // modified 2017 for some CN functions by luigi1111 + + var gf = function(init) { + var i, + r = new Float64Array(16); + if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; + return r; + }; + + // Pluggable, initialized in high-level API below. + var randombytes = function(/* x, n */) { + throw new Error("no PRNG"); + }; + + var _0 = new Uint8Array(16); + var _9 = new Uint8Array(32); + _9[0] = 9; + + var gf0 = gf(), + gf1 = gf([1]), + _121665 = gf([0xdb41, 1]), + D = gf([ + 0x78a3, + 0x1359, + 0x4dca, + 0x75eb, + 0xd8ab, + 0x4141, + 0x0a4d, + 0x0070, + 0xe898, + 0x7779, + 0x4079, + 0x8cc7, + 0xfe73, + 0x2b6f, + 0x6cee, + 0x5203, + ]), + D2 = gf([ + 0xf159, + 0x26b2, + 0x9b94, + 0xebd6, + 0xb156, + 0x8283, + 0x149a, + 0x00e0, + 0xd130, + 0xeef3, + 0x80f2, + 0x198e, + 0xfce7, + 0x56df, + 0xd9dc, + 0x2406, + ]), + X = gf([ + 0xd51a, + 0x8f25, + 0x2d60, + 0xc956, + 0xa7b2, + 0x9525, + 0xc760, + 0x692c, + 0xdc5c, + 0xfdd6, + 0xe231, + 0xc0a4, + 0x53fe, + 0xcd6e, + 0x36d3, + 0x2169, + ]), + Y = gf([ + 0x6658, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + 0x6666, + ]), + I = gf([ + 0xa0b0, + 0x4a0e, + 0x1b27, + 0xc4ee, + 0xe478, + 0xad2f, + 0x1806, + 0x2f43, + 0xd7a7, + 0x3dfb, + 0x0099, + 0x2b4d, + 0xdf0b, + 0x4fc1, + 0x2480, + 0x2b83, + ]); + + function vn(x, xi, y, yi, n) { + var i, + d = 0; + for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; + return (1 & ((d - 1) >>> 8)) - 1; + } + + function crypto_verify_16(x, xi, y, yi) { + return vn(x, xi, y, yi, 16); + } + + function crypto_verify_32(x, xi, y, yi) { + return vn(x, xi, y, yi, 32); + } + + function set25519(r, a) { + var i; + for (i = 0; i < 16; i++) r[i] = a[i] | 0; + } + + function car25519(o) { + var i, + v, + c = 1; + for (i = 0; i < 16; i++) { + v = o[i] + c + 65535; + c = Math.floor(v / 65536); + o[i] = v - c * 65536; + } + o[0] += c - 1 + 37 * (c - 1); + } + + function sel25519(p, q, b) { + var t, + c = ~(b - 1); + for (var i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } + } + + function pack25519(o, n) { + var i, j, b; + var m = gf(), + t = gf(); + for (i = 0; i < 16; i++) t[i] = n[i]; + car25519(t); + car25519(t); + car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + b = (m[15] >> 16) & 1; + m[14] &= 0xffff; + sel25519(t, m, 1 - b); + } + for (i = 0; i < 16; i++) { + o[2 * i] = t[i] & 0xff; + o[2 * i + 1] = t[i] >> 8; + } + } + + function neq25519(a, b) { + var c = new Uint8Array(32), + d = new Uint8Array(32); + pack25519(c, a); + pack25519(d, b); + return crypto_verify_32(c, 0, d, 0); + } + + function par25519(a) { + var d = new Uint8Array(32); + pack25519(d, a); + return d[0] & 1; + } + + function unpack25519(o, n) { + var i; + for (i = 0; i < 16; i++) o[i] = n[2 * i] + (n[2 * i + 1] << 8); + o[15] &= 0x7fff; + } + + function A(o, a, b) { + for (var i = 0; i < 16; i++) o[i] = a[i] + b[i]; + } + + function Z(o, a, b) { + for (var i = 0; i < 16; i++) o[i] = a[i] - b[i]; + } + + function M(o, a, b) { + var v, + c, + t0 = 0, + t1 = 0, + t2 = 0, + t3 = 0, + t4 = 0, + t5 = 0, + t6 = 0, + t7 = 0, + t8 = 0, + t9 = 0, + t10 = 0, + t11 = 0, + t12 = 0, + t13 = 0, + t14 = 0, + t15 = 0, + t16 = 0, + t17 = 0, + t18 = 0, + t19 = 0, + t20 = 0, + t21 = 0, + t22 = 0, + t23 = 0, + t24 = 0, + t25 = 0, + t26 = 0, + t27 = 0, + t28 = 0, + t29 = 0, + t30 = 0, + b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7], + b8 = b[8], + b9 = b[9], + b10 = b[10], + b11 = b[11], + b12 = b[12], + b13 = b[13], + b14 = b[14], + b15 = b[15]; + + v = a[0]; + t0 += v * b0; + t1 += v * b1; + t2 += v * b2; + t3 += v * b3; + t4 += v * b4; + t5 += v * b5; + t6 += v * b6; + t7 += v * b7; + t8 += v * b8; + t9 += v * b9; + t10 += v * b10; + t11 += v * b11; + t12 += v * b12; + t13 += v * b13; + t14 += v * b14; + t15 += v * b15; + v = a[1]; + t1 += v * b0; + t2 += v * b1; + t3 += v * b2; + t4 += v * b3; + t5 += v * b4; + t6 += v * b5; + t7 += v * b6; + t8 += v * b7; + t9 += v * b8; + t10 += v * b9; + t11 += v * b10; + t12 += v * b11; + t13 += v * b12; + t14 += v * b13; + t15 += v * b14; + t16 += v * b15; + v = a[2]; + t2 += v * b0; + t3 += v * b1; + t4 += v * b2; + t5 += v * b3; + t6 += v * b4; + t7 += v * b5; + t8 += v * b6; + t9 += v * b7; + t10 += v * b8; + t11 += v * b9; + t12 += v * b10; + t13 += v * b11; + t14 += v * b12; + t15 += v * b13; + t16 += v * b14; + t17 += v * b15; + v = a[3]; + t3 += v * b0; + t4 += v * b1; + t5 += v * b2; + t6 += v * b3; + t7 += v * b4; + t8 += v * b5; + t9 += v * b6; + t10 += v * b7; + t11 += v * b8; + t12 += v * b9; + t13 += v * b10; + t14 += v * b11; + t15 += v * b12; + t16 += v * b13; + t17 += v * b14; + t18 += v * b15; + v = a[4]; + t4 += v * b0; + t5 += v * b1; + t6 += v * b2; + t7 += v * b3; + t8 += v * b4; + t9 += v * b5; + t10 += v * b6; + t11 += v * b7; + t12 += v * b8; + t13 += v * b9; + t14 += v * b10; + t15 += v * b11; + t16 += v * b12; + t17 += v * b13; + t18 += v * b14; + t19 += v * b15; + v = a[5]; + t5 += v * b0; + t6 += v * b1; + t7 += v * b2; + t8 += v * b3; + t9 += v * b4; + t10 += v * b5; + t11 += v * b6; + t12 += v * b7; + t13 += v * b8; + t14 += v * b9; + t15 += v * b10; + t16 += v * b11; + t17 += v * b12; + t18 += v * b13; + t19 += v * b14; + t20 += v * b15; + v = a[6]; + t6 += v * b0; + t7 += v * b1; + t8 += v * b2; + t9 += v * b3; + t10 += v * b4; + t11 += v * b5; + t12 += v * b6; + t13 += v * b7; + t14 += v * b8; + t15 += v * b9; + t16 += v * b10; + t17 += v * b11; + t18 += v * b12; + t19 += v * b13; + t20 += v * b14; + t21 += v * b15; + v = a[7]; + t7 += v * b0; + t8 += v * b1; + t9 += v * b2; + t10 += v * b3; + t11 += v * b4; + t12 += v * b5; + t13 += v * b6; + t14 += v * b7; + t15 += v * b8; + t16 += v * b9; + t17 += v * b10; + t18 += v * b11; + t19 += v * b12; + t20 += v * b13; + t21 += v * b14; + t22 += v * b15; + v = a[8]; + t8 += v * b0; + t9 += v * b1; + t10 += v * b2; + t11 += v * b3; + t12 += v * b4; + t13 += v * b5; + t14 += v * b6; + t15 += v * b7; + t16 += v * b8; + t17 += v * b9; + t18 += v * b10; + t19 += v * b11; + t20 += v * b12; + t21 += v * b13; + t22 += v * b14; + t23 += v * b15; + v = a[9]; + t9 += v * b0; + t10 += v * b1; + t11 += v * b2; + t12 += v * b3; + t13 += v * b4; + t14 += v * b5; + t15 += v * b6; + t16 += v * b7; + t17 += v * b8; + t18 += v * b9; + t19 += v * b10; + t20 += v * b11; + t21 += v * b12; + t22 += v * b13; + t23 += v * b14; + t24 += v * b15; + v = a[10]; + t10 += v * b0; + t11 += v * b1; + t12 += v * b2; + t13 += v * b3; + t14 += v * b4; + t15 += v * b5; + t16 += v * b6; + t17 += v * b7; + t18 += v * b8; + t19 += v * b9; + t20 += v * b10; + t21 += v * b11; + t22 += v * b12; + t23 += v * b13; + t24 += v * b14; + t25 += v * b15; + v = a[11]; + t11 += v * b0; + t12 += v * b1; + t13 += v * b2; + t14 += v * b3; + t15 += v * b4; + t16 += v * b5; + t17 += v * b6; + t18 += v * b7; + t19 += v * b8; + t20 += v * b9; + t21 += v * b10; + t22 += v * b11; + t23 += v * b12; + t24 += v * b13; + t25 += v * b14; + t26 += v * b15; + v = a[12]; + t12 += v * b0; + t13 += v * b1; + t14 += v * b2; + t15 += v * b3; + t16 += v * b4; + t17 += v * b5; + t18 += v * b6; + t19 += v * b7; + t20 += v * b8; + t21 += v * b9; + t22 += v * b10; + t23 += v * b11; + t24 += v * b12; + t25 += v * b13; + t26 += v * b14; + t27 += v * b15; + v = a[13]; + t13 += v * b0; + t14 += v * b1; + t15 += v * b2; + t16 += v * b3; + t17 += v * b4; + t18 += v * b5; + t19 += v * b6; + t20 += v * b7; + t21 += v * b8; + t22 += v * b9; + t23 += v * b10; + t24 += v * b11; + t25 += v * b12; + t26 += v * b13; + t27 += v * b14; + t28 += v * b15; + v = a[14]; + t14 += v * b0; + t15 += v * b1; + t16 += v * b2; + t17 += v * b3; + t18 += v * b4; + t19 += v * b5; + t20 += v * b6; + t21 += v * b7; + t22 += v * b8; + t23 += v * b9; + t24 += v * b10; + t25 += v * b11; + t26 += v * b12; + t27 += v * b13; + t28 += v * b14; + t29 += v * b15; + v = a[15]; + t15 += v * b0; + t16 += v * b1; + t17 += v * b2; + t18 += v * b3; + t19 += v * b4; + t20 += v * b5; + t21 += v * b6; + t22 += v * b7; + t23 += v * b8; + t24 += v * b9; + t25 += v * b10; + t26 += v * b11; + t27 += v * b12; + t28 += v * b13; + t29 += v * b14; + t30 += v * b15; + + t0 += 38 * t16; + t1 += 38 * t17; + t2 += 38 * t18; + t3 += 38 * t19; + t4 += 38 * t20; + t5 += 38 * t21; + t6 += 38 * t22; + t7 += 38 * t23; + t8 += 38 * t24; + t9 += 38 * t25; + t10 += 38 * t26; + t11 += 38 * t27; + t12 += 38 * t28; + t13 += 38 * t29; + t14 += 38 * t30; // t15 left as is + + // first car + c = 1; + v = t0 + c + 65535; + c = Math.floor(v / 65536); + t0 = v - c * 65536; + v = t1 + c + 65535; + c = Math.floor(v / 65536); + t1 = v - c * 65536; + v = t2 + c + 65535; + c = Math.floor(v / 65536); + t2 = v - c * 65536; + v = t3 + c + 65535; + c = Math.floor(v / 65536); + t3 = v - c * 65536; + v = t4 + c + 65535; + c = Math.floor(v / 65536); + t4 = v - c * 65536; + v = t5 + c + 65535; + c = Math.floor(v / 65536); + t5 = v - c * 65536; + v = t6 + c + 65535; + c = Math.floor(v / 65536); + t6 = v - c * 65536; + v = t7 + c + 65535; + c = Math.floor(v / 65536); + t7 = v - c * 65536; + v = t8 + c + 65535; + c = Math.floor(v / 65536); + t8 = v - c * 65536; + v = t9 + c + 65535; + c = Math.floor(v / 65536); + t9 = v - c * 65536; + v = t10 + c + 65535; + c = Math.floor(v / 65536); + t10 = v - c * 65536; + v = t11 + c + 65535; + c = Math.floor(v / 65536); + t11 = v - c * 65536; + v = t12 + c + 65535; + c = Math.floor(v / 65536); + t12 = v - c * 65536; + v = t13 + c + 65535; + c = Math.floor(v / 65536); + t13 = v - c * 65536; + v = t14 + c + 65535; + c = Math.floor(v / 65536); + t14 = v - c * 65536; + v = t15 + c + 65535; + c = Math.floor(v / 65536); + t15 = v - c * 65536; + t0 += c - 1 + 37 * (c - 1); + + // second car + c = 1; + v = t0 + c + 65535; + c = Math.floor(v / 65536); + t0 = v - c * 65536; + v = t1 + c + 65535; + c = Math.floor(v / 65536); + t1 = v - c * 65536; + v = t2 + c + 65535; + c = Math.floor(v / 65536); + t2 = v - c * 65536; + v = t3 + c + 65535; + c = Math.floor(v / 65536); + t3 = v - c * 65536; + v = t4 + c + 65535; + c = Math.floor(v / 65536); + t4 = v - c * 65536; + v = t5 + c + 65535; + c = Math.floor(v / 65536); + t5 = v - c * 65536; + v = t6 + c + 65535; + c = Math.floor(v / 65536); + t6 = v - c * 65536; + v = t7 + c + 65535; + c = Math.floor(v / 65536); + t7 = v - c * 65536; + v = t8 + c + 65535; + c = Math.floor(v / 65536); + t8 = v - c * 65536; + v = t9 + c + 65535; + c = Math.floor(v / 65536); + t9 = v - c * 65536; + v = t10 + c + 65535; + c = Math.floor(v / 65536); + t10 = v - c * 65536; + v = t11 + c + 65535; + c = Math.floor(v / 65536); + t11 = v - c * 65536; + v = t12 + c + 65535; + c = Math.floor(v / 65536); + t12 = v - c * 65536; + v = t13 + c + 65535; + c = Math.floor(v / 65536); + t13 = v - c * 65536; + v = t14 + c + 65535; + c = Math.floor(v / 65536); + t14 = v - c * 65536; + v = t15 + c + 65535; + c = Math.floor(v / 65536); + t15 = v - c * 65536; + t0 += c - 1 + 37 * (c - 1); + + o[0] = t0; + o[1] = t1; + o[2] = t2; + o[3] = t3; + o[4] = t4; + o[5] = t5; + o[6] = t6; + o[7] = t7; + o[8] = t8; + o[9] = t9; + o[10] = t10; + o[11] = t11; + o[12] = t12; + o[13] = t13; + o[14] = t14; + o[15] = t15; + } + + function S(o, a) { + M(o, a, a); + } + + function inv25519(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + S(c, c); + if (a !== 2 && a !== 4) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + function pow2523(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 250; a >= 0; a--) { + S(c, c); + if (a !== 1) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + function add(p, q) { + var a = gf(), + b = gf(), + c = gf(), + d = gf(), + e = gf(), + f = gf(), + g = gf(), + h = gf(), + t = gf(); + + Z(a, p[1], p[0]); + Z(t, q[1], q[0]); + M(a, a, t); + A(b, p[0], p[1]); + A(t, q[0], q[1]); + M(b, b, t); + M(c, p[3], q[3]); + M(c, c, D2); + M(d, p[2], q[2]); + A(d, d, d); + Z(e, b, a); + Z(f, d, c); + A(g, d, c); + A(h, b, a); + + M(p[0], e, f); + M(p[1], h, g); + M(p[2], g, f); + M(p[3], e, h); + } + + function cswap(p, q, b) { + var i; + for (i = 0; i < 4; i++) { + sel25519(p[i], q[i], b); + } + } + + function pack(r, p) { + var tx = gf(), + ty = gf(), + zi = gf(); + inv25519(zi, p[2]); + M(tx, p[0], zi); + M(ty, p[1], zi); + pack25519(r, ty); + r[31] ^= par25519(tx) << 7; + } + + function scalarmult(p, q, s) { + var b, i; + set25519(p[0], gf0); + set25519(p[1], gf1); + set25519(p[2], gf1); + set25519(p[3], gf0); + for (i = 255; i >= 0; --i) { + b = (s[(i / 8) | 0] >> (i & 7)) & 1; + cswap(p, q, b); + add(q, p); + add(p, p); + cswap(p, q, b); + } + } + + function scalarbase(p, s) { + var q = [gf(), gf(), gf(), gf()]; + set25519(q[0], X); + set25519(q[1], Y); + set25519(q[2], gf1); + M(q[3], X, Y); + scalarmult(p, q, s); + } + + //new functions for CN - scalar operations are handled externally + // this only handles curve operations, except for Hp() + + //why do we negate points when unpacking them??? + function ge_neg(pub) { + pub[31] ^= 0x80; + } + + //res = s*G + function ge_scalarmult_base(s) { + var p = [gf(), gf(), gf(), gf()]; + scalarbase(p, s); + var pk = new Uint8Array(32); + pack(pk, p); + return pk; + } + + //res = s*P + function ge_scalarmult(P, s) { + var p = [gf(), gf(), gf(), gf()], + upk = [gf(), gf(), gf(), gf()], + res = new Uint8Array(32); + ge_neg(P); + if (unpackneg(upk, P) !== 0) throw Error("non-0 error on point decode"); + scalarmult(p, upk, s); + pack(res, p); + return res; + } + + //res = c*P + r*G + function ge_double_scalarmult_base_vartime(c, P, r) { + var uP = [gf(), gf(), gf(), gf()], + cP = [gf(), gf(), gf(), gf()], + rG = [gf(), gf(), gf(), gf()], + res = new Uint8Array(32); + ge_neg(P); + if (unpackneg(uP, P) !== 0) throw Error("non-0 error on point decode"); + scalarmult(cP, uP, c); + scalarbase(rG, r); + add(rG, cP); + pack(res, rG); + return res; + } + + //name changed to reflect not using precomp; res = r*Pb + c*I + function ge_double_scalarmult_postcomp_vartime(r, Pb, c, I) { + var uPb = [gf(), gf(), gf(), gf()], + uI = [gf(), gf(), gf(), gf()], + cI = [gf(), gf(), gf(), gf()], + rPb = [gf(), gf(), gf(), gf()], + res = new Uint8Array(32); + ge_neg(Pb); + if (unpackneg(uPb, Pb) !== 0) + throw Error("non-0 error on point decode"); + scalarmult(rPb, uPb, r); + ge_neg(I); + if (unpackneg(uI, I) !== 0) throw Error("non-0 error on point decode"); + scalarmult(cI, uI, c); + add(rPb, cI); + pack(res, rPb); + return res; + } + + //res = P + Q + function ge_add(P, Q) { + var uP = [gf(), gf(), gf(), gf()], + uQ = [gf(), gf(), gf(), gf()], + res = new Uint8Array(32); + ge_neg(P); + ge_neg(Q); + if (unpackneg(uP, P) !== 0) throw Error("non-0 error on point decode"); + if (unpackneg(uQ, Q) !== 0) throw Error("non-0 error on point decode"); + add(uP, uQ); + pack(res, uP); + return res; + } + + var L = new Float64Array([ + 0xed, + 0xd3, + 0xf5, + 0x5c, + 0x1a, + 0x63, + 0x12, + 0x58, + 0xd6, + 0x9c, + 0xf7, + 0xa2, + 0xde, + 0xf9, + 0xde, + 0x14, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0x10, + ]); + + function modL(r, x) { + var carry, i, j, k; + for (i = 63; i >= 32; --i) { + carry = 0; + for (j = i - 32, k = i - 12; j < k; ++j) { + x[j] += carry - 16 * x[i] * L[j - (i - 32)]; + carry = (x[j] + 128) >> 8; + x[j] -= carry * 256; + } + x[j] += carry; + x[i] = 0; + } + carry = 0; + for (j = 0; j < 32; j++) { + x[j] += carry - (x[31] >> 4) * L[j]; + carry = x[j] >> 8; + x[j] &= 255; + } + for (j = 0; j < 32; j++) x[j] -= carry * L[j]; + for (i = 0; i < 32; i++) { + x[i + 1] += x[i] >> 8; + r[i] = x[i] & 255; + } + } + + function reduce(r) { + var x = new Float64Array(64), + i; + for (i = 0; i < 64; i++) x[i] = r[i]; + for (i = 0; i < 64; i++) r[i] = 0; + modL(r, x); + } + + function unpackneg(r, p) { + var t = gf(), + chk = gf(), + num = gf(), + den = gf(), + den2 = gf(), + den4 = gf(), + den6 = gf(); + + set25519(r[2], gf1); + unpack25519(r[1], p); + S(num, r[1]); + M(den, num, D); + Z(num, num, r[2]); + A(den, r[2], den); + + S(den2, den); + S(den4, den2); + M(den6, den4, den2); + M(t, den6, num); + M(t, t, den); + + pow2523(t, t); + M(t, t, num); + M(t, t, den); + M(t, t, den); + M(r[0], t, den); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) M(r[0], r[0], I); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) === p[31] >> 7) Z(r[0], gf0, r[0]); + + M(r[3], r[0], r[1]); + return 0; + } + + nacl.ll = { + ge_scalarmult_base: ge_scalarmult_base, + ge_scalarmult: ge_scalarmult, + ge_double_scalarmult_base_vartime: ge_double_scalarmult_base_vartime, + ge_add: ge_add, + ge_double_scalarmult_postcomp_vartime: ge_double_scalarmult_postcomp_vartime, + }; + + /* High-level API */ + + function cleanup(arr) { + for (var i = 0; i < arr.length; i++) arr[i] = 0; + } + + nacl.randomBytes = function(n) { + var b = new Uint8Array(n); + randombytes(b, n); + return b; + }; + + nacl.setPRNG = function(fn) { + randombytes = fn; + }; + + (function() { + // Initialize PRNG if environment provides CSPRNG. + // If not, methods calling randombytes will throw. + var crypto = + typeof self !== "undefined" ? self.crypto || self.msCrypto : null; + if (crypto && crypto.getRandomValues) { + // Browsers. + var QUOTA = 65536; + nacl.setPRNG(function(x, n) { + var i, + v = new Uint8Array(n); + for (i = 0; i < n; i += QUOTA) { + crypto.getRandomValues( + v.subarray(i, i + Math.min(n - i, QUOTA)), + ); + } + for (i = 0; i < n; i++) x[i] = v[i]; + cleanup(v); + }); + } else if (typeof require !== "undefined") { + // Node.js. + crypto = require("crypto"); + if (crypto && crypto.randomBytes) { + nacl.setPRNG(function(x, n) { + var i, + v = crypto.randomBytes(n); + for (i = 0; i < n; i++) x[i] = v[i]; + cleanup(v); + }); + } + } + })(); +})( + typeof module !== "undefined" && module.exports + ? module.exports + : (self.nacl = self.nacl || {}), +); diff --git a/cryptonote_utils/nettype.ts b/src/cryptonote_utils/nettype.ts similarity index 83% rename from cryptonote_utils/nettype.ts rename to src/cryptonote_utils/nettype.ts index d243b71..1a5a919 100644 --- a/cryptonote_utils/nettype.ts +++ b/src/cryptonote_utils/nettype.ts @@ -45,44 +45,34 @@ const __STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 25; const __STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 36; export function cryptonoteBase58PrefixForStandardAddressOn(nettype: NetType) { - if (!nettype) { - console.warn("Unexpected nil nettype"); - } - - if (nettype == NetType.MAINNET) { + if (nettype === NetType.MAINNET) { return __MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; - } else if (nettype == NetType.TESTNET) { + } else if (nettype === NetType.TESTNET) { return __TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; - } else if (nettype == NetType.STAGENET) { + } else if (nettype === NetType.STAGENET) { return __STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; } - throw "Illegal nettype"; + throw Error("Illegal nettype"); } export function cryptonoteBase58PrefixForIntegratedAddressOn(nettype: NetType) { - if (!nettype) { - console.warn("Unexpected nil nettype"); - } - if (nettype == NetType.MAINNET) { + if (nettype === NetType.MAINNET) { return __MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; - } else if (nettype == NetType.TESTNET) { + } else if (nettype === NetType.TESTNET) { return __TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; - } else if (nettype == NetType.STAGENET) { + } else if (nettype === NetType.STAGENET) { return __STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; } - throw "Illegal nettype"; + throw Error("Illegal nettype"); } export function cryptonoteBase58PrefixForSubAddressOn(nettype: NetType) { - if (!nettype) { - console.warn("Unexpected nil nettype"); - } - if (nettype == NetType.MAINNET) { + if (nettype === NetType.MAINNET) { return __MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX; - } else if (nettype == NetType.TESTNET) { + } else if (nettype === NetType.TESTNET) { return __TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX; - } else if (nettype == NetType.STAGENET) { + } else if (nettype === NetType.STAGENET) { return __STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX; } - throw "Illegal nettype"; + throw Error("Illegal nettype"); } diff --git a/extern-types/keccakjs.d.ts b/src/extern-types/keccakjs.d.ts similarity index 76% rename from extern-types/keccakjs.d.ts rename to src/extern-types/keccakjs.d.ts index 5ad534d..751a1a2 100644 --- a/extern-types/keccakjs.d.ts +++ b/src/extern-types/keccakjs.d.ts @@ -2,6 +2,8 @@ declare module "keccakjs" { type Message = Buffer | string; class Hasher { + constructor(bitlength: number); + /** * Update hash * @@ -12,7 +14,7 @@ declare module "keccakjs" { /** * Return hash in integer array. */ - digest(): number[]; + digest(encoding?: "hex" | "binary"): string; } export = Hasher; diff --git a/src/hostAPI/index.ts b/src/hostAPI/index.ts new file mode 100644 index 0000000..39d9e4e --- /dev/null +++ b/src/hostAPI/index.ts @@ -0,0 +1,2 @@ +export * from "./net_service_utils"; +export * from "./response_parser_utils"; diff --git a/hostAPI/net_service_utils.ts b/src/hostAPI/net_service_utils.ts similarity index 100% rename from hostAPI/net_service_utils.ts rename to src/hostAPI/net_service_utils.ts diff --git a/hostAPI/response_parser_utils/index.ts b/src/hostAPI/response_parser_utils/index.ts similarity index 100% rename from hostAPI/response_parser_utils/index.ts rename to src/hostAPI/response_parser_utils/index.ts diff --git a/hostAPI/response_parser_utils/response_parser_utils.ts b/src/hostAPI/response_parser_utils/response_parser_utils.ts similarity index 95% rename from hostAPI/response_parser_utils/response_parser_utils.ts rename to src/hostAPI/response_parser_utils/response_parser_utils.ts index c58adf1..5d05ff2 100644 --- a/hostAPI/response_parser_utils/response_parser_utils.ts +++ b/src/hostAPI/response_parser_utils/response_parser_utils.ts @@ -26,7 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; import { genKeyImage, KeyImageCache, @@ -88,16 +88,16 @@ export function parseAddressInfo( ); if (!isKeyImageEqual(spent_output, key_image)) { - total_sent = new JSBigInt(total_sent) + total_sent = new BigInt(total_sent) .subtract(spent_output.amount) .toString(); } } return { - total_received: new JSBigInt(total_received), - locked_balance: new JSBigInt(locked_balance), - total_sent: new JSBigInt(total_sent), + total_received: new BigInt(total_received), + locked_balance: new BigInt(locked_balance), + total_sent: new BigInt(total_sent), spent_outputs, account_scanned_tx_height, @@ -123,7 +123,7 @@ export function parseAddressTransactions( scanned_block_height: account_scanned_block_height, scanned_height: account_scanned_height, start_height: account_scan_start_height, - total_received, + /*total_received*/ transaction_height, transactions, } = normalizeAddressTransactions(data); @@ -145,7 +145,7 @@ export function parseAddressTransactions( ); if (!isKeyImageEqual(transaction.spent_outputs[j], keyImage)) { - transaction.total_sent = new JSBigInt( + transaction.total_sent = new BigInt( transaction.total_sent, ).subtract(transaction.spent_outputs[j].amount); @@ -267,6 +267,6 @@ export function parseUnspentOuts( return { unspentOuts, unusedOuts: [...unspentOuts], - per_kb_fee: new JSBigInt(per_kb_fee), + per_kb_fee: new BigInt(per_kb_fee), }; } diff --git a/hostAPI/response_parser_utils/types.ts b/src/hostAPI/response_parser_utils/types.ts similarity index 93% rename from hostAPI/response_parser_utils/types.ts rename to src/hostAPI/response_parser_utils/types.ts index 4a77e78..5ea774a 100644 --- a/hostAPI/response_parser_utils/types.ts +++ b/src/hostAPI/response_parser_utils/types.ts @@ -1,4 +1,5 @@ -import { JSBigInt, Omit } from "types"; +import { Omit } from "types"; +import { BigInt } from "biginteger"; export interface SpentOutput { amount: string; @@ -35,8 +36,8 @@ export interface AddressTransactions { export interface NormalizedTransaction extends Required> { - total_sent: JSBigInt; - amount: JSBigInt; + total_sent: BigInt; + amount: BigInt; approx_float_amount: number; timestamp: Date; } diff --git a/hostAPI/response_parser_utils/utils.ts b/src/hostAPI/response_parser_utils/utils.ts similarity index 91% rename from hostAPI/response_parser_utils/utils.ts rename to src/hostAPI/response_parser_utils/utils.ts index f565e56..9fef1f9 100644 --- a/hostAPI/response_parser_utils/utils.ts +++ b/src/hostAPI/response_parser_utils/utils.ts @@ -1,4 +1,3 @@ -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; import { AddressTransactions, AddressTransactionsTx, @@ -7,7 +6,9 @@ import { AddressInfo, UnspentOutput, } from "./types"; -import { JSBigInt, Omit } from "types"; +import { Omit } from "types"; +import { BigInt } from "biginteger"; +import { formatMoney } from "cryptonote_utils/formatters"; export function isKeyImageEqual({ key_image }: SpentOutput, keyImage: string) { return key_image === keyImage; @@ -56,7 +57,7 @@ export function normalizeTransaction( tx: AddressTransactionsTx, ): NormalizedTransaction { const defaultObj: Omit = { - amount: new JSBigInt(0), + amount: new BigInt(0), approx_float_amount: 0, hash: "", height: 0, @@ -66,7 +67,7 @@ export function normalizeTransaction( mixin: 0, spent_outputs: [] as SpentOutput[], total_received: "0", - total_sent: new JSBigInt(0), + total_sent: new BigInt(0), unlock_time: 0, payment_id: "", }; @@ -75,7 +76,7 @@ export function normalizeTransaction( ...defaultObj, ...tx, total_sent: tx.total_sent - ? new JSBigInt(tx.total_sent) + ? new BigInt(tx.total_sent) : defaultObj.total_sent, timestamp: new Date(tx.timestamp), }; @@ -92,18 +93,18 @@ export function zeroTransactionAmount({ total_received, total_sent, }: NormalizedTransaction) { - return new JSBigInt(total_received).add(total_sent).compare(0) <= 0; + return new BigInt(total_received).add(total_sent).compare(0) <= 0; } export function calculateTransactionAmount({ total_received, total_sent, }: NormalizedTransaction) { - return new JSBigInt(total_received).subtract(total_sent); + return new BigInt(total_received).subtract(total_sent); } export function estimateTransactionAmount({ amount }: NormalizedTransaction) { - return parseFloat(monero_utils.formatMoney(amount)); + return parseFloat(formatMoney(amount)); } /** diff --git a/monero_utils/monero_cryptonote_utils_instance.js b/src/index.ts similarity index 84% rename from monero_utils/monero_cryptonote_utils_instance.js rename to src/index.ts index b5a51c4..d760ce0 100644 --- a/monero_utils/monero_cryptonote_utils_instance.js +++ b/src/index.ts @@ -25,11 +25,8 @@ // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -"use strict"; -// -const monero_config = require("./monero_config"); -const cryptonote_utils = require("../cryptonote_utils/cryptonote_utils").cnUtil; -const monero_cryptonote_utils_instance = cryptonote_utils(monero_config); -// -module.exports = monero_cryptonote_utils_instance; + +export * from "./biginteger"; +export * from "./cryptonote_utils"; +export * from "./hostAPI"; +export * from "./monero_utils"; diff --git a/src/monero_utils/index.ts b/src/monero_utils/index.ts new file mode 100644 index 0000000..b86da03 --- /dev/null +++ b/src/monero_utils/index.ts @@ -0,0 +1,6 @@ +export * from "./sending_funds"; +export * from "./key_image_utils"; +export * from "./monero_config"; +export * from "./monero_paymentID_utils"; +export * from "./monero_txParsing_utils"; +export * from "./request_funds_uri_utils"; diff --git a/monero_utils/key_image_utils.ts b/src/monero_utils/key_image_utils.ts similarity index 94% rename from monero_utils/key_image_utils.ts rename to src/monero_utils/key_image_utils.ts index 2f4aa70..453ca8c 100644 --- a/monero_utils/key_image_utils.ts +++ b/src/monero_utils/key_image_utils.ts @@ -1,3 +1,5 @@ +import { generate_key_image } from "cryptonote_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -26,8 +28,6 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const monero_utils = require("./monero_cryptonote_utils_instance"); - // Managed caches - Can be used by apps which can't send a mutable_keyImagesByCacheKey export type KeyImageCache = { [cacheIndex: string]: string }; @@ -62,7 +62,7 @@ export function genKeyImage( return cachedKeyImage; } - const { key_image } = monero_utils.generate_key_image( + const { key_image } = generate_key_image( txPubKey, privViewKey, pubSpendKey, @@ -110,7 +110,7 @@ export function clearKeyImageCache(address: string) { const cache = keyImagesByWalletId[cacheId]; if (cache) { - throw "Key image cache still exists after deletion"; + throw Error("Key image cache still exists after deletion"); } } @@ -122,7 +122,7 @@ function parseAddress(address: string) { // NOTE: making the assumption that public_address is unique enough to identify a wallet for caching.... // FIXME: with subaddresses, is that still the case? would we need to split them up by subaddr anyway? if (!address) { - throw "Address does not exist"; + throw Error("Address does not exist"); } return address.toString(); diff --git a/monero_utils/monero_config.ts b/src/monero_utils/monero_config.ts similarity index 92% rename from monero_utils/monero_config.ts rename to src/monero_utils/monero_config.ts index a3d8fdc..9723682 100644 --- a/monero_utils/monero_config.ts +++ b/src/monero_utils/monero_config.ts @@ -1,4 +1,4 @@ -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; // Copyright (c) 2014-2018, MyMonero.com // @@ -30,6 +30,7 @@ import { JSBigInt } from "types"; export interface XMRConfig { readonly coinUnitPlaces: 12; + readonly coinUnits: BigInt; readonly txMinConfirms: 10; readonly coinSymbol: "XMR"; readonly openAliasPrefix: "xmr"; @@ -38,14 +39,18 @@ export interface XMRConfig { readonly addressPrefix: 18; readonly integratedAddressPrefix: 19; readonly subaddressPrefix: 42; - readonly dustThreshold: JSBigInt; + readonly dustThreshold: BigInt; readonly maxBlockNumber: 500000000; readonly avgBlockTime: 60; } +const coinUnitPlaces = 12; + export const config: XMRConfig = { // Number of atomic units in one unit of currency. e.g. 12 => 10^12 = 1000000000000 - coinUnitPlaces: 12, + coinUnitPlaces, + + coinUnits: new BigInt(10).pow(coinUnitPlaces), // Minimum number of confirmations for a transaction to show as confirmed txMinConfirms: 10, @@ -69,7 +74,7 @@ export const config: XMRConfig = { // Dust threshold in atomic units // 2*10^9 used for choosing outputs/change - we decompose all the way down if the receiver wants now regardless of threshold - dustThreshold: new JSBigInt("2000000000"), + dustThreshold: new BigInt("2000000000"), // Maximum block number, used for tx unlock time maxBlockNumber: 500000000, diff --git a/monero_utils/monero_paymentID_utils.ts b/src/monero_utils/monero_paymentID_utils.ts similarity index 93% rename from monero_utils/monero_paymentID_utils.ts rename to src/monero_utils/monero_paymentID_utils.ts index dd7f537..1789455 100644 --- a/monero_utils/monero_paymentID_utils.ts +++ b/src/monero_utils/monero_paymentID_utils.ts @@ -1,3 +1,5 @@ +import { rand_8 } from "cryptonote_utils"; + // Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. @@ -26,13 +28,11 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import monero_utils from "./monero_cryptonote_utils_instance"; - export function makePaymentID() { - return monero_utils.rand_8(); + return rand_8(); } -export function isValidOrNoPaymentID(pid?: string) { +export function isValidOrNoPaymentID(pid?: string | null) { if (!pid) { return true; } diff --git a/monero_utils/monero_txParsing_utils.ts b/src/monero_utils/monero_txParsing_utils.ts similarity index 60% rename from monero_utils/monero_txParsing_utils.ts rename to src/monero_utils/monero_txParsing_utils.ts index e3f1f1c..fbc2e28 100644 --- a/monero_utils/monero_txParsing_utils.ts +++ b/src/monero_utils/monero_txParsing_utils.ts @@ -1,4 +1,5 @@ import { NormalizedTransaction } from "hostAPI/response_parser_utils/types"; +import moment from "moment"; // Copyright (c) 2014-2018, MyMonero.com // @@ -29,7 +30,6 @@ import { NormalizedTransaction } from "hostAPI/response_parser_utils/types"; // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import { config } from "./monero_config"; -import monero_utils from "./monero_cryptonote_utils_instance"; export function IsTransactionConfirmed( tx: NormalizedTransaction, @@ -39,15 +39,58 @@ export function IsTransactionConfirmed( } export function IsTransactionUnlocked( - tx: NormalizedTransaction, + { unlock_time }: NormalizedTransaction, blockchainHeight: number, ) { - return monero_utils.is_tx_unlocked(tx.unlock_time, blockchainHeight); + if (!config.maxBlockNumber) { + throw Error("Max block number is not set in config!"); + } + if (unlock_time < config.maxBlockNumber) { + // unlock time is block height + return blockchainHeight >= unlock_time; + } else { + // unlock time is timestamp + const current_time = Math.round(new Date().getTime() / 1000); + return current_time >= unlock_time; + } } export function TransactionLockedReason( - tx: NormalizedTransaction, + { unlock_time }: NormalizedTransaction, blockchainHeight: number, ) { - return monero_utils.tx_locked_reason(tx.unlock_time, blockchainHeight); + if (unlock_time < config.maxBlockNumber) { + // unlock time is block height + const numBlocks = unlock_time - blockchainHeight; + if (numBlocks <= 0) { + return "Transaction is unlocked"; + } + const unlock_prediction = moment().add( + numBlocks * config.avgBlockTime, + "seconds", + ); + return ( + "Will be unlocked in " + + numBlocks + + " blocks, ~" + + unlock_prediction.fromNow(true) + + ", " + + unlock_prediction.calendar() + + "" + ); + } else { + // unlock time is timestamp + const current_time = Math.round(new Date().getTime() / 1000); + const time_difference = unlock_time - current_time; + if (time_difference <= 0) { + return "Transaction is unlocked"; + } + const unlock_moment = moment(unlock_time * 1000); + return ( + "Will be unlocked " + + unlock_moment.fromNow() + + ", " + + unlock_moment.calendar() + ); + } } diff --git a/monero_utils/request_funds_uri_utils.ts b/src/monero_utils/request_funds_uri_utils.ts similarity index 94% rename from monero_utils/request_funds_uri_utils.ts rename to src/monero_utils/request_funds_uri_utils.ts index 2e12950..277d054 100644 --- a/monero_utils/request_funds_uri_utils.ts +++ b/src/monero_utils/request_funds_uri_utils.ts @@ -27,10 +27,10 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import { config } from "./monero_config"; -import monero_utils from "./monero_cryptonote_utils_instance"; import { NetType } from "cryptonote_utils/nettype"; import { possibleOAAddress } from "./sending_funds/internal_libs/open_alias_lite"; import { Omit } from "types"; +import { decode_address } from "cryptonote_utils"; export enum URITypes { addressAsFirstPathComponent = 1, @@ -53,7 +53,7 @@ type FundRequestPayload = { export function encodeFundRequest(args: FundRequestPayload) { const address = args.address; if (!address) { - throw "missing address"; + throw Error("missing address"); } let mutable_uri = config.coinUriPrefix; @@ -64,7 +64,7 @@ export function encodeFundRequest(args: FundRequestPayload) { } else if (uriType === URITypes.addressAsFirstPathComponent) { // nothing to do } else { - throw "Illegal args.uriType"; + throw Error("Illegal args.uriType"); } mutable_uri += address; @@ -116,7 +116,7 @@ export function decodeFundRequest( if (!str.startsWith(config.coinUriPrefix)) { if (str.includes("?")) { // fairly sure this is correct.. (just an extra failsafe/filter) - throw "Unrecognized URI format"; + throw Error("Unrecognized URI format"); } if (possibleOAAddress(str)) { @@ -126,9 +126,9 @@ export function decodeFundRequest( } try { - monero_utils.decode_address(str, nettype); + decode_address(str, nettype); } catch (e) { - throw "No Monero request info"; + throw Error("No Monero request info"); } // then it looks like a monero address @@ -141,7 +141,7 @@ export function decodeFundRequest( const protocol = url.protocol; if (protocol !== config.coinUriPrefix) { - throw "Request URI has non-Monero protocol"; + throw Error("Request URI has non-Monero protocol"); } // it seems that if the URL has // in it, pathname will be empty, but host will contain the address instead diff --git a/monero_utils/sending_funds/index.ts b/src/monero_utils/sending_funds/index.ts similarity index 100% rename from monero_utils/sending_funds/index.ts rename to src/monero_utils/sending_funds/index.ts diff --git a/monero_utils/sending_funds/internal_libs/amt_utils.ts b/src/monero_utils/sending_funds/internal_libs/amt_utils.ts similarity index 80% rename from monero_utils/sending_funds/internal_libs/amt_utils.ts rename to src/monero_utils/sending_funds/internal_libs/amt_utils.ts index 8df8573..5c3935c 100644 --- a/monero_utils/sending_funds/internal_libs/amt_utils.ts +++ b/src/monero_utils/sending_funds/internal_libs/amt_utils.ts @@ -1,4 +1,4 @@ -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; /** * @description Gets a starting total amount. @@ -11,19 +11,19 @@ import { JSBigInt } from "types"; * * @export * @param {boolean} isSweeping - * @param {JSBigInt} feelessTotal - * @param {JSBigInt} networkFee + * @param {BigInt} feelessTotal + * @param {BigInt} networkFee * @returns */ export function getBaseTotalAmount( isSweeping: boolean, - feelessTotal: JSBigInt, - networkFee: JSBigInt, + feelessTotal: BigInt, + networkFee: BigInt, ) { // const hostingService_chargeAmount = hostedMoneroAPIClient.HostingServiceChargeFor_transactionWithNetworkFee(attemptAt_network_minimumFee) if (isSweeping) { - return new JSBigInt("18450000000000000000"); //~uint64 max + return new BigInt("18450000000000000000"); //~uint64 max } else { return feelessTotal.add( networkFee, diff --git a/monero_utils/sending_funds/internal_libs/arr_utils.ts b/src/monero_utils/sending_funds/internal_libs/arr_utils.ts similarity index 100% rename from monero_utils/sending_funds/internal_libs/arr_utils.ts rename to src/monero_utils/sending_funds/internal_libs/arr_utils.ts diff --git a/monero_utils/sending_funds/internal_libs/async_node_api.ts b/src/monero_utils/sending_funds/internal_libs/async_node_api.ts similarity index 94% rename from monero_utils/sending_funds/internal_libs/async_node_api.ts rename to src/monero_utils/sending_funds/internal_libs/async_node_api.ts index c3a1cc1..0e52053 100644 --- a/monero_utils/sending_funds/internal_libs/async_node_api.ts +++ b/src/monero_utils/sending_funds/internal_libs/async_node_api.ts @@ -1,7 +1,7 @@ import { ViewSendKeys, Output, AmountOutput } from "./types"; import { Log } from "./logger"; import { ERR } from "./errors"; -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; export class WrappedNodeApi { private api: any; @@ -18,7 +18,7 @@ export class WrappedNodeApi { ) { type ResolveVal = { unusedOuts: Output[]; - dynamicFeePerKB: JSBigInt; + dynamicFeePerKB: BigInt; }; return new Promise((resolve, reject) => { @@ -28,7 +28,7 @@ export class WrappedNodeApi { err: Error, _: Output[], // unspent outs, the original copy of unusedOuts unusedOuts: Output[], - dynamicFeePerKB: JSBigInt, + dynamicFeePerKB: BigInt, ) => { if (err) { return reject(err); diff --git a/monero_utils/sending_funds/internal_libs/construct_tx_and_send/construct_tx_and_send.ts b/src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/construct_tx_and_send.ts similarity index 100% rename from monero_utils/sending_funds/internal_libs/construct_tx_and_send/construct_tx_and_send.ts rename to src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/construct_tx_and_send.ts diff --git a/monero_utils/sending_funds/internal_libs/construct_tx_and_send/index.ts b/src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/index.ts similarity index 100% rename from monero_utils/sending_funds/internal_libs/construct_tx_and_send/index.ts rename to src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/index.ts diff --git a/monero_utils/sending_funds/internal_libs/construct_tx_and_send/types.ts b/src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/types.ts similarity index 82% rename from monero_utils/sending_funds/internal_libs/construct_tx_and_send/types.ts rename to src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/types.ts index 228d4a3..c9715ed 100644 --- a/monero_utils/sending_funds/internal_libs/construct_tx_and_send/types.ts +++ b/src/monero_utils/sending_funds/internal_libs/construct_tx_and_send/types.ts @@ -2,13 +2,13 @@ import { WrappedNodeApi } from "../async_node_api"; import { NetType } from "cryptonote_utils/nettype"; import { ViewSendKeys, - JSBigInt, ParsedTarget, Pid, Output, AmountOutput, } from "../types"; import { Status } from "../../status_update_constants"; +import { BigInt } from "biginteger"; export type GetFundTargetsAndFeeParams = { senderAddress: string; @@ -22,9 +22,9 @@ export type GetFundTargetsAndFeeParams = { unusedOuts: Output[]; simplePriority: number; - feelessTotal: JSBigInt; - feePerKB: JSBigInt; // obtained from server, so passed in - networkFee: JSBigInt; + feelessTotal: BigInt; + feePerKB: BigInt; // obtained from server, so passed in + networkFee: BigInt; isSweeping: boolean; isRingCT: boolean; @@ -52,9 +52,9 @@ export type CreateTxAndAttemptToSendParams = { usingOuts: Output[]; simplePriority: number; - feelessTotal: JSBigInt; - feePerKB: JSBigInt; // obtained from server, so passed in - networkFee: JSBigInt; + feelessTotal: BigInt; + feePerKB: BigInt; // obtained from server, so passed in + networkFee: BigInt; isSweeping: boolean; isRingCT: boolean; diff --git a/monero_utils/sending_funds/internal_libs/errors.ts b/src/monero_utils/sending_funds/internal_libs/errors.ts similarity index 86% rename from monero_utils/sending_funds/internal_libs/errors.ts rename to src/monero_utils/sending_funds/internal_libs/errors.ts index 6d4b219..36de8d2 100644 --- a/monero_utils/sending_funds/internal_libs/errors.ts +++ b/src/monero_utils/sending_funds/internal_libs/errors.ts @@ -1,6 +1,6 @@ -import { JSBigInt } from "types"; import { config } from "monero_utils/monero_config"; -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; +import { BigInt } from "biginteger"; +import { formatMoney } from "cryptonote_utils/formatters"; export namespace ERR { export namespace RING { @@ -29,10 +29,10 @@ export namespace ERR { } export namespace BAL { - export function insuff(amtAvail: JSBigInt, requiredAmt: JSBigInt) { + export function insuff(amtAvail: BigInt, requiredAmt: BigInt) { const { coinSymbol } = config; - const amtAvailStr = monero_utils.formatMoney(amtAvail); - const requiredAmtStr = monero_utils.formatMoney(requiredAmt); + const amtAvailStr = formatMoney(amtAvail); + const requiredAmtStr = formatMoney(requiredAmt); const errStr = `Your spendable balance is too low. Have ${amtAvailStr} ${coinSymbol} spendable, need ${requiredAmtStr} ${coinSymbol}.`; return Error(errStr); } diff --git a/monero_utils/sending_funds/internal_libs/fee_utils.ts b/src/monero_utils/sending_funds/internal_libs/fee_utils.ts similarity index 66% rename from monero_utils/sending_funds/internal_libs/fee_utils.ts rename to src/monero_utils/sending_funds/internal_libs/fee_utils.ts index 59bb523..68edfab 100644 --- a/monero_utils/sending_funds/internal_libs/fee_utils.ts +++ b/src/monero_utils/sending_funds/internal_libs/fee_utils.ts @@ -1,23 +1,23 @@ -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; export const DEFAULT_FEE_PRIORITY = 1; export function calculateFee( - feePerKB: JSBigInt, + feePerKB: BigInt, numOfBytes: number, feeMultiplier: number, ) { - const numberOf_kB = new JSBigInt((numOfBytes + 1023.0) / 1024.0); // i.e. ceil + const numberOf_kB = new BigInt((numOfBytes + 1023.0) / 1024.0); // i.e. ceil return calculateFeeKb(feePerKB, numberOf_kB, feeMultiplier); } export function calculateFeeKb( - feePerKB: JSBigInt, - numOfBytes: JSBigInt | number, + feePerKB: BigInt, + numOfBytes: BigInt | number, feeMultiplier: number, ) { - const numberOf_kB = new JSBigInt(numOfBytes); + const numberOf_kB = new BigInt(numOfBytes); const fee = feePerKB.multiply(feeMultiplier).multiply(numberOf_kB); return fee; @@ -29,7 +29,9 @@ export function multiplyFeePriority(prio: number) { const priority = prio || DEFAULT_FEE_PRIORITY; if (priority <= 0 || priority > fee_multiplier.length) { - throw "fee_multiplier_for_priority: simple_priority out of bounds"; + throw Error( + "fee_multiplier_for_priority: simple_priority out of bounds", + ); } const priority_idx = priority - 1; return fee_multiplier[priority_idx]; diff --git a/monero_utils/sending_funds/internal_libs/key_utils.ts b/src/monero_utils/sending_funds/internal_libs/key_utils.ts similarity index 64% rename from monero_utils/sending_funds/internal_libs/key_utils.ts rename to src/monero_utils/sending_funds/internal_libs/key_utils.ts index 00d11ef..88c7327 100644 --- a/monero_utils/sending_funds/internal_libs/key_utils.ts +++ b/src/monero_utils/sending_funds/internal_libs/key_utils.ts @@ -1,6 +1,6 @@ import { NetType } from "cryptonote_utils/nettype"; -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; import { Log } from "./logger"; +import { decode_address } from "cryptonote_utils"; export function getTargetPubViewKey( encPid: boolean, @@ -8,7 +8,7 @@ export function getTargetPubViewKey( nettype: NetType, ): string | undefined { if (encPid) { - const key = monero_utils.decode_address(targetAddress, nettype).view; + const key = decode_address(targetAddress, nettype).view; Log.Target.viewKey(key); return key; diff --git a/monero_utils/sending_funds/internal_libs/logger.ts b/src/monero_utils/sending_funds/internal_libs/logger.ts similarity index 62% rename from monero_utils/sending_funds/internal_libs/logger.ts rename to src/monero_utils/sending_funds/internal_libs/logger.ts index 3430b02..7ebea50 100644 --- a/monero_utils/sending_funds/internal_libs/logger.ts +++ b/src/monero_utils/sending_funds/internal_libs/logger.ts @@ -1,28 +1,35 @@ -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; import { ParsedTarget, Output } from "./types"; -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; +import { + formatMoneySymbol, + formatMoneyFullSymbol, + formatMoney, + formatMoneyFull, + printDsts, +} from "cryptonote_utils/formatters"; +import { SignedTransaction } from "cryptonote_utils"; export namespace Log { export namespace Amount { - export function beforeFee(feelessTotal: JSBigInt, isSweeping: boolean) { + export function beforeFee(feelessTotal: BigInt, isSweeping: boolean) { const feeless_total = isSweeping ? "all" - : monero_utils.formatMoney(feelessTotal); + : formatMoney(feelessTotal); console.log(`💬 Total to send, before fee: ${feeless_total}`); } - export function change(changeAmount: JSBigInt) { + export function change(changeAmount: BigInt) { console.log("changeAmount", changeAmount); } - export function changeAmountDivRem(amt: [JSBigInt, JSBigInt]) { + export function changeAmountDivRem(amt: [BigInt, BigInt]) { console.log("💬 changeAmountDivRem", amt); } - export function toSelf(changeAmount: JSBigInt, selfAddress: string) { + export function toSelf(changeAmount: BigInt, selfAddress: string) { console.log( "Sending change of " + - monero_utils.formatMoneySymbol(changeAmount) + + formatMoneySymbol(changeAmount) + " to " + selfAddress, ); @@ -30,41 +37,41 @@ export namespace Log { } export namespace Fee { - export function dynPerKB(dynFeePerKB: JSBigInt) { + export function dynPerKB(dynFeePerKB: BigInt) { console.log( "Received dynamic per kb fee", - monero_utils.formatMoneySymbol(dynFeePerKB), + formatMoneySymbol(dynFeePerKB), ); } export function basedOnInputs( - newNeededFee: JSBigInt, + newNeededFee: BigInt, usingOuts: Output[], ) { console.log( "New fee: " + - monero_utils.formatMoneySymbol(newNeededFee) + + formatMoneySymbol(newNeededFee) + " for " + usingOuts.length + " inputs", ); } - export function belowDustThreshold(changeDivDustRemainder: JSBigInt) { + export function belowDustThreshold(changeDivDustRemainder: BigInt) { console.log( "💬 Miners will add change of " + - monero_utils.formatMoneyFullSymbol(changeDivDustRemainder) + + formatMoneyFullSymbol(changeDivDustRemainder) + " to transaction fee (below dust threshold)", ); } export function estLowerThanReal( - estMinNetworkFee: JSBigInt, - feeActuallyNeededByNetwork: JSBigInt, + estMinNetworkFee: BigInt, + feeActuallyNeededByNetwork: BigInt, ) { console.log( "💬 Need to reconstruct the tx with enough of a network fee. Previous fee: " + - monero_utils.formatMoneyFull(estMinNetworkFee) + + formatMoneyFull(estMinNetworkFee) + " New fee: " + - monero_utils.formatMoneyFull(feeActuallyNeededByNetwork), + formatMoneyFull(feeActuallyNeededByNetwork), ); console.log("Reconstructing tx...."); } @@ -72,45 +79,40 @@ export namespace Log { export function txKB( txBlobBytes: number, numOfKB: number, - estMinNetworkFee: JSBigInt, + estMinNetworkFee: BigInt, ) { console.log( txBlobBytes + " bytes <= " + numOfKB + " KB (current fee: " + - monero_utils.formatMoneyFull(estMinNetworkFee) + + formatMoneyFull(estMinNetworkFee) + ")", ); } - export function successfulTx(finalNetworkFee: JSBigInt) { + export function successfulTx(finalNetworkFee: BigInt) { console.log( "💬 Successful tx generation, submitting tx. Going with final_networkFee of ", - monero_utils.formatMoney(finalNetworkFee), + formatMoney(finalNetworkFee), ); } } export namespace Balance { - export function requiredBase( - totalAmount: JSBigInt, - isSweeping: boolean, - ) { + export function requiredBase(totalAmount: BigInt, isSweeping: boolean) { if (isSweeping) { console.log("Balance required: all"); } else { console.log( - "Balance required: " + - monero_utils.formatMoneySymbol(totalAmount), + "Balance required: " + formatMoneySymbol(totalAmount), ); } } - export function requiredPostRct(totalAmount: JSBigInt) { + export function requiredPostRct(totalAmount: BigInt) { console.log( - "~ Balance required: " + - monero_utils.formatMoneySymbol(totalAmount), + "~ Balance required: " + formatMoneySymbol(totalAmount), ); } } @@ -126,7 +128,7 @@ export namespace Log { export function display(out: Output) { console.log( "Using output: " + - monero_utils.formatMoney(out.amount) + + formatMoney(out.amount) + " - " + JSON.stringify(out), ); @@ -140,12 +142,12 @@ export namespace Log { export function fullDisplay(fundTargets: ParsedTarget[]) { console.log("Destinations: "); - monero_utils.printDsts(fundTargets); + printDsts(fundTargets); } export function displayDecomposed(splitDestinations: ParsedTarget[]) { console.log("Decomposed destinations:"); - monero_utils.printDsts(splitDestinations); + printDsts(splitDestinations); } export function viewKey(viewKey: string) { @@ -154,7 +156,7 @@ export namespace Log { } export namespace Transaction { - export function signed(signedTx) { + export function signed(signedTx: SignedTransaction) { console.log("signed tx: ", JSON.stringify(signedTx)); } export function serializedAndHash( @@ -167,10 +169,10 @@ export namespace Log { } export namespace SelectOutsAndAmtForMix { - export function target(targetAmount: JSBigInt) { + export function target(targetAmount: BigInt) { console.log( "Selecting outputs to use. target: " + - monero_utils.formatMoney(targetAmount), + formatMoney(targetAmount), ); } @@ -192,11 +194,11 @@ export namespace Log { } } - export function usingOut(outAmount: JSBigInt, out: Output) { + export function usingOut(outAmount: BigInt, out: Output) { console.log( - `Using output: ${monero_utils.formatMoney( - outAmount, - )} - ${JSON.stringify(out)}`, + `Using output: ${formatMoney(outAmount)} - ${JSON.stringify( + out, + )}`, ); } } diff --git a/monero_utils/sending_funds/internal_libs/open_alias_lite.ts b/src/monero_utils/sending_funds/internal_libs/open_alias_lite.ts similarity index 100% rename from monero_utils/sending_funds/internal_libs/open_alias_lite.ts rename to src/monero_utils/sending_funds/internal_libs/open_alias_lite.ts diff --git a/monero_utils/sending_funds/internal_libs/output_selection.ts b/src/monero_utils/sending_funds/internal_libs/output_selection.ts similarity index 89% rename from monero_utils/sending_funds/internal_libs/output_selection.ts rename to src/monero_utils/sending_funds/internal_libs/output_selection.ts index 9fb0b53..085a844 100644 --- a/monero_utils/sending_funds/internal_libs/output_selection.ts +++ b/src/monero_utils/sending_funds/internal_libs/output_selection.ts @@ -2,17 +2,17 @@ import { Output } from "./types"; import { popRandElement } from "./arr_utils"; import { Log } from "./logger"; import { config } from "monero_utils/monero_config"; -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; export function selectOutputsAndAmountForMixin( - targetAmount: JSBigInt, + targetAmount: BigInt, unusedOuts: Output[], isRingCT: boolean, sweeping: boolean, ) { Log.SelectOutsAndAmtForMix.target(targetAmount); - let usingOutsAmount = new JSBigInt(0); + let usingOutsAmount = new BigInt(0); const usingOuts: Output[] = []; const remainingUnusedOuts = unusedOuts.slice(); // take copy so as to prevent issue if we must re-enter tx building fn if fee too low after building while ( @@ -24,7 +24,7 @@ export function selectOutputsAndAmountForMixin( // out.rct is set by the server continue; // skip rct outputs if not creating rct tx } - const outAmount = new JSBigInt(out.amount); + const outAmount = new BigInt(out.amount); if (outAmount.compare(config.dustThreshold) < 0) { // amount is dusty.. if (!sweeping) { diff --git a/monero_utils/sending_funds/internal_libs/parse_target.ts b/src/monero_utils/sending_funds/internal_libs/parse_target.ts similarity index 84% rename from monero_utils/sending_funds/internal_libs/parse_target.ts rename to src/monero_utils/sending_funds/internal_libs/parse_target.ts index b2abf13..4bdd409 100644 --- a/monero_utils/sending_funds/internal_libs/parse_target.ts +++ b/src/monero_utils/sending_funds/internal_libs/parse_target.ts @@ -1,9 +1,9 @@ import { ParsedTarget, RawTarget } from "./types"; import { NetType } from "cryptonote_utils/nettype"; import { ERR } from "./errors"; -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; import { possibleOAAddress } from "./open_alias_lite"; -import { JSBigInt } from "types"; +import { decode_address } from "cryptonote_utils"; +import { parseMoney } from "cryptonote_utils/formatters"; /** * @description Map through the provided targets and normalize each address/amount pair @@ -35,13 +35,13 @@ export function parseTargets( const amountStr = amount.toString(); try { - monero_utils.decode_address(address, nettype); + decode_address(address, nettype); } catch (e) { throw ERR.PARSE_TRGT.decodeAddress(address, e); } try { - const parsedAmount: JSBigInt = monero_utils.parseMoney(amountStr); + const parsedAmount = parseMoney(amountStr); return { address, amount: parsedAmount }; } catch (e) { throw ERR.PARSE_TRGT.amount(amountStr, e); diff --git a/monero_utils/sending_funds/internal_libs/pid_utils.ts b/src/monero_utils/sending_funds/internal_libs/pid_utils.ts similarity index 80% rename from monero_utils/sending_funds/internal_libs/pid_utils.ts rename to src/monero_utils/sending_funds/internal_libs/pid_utils.ts index 1302a11..9510bdc 100644 --- a/monero_utils/sending_funds/internal_libs/pid_utils.ts +++ b/src/monero_utils/sending_funds/internal_libs/pid_utils.ts @@ -1,7 +1,7 @@ -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; -import monero_paymentID_utils from "monero_utils/monero_paymentID_utils"; +import { isValidOrNoPaymentID } from "monero_utils/monero_paymentID_utils"; import { NetType } from "cryptonote_utils/nettype"; import { ERR } from "./errors"; +import { decode_address, is_subaddress } from "cryptonote_utils"; /** * @@ -35,13 +35,13 @@ export function checkAddressAndPidValidity( let retPid = pid; let encryptPid = false; - const decodedAddress = monero_utils.decode_address(address, nettype); + const decodedAddress = decode_address(address, nettype); // assert that the target address is not of type integrated nor subaddress // if a payment id is included if (retPid) { if (decodedAddress.intPaymentId) { throw ERR.PID.NO_INTEG_ADDR; - } else if (monero_utils.is_subaddress(address, nettype)) { + } else if (is_subaddress(address, nettype)) { throw ERR.PID.NO_SUB_ADDR; } } @@ -52,7 +52,7 @@ export function checkAddressAndPidValidity( if (decodedAddress.intPaymentId) { retPid = decodedAddress.intPaymentId; encryptPid = true; - } else if (!monero_paymentID_utils.IsValidPaymentIDOrNoPaymentID(retPid)) { + } else if (!isValidOrNoPaymentID(retPid)) { throw ERR.PID.INVAL; } diff --git a/monero_utils/sending_funds/internal_libs/tx_utils/index.ts b/src/monero_utils/sending_funds/internal_libs/tx_utils/index.ts similarity index 100% rename from monero_utils/sending_funds/internal_libs/tx_utils/index.ts rename to src/monero_utils/sending_funds/internal_libs/tx_utils/index.ts diff --git a/monero_utils/sending_funds/internal_libs/tx_utils/tx_utils.ts b/src/monero_utils/sending_funds/internal_libs/tx_utils/tx_utils.ts similarity index 90% rename from monero_utils/sending_funds/internal_libs/tx_utils/tx_utils.ts rename to src/monero_utils/sending_funds/internal_libs/tx_utils/tx_utils.ts index 9929168..b6c2bd7 100644 --- a/monero_utils/sending_funds/internal_libs/tx_utils/tx_utils.ts +++ b/src/monero_utils/sending_funds/internal_libs/tx_utils/tx_utils.ts @@ -1,4 +1,3 @@ -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; import { config } from "monero_utils/monero_config"; import { getTargetPubViewKey } from "../key_utils"; import { @@ -12,7 +11,18 @@ import { Log } from "../logger"; import { popRandElement } from "../arr_utils"; import { calculateFee, multiplyFeePriority } from "../fee_utils"; import { ParsedTarget } from "../types"; -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; +import { + estimateRctSize, + random_scalar, + create_address, + create_transaction, + serialize_tx, + cn_fast_hash, + serialize_rct_tx_with_hash, + SignedTransaction, +} from "cryptonote_utils"; +import { decompose_tx_destinations } from "cryptonote_utils/formatters"; // #region totalAmtAndEstFee @@ -65,7 +75,7 @@ function estRctFeeAndAmt(params: EstRctFeeAndAmtParams) { let feeBasedOnOuts = calculateFee( feePerKB, - monero_utils.estimateRctSize(usingOuts.length, mixin, 2), + estimateRctSize(usingOuts.length, mixin, 2), multiplyFeePriority(simplePriority), ); @@ -82,7 +92,7 @@ function estRctFeeAndAmt(params: EstRctFeeAndAmtParams) { return { totalAmount, newFee }; } -function estRctSwpingAmt(usingOutsAmount: JSBigInt, fee: JSBigInt) { +function estRctSwpingAmt(usingOutsAmount: BigInt, fee: BigInt) { /* // When/if sending to multiple destinations supported, uncomment and port this: if (dsts.length !== 1) { @@ -107,7 +117,7 @@ function estRctSwpingAmt(usingOutsAmount: JSBigInt, fee: JSBigInt) { return [totalAmount, fee]; } -function estRctNonSwpAmt(params: EstRctFeeAndAmtParams, fee: JSBigInt) { +function estRctNonSwpAmt(params: EstRctFeeAndAmtParams, fee: BigInt) { const { mixin, remainingUnusedOuts, @@ -141,7 +151,7 @@ function estRctNonSwpAmt(params: EstRctFeeAndAmtParams, fee: JSBigInt) { // and recalculate invalidated values newFee = calculateFee( feePerKB, - monero_utils.estimateRctSize(usingOuts.length, mixin, 2), + estimateRctSize(usingOuts.length, mixin, 2), multiplyFeePriority(simplePriority), ); currTotalAmount = feelessTotal.add(newFee); @@ -267,16 +277,14 @@ export function validateAndConstructFundTargets( if (isRingCT) { // then create random destination to keep 2 outputs always in case of 0 change // so we dont create 1 output (outlier) - const fakeAddress = monero_utils.create_address( - monero_utils.random_scalar(), - nettype, - ).public_addr; + const fakeAddress = create_address(random_scalar(), nettype) + .public_addr; Log.Output.uniformity(fakeAddress); fundTargets.push({ address: fakeAddress, - amount: JSBigInt.ZERO, + amount: BigInt.ZERO, }); } } @@ -327,13 +335,13 @@ function makeSignedTx(params: ConstructTxParams) { nettype, ); - const splitDestinations: ParsedTarget[] = monero_utils.decompose_tx_destinations( + const splitDestinations: ParsedTarget[] = decompose_tx_destinations( fundTargets, isRingCT, ); Log.Target.displayDecomposed(splitDestinations); - const signedTx = monero_utils.create_transaction( + const signedTx = create_transaction( senderPublicKeys, senderPrivateKeys, splitDestinations, @@ -357,7 +365,7 @@ function makeSignedTx(params: ConstructTxParams) { } } -function getSerializedTxAndHash(signedTx) { +function getSerializedTxAndHash(signedTx: SignedTransaction) { type ReturnVal = { serializedSignedTx: string; txHash: string; @@ -365,8 +373,8 @@ function getSerializedTxAndHash(signedTx) { // pre rct if (signedTx.version === 1) { - const serializedSignedTx = monero_utils.serialize_tx(signedTx); - const txHash = monero_utils.cn_fast_hash(serializedSignedTx); + const serializedSignedTx = serialize_tx(signedTx); + const txHash = cn_fast_hash(serializedSignedTx); const ret: ReturnVal = { serializedSignedTx, @@ -379,7 +387,7 @@ function getSerializedTxAndHash(signedTx) { } // rct else { - const { raw, hash } = monero_utils.serialize_rct_tx_with_hash(signedTx); + const { raw, hash } = serialize_rct_tx_with_hash(signedTx); const ret: ReturnVal = { serializedSignedTx: raw, @@ -392,7 +400,7 @@ function getSerializedTxAndHash(signedTx) { } } -function getTxSize(serializedSignedTx: string, estMinNetworkFee: JSBigInt) { +function getTxSize(serializedSignedTx: string, estMinNetworkFee: BigInt) { // work out per-kb fee for transaction and verify that it's enough const txBlobBytes = serializedSignedTx.length / 2; let numOfKB = Math.floor(txBlobBytes / 1024); diff --git a/monero_utils/sending_funds/internal_libs/tx_utils/types.ts b/src/monero_utils/sending_funds/internal_libs/tx_utils/types.ts similarity index 68% rename from monero_utils/sending_funds/internal_libs/tx_utils/types.ts rename to src/monero_utils/sending_funds/internal_libs/tx_utils/types.ts index f6fc0f0..b7a577c 100644 --- a/monero_utils/sending_funds/internal_libs/tx_utils/types.ts +++ b/src/monero_utils/sending_funds/internal_libs/tx_utils/types.ts @@ -6,7 +6,7 @@ import { Output, AmountOutput, } from "../types"; -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; export type ConstructTxParams = { senderPublicKeys: ViewSendKeys; @@ -22,7 +22,7 @@ export type ConstructTxParams = { mixin: number; usingOuts: Output[]; - networkFee: JSBigInt; + networkFee: BigInt; isRingCT: boolean; @@ -30,17 +30,17 @@ export type ConstructTxParams = { }; export type TotalAmtAndEstFeeParams = { - usingOutsAmount: JSBigInt; - baseTotalAmount: JSBigInt; + usingOutsAmount: BigInt; + baseTotalAmount: BigInt; mixin: number; remainingUnusedOuts: Output[]; usingOuts: Output[]; simplePriority: number; - feelessTotal: JSBigInt; - feePerKB: JSBigInt; // obtained from server, so passed in - networkFee: JSBigInt; + feelessTotal: BigInt; + feePerKB: BigInt; // obtained from server, so passed in + networkFee: BigInt; isSweeping: boolean; isRingCT: boolean; @@ -48,14 +48,14 @@ export type TotalAmtAndEstFeeParams = { export type EstRctFeeAndAmtParams = { mixin: number; - usingOutsAmount: JSBigInt; + usingOutsAmount: BigInt; remainingUnusedOuts: Output[]; usingOuts: Output[]; simplePriority: number; - feelessTotal: JSBigInt; - feePerKB: JSBigInt; // obtained from server, so passed in - networkFee: JSBigInt; + feelessTotal: BigInt; + feePerKB: BigInt; // obtained from server, so passed in + networkFee: BigInt; isSweeping: boolean; }; @@ -64,9 +64,9 @@ export type ConstructFundTargetsParams = { senderAddress: string; targetAddress: string; - feelessTotal: JSBigInt; - totalAmount: JSBigInt; - usingOutsAmount: JSBigInt; + feelessTotal: BigInt; + totalAmount: BigInt; + usingOutsAmount: BigInt; isSweeping: boolean; isRingCT: boolean; diff --git a/monero_utils/sending_funds/internal_libs/types.ts b/src/monero_utils/sending_funds/internal_libs/types.ts similarity index 92% rename from monero_utils/sending_funds/internal_libs/types.ts rename to src/monero_utils/sending_funds/internal_libs/types.ts index e0ddf1a..e8d45d5 100644 --- a/monero_utils/sending_funds/internal_libs/types.ts +++ b/src/monero_utils/sending_funds/internal_libs/types.ts @@ -1,4 +1,4 @@ -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; export type ViewSendKeys = { view: string; @@ -11,7 +11,7 @@ export type RawTarget = { export type ParsedTarget = { address: string; - amount: JSBigInt; + amount: BigInt; }; export type Pid = string | null; diff --git a/monero_utils/sending_funds/mixin_utils.ts b/src/monero_utils/sending_funds/mixin_utils.ts similarity index 100% rename from monero_utils/sending_funds/mixin_utils.ts rename to src/monero_utils/sending_funds/mixin_utils.ts diff --git a/monero_utils/sending_funds/sending_funds.ts b/src/monero_utils/sending_funds/sending_funds.ts similarity index 95% rename from monero_utils/sending_funds/sending_funds.ts rename to src/monero_utils/sending_funds/sending_funds.ts index d3d60b6..28f57f8 100644 --- a/monero_utils/sending_funds/sending_funds.ts +++ b/src/monero_utils/sending_funds/sending_funds.ts @@ -27,7 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -import monero_utils from "monero_utils/monero_cryptonote_utils_instance"; import { NetType } from "cryptonote_utils/nettype"; import { RawTarget, Pid, ViewSendKeys } from "./internal_libs/types"; import { @@ -46,11 +45,13 @@ import { getRestOfTxData, createTxAndAttemptToSend, } from "./internal_libs/construct_tx_and_send"; -import { JSBigInt } from "types"; +import { BigInt } from "biginteger"; +import { estimateRctSize } from "cryptonote_utils"; +import { formatMoneyFull } from "cryptonote_utils/formatters"; export function estimatedTransactionNetworkFee( nonZeroMixin: number, - feePerKB: JSBigInt, + feePerKB: BigInt, simplePriority: number, ) { const numOfInputs = 2; // this might change -- might select inputs @@ -58,7 +59,7 @@ export function estimatedTransactionNetworkFee( 1 /*dest*/ + 1 /*change*/ + 0; /*no mymonero fee presently*/ // TODO: update est tx size for bulletproofs // TODO: normalize est tx size fn naming - const estimatedTxSize = monero_utils.estimateRctSize( + const estimatedTxSize = estimateRctSize( numOfInputs, nonZeroMixin, numOfOutputs, @@ -77,7 +78,7 @@ export type SendFundsRet = { sentAmount: number; pid: Pid; txHash: string; - txFee: JSBigInt; + txFee: BigInt; }; export async function SendFunds( @@ -118,7 +119,7 @@ export async function SendFunds( } const { address, amount } = singleTarget; - const feelessTotal = new JSBigInt(amount); + const feelessTotal = new BigInt(amount); Log.Amount.beforeFee(feelessTotal, isSweeping); @@ -228,7 +229,7 @@ export async function SendFunds( if (success) { const sentAmount = isSweeping - ? parseFloat(monero_utils.formatMoneyFull(feelessTotal)) + ? parseFloat(formatMoneyFull(feelessTotal)) : targetAmount; return { diff --git a/monero_utils/sending_funds/status_update_constants.ts b/src/monero_utils/sending_funds/status_update_constants.ts similarity index 100% rename from monero_utils/sending_funds/status_update_constants.ts rename to src/monero_utils/sending_funds/status_update_constants.ts diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..49823a0 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +export type Omit = Pick>; + +export interface SecretCommitment { + x: string; + a: string; +} + +export interface MixCommitment { + dest: string; + mask: string; +} diff --git a/tsconfig.json b/tsconfig.json index cdf00ab..9e9a074 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "target": "es2017", "sourceMap": false, "module": "commonjs", - "baseUrl": "./", + "baseUrl": "src", + "outDir": "dist", "lib": ["es2017", "dom"], "allowSyntheticDefaultImports": true, @@ -20,5 +21,5 @@ "pretty": true, "resolveJsonModule": true }, - "include": ["./src/", "./__test__/", "examples", "./"] + "include": ["./src/", "./__test__/", "examples"] } diff --git a/types.ts b/types.ts deleted file mode 100644 index 5bd49bc..0000000 --- a/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import BigInt = require("cryptonote_utils/biginteger"); -export const JSBigInt = BigInt.BigInteger; - -export type JSBigInt = BigInt.BigInteger; - -export type Omit = Pick>; diff --git a/yarn.lock b/yarn.lock index 7a7133a..043d6a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,6 +77,13 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -129,6 +136,18 @@ array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" +array-filter@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" + +array-map@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" + +array-reduce@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" + array-unique@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" @@ -157,6 +176,10 @@ astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" @@ -246,6 +269,13 @@ babel-jest@^23.0.1: babel-plugin-istanbul "^4.1.6" babel-preset-jest "^23.0.1" +babel-jest@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.4.0.tgz#22c34c392e2176f6a4c367992a7fcff69d2e8557" + dependencies: + babel-plugin-istanbul "^4.1.6" + babel-preset-jest "^23.2.0" + babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -265,10 +295,21 @@ babel-plugin-jest-hoist@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.0.1.tgz#eaa11c964563aea9c21becef2bdf7853f7f3c148" +babel-plugin-jest-hoist@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167" + babel-plugin-syntax-object-rest-spread@^6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" +babel-preset-jest@^23.0.0, babel-preset-jest@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46" + dependencies: + babel-plugin-jest-hoist "^23.2.0" + babel-plugin-syntax-object-rest-spread "^6.13.0" + babel-preset-jest@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.0.1.tgz#631cc545c6cf021943013bcaf22f45d87fe62198" @@ -288,7 +329,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -305,7 +346,7 @@ babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: babylon "^6.18.0" lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.26.0: +babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: @@ -319,7 +360,7 @@ babel-traverse@^6.18.0, babel-traverse@^6.26.0: invariant "^2.2.2" lodash "^4.17.4" -babel-types@^6.18.0, babel-types@^6.26.0: +babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: @@ -354,6 +395,10 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +binary-extensions@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -388,7 +433,7 @@ browser-process-hrtime@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e" -browser-resolve@^1.11.2: +browser-resolve@^1.11.2, browser-resolve@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" dependencies: @@ -475,6 +520,21 @@ chalk@^2.0.0, chalk@^2.0.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chokidar@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" @@ -571,6 +631,22 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +cpx@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" + dependencies: + babel-runtime "^6.9.2" + chokidar "^1.6.0" + duplexer "^0.1.1" + glob "^7.0.5" + glob2base "^0.0.12" + minimatch "^3.0.2" + mkdirp "^0.5.1" + resolve "^1.1.7" + safe-buffer "^5.0.1" + shell-quote "^1.6.1" + subarg "^1.0.0" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -619,6 +695,12 @@ decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +decamelize@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-2.0.0.tgz#656d7bbc8094c4c788ea53c5840908c9c7d063c7" + dependencies: + xregexp "4.0.0" + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -699,6 +781,10 @@ domexception@^1.0.0: dependencies: webidl-conversions "^4.0.2" +duplexer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + ecc-jsbn@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" @@ -817,6 +903,17 @@ expect@^23.1.0: jest-message-util "^23.1.0" jest-regex-util "^23.0.0" +expect@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-23.4.0.tgz#6da4ecc99c1471253e7288338983ad1ebadb60c3" + dependencies: + ansi-styles "^3.2.0" + jest-diff "^23.2.0" + jest-get-type "^22.1.0" + jest-matcher-utils "^23.2.0" + jest-message-util "^23.4.0" + jest-regex-util "^23.3.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -909,6 +1006,10 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +find-index@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -922,6 +1023,12 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + dependencies: + locate-path "^3.0.0" + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -954,6 +1061,14 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +fs-extra@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -964,7 +1079,7 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.2.3: +fsevents@^1.0.0, fsevents@^1.2.3: version "1.2.4" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: @@ -1019,6 +1134,12 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + dependencies: + find-index "^0.1.1" + glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -1034,7 +1155,7 @@ globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" -graceful-fs@^4.1.11, graceful-fs@^4.1.2: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1173,7 +1294,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -1207,6 +1328,12 @@ is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -1503,6 +1630,24 @@ jest-cli@^23.1.0: which "^1.2.12" yargs "^11.0.0" +jest-config@^23.0.0: + version "23.4.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.4.1.tgz#3172fa21f0507d7f8a088ed1dbe4157057f201e9" + dependencies: + babel-core "^6.0.0" + babel-jest "^23.4.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^23.4.0" + jest-environment-node "^23.4.0" + jest-get-type "^22.1.0" + jest-jasmine2 "^23.4.1" + jest-regex-util "^23.3.0" + jest-resolve "^23.4.1" + jest-util "^23.4.0" + jest-validate "^23.4.0" + pretty-format "^23.2.0" + jest-config@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.1.0.tgz#708ca0f431d356ee424fb4895d3308006bdd8241" @@ -1530,6 +1675,15 @@ jest-diff@^23.0.1: jest-get-type "^22.1.0" pretty-format "^23.0.1" +jest-diff@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.2.0.tgz#9f2cf4b51e12c791550200abc16b47130af1062a" + dependencies: + chalk "^2.0.1" + diff "^3.2.0" + jest-get-type "^22.1.0" + pretty-format "^23.2.0" + jest-docblock@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.0.1.tgz#deddd18333be5dc2415260a04ef3fce9276b5725" @@ -1543,6 +1697,13 @@ jest-each@^23.1.0: chalk "^2.0.1" pretty-format "^23.0.1" +jest-each@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.4.0.tgz#2fa9edd89daa1a4edc9ff9bf6062a36b71345143" + dependencies: + chalk "^2.0.1" + pretty-format "^23.2.0" + jest-environment-jsdom@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.1.0.tgz#85929914e23bed3577dac9755f4106d0697c479c" @@ -1551,6 +1712,14 @@ jest-environment-jsdom@^23.1.0: jest-util "^23.1.0" jsdom "^11.5.1" +jest-environment-jsdom@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023" + dependencies: + jest-mock "^23.2.0" + jest-util "^23.4.0" + jsdom "^11.5.1" + jest-environment-node@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.1.0.tgz#452c0bf949cfcbbacda1e1762eeed70bc784c7d5" @@ -1558,6 +1727,13 @@ jest-environment-node@^23.1.0: jest-mock "^23.1.0" jest-util "^23.1.0" +jest-environment-node@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10" + dependencies: + jest-mock "^23.2.0" + jest-util "^23.4.0" + jest-get-type@^22.1.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" @@ -1590,6 +1766,22 @@ jest-jasmine2@^23.1.0: jest-util "^23.1.0" pretty-format "^23.0.1" +jest-jasmine2@^23.4.1: + version "23.4.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.4.1.tgz#fa192262430d418e827636e4a98423e5e7ff0fce" + dependencies: + chalk "^2.0.1" + co "^4.6.0" + expect "^23.4.0" + is-generator-fn "^1.0.0" + jest-diff "^23.2.0" + jest-each "^23.4.0" + jest-matcher-utils "^23.2.0" + jest-message-util "^23.4.0" + jest-snapshot "^23.4.1" + jest-util "^23.4.0" + pretty-format "^23.2.0" + jest-leak-detector@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.0.1.tgz#9dba07505ac3495c39d3ec09ac1e564599e861a0" @@ -1604,6 +1796,14 @@ jest-matcher-utils@^23.0.1: jest-get-type "^22.1.0" pretty-format "^23.0.1" +jest-matcher-utils@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.2.0.tgz#4d4981f23213e939e3cedf23dc34c747b5ae1913" + dependencies: + chalk "^2.0.1" + jest-get-type "^22.1.0" + pretty-format "^23.2.0" + jest-message-util@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.1.0.tgz#9a809ba487ecac5ce511d4e698ee3b5ee2461ea9" @@ -1614,14 +1814,32 @@ jest-message-util@^23.1.0: slash "^1.0.0" stack-utils "^1.0.1" +jest-message-util@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f" + dependencies: + "@babel/code-frame" "^7.0.0-beta.35" + chalk "^2.0.1" + micromatch "^2.3.11" + slash "^1.0.0" + stack-utils "^1.0.1" + jest-mock@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.1.0.tgz#a381c31b121ab1f60c462a2dadb7b86dcccac487" +jest-mock@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134" + jest-regex-util@^23.0.0: version "23.0.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.0.0.tgz#dd5c1fde0c46f4371314cf10f7a751a23f4e8f76" +jest-regex-util@^23.3.0: + version "23.3.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5" + jest-resolve-dependencies@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.0.1.tgz#d01a10ddad9152c4cecdf5eac2b88571c4b6a64d" @@ -1637,6 +1855,14 @@ jest-resolve@^23.1.0: chalk "^2.0.1" realpath-native "^1.0.0" +jest-resolve@^23.4.1: + version "23.4.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.4.1.tgz#7f3c17104732a2c0c940a01256025ed745814982" + dependencies: + browser-resolve "^1.11.3" + chalk "^2.0.1" + realpath-native "^1.0.0" + jest-runner@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.1.0.tgz#fa20a933fff731a5432b3561e7f6426594fa29b5" @@ -1696,6 +1922,22 @@ jest-snapshot@^23.0.1: natural-compare "^1.4.0" pretty-format "^23.0.1" +jest-snapshot@^23.4.1: + version "23.4.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.4.1.tgz#090de9acae927f6a3af3005bda40d912b83e9c96" + dependencies: + babel-traverse "^6.0.0" + babel-types "^6.0.0" + chalk "^2.0.1" + jest-diff "^23.2.0" + jest-matcher-utils "^23.2.0" + jest-message-util "^23.4.0" + jest-resolve "^23.4.1" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^23.2.0" + semver "^5.5.0" + jest-util@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.1.0.tgz#c0251baf34644c6dd2fea78a962f4263ac55772d" @@ -1709,6 +1951,19 @@ jest-util@^23.1.0: slash "^1.0.0" source-map "^0.6.0" +jest-util@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561" + dependencies: + callsites "^2.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + jest-message-util "^23.4.0" + mkdirp "^0.5.1" + slash "^1.0.0" + source-map "^0.6.0" + jest-validate@^23.0.1: version "23.0.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.0.1.tgz#cd9f01a89d26bb885f12a8667715e9c865a5754f" @@ -1718,6 +1973,15 @@ jest-validate@^23.0.1: leven "^2.1.0" pretty-format "^23.0.1" +jest-validate@^23.4.0: + version "23.4.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.4.0.tgz#d96eede01ef03ac909c009e9c8e455197d48c201" + dependencies: + chalk "^2.0.1" + jest-get-type "^22.1.0" + leven "^2.1.0" + pretty-format "^23.2.0" + jest-watcher@^23.1.0: version "23.1.0" resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.1.0.tgz#a8d5842e38d9fb4afff823df6abb42a58ae6cdbd" @@ -1809,6 +2073,16 @@ json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -1887,6 +2161,13 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -1952,7 +2233,7 @@ merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" -micromatch@^2.3.11: +micromatch@^2.1.5, micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" dependencies: @@ -2002,7 +2283,7 @@ mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" -minimatch@^3.0.3, minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -2012,7 +2293,7 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.1, minimist@^1.2.0: +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -2046,6 +2327,10 @@ mkdirp@^0.5.0, mkdirp@^0.5.1: dependencies: minimist "0.0.8" +moment@^2.22.2: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2127,7 +2412,7 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -2270,16 +2555,32 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" +p-limit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + dependencies: + p-limit "^2.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -2357,6 +2658,12 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + dependencies: + find-up "^3.0.0" + pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -2380,6 +2687,13 @@ pretty-format@^23.0.1: ansi-regex "^3.0.0" ansi-styles "^3.2.0" +pretty-format@^23.2.0: + version "23.2.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.2.0.tgz#3b0aaa63c018a53583373c1cb3a5d96cc5e83017" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -2440,7 +2754,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.1, readable-stream@^2.0.6: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -2452,6 +2766,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + realpath-native@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.0.tgz#7885721a83b43bd5327609f0ddecb2482305fdf0" @@ -2558,6 +2881,12 @@ resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" +resolve@^1.1.7: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + dependencies: + path-parse "^1.0.5" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -2611,7 +2940,7 @@ sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -2619,6 +2948,10 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" @@ -2653,6 +2986,15 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" +shell-quote@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + dependencies: + array-filter "~0.0.0" + array-map "~0.0.0" + array-reduce "~0.0.0" + jsonify "~0.0.0" + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -2853,6 +3195,12 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +subarg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" + dependencies: + minimist "^1.1.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -2952,6 +3300,19 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" +ts-jest@^23.0.1: + version "23.0.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.0.1.tgz#c90e747b2787d3394319cde77c2748a87aaf2f48" + dependencies: + babel-plugin-istanbul "^4.1.6" + babel-preset-jest "^23.0.0" + cpx "^1.5.0" + fs-extra "6.0.1" + jest-config "^23.0.0" + lodash "^4.17.10" + pkg-dir "^3.0.0" + yargs "^12.0.1" + ts-node@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.0.tgz#a94a13c75e5e1aa6b82814b84c68deb339ba7bff" @@ -2990,6 +3351,10 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +typescript@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" + uglify-js@^2.6: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -3012,6 +3377,10 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" @@ -3162,10 +3531,18 @@ xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" +xregexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" + y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" +"y18n@^3.2.1 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -3174,6 +3551,12 @@ yallist@^3.0.0, yallist@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" +yargs-parser@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" + dependencies: + camelcase "^4.1.0" + yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -3197,6 +3580,23 @@ yargs@^11.0.0: y18n "^3.2.1" yargs-parser "^9.0.2" +yargs@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.1.tgz#6432e56123bb4e7c3562115401e98374060261c2" + dependencies: + cliui "^4.0.0" + decamelize "^2.0.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^10.1.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"