Merge 2832c48493
into f69da791fc
commit
28f7addb62
@ -1,4 +1,7 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
coverage
|
||||
.vscode
|
||||
.vscode
|
||||
yarn-error.log
|
||||
tests/fixtures
|
||||
dist
|
@ -0,0 +1,23 @@
|
||||
language: node_js
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
install:
|
||||
- yarn --silent
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
script: yarn test:prod -- --maxWorkers=2 --silent && yarn report-coverage
|
||||
- stage: test
|
||||
script: yarn tsc
|
||||
- stage: test
|
||||
script: yarn lint
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
on_failure: never
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,97 +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.
|
||||
|
||||
(function(exports)
|
||||
{
|
||||
var crc32 = (function () {
|
||||
'use strict';
|
||||
var crc32 = {};
|
||||
|
||||
crc32.Utf8Encode = function (string) {
|
||||
return unescape(encodeURIComponent(string));
|
||||
};
|
||||
|
||||
crc32.run = function (str) {
|
||||
var crc = new crc32.Type();
|
||||
crc.processString(str);
|
||||
return crc.checksum();
|
||||
};
|
||||
|
||||
crc32.table = [
|
||||
0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035,
|
||||
249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049,
|
||||
498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639,
|
||||
325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317,
|
||||
997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443,
|
||||
901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665,
|
||||
651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303,
|
||||
671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565,
|
||||
1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059,
|
||||
2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297,
|
||||
1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223,
|
||||
1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405,
|
||||
1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995,
|
||||
1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649,
|
||||
1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015,
|
||||
1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989,
|
||||
3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523,
|
||||
3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377,
|
||||
4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879,
|
||||
4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637,
|
||||
3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859,
|
||||
3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161,
|
||||
3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815,
|
||||
3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221,
|
||||
2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371,
|
||||
2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881,
|
||||
2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567,
|
||||
2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701,
|
||||
2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035,
|
||||
2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897,
|
||||
3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431,
|
||||
3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117
|
||||
];
|
||||
crc32.Type = function () {
|
||||
this.rem_ = 0xFFFFFFFF;
|
||||
this.checksum = function () {
|
||||
return ((this.rem_ ^ 0xFFFFFFFF) >>> 0);
|
||||
};
|
||||
this.processString = function (str) {
|
||||
str = crc32.Utf8Encode(str);
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var byte_index = ((str.charCodeAt(i) ^ this.rem_) >>> 0) & 0xFF;
|
||||
this.rem_ = ((this.rem_ >>> 8) ^ crc32.table[byte_index]) >>> 0;
|
||||
}
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
return crc32;
|
||||
})();
|
||||
exports.crc32 = crc32;
|
||||
})(typeof exports !== 'undefined' ? exports : this);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 || {}));
|
@ -1,473 +0,0 @@
|
||||
/*
|
||||
* js-sha3 v0.5.1
|
||||
* https://github.com/emn178/js-sha3
|
||||
*
|
||||
* Copyright 2015, emn178@gmail.com
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
;(function(root, undefined) {
|
||||
'use strict';
|
||||
|
||||
var NODE_JS = typeof(module) != 'undefined';
|
||||
if(NODE_JS) {
|
||||
root = global;
|
||||
if(root.JS_SHA3_TEST) {
|
||||
root.navigator = { userAgent: 'Chrome'};
|
||||
}
|
||||
}
|
||||
var HEX_CHARS = '0123456789abcdef'.split('');
|
||||
var SHAKE_PADDING = [31, 7936, 2031616, 520093696];
|
||||
var KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
var PADDING = [6, 1536, 393216, 100663296];
|
||||
var SHIFT = [0, 8, 16, 24];
|
||||
var RC = [1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
||||
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
||||
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
||||
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
||||
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
var BITS = [224, 256, 384, 512];
|
||||
var SHAKE_BITS = [128, 256];
|
||||
var OUTPUT_TYPES = ['hex', 'buffer', 'array'];
|
||||
|
||||
var createOutputMethod = function(bits, padding, outputType) {
|
||||
return function(message) {
|
||||
return new Keccak(bits, padding, bits).update(message)[outputType]();
|
||||
}
|
||||
};
|
||||
|
||||
var createShakeOutputMethod = function(bits, padding, outputType) {
|
||||
return function(message, outputBits) {
|
||||
return new Keccak(bits, padding, outputBits).update(message)[outputType]();
|
||||
}
|
||||
};
|
||||
|
||||
var createMethod = function(bits, padding) {
|
||||
var method = createOutputMethod(bits, padding, 'hex');
|
||||
method.create = function() {
|
||||
return new Keccak(bits, padding, bits);
|
||||
};
|
||||
method.update = function(message) {
|
||||
return method.create().update(message);
|
||||
};
|
||||
for(var i = 0;i < OUTPUT_TYPES.length;++i) {
|
||||
var type = OUTPUT_TYPES[i];
|
||||
method[type] = createOutputMethod(bits, padding, type);
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
var createShakeMethod = function(bits, padding) {
|
||||
var method = createShakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function(outputBits) {
|
||||
return new Keccak(bits, padding, outputBits);
|
||||
};
|
||||
method.update = function(message, outputBits) {
|
||||
return method.create(outputBits).update(message);
|
||||
};
|
||||
for(var i = 0;i < OUTPUT_TYPES.length;++i) {
|
||||
var type = OUTPUT_TYPES[i];
|
||||
method[type] = createShakeOutputMethod(bits, padding, type);
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
var algorithms = [
|
||||
{name: 'keccak', padding: KECCAK_PADDING, bits: BITS, createMethod: createMethod},
|
||||
{name: 'sha3', padding: PADDING, bits: BITS, createMethod: createMethod},
|
||||
{name: 'shake', padding: SHAKE_PADDING, bits: SHAKE_BITS, createMethod: createShakeMethod}
|
||||
];
|
||||
|
||||
var methods = {};
|
||||
|
||||
for(var i = 0;i < algorithms.length;++i) {
|
||||
var algorithm = algorithms[i];
|
||||
var bits = algorithm.bits;
|
||||
var createMethod = algorithm.createMethod;
|
||||
for(var j = 0;j < bits.length;++j) {
|
||||
var method = algorithm.createMethod(bits[j], algorithm.padding);
|
||||
methods[algorithm.name +'_' + bits[j]] = method;
|
||||
}
|
||||
}
|
||||
|
||||
function Keccak(bits, padding, outputBits) {
|
||||
this.blocks = [];
|
||||
this.s = [];
|
||||
this.padding = padding;
|
||||
this.outputBits = outputBits;
|
||||
this.reset = true;
|
||||
this.block = 0;
|
||||
this.start = 0;
|
||||
this.blockCount = (1600 - (bits << 1)) >> 5;
|
||||
this.byteCount = this.blockCount << 2;
|
||||
this.outputBlocks = outputBits >> 5;
|
||||
this.extraBytes = (outputBits & 31) >> 3;
|
||||
|
||||
for(var i = 0;i < 50;++i) {
|
||||
this.s[i] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
Keccak.prototype.update = function(message) {
|
||||
var notString = typeof(message) != 'string';
|
||||
if(notString && message.constructor == root.ArrayBuffer) {
|
||||
message = new Uint8Array(message);
|
||||
}
|
||||
var length = message.length, blocks = this.blocks, byteCount = this.byteCount,
|
||||
blockCount = this.blockCount, index = 0, s = this.s, i, code;
|
||||
|
||||
while(index < length) {
|
||||
if(this.reset) {
|
||||
this.reset = false;
|
||||
blocks[0] = this.block;
|
||||
for(i = 1;i < blockCount + 1;++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if(notString) {
|
||||
for (i = this.start;index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
} else {
|
||||
for (i = this.start;index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lastByteIndex = i;
|
||||
if(i >= byteCount) {
|
||||
this.start = i - byteCount;
|
||||
this.block = blocks[blockCount];
|
||||
for(i = 0;i < blockCount;++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
this.reset = true;
|
||||
} else {
|
||||
this.start = i;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.finalize = function() {
|
||||
var blocks = this.blocks, i = this.lastByteIndex, blockCount = this.blockCount, s = this.s;
|
||||
blocks[i >> 2] |= this.padding[i & 3];
|
||||
if(this.lastByteIndex == this.byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for(i = 1;i < blockCount + 1;++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for(i = 0;i < blockCount;++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
};
|
||||
|
||||
Keccak.prototype.toString = Keccak.prototype.hex = function() {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var hex = '', block;
|
||||
while(j < outputBlocks) {
|
||||
for(i = 0;i < blockCount && j < outputBlocks;++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
||||
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
||||
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
||||
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F];
|
||||
}
|
||||
if(j % blockCount == 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if(extraBytes) {
|
||||
block = s[i];
|
||||
if(extraBytes > 0) {
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F];
|
||||
}
|
||||
if(extraBytes > 1) {
|
||||
hex += HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F];
|
||||
}
|
||||
if(extraBytes > 2) {
|
||||
hex += HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F];
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
Keccak.prototype.buffer = function() {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var bytes = this.outputBits >> 3;
|
||||
var buffer;
|
||||
if(extraBytes) {
|
||||
buffer = new ArrayBuffer((outputBlocks + 1) << 2);
|
||||
} else {
|
||||
buffer = new ArrayBuffer(bytes);
|
||||
}
|
||||
var array = new Uint32Array(buffer);
|
||||
while(j < outputBlocks) {
|
||||
for(i = 0;i < blockCount && j < outputBlocks;++i, ++j) {
|
||||
array[j] = s[i];
|
||||
}
|
||||
if(j % blockCount == 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if(extraBytes) {
|
||||
array[i] = s[i];
|
||||
buffer = buffer.slice(0, bytes);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Keccak.prototype.digest = Keccak.prototype.array = function() {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var array = [], offset, block;
|
||||
while(j < outputBlocks) {
|
||||
for(i = 0;i < blockCount && j < outputBlocks;++i, ++j) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
array[offset + 3] = (block >> 24) & 0xFF;
|
||||
}
|
||||
if(j % blockCount == 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if(extraBytes) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
if(extraBytes > 0) {
|
||||
array[offset] = block & 0xFF;
|
||||
}
|
||||
if(extraBytes > 1) {
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
}
|
||||
if(extraBytes > 2) {
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
var f = function(s) {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9,
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17,
|
||||
b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33,
|
||||
b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
for(n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ ((c2 << 1) | (c3 >>> 31));
|
||||
l = c9 ^ ((c3 << 1) | (c2 >>> 31));
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ ((c4 << 1) | (c5 >>> 31));
|
||||
l = c1 ^ ((c5 << 1) | (c4 >>> 31));
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ ((c6 << 1) | (c7 >>> 31));
|
||||
l = c3 ^ ((c7 << 1) | (c6 >>> 31));
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ ((c8 << 1) | (c9 >>> 31));
|
||||
l = c5 ^ ((c9 << 1) | (c8 >>> 31));
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ ((c0 << 1) | (c1 >>> 31));
|
||||
l = c7 ^ ((c1 << 1) | (c0 >>> 31));
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = (s[11] << 4) | (s[10] >>> 28);
|
||||
b33 = (s[10] << 4) | (s[11] >>> 28);
|
||||
b14 = (s[20] << 3) | (s[21] >>> 29);
|
||||
b15 = (s[21] << 3) | (s[20] >>> 29);
|
||||
b46 = (s[31] << 9) | (s[30] >>> 23);
|
||||
b47 = (s[30] << 9) | (s[31] >>> 23);
|
||||
b28 = (s[40] << 18) | (s[41] >>> 14);
|
||||
b29 = (s[41] << 18) | (s[40] >>> 14);
|
||||
b20 = (s[2] << 1) | (s[3] >>> 31);
|
||||
b21 = (s[3] << 1) | (s[2] >>> 31);
|
||||
b2 = (s[13] << 12) | (s[12] >>> 20);
|
||||
b3 = (s[12] << 12) | (s[13] >>> 20);
|
||||
b34 = (s[22] << 10) | (s[23] >>> 22);
|
||||
b35 = (s[23] << 10) | (s[22] >>> 22);
|
||||
b16 = (s[33] << 13) | (s[32] >>> 19);
|
||||
b17 = (s[32] << 13) | (s[33] >>> 19);
|
||||
b48 = (s[42] << 2) | (s[43] >>> 30);
|
||||
b49 = (s[43] << 2) | (s[42] >>> 30);
|
||||
b40 = (s[5] << 30) | (s[4] >>> 2);
|
||||
b41 = (s[4] << 30) | (s[5] >>> 2);
|
||||
b22 = (s[14] << 6) | (s[15] >>> 26);
|
||||
b23 = (s[15] << 6) | (s[14] >>> 26);
|
||||
b4 = (s[25] << 11) | (s[24] >>> 21);
|
||||
b5 = (s[24] << 11) | (s[25] >>> 21);
|
||||
b36 = (s[34] << 15) | (s[35] >>> 17);
|
||||
b37 = (s[35] << 15) | (s[34] >>> 17);
|
||||
b18 = (s[45] << 29) | (s[44] >>> 3);
|
||||
b19 = (s[44] << 29) | (s[45] >>> 3);
|
||||
b10 = (s[6] << 28) | (s[7] >>> 4);
|
||||
b11 = (s[7] << 28) | (s[6] >>> 4);
|
||||
b42 = (s[17] << 23) | (s[16] >>> 9);
|
||||
b43 = (s[16] << 23) | (s[17] >>> 9);
|
||||
b24 = (s[26] << 25) | (s[27] >>> 7);
|
||||
b25 = (s[27] << 25) | (s[26] >>> 7);
|
||||
b6 = (s[36] << 21) | (s[37] >>> 11);
|
||||
b7 = (s[37] << 21) | (s[36] >>> 11);
|
||||
b38 = (s[47] << 24) | (s[46] >>> 8);
|
||||
b39 = (s[46] << 24) | (s[47] >>> 8);
|
||||
b30 = (s[8] << 27) | (s[9] >>> 5);
|
||||
b31 = (s[9] << 27) | (s[8] >>> 5);
|
||||
b12 = (s[18] << 20) | (s[19] >>> 12);
|
||||
b13 = (s[19] << 20) | (s[18] >>> 12);
|
||||
b44 = (s[29] << 7) | (s[28] >>> 25);
|
||||
b45 = (s[28] << 7) | (s[29] >>> 25);
|
||||
b26 = (s[38] << 8) | (s[39] >>> 24);
|
||||
b27 = (s[39] << 8) | (s[38] >>> 24);
|
||||
b8 = (s[48] << 14) | (s[49] >>> 18);
|
||||
b9 = (s[49] << 14) | (s[48] >>> 18);
|
||||
|
||||
s[0] = b0 ^ (~b2 & b4);
|
||||
s[1] = b1 ^ (~b3 & b5);
|
||||
s[10] = b10 ^ (~b12 & b14);
|
||||
s[11] = b11 ^ (~b13 & b15);
|
||||
s[20] = b20 ^ (~b22 & b24);
|
||||
s[21] = b21 ^ (~b23 & b25);
|
||||
s[30] = b30 ^ (~b32 & b34);
|
||||
s[31] = b31 ^ (~b33 & b35);
|
||||
s[40] = b40 ^ (~b42 & b44);
|
||||
s[41] = b41 ^ (~b43 & b45);
|
||||
s[2] = b2 ^ (~b4 & b6);
|
||||
s[3] = b3 ^ (~b5 & b7);
|
||||
s[12] = b12 ^ (~b14 & b16);
|
||||
s[13] = b13 ^ (~b15 & b17);
|
||||
s[22] = b22 ^ (~b24 & b26);
|
||||
s[23] = b23 ^ (~b25 & b27);
|
||||
s[32] = b32 ^ (~b34 & b36);
|
||||
s[33] = b33 ^ (~b35 & b37);
|
||||
s[42] = b42 ^ (~b44 & b46);
|
||||
s[43] = b43 ^ (~b45 & b47);
|
||||
s[4] = b4 ^ (~b6 & b8);
|
||||
s[5] = b5 ^ (~b7 & b9);
|
||||
s[14] = b14 ^ (~b16 & b18);
|
||||
s[15] = b15 ^ (~b17 & b19);
|
||||
s[24] = b24 ^ (~b26 & b28);
|
||||
s[25] = b25 ^ (~b27 & b29);
|
||||
s[34] = b34 ^ (~b36 & b38);
|
||||
s[35] = b35 ^ (~b37 & b39);
|
||||
s[44] = b44 ^ (~b46 & b48);
|
||||
s[45] = b45 ^ (~b47 & b49);
|
||||
s[6] = b6 ^ (~b8 & b0);
|
||||
s[7] = b7 ^ (~b9 & b1);
|
||||
s[16] = b16 ^ (~b18 & b10);
|
||||
s[17] = b17 ^ (~b19 & b11);
|
||||
s[26] = b26 ^ (~b28 & b20);
|
||||
s[27] = b27 ^ (~b29 & b21);
|
||||
s[36] = b36 ^ (~b38 & b30);
|
||||
s[37] = b37 ^ (~b39 & b31);
|
||||
s[46] = b46 ^ (~b48 & b40);
|
||||
s[47] = b47 ^ (~b49 & b41);
|
||||
s[8] = b8 ^ (~b0 & b2);
|
||||
s[9] = b9 ^ (~b1 & b3);
|
||||
s[18] = b18 ^ (~b10 & b12);
|
||||
s[19] = b19 ^ (~b11 & b13);
|
||||
s[28] = b28 ^ (~b20 & b22);
|
||||
s[29] = b29 ^ (~b21 & b23);
|
||||
s[38] = b38 ^ (~b30 & b32);
|
||||
s[39] = b39 ^ (~b31 & b33);
|
||||
s[48] = b48 ^ (~b40 & b42);
|
||||
s[49] = b49 ^ (~b41 & b43);
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if(!root.JS_SHA3_TEST && NODE_JS) {
|
||||
module.exports = methods;
|
||||
} else if(root) {
|
||||
for(var key in methods) {
|
||||
root[key] = methods[key];
|
||||
}
|
||||
}
|
||||
}(this));
|
@ -1,156 +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";
|
||||
//
|
||||
const response_parser_utils = require("./response_parser_utils");
|
||||
const JSBigInt = require("../cryptonote_utils/biginteger").BigInteger; // important: grab defined export
|
||||
//
|
||||
function New_ParametersForWalletRequest(address, view_key__private) {
|
||||
return {
|
||||
address: address,
|
||||
view_key: view_key__private,
|
||||
};
|
||||
}
|
||||
exports.New_ParametersForWalletRequest = New_ParametersForWalletRequest;
|
||||
//
|
||||
function AddUserAgentParamters(
|
||||
parameters,
|
||||
appUserAgent_product,
|
||||
appUserAgent_version,
|
||||
) {
|
||||
// setting these on params instead of as header field User-Agent so as to retain all info found in User-Agent, such as platform… and these are set so server has option to control delivery
|
||||
parameters["app_name"] = appUserAgent_product;
|
||||
parameters["app_version"] = appUserAgent_version;
|
||||
}
|
||||
exports.AddUserAgentParamters = AddUserAgentParamters;
|
||||
//
|
||||
function HTTPRequest(
|
||||
request_conformant_module, // such as 'request' or 'xhr' .. TODO: consider switching to 'fetch'
|
||||
apiAddress_authority, // authority means [subdomain.]host.…[:…] with no trailing slash
|
||||
endpointPath,
|
||||
final_parameters,
|
||||
fn,
|
||||
) {
|
||||
// fn: (err?, data?) -> new Request
|
||||
if (typeof final_parameters == "undefined" || final_parameters == null) {
|
||||
throw "final_parameters must not be nil";
|
||||
// return null
|
||||
}
|
||||
const completeURL =
|
||||
_new_APIAddress_baseURLString(apiAddress_authority) + endpointPath;
|
||||
console.log("📡 " + completeURL);
|
||||
//
|
||||
const request_options = _new_requestOptions_base(
|
||||
"POST",
|
||||
completeURL,
|
||||
final_parameters,
|
||||
);
|
||||
const requestHandle = request_conformant_module(request_options, function(
|
||||
err_orProgressEvent,
|
||||
res,
|
||||
body,
|
||||
) {
|
||||
_new_HTTPRequestHandlerFunctionCallingFn(fn)(
|
||||
// <- called manually instead of directly passed to request_conformant_module call to enable passing completeURL
|
||||
completeURL,
|
||||
err_orProgressEvent,
|
||||
res,
|
||||
body,
|
||||
);
|
||||
});
|
||||
//
|
||||
return requestHandle;
|
||||
}
|
||||
exports.HTTPRequest = HTTPRequest;
|
||||
//
|
||||
function _new_APIAddress_baseURLString(
|
||||
apiAddress_authority, // authority means [subdomain.]host.…[:…]
|
||||
) {
|
||||
return "https" + "://" + apiAddress_authority + "/";
|
||||
}
|
||||
function _new_requestOptions_base(methodName, completeURL, json_parameters) {
|
||||
return {
|
||||
method: methodName,
|
||||
url: completeURL,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
json: json_parameters,
|
||||
useXDR: true, // CORS
|
||||
withCredentials: true, // CORS
|
||||
};
|
||||
}
|
||||
function _new_HTTPRequestHandlerFunctionCallingFn(fn) {
|
||||
return function(completeURL, err_orProgressEvent, res, body) {
|
||||
// err appears to actually be a ProgressEvent
|
||||
var err = null;
|
||||
const statusCode = typeof res !== "undefined" ? res.statusCode : -1;
|
||||
if (statusCode == 0 || statusCode == -1) {
|
||||
// we'll treat 0 as a lack of internet connection.. unless there's a better way to make use of err_orProgressEvent which is apparently going to be typeof ProgressEvent here
|
||||
err = new Error("Connection Failure");
|
||||
} else if (statusCode !== 200) {
|
||||
const body_Error =
|
||||
body && typeof body == "object" ? body.Error : undefined;
|
||||
const statusMessage =
|
||||
res && res.statusMessage ? res.statusMessage : undefined;
|
||||
if (typeof body_Error !== "undefined" && body_Error) {
|
||||
err = new Error(body_Error);
|
||||
} else if (typeof statusMessage !== "undefined" && statusMessage) {
|
||||
err = new Error(statusMessage);
|
||||
} else {
|
||||
err = new Error("Unknown " + statusCode + " error");
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
console.error("❌ " + err);
|
||||
// console.error("Body:", body)
|
||||
fn(err, null);
|
||||
return;
|
||||
}
|
||||
var json;
|
||||
if (typeof body === "string") {
|
||||
try {
|
||||
json = JSON.parse(body);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"❌ HostedMoneroAPIClient Error: Unable to parse json with exception:",
|
||||
e,
|
||||
"\nbody:",
|
||||
body,
|
||||
);
|
||||
fn(e, null);
|
||||
}
|
||||
} else {
|
||||
json = body;
|
||||
}
|
||||
console.log("✅ " + completeURL + " " + statusCode);
|
||||
fn(null, json);
|
||||
};
|
||||
}
|
@ -1,476 +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";
|
||||
//
|
||||
const JSBigInt = require("../cryptonote_utils/biginteger").BigInteger;
|
||||
const monero_utils = require("../monero_utils/monero_cryptonote_utils_instance");
|
||||
const monero_keyImage_cache_utils = require("../monero_utils/monero_keyImage_cache_utils");
|
||||
//
|
||||
function Parsed_AddressInfo__sync(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
// -> returnValuesByKey
|
||||
const total_received = new JSBigInt(data.total_received || 0);
|
||||
const locked_balance = new JSBigInt(data.locked_funds || 0);
|
||||
var total_sent = new JSBigInt(data.total_sent || 0); // will be modified in place
|
||||
//
|
||||
const account_scanned_tx_height = data.scanned_height || 0;
|
||||
const account_scanned_block_height = data.scanned_block_height || 0;
|
||||
const account_scan_start_height = data.start_height || 0;
|
||||
const transaction_height = data.transaction_height || 0;
|
||||
const blockchain_height = data.blockchain_height || 0;
|
||||
const spent_outputs = data.spent_outputs || [];
|
||||
//
|
||||
for (let spent_output of spent_outputs) {
|
||||
var key_image = monero_keyImage_cache_utils.Lazy_KeyImage(
|
||||
keyImage_cache,
|
||||
spent_output.tx_pub_key,
|
||||
spent_output.out_index,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
if (spent_output.key_image !== key_image) {
|
||||
// console.log('💬 Output used as mixin (' + spent_output.key_image + '/' + key_image + ')')
|
||||
total_sent = new JSBigInt(total_sent).subtract(spent_output.amount);
|
||||
}
|
||||
}
|
||||
//
|
||||
const ratesBySymbol = data.rates || {}; // jic it's not there
|
||||
//
|
||||
const returnValuesByKey = {
|
||||
total_received_String: total_received
|
||||
? total_received.toString()
|
||||
: null,
|
||||
locked_balance_String: locked_balance
|
||||
? locked_balance.toString()
|
||||
: null,
|
||||
total_sent_String: total_sent ? total_sent.toString() : null,
|
||||
// ^serialized JSBigInt
|
||||
spent_outputs: spent_outputs,
|
||||
account_scanned_tx_height: account_scanned_tx_height,
|
||||
account_scanned_block_height: account_scanned_block_height,
|
||||
account_scan_start_height: account_scan_start_height,
|
||||
transaction_height: transaction_height,
|
||||
blockchain_height: blockchain_height,
|
||||
//
|
||||
ratesBySymbol: ratesBySymbol,
|
||||
};
|
||||
return returnValuesByKey;
|
||||
}
|
||||
function Parsed_AddressInfo__sync__keyImageManaged(
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
// -> returnValuesByKey
|
||||
const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(
|
||||
address,
|
||||
);
|
||||
return Parsed_AddressInfo__sync(
|
||||
keyImageCache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
}
|
||||
function Parsed_AddressInfo(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn, // (err?, returnValuesByKey) -> Void
|
||||
) {
|
||||
const returnValuesByKey = Parsed_AddressInfo__sync(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
fn(null, returnValuesByKey);
|
||||
}
|
||||
function Parsed_AddressInfo__keyImageManaged(
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn,
|
||||
) {
|
||||
// -> returnValuesByKey
|
||||
Parsed_AddressInfo(
|
||||
monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address),
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn,
|
||||
);
|
||||
}
|
||||
exports.Parsed_AddressInfo = Parsed_AddressInfo;
|
||||
exports.Parsed_AddressInfo__keyImageManaged = Parsed_AddressInfo__keyImageManaged; // in case you can't send a mutable key image cache dictionary
|
||||
exports.Parsed_AddressInfo__sync__keyImageManaged = Parsed_AddressInfo__sync__keyImageManaged; // in case you can't send a mutable key image cache dictionary
|
||||
exports.Parsed_AddressInfo__sync = Parsed_AddressInfo__sync;
|
||||
//
|
||||
function Parsed_AddressTransactions(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn, // (err?, returnValuesByKey) -> Void
|
||||
) {
|
||||
const returnValuesByKey = Parsed_AddressTransactions__sync(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
fn(null, returnValuesByKey);
|
||||
}
|
||||
function Parsed_AddressTransactions__sync(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
const account_scanned_height = data.scanned_height || 0;
|
||||
const account_scanned_block_height = data.scanned_block_height || 0;
|
||||
const account_scan_start_height = data.start_height || 0;
|
||||
const transaction_height = data.transaction_height || 0;
|
||||
const blockchain_height = data.blockchain_height || 0;
|
||||
//
|
||||
const transactions = data.transactions || [];
|
||||
//
|
||||
// TODO: rewrite this with more clarity if possible
|
||||
for (let i = 0; i < transactions.length; ++i) {
|
||||
if ((transactions[i].spent_outputs || []).length > 0) {
|
||||
for (var j = 0; j < transactions[i].spent_outputs.length; ++j) {
|
||||
var key_image = monero_keyImage_cache_utils.Lazy_KeyImage(
|
||||
keyImage_cache,
|
||||
transactions[i].spent_outputs[j].tx_pub_key,
|
||||
transactions[i].spent_outputs[j].out_index,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
if (transactions[i].spent_outputs[j].key_image !== key_image) {
|
||||
// console.log('Output used as mixin, ignoring (' + transactions[i].spent_outputs[j].key_image + '/' + key_image + ')')
|
||||
transactions[i].total_sent = new JSBigInt(
|
||||
transactions[i].total_sent,
|
||||
)
|
||||
.subtract(transactions[i].spent_outputs[j].amount)
|
||||
.toString();
|
||||
transactions[i].spent_outputs.splice(j, 1);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
new JSBigInt(transactions[i].total_received || 0)
|
||||
.add(transactions[i].total_sent || 0)
|
||||
.compare(0) <= 0
|
||||
) {
|
||||
transactions.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
transactions[i].amount = new JSBigInt(
|
||||
transactions[i].total_received || 0,
|
||||
)
|
||||
.subtract(transactions[i].total_sent || 0)
|
||||
.toString();
|
||||
transactions[i].approx_float_amount = parseFloat(
|
||||
monero_utils.formatMoney(transactions[i].amount),
|
||||
);
|
||||
transactions[i].timestamp = transactions[i].timestamp;
|
||||
const record__payment_id = transactions[i].payment_id;
|
||||
if (typeof record__payment_id !== "undefined" && record__payment_id) {
|
||||
if (record__payment_id.length == 16) {
|
||||
// short (encrypted) pid
|
||||
if (transactions[i].approx_float_amount < 0) {
|
||||
// outgoing
|
||||
delete transactions[i]["payment_id"]; // need to filter these out .. because the server can't filter out short (encrypted) pids on outgoing txs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transactions.sort(function(a, b) {
|
||||
if (a.mempool == true) {
|
||||
if (b.mempool != true) {
|
||||
return -1; // a first
|
||||
}
|
||||
// both mempool - fall back to .id compare
|
||||
} else if (b.mempool == true) {
|
||||
return 1; // b first
|
||||
}
|
||||
return b.id - a.id;
|
||||
});
|
||||
// prepare transactions to be serialized
|
||||
for (let transaction of transactions) {
|
||||
transaction.amount = transaction.amount.toString(); // JSBigInt -> String
|
||||
if (
|
||||
typeof transaction.total_sent !== "undefined" &&
|
||||
transaction.total_sent !== null
|
||||
) {
|
||||
transaction.total_sent = transaction.total_sent.toString();
|
||||
}
|
||||
}
|
||||
// on the other side, we convert transactions timestamp to Date obj
|
||||
const returnValuesByKey = {
|
||||
account_scanned_height: account_scanned_height,
|
||||
account_scanned_block_height: account_scanned_block_height,
|
||||
account_scan_start_height: account_scan_start_height,
|
||||
transaction_height: transaction_height,
|
||||
blockchain_height: blockchain_height,
|
||||
serialized_transactions: transactions,
|
||||
};
|
||||
return returnValuesByKey;
|
||||
}
|
||||
function Parsed_AddressTransactions__sync__keyImageManaged(
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(
|
||||
address,
|
||||
);
|
||||
return Parsed_AddressTransactions__sync(
|
||||
keyImageCache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
}
|
||||
function Parsed_AddressTransactions__keyImageManaged(
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn,
|
||||
) {
|
||||
Parsed_AddressTransactions(
|
||||
monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address),
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn,
|
||||
);
|
||||
}
|
||||
exports.Parsed_AddressTransactions = Parsed_AddressTransactions;
|
||||
exports.Parsed_AddressTransactions__keyImageManaged = Parsed_AddressTransactions__keyImageManaged;
|
||||
exports.Parsed_AddressTransactions__sync = Parsed_AddressTransactions__sync;
|
||||
exports.Parsed_AddressTransactions__sync__keyImageManaged = Parsed_AddressTransactions__sync__keyImageManaged;
|
||||
//
|
||||
function Parsed_UnspentOuts(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn, // (err?, returnValuesByKey)
|
||||
) {
|
||||
const returnValuesByKey = Parsed_UnspentOuts__sync(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
fn(null, returnValuesByKey);
|
||||
}
|
||||
function Parsed_UnspentOuts__sync(
|
||||
keyImage_cache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
const data_outputs = data.outputs;
|
||||
const finalized_unspentOutputs = data.outputs || []; // to finalize:
|
||||
for (var i = 0; i < finalized_unspentOutputs.length; i++) {
|
||||
const unspent_output = finalized_unspentOutputs[i];
|
||||
if (
|
||||
unspent_output === null ||
|
||||
typeof unspent_output === "undefined" ||
|
||||
!unspent_output // just preserving what was in the original code
|
||||
) {
|
||||
throw "unspent_output at index " + i + " was null";
|
||||
}
|
||||
const spend_key_images = unspent_output.spend_key_images;
|
||||
if (
|
||||
spend_key_images === null ||
|
||||
typeof spend_key_images === "undefined"
|
||||
) {
|
||||
throw "spend_key_images of unspent_output at index " +
|
||||
i +
|
||||
" was null";
|
||||
}
|
||||
for (var j = 0; j < spend_key_images.length; j++) {
|
||||
const finalized_unspentOutput_atI_beforeSplice =
|
||||
finalized_unspentOutputs[i];
|
||||
if (
|
||||
!finalized_unspentOutput_atI_beforeSplice ||
|
||||
typeof finalized_unspentOutput_atI_beforeSplice === "undefined"
|
||||
) {
|
||||
console.warn(
|
||||
`This unspent output at i ${i} was literally undefined! Skipping.`,
|
||||
); // NOTE: Looks like the i-- code below should exit earlier if this is necessary
|
||||
continue;
|
||||
}
|
||||
const beforeSplice__tx_pub_key =
|
||||
finalized_unspentOutput_atI_beforeSplice.tx_pub_key;
|
||||
const beforeSplice__index =
|
||||
finalized_unspentOutput_atI_beforeSplice.index;
|
||||
if (
|
||||
typeof beforeSplice__tx_pub_key === "undefined" ||
|
||||
!beforeSplice__tx_pub_key
|
||||
) {
|
||||
console.warn(
|
||||
"This unspent out was missing a tx_pub_key! Skipping.",
|
||||
finalized_unspentOutput_atI_beforeSplice,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
var key_image = monero_keyImage_cache_utils.Lazy_KeyImage(
|
||||
keyImage_cache,
|
||||
beforeSplice__tx_pub_key,
|
||||
beforeSplice__index,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
if (
|
||||
key_image ===
|
||||
finalized_unspentOutput_atI_beforeSplice.spend_key_images[j]
|
||||
) {
|
||||
// console.log("💬 Output was spent; key image: " + key_image + " amount: " + monero_utils.formatMoneyFull(finalized_unspentOutputs[i].amount));
|
||||
// Remove output from list
|
||||
finalized_unspentOutputs.splice(i, 1);
|
||||
const finalized_unspentOutput_atI_afterSplice =
|
||||
finalized_unspentOutputs[i];
|
||||
if (finalized_unspentOutput_atI_afterSplice) {
|
||||
j =
|
||||
finalized_unspentOutput_atI_afterSplice.spend_key_images
|
||||
.length;
|
||||
}
|
||||
i--;
|
||||
} else {
|
||||
console.log(
|
||||
"💬 Output used as mixin (" +
|
||||
key_image +
|
||||
"/" +
|
||||
finalized_unspentOutputs[i].spend_key_images[j] +
|
||||
")",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("Unspent outs: " + JSON.stringify(finalized_unspentOutputs));
|
||||
const unusedOuts = finalized_unspentOutputs.slice(0);
|
||||
const returnValuesByKey = {
|
||||
unspentOutputs: finalized_unspentOutputs,
|
||||
unusedOuts: unusedOuts,
|
||||
per_kb_fee: data.per_kb_fee, // String
|
||||
};
|
||||
return returnValuesByKey;
|
||||
}
|
||||
function Parsed_UnspentOuts__sync__keyImageManaged(
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
const keyImageCache = monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(
|
||||
address,
|
||||
);
|
||||
return Parsed_UnspentOuts__sync(
|
||||
keyImageCache,
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
);
|
||||
}
|
||||
function Parsed_UnspentOuts__keyImageManaged(
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn,
|
||||
) {
|
||||
Parsed_UnspentOuts(
|
||||
monero_keyImage_cache_utils.Lazy_KeyImageCacheForWalletWith(address),
|
||||
data,
|
||||
address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
fn,
|
||||
);
|
||||
}
|
||||
exports.Parsed_UnspentOuts = Parsed_UnspentOuts;
|
||||
exports.Parsed_UnspentOuts__keyImageManaged = Parsed_UnspentOuts__keyImageManaged;
|
||||
exports.Parsed_UnspentOuts__sync = Parsed_UnspentOuts__sync;
|
||||
exports.Parsed_UnspentOuts__sync__keyImageManaged = Parsed_UnspentOuts__sync__keyImageManaged;
|
@ -1,48 +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_wallet_utils = require("./monero_utils/monero_wallet_utils");
|
||||
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.monero_requestURI_utils = require("./monero_utils/monero_requestURI_utils");
|
||||
mymonero_core_js.monero_keyImage_cache_utils = require("./monero_utils/monero_keyImage_cache_utils");
|
||||
mymonero_core_js.monero_wallet_locale = require("./monero_utils/monero_wallet_locale");
|
||||
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;
|
@ -1,35 +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";
|
||||
//
|
||||
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;
|
@ -1,101 +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";
|
||||
//
|
||||
const monero_utils = require("./monero_cryptonote_utils_instance");
|
||||
//
|
||||
const Lazy_KeyImage = function(
|
||||
mutable_keyImagesByCacheKey, // pass a mutable JS dictionary
|
||||
tx_pub_key,
|
||||
out_index,
|
||||
public_address,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
) {
|
||||
var cache_index = tx_pub_key + ":" + public_address + ":" + out_index;
|
||||
const cached__key_image = mutable_keyImagesByCacheKey[cache_index];
|
||||
if (
|
||||
typeof cached__key_image !== "undefined" &&
|
||||
cached__key_image !== null
|
||||
) {
|
||||
return cached__key_image;
|
||||
}
|
||||
var key_image = monero_utils.generate_key_image(
|
||||
tx_pub_key,
|
||||
view_key__private,
|
||||
spend_key__public,
|
||||
spend_key__private,
|
||||
out_index,
|
||||
).key_image;
|
||||
// cache:
|
||||
mutable_keyImagesByCacheKey[cache_index] = key_image;
|
||||
//
|
||||
return key_image;
|
||||
};
|
||||
exports.Lazy_KeyImage = Lazy_KeyImage;
|
||||
//
|
||||
//
|
||||
// Managed caches - Can be used by apps which can't send a mutable_keyImagesByCacheKey
|
||||
const __global_managed_keyImageCaches_by_walletId = {};
|
||||
function _managedKeyImageCacheWalletIdForWalletWith(public_address) {
|
||||
// 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 (
|
||||
public_address == "" ||
|
||||
!public_address ||
|
||||
typeof public_address == "undefined"
|
||||
) {
|
||||
throw "managedKeyImageCacheIdentifierForWalletWith: Illegal public_address";
|
||||
}
|
||||
return "" + public_address;
|
||||
}
|
||||
const Lazy_KeyImageCacheForWalletWith = function(public_address) {
|
||||
var cacheId = _managedKeyImageCacheWalletIdForWalletWith(public_address);
|
||||
var cache = __global_managed_keyImageCaches_by_walletId[cacheId];
|
||||
if (typeof cache === "undefined" || !cache) {
|
||||
cache = {};
|
||||
__global_managed_keyImageCaches_by_walletId[cacheId] = cache;
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
exports.Lazy_KeyImageCacheForWalletWith = Lazy_KeyImageCacheForWalletWith;
|
||||
//
|
||||
const DeleteManagedKeyImagesForWalletWith = function(public_address) {
|
||||
// IMPORTANT: Ensure you call this method when you want to clear your wallet from
|
||||
// memory or delete it, or else you could leak key images and public addresses.
|
||||
const cacheId = _managedKeyImageCacheWalletIdForWalletWith(public_address);
|
||||
delete __global_managed_keyImageCaches_by_walletId[cacheId];
|
||||
//
|
||||
const cache = __global_managed_keyImageCaches_by_walletId[cacheId];
|
||||
if (typeof cache !== "undefined") {
|
||||
throw "Key image cache still exists after deletion";
|
||||
}
|
||||
};
|
||||
exports.DeleteManagedKeyImagesForWalletWith = DeleteManagedKeyImagesForWalletWith;
|
@ -1,86 +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";
|
||||
//
|
||||
const monero_utils = require("./monero_cryptonote_utils_instance");
|
||||
//
|
||||
// Note: long (64 char, plaintext) payment ids are deprecated.
|
||||
//
|
||||
function New_Short_TransactionID() {
|
||||
return monero_utils.rand_8();
|
||||
}
|
||||
exports.New_Short_TransactionID = New_Short_TransactionID;
|
||||
exports.New_TransactionID = New_Short_TransactionID;
|
||||
//
|
||||
function IsValidPaymentIDOrNoPaymentID(payment_id__orNil) {
|
||||
if (
|
||||
payment_id__orNil == null ||
|
||||
payment_id__orNil == "" ||
|
||||
typeof payment_id__orNil == "undefined"
|
||||
) {
|
||||
return true; // no pid
|
||||
}
|
||||
let payment_id = payment_id__orNil;
|
||||
if (IsValidShortPaymentID(payment_id)) {
|
||||
return true;
|
||||
}
|
||||
if (IsValidLongPaymentID(payment_id)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.IsValidPaymentIDOrNoPaymentID = IsValidPaymentIDOrNoPaymentID;
|
||||
//
|
||||
function IsValidShortPaymentID(payment_id) {
|
||||
return IsValidPaymentIDOfLength(payment_id, 16);
|
||||
}
|
||||
exports.IsValidShortPaymentID = IsValidShortPaymentID;
|
||||
//
|
||||
function IsValidLongPaymentID(payment_id) {
|
||||
return IsValidPaymentIDOfLength(payment_id, 64);
|
||||
}
|
||||
exports.IsValidLongPaymentID = IsValidLongPaymentID;
|
||||
//
|
||||
function IsValidPaymentIDOfLength(payment_id, required_length) {
|
||||
if (required_length != 16 && required_length != 64) {
|
||||
throw "unexpected IsValidPaymentIDOfLength required_length";
|
||||
}
|
||||
let payment_id_length = payment_id.length;
|
||||
if (payment_id_length !== required_length) {
|
||||
// new encrypted short
|
||||
return false; // invalid length
|
||||
}
|
||||
let pattern = RegExp("^[0-9a-fA-F]{" + required_length + "}$");
|
||||
if (pattern.test(payment_id) != true) {
|
||||
// not a valid required_length char pid
|
||||
return false; // then not valid
|
||||
}
|
||||
return true;
|
||||
}
|
||||
exports.IsValidShortPaymentID = IsValidShortPaymentID;
|
@ -1,162 +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";
|
||||
//
|
||||
const monero_config = require("./monero_config");
|
||||
const monero_utils = require("./monero_cryptonote_utils_instance");
|
||||
//
|
||||
const URITypes = {
|
||||
addressAsFirstPathComponent: 1,
|
||||
addressAsAuthority: 2,
|
||||
};
|
||||
exports.URITypes = URITypes;
|
||||
//
|
||||
function New_RequestFunds_URI(args) {
|
||||
// -> String?
|
||||
const address = args.address;
|
||||
if (!address) {
|
||||
throw "missing address";
|
||||
// return null
|
||||
}
|
||||
var mutable_uri = "";
|
||||
mutable_uri += monero_config.coinUriPrefix;
|
||||
{
|
||||
const uriType = args.uriType;
|
||||
if (uriType === URITypes.addressAsAuthority) {
|
||||
mutable_uri += "//"; // use for inserting a // so data detectors pick it up…
|
||||
} else if (uriType === URITypes.addressAsFirstPathComponent) {
|
||||
// nothing to do
|
||||
} else {
|
||||
throw "Illegal args.uriType";
|
||||
}
|
||||
}
|
||||
mutable_uri += address;
|
||||
var isAppendingParam0 = true;
|
||||
function addParam(parameterName, value) {
|
||||
if (
|
||||
value == null ||
|
||||
value == "" /*important*/ ||
|
||||
typeof value === "undefined"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
var conjunctionStr = "&";
|
||||
if (isAppendingParam0 === true) {
|
||||
isAppendingParam0 = false;
|
||||
conjunctionStr = "?";
|
||||
}
|
||||
mutable_uri += conjunctionStr;
|
||||
mutable_uri += parameterName + "=" + encodeURIComponent(value);
|
||||
}
|
||||
{
|
||||
addParam("tx_amount", args.amount);
|
||||
if (
|
||||
(args.amountCcySymbol || "").toLowerCase() !=
|
||||
monero_config.coinSymbol.toLowerCase()
|
||||
) {
|
||||
addParam("tx_amount_ccy", args.amountCcySymbol);
|
||||
}
|
||||
addParam("tx_description", args.description);
|
||||
addParam("tx_payment_id", args.payment_id);
|
||||
addParam("tx_message", args.message);
|
||||
}
|
||||
return mutable_uri;
|
||||
}
|
||||
exports.New_RequestFunds_URI = New_RequestFunds_URI;
|
||||
//
|
||||
function New_ParsedPayload_FromPossibleRequestURIString(string, nettype) {
|
||||
// throws; -> {}
|
||||
//
|
||||
// detect no-scheme moneroAddr and possible OA addr - if has no monero: prefix
|
||||
if (string.indexOf(monero_config.coinUriPrefix) !== 0) {
|
||||
const stringHasQMark = string.indexOf("?") !== -1;
|
||||
if (stringHasQMark) {
|
||||
// fairly sure this is correct.. (just an extra failsafe/filter)
|
||||
throw "Unrecognized URI format";
|
||||
}
|
||||
let couldBeOAAddress = string.indexOf(".") != -1; // contains period - would be nice to get this from DoesStringContainPeriodChar_excludingAsXMRAddress_qualifyingAsPossibleOAAddress so maybe mymonero_core_js should gain local_modules/OpenAlias
|
||||
if (couldBeOAAddress) {
|
||||
return {
|
||||
address: string,
|
||||
};
|
||||
}
|
||||
var address__decode_result;
|
||||
try {
|
||||
address__decode_result = monero_utils.decode_address(
|
||||
string,
|
||||
nettype,
|
||||
);
|
||||
} catch (e) {
|
||||
throw "No Monero request info";
|
||||
return;
|
||||
}
|
||||
// then it looks like a monero address
|
||||
return {
|
||||
address: string,
|
||||
};
|
||||
}
|
||||
const uriString = string;
|
||||
const url = new URL(uriString);
|
||||
const protocol = url.protocol;
|
||||
if (protocol !== monero_config.coinUriPrefix) {
|
||||
throw "Request URI has non-Monero protocol";
|
||||
}
|
||||
var target_address = url.pathname; // var instead of const as have to finalize it
|
||||
// it seems that if the URL has // in it, pathname will be empty, but host will contain the address instead
|
||||
if (
|
||||
target_address === "" ||
|
||||
typeof target_address === "undefined" ||
|
||||
!target_address
|
||||
) {
|
||||
target_address = url.host || url.hostname;
|
||||
}
|
||||
if (target_address.indexOf("//") == 0) {
|
||||
target_address = target_address.slice(
|
||||
0 + "//".length,
|
||||
target_address.length,
|
||||
); // strip prefixing "//" in case URL had protocol:// instead of protocol:
|
||||
}
|
||||
const searchParams = url.searchParams; // needs to be parsed it seems
|
||||
//
|
||||
const payload = {
|
||||
address: target_address,
|
||||
};
|
||||
const keyPrefixToTrim = "tx_";
|
||||
const lengthOf_keyPrefixToTrim = keyPrefixToTrim.length;
|
||||
searchParams.forEach(function(value, key) {
|
||||
var storeAt_key = key;
|
||||
if (key.indexOf(keyPrefixToTrim) === 0) {
|
||||
storeAt_key = key.slice(lengthOf_keyPrefixToTrim, key.length);
|
||||
}
|
||||
payload["" + storeAt_key] = value;
|
||||
});
|
||||
//
|
||||
return payload;
|
||||
}
|
||||
exports.New_ParsedPayload_FromPossibleRequestURIString = New_ParsedPayload_FromPossibleRequestURIString;
|
@ -1,926 +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";
|
||||
//
|
||||
const async = require("async");
|
||||
//
|
||||
const monero_config = require("./monero_config");
|
||||
const monero_utils = require("./monero_cryptonote_utils_instance");
|
||||
const monero_paymentID_utils = require("./monero_paymentID_utils");
|
||||
const JSBigInt = require("../cryptonote_utils/biginteger").BigInteger;
|
||||
//
|
||||
const hostAPI_net_service_utils = require("../hostAPI/net_service_utils");
|
||||
//
|
||||
function _forkv7_minimumMixin() {
|
||||
return 6;
|
||||
}
|
||||
function _mixinToRingsize(mixin) {
|
||||
return mixin + 1;
|
||||
}
|
||||
//
|
||||
function thisFork_minMixin() {
|
||||
return _forkv7_minimumMixin();
|
||||
}
|
||||
function thisFork_minRingSize() {
|
||||
return _mixinToRingsize(thisFork_minMixin());
|
||||
}
|
||||
exports.thisFork_minMixin = thisFork_minMixin;
|
||||
exports.thisFork_minRingSize = thisFork_minRingSize;
|
||||
//
|
||||
function fixedMixin() {
|
||||
return thisFork_minMixin(); /* using the monero app default to remove MM user identifiers */
|
||||
}
|
||||
function fixedRingsize() {
|
||||
return _mixinToRingsize(fixedMixin());
|
||||
}
|
||||
exports.fixedMixin = fixedMixin;
|
||||
exports.fixedRingsize = fixedRingsize;
|
||||
//
|
||||
//
|
||||
function default_priority() {
|
||||
return 1;
|
||||
} // aka .low
|
||||
exports.default_priority = default_priority;
|
||||
//
|
||||
//
|
||||
function calculate_fee(fee_per_kb_JSBigInt, numberOf_bytes, fee_multiplier) {
|
||||
const numberOf_kB_JSBigInt = new JSBigInt(
|
||||
(numberOf_bytes + 1023.0) / 1024.0,
|
||||
); // i.e. ceil
|
||||
//
|
||||
return calculate_fee__kb(
|
||||
fee_per_kb_JSBigInt,
|
||||
numberOf_kB_JSBigInt,
|
||||
fee_multiplier,
|
||||
);
|
||||
}
|
||||
function calculate_fee__kb(fee_per_kb_JSBigInt, numberOf_kb, fee_multiplier) {
|
||||
const numberOf_kB_JSBigInt = new JSBigInt(numberOf_kb);
|
||||
const fee = fee_per_kb_JSBigInt
|
||||
.multiply(fee_multiplier)
|
||||
.multiply(numberOf_kB_JSBigInt);
|
||||
//
|
||||
return fee;
|
||||
}
|
||||
const newer_multipliers = [1, 4, 20, 166];
|
||||
function fee_multiplier_for_priority(priority__or0ForDefault) {
|
||||
const final_priorityInt =
|
||||
!priority__or0ForDefault || priority__or0ForDefault == 0
|
||||
? default_priority()
|
||||
: priority__or0ForDefault;
|
||||
if (
|
||||
final_priorityInt <= 0 ||
|
||||
final_priorityInt > newer_multipliers.length
|
||||
) {
|
||||
throw "fee_multiplier_for_priority: simple_priority out of bounds";
|
||||
}
|
||||
const priority_as_idx = final_priorityInt - 1;
|
||||
return newer_multipliers[priority_as_idx];
|
||||
}
|
||||
function EstimatedTransaction_networkFee(
|
||||
nonZero_mixin_int,
|
||||
feePerKB_JSBigInt,
|
||||
simple_priority,
|
||||
) {
|
||||
const numberOf_inputs = 2; // this might change -- might select inputs
|
||||
const numberOf_outputs =
|
||||
1 /*dest*/ + 1 /*change*/ + 0; /*no mymonero fee presently*/
|
||||
// TODO: update est tx size for bulletproofs
|
||||
// TODO: normalize est tx size fn naming
|
||||
const estimated_txSize = monero_utils.estimateRctSize(
|
||||
numberOf_inputs,
|
||||
nonZero_mixin_int,
|
||||
numberOf_outputs,
|
||||
);
|
||||
const estimated_fee = calculate_fee(
|
||||
feePerKB_JSBigInt,
|
||||
estimated_txSize,
|
||||
fee_multiplier_for_priority(simple_priority),
|
||||
);
|
||||
//
|
||||
return estimated_fee;
|
||||
}
|
||||
exports.EstimatedTransaction_networkFee = EstimatedTransaction_networkFee;
|
||||
//
|
||||
const SendFunds_ProcessStep_Code = {
|
||||
fetchingLatestBalance: 1,
|
||||
calculatingFee: 2,
|
||||
fetchingDecoyOutputs: 3, // may get skipped if 0 mixin
|
||||
constructingTransaction: 4, // may go back to .calculatingFee
|
||||
submittingTransaction: 5,
|
||||
};
|
||||
exports.SendFunds_ProcessStep_Code = SendFunds_ProcessStep_Code;
|
||||
const SendFunds_ProcessStep_MessageSuffix = {
|
||||
1: "Fetching latest balance.",
|
||||
2: "Calculating fee.",
|
||||
3: "Fetching decoy outputs.",
|
||||
4: "Constructing transaction.", // may go back to .calculatingFee
|
||||
5: "Submitting transaction.",
|
||||
};
|
||||
exports.SendFunds_ProcessStep_MessageSuffix = SendFunds_ProcessStep_MessageSuffix;
|
||||
//
|
||||
function SendFunds(
|
||||
target_address, // currency-ready wallet address, but not an OA address (resolve before calling)
|
||||
nettype,
|
||||
amount_orZeroWhenSweep, // number - value will be ignoring for sweep
|
||||
isSweep_orZeroWhenAmount, // send true to sweep - amount_orZeroWhenSweep will be ignored
|
||||
wallet__public_address,
|
||||
wallet__private_keys,
|
||||
wallet__public_keys,
|
||||
hostedMoneroAPIClient, // TODO: possibly factor this dependency
|
||||
monero_openalias_utils,
|
||||
payment_id,
|
||||
mixin,
|
||||
simple_priority,
|
||||
preSuccess_nonTerminal_statusUpdate_fn, // (_ stepCode: SendFunds_ProcessStep_Code) -> Void
|
||||
success_fn,
|
||||
// success_fn: (
|
||||
// moneroReady_targetDescription_address?,
|
||||
// sentAmount?,
|
||||
// final__payment_id?,
|
||||
// tx_hash?,
|
||||
// tx_fee?
|
||||
// )
|
||||
failWithErr_fn,
|
||||
// failWithErr_fn: (
|
||||
// err
|
||||
// )
|
||||
) {
|
||||
var isRingCT = true;
|
||||
var sweeping = isSweep_orZeroWhenAmount === true; // rather than, say, undefined
|
||||
//
|
||||
// some callback trampoline function declarations…
|
||||
function __trampolineFor_success(
|
||||
moneroReady_targetDescription_address,
|
||||
sentAmount,
|
||||
final__payment_id,
|
||||
tx_hash,
|
||||
tx_fee,
|
||||
) {
|
||||
success_fn(
|
||||
moneroReady_targetDescription_address,
|
||||
sentAmount,
|
||||
final__payment_id,
|
||||
tx_hash,
|
||||
tx_fee,
|
||||
);
|
||||
}
|
||||
function __trampolineFor_err_withErr(err) {
|
||||
failWithErr_fn(err);
|
||||
}
|
||||
function __trampolineFor_err_withStr(errStr) {
|
||||
const err = new Error(errStr);
|
||||
console.error(errStr);
|
||||
failWithErr_fn(err);
|
||||
}
|
||||
if (mixin < thisFork_minMixin()) {
|
||||
__trampolineFor_err_withStr("Ringsize is below the minimum.");
|
||||
return;
|
||||
}
|
||||
//
|
||||
// parse & normalize the target descriptions by mapping them to Monero addresses & amounts
|
||||
var amount = sweeping ? 0 : amount_orZeroWhenSweep;
|
||||
const targetDescription = {
|
||||
address: target_address,
|
||||
amount: amount,
|
||||
};
|
||||
new_moneroReadyTargetDescriptions_fromTargetDescriptions(
|
||||
monero_openalias_utils,
|
||||
[targetDescription], // requires a list of descriptions - but SendFunds was
|
||||
// not written with multiple target support as MyMonero does not yet support it
|
||||
nettype,
|
||||
function(err, moneroReady_targetDescriptions) {
|
||||
if (err) {
|
||||
__trampolineFor_err_withErr(err);
|
||||
return;
|
||||
}
|
||||
const invalidOrZeroDestination_errStr =
|
||||
"You need to enter a valid destination";
|
||||
if (moneroReady_targetDescriptions.length === 0) {
|
||||
__trampolineFor_err_withStr(invalidOrZeroDestination_errStr);
|
||||
return;
|
||||
}
|
||||
const moneroReady_targetDescription =
|
||||
moneroReady_targetDescriptions[0];
|
||||
if (
|
||||
moneroReady_targetDescription === null ||
|
||||
typeof moneroReady_targetDescription === "undefined"
|
||||
) {
|
||||
__trampolineFor_err_withStr(invalidOrZeroDestination_errStr);
|
||||
return;
|
||||
}
|
||||
_proceedTo_prepareToSendFundsTo_moneroReady_targetDescription(
|
||||
moneroReady_targetDescription,
|
||||
);
|
||||
},
|
||||
);
|
||||
function _proceedTo_prepareToSendFundsTo_moneroReady_targetDescription(
|
||||
moneroReady_targetDescription,
|
||||
) {
|
||||
var moneroReady_targetDescription_address =
|
||||
moneroReady_targetDescription.address;
|
||||
var moneroReady_targetDescription_amount =
|
||||
moneroReady_targetDescription.amount;
|
||||
//
|
||||
var totalAmountWithoutFee_JSBigInt = new JSBigInt(0).add(
|
||||
moneroReady_targetDescription_amount,
|
||||
);
|
||||
console.log(
|
||||
"💬 Total to send, before fee: " + sweeping
|
||||
? "all"
|
||||
: monero_utils.formatMoney(totalAmountWithoutFee_JSBigInt),
|
||||
);
|
||||
if (!sweeping && totalAmountWithoutFee_JSBigInt.compare(0) <= 0) {
|
||||
const errStr = "The amount you've entered is too low";
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
}
|
||||
//
|
||||
// Derive/finalize some values…
|
||||
var final__payment_id = payment_id;
|
||||
var final__pid_encrypt = false; // we don't want to encrypt payment ID unless we find an integrated one
|
||||
var address__decode_result;
|
||||
try {
|
||||
address__decode_result = monero_utils.decode_address(
|
||||
moneroReady_targetDescription_address,
|
||||
nettype,
|
||||
);
|
||||
} catch (e) {
|
||||
__trampolineFor_err_withStr(
|
||||
typeof e === "string" ? e : e.toString(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (payment_id) {
|
||||
if (address__decode_result.intPaymentId) {
|
||||
const errStr =
|
||||
"Payment ID must be blank when using an Integrated Address";
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
} else if (
|
||||
monero_utils.is_subaddress(
|
||||
moneroReady_targetDescription_address,
|
||||
nettype,
|
||||
)
|
||||
) {
|
||||
const errStr =
|
||||
"Payment ID must be blank when using a Subaddress";
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (address__decode_result.intPaymentId) {
|
||||
final__payment_id = address__decode_result.intPaymentId;
|
||||
final__pid_encrypt = true; // we do want to encrypt if using an integrated address
|
||||
} else if (
|
||||
monero_paymentID_utils.IsValidPaymentIDOrNoPaymentID(
|
||||
final__payment_id,
|
||||
) === false
|
||||
) {
|
||||
const errStr = "Invalid payment ID.";
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
}
|
||||
//
|
||||
_proceedTo_getUnspentOutsUsableForMixin(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
);
|
||||
}
|
||||
function _proceedTo_getUnspentOutsUsableForMixin(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id, // non-existent or valid
|
||||
final__pid_encrypt, // true or false
|
||||
) {
|
||||
preSuccess_nonTerminal_statusUpdate_fn(
|
||||
SendFunds_ProcessStep_Code.fetchingLatestBalance,
|
||||
);
|
||||
hostedMoneroAPIClient.UnspentOuts(
|
||||
wallet__public_address,
|
||||
wallet__private_keys.view,
|
||||
wallet__public_keys.spend,
|
||||
wallet__private_keys.spend,
|
||||
mixin,
|
||||
sweeping,
|
||||
function(err, unspentOuts, unusedOuts, dynamic_feePerKB_JSBigInt) {
|
||||
if (err) {
|
||||
__trampolineFor_err_withErr(err);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
"Received dynamic per kb fee",
|
||||
monero_utils.formatMoneySymbol(dynamic_feePerKB_JSBigInt),
|
||||
);
|
||||
_proceedTo_constructFundTransferListAndSendFundsByUsingUnusedUnspentOutsForMixin(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
unusedOuts,
|
||||
dynamic_feePerKB_JSBigInt,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
function _proceedTo_constructFundTransferListAndSendFundsByUsingUnusedUnspentOutsForMixin(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
unusedOuts,
|
||||
dynamic_feePerKB_JSBigInt,
|
||||
) {
|
||||
// status: constructing transaction…
|
||||
const feePerKB_JSBigInt = dynamic_feePerKB_JSBigInt;
|
||||
// Transaction will need at least 1KB fee (or 13KB for RingCT)
|
||||
const network_minimumTXSize_kb = /*isRingCT ? */ 13; /* : 1*/
|
||||
const network_minimumFee = calculate_fee__kb(
|
||||
feePerKB_JSBigInt,
|
||||
network_minimumTXSize_kb,
|
||||
fee_multiplier_for_priority(simple_priority),
|
||||
);
|
||||
// ^-- now we're going to try using this minimum fee but the codepath has to be able to be re-entered if we find after constructing the whole tx that it is larger in kb than the minimum fee we're attempting to send it off with
|
||||
__reenterable_constructFundTransferListAndSendFunds_findingLowestNetworkFee(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
unusedOuts,
|
||||
feePerKB_JSBigInt, // obtained from server, so passed in
|
||||
network_minimumFee,
|
||||
);
|
||||
}
|
||||
function __reenterable_constructFundTransferListAndSendFunds_findingLowestNetworkFee(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
unusedOuts,
|
||||
feePerKB_JSBigInt,
|
||||
passedIn_attemptAt_network_minimumFee,
|
||||
) {
|
||||
// Now we need to establish some values for balance validation and to construct the transaction
|
||||
preSuccess_nonTerminal_statusUpdate_fn(
|
||||
SendFunds_ProcessStep_Code.calculatingFee,
|
||||
);
|
||||
//
|
||||
var attemptAt_network_minimumFee = passedIn_attemptAt_network_minimumFee; // we may change this if isRingCT
|
||||
// const hostingService_chargeAmount = hostedMoneroAPIClient.HostingServiceChargeFor_transactionWithNetworkFee(attemptAt_network_minimumFee)
|
||||
var totalAmountIncludingFees;
|
||||
if (sweeping) {
|
||||
totalAmountIncludingFees = new JSBigInt("18450000000000000000"); //~uint64 max
|
||||
console.log("Balance required: all");
|
||||
} else {
|
||||
totalAmountIncludingFees = totalAmountWithoutFee_JSBigInt.add(
|
||||
attemptAt_network_minimumFee,
|
||||
); /*.add(hostingService_chargeAmount) NOTE service fee removed for now */
|
||||
console.log(
|
||||
"Balance required: " +
|
||||
monero_utils.formatMoneySymbol(totalAmountIncludingFees),
|
||||
);
|
||||
}
|
||||
const usableOutputsAndAmounts = _outputsAndAmountToUseForMixin(
|
||||
totalAmountIncludingFees,
|
||||
unusedOuts,
|
||||
isRingCT,
|
||||
sweeping,
|
||||
);
|
||||
// v-- now if RingCT compute fee as closely as possible before hand
|
||||
var usingOuts = usableOutputsAndAmounts.usingOuts;
|
||||
var usingOutsAmount = usableOutputsAndAmounts.usingOutsAmount;
|
||||
var remaining_unusedOuts = usableOutputsAndAmounts.remaining_unusedOuts; // this is a copy of the pre-mutation usingOuts
|
||||
if (/*usingOuts.length > 1 &&*/ isRingCT) {
|
||||
var newNeededFee = calculate_fee(
|
||||
feePerKB_JSBigInt,
|
||||
monero_utils.estimateRctSize(usingOuts.length, mixin, 2),
|
||||
fee_multiplier_for_priority(simple_priority),
|
||||
);
|
||||
// if newNeededFee < neededFee, use neededFee instead (should only happen on the 2nd or later times through (due to estimated fee being too low))
|
||||
if (newNeededFee.compare(attemptAt_network_minimumFee) < 0) {
|
||||
newNeededFee = attemptAt_network_minimumFee;
|
||||
}
|
||||
if (sweeping) {
|
||||
/*
|
||||
// When/if sending to multiple destinations supported, uncomment and port this:
|
||||
if (dsts.length !== 1) {
|
||||
deferred.reject("Sweeping to multiple accounts is not allowed");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
totalAmountWithoutFee_JSBigInt = usingOutsAmount.subtract(
|
||||
newNeededFee,
|
||||
);
|
||||
if (totalAmountWithoutFee_JSBigInt.compare(0) < 1) {
|
||||
const errStr = `Your spendable balance is too low. Have ${monero_utils.formatMoney(
|
||||
usingOutsAmount,
|
||||
)} ${
|
||||
monero_config.coinSymbol
|
||||
} spendable, need ${monero_utils.formatMoney(
|
||||
newNeededFee,
|
||||
)} ${monero_config.coinSymbol}.`;
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
}
|
||||
totalAmountIncludingFees = totalAmountWithoutFee_JSBigInt.add(
|
||||
newNeededFee,
|
||||
);
|
||||
} else {
|
||||
totalAmountIncludingFees = totalAmountWithoutFee_JSBigInt.add(
|
||||
newNeededFee,
|
||||
);
|
||||
// add outputs 1 at a time till we either have them all or can meet the fee
|
||||
while (
|
||||
usingOutsAmount.compare(totalAmountIncludingFees) < 0 &&
|
||||
remaining_unusedOuts.length > 0
|
||||
) {
|
||||
const out = _popAndReturnRandomElementFromList(
|
||||
remaining_unusedOuts,
|
||||
);
|
||||
console.log(
|
||||
"Using output: " +
|
||||
monero_utils.formatMoney(out.amount) +
|
||||
" - " +
|
||||
JSON.stringify(out),
|
||||
);
|
||||
// and recalculate invalidated values
|
||||
newNeededFee = calculate_fee(
|
||||
feePerKB_JSBigInt,
|
||||
monero_utils.estimateRctSize(
|
||||
usingOuts.length,
|
||||
mixin,
|
||||
2,
|
||||
),
|
||||
fee_multiplier_for_priority(simple_priority),
|
||||
);
|
||||
totalAmountIncludingFees = totalAmountWithoutFee_JSBigInt.add(
|
||||
newNeededFee,
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
"New fee: " +
|
||||
monero_utils.formatMoneySymbol(newNeededFee) +
|
||||
" for " +
|
||||
usingOuts.length +
|
||||
" inputs",
|
||||
);
|
||||
attemptAt_network_minimumFee = newNeededFee;
|
||||
}
|
||||
console.log(
|
||||
"~ Balance required: " +
|
||||
monero_utils.formatMoneySymbol(totalAmountIncludingFees),
|
||||
);
|
||||
// Now we can validate available balance with usingOutsAmount (TODO? maybe this check can be done before selecting outputs?)
|
||||
const usingOutsAmount_comparedTo_totalAmount = usingOutsAmount.compare(
|
||||
totalAmountIncludingFees,
|
||||
);
|
||||
if (usingOutsAmount_comparedTo_totalAmount < 0) {
|
||||
const errStr = `Your spendable balance is too low. Have ${monero_utils.formatMoney(
|
||||
usingOutsAmount,
|
||||
)} ${
|
||||
monero_config.coinSymbol
|
||||
} spendable, need ${monero_utils.formatMoney(
|
||||
totalAmountIncludingFees,
|
||||
)} ${monero_config.coinSymbol}.`;
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
}
|
||||
// Now we can put together the list of fund transfers we need to perform
|
||||
const fundTransferDescriptions = []; // to build…
|
||||
// I. the actual transaction the user is asking to do
|
||||
fundTransferDescriptions.push({
|
||||
address: moneroReady_targetDescription_address,
|
||||
amount: totalAmountWithoutFee_JSBigInt,
|
||||
});
|
||||
// II. the fee that the hosting provider charges
|
||||
// NOTE: The fee has been removed for RCT until a later date
|
||||
// fundTransferDescriptions.push({
|
||||
// address: hostedMoneroAPIClient.HostingServiceFeeDepositAddress(),
|
||||
// amount: hostingService_chargeAmount
|
||||
// })
|
||||
// III. some amount of the total outputs will likely need to be returned to the user as "change":
|
||||
if (usingOutsAmount_comparedTo_totalAmount > 0) {
|
||||
if (sweeping) {
|
||||
throw "Unexpected usingOutsAmount_comparedTo_totalAmount > 0 && sweeping";
|
||||
}
|
||||
var changeAmount = usingOutsAmount.subtract(
|
||||
totalAmountIncludingFees,
|
||||
);
|
||||
console.log("changeAmount", changeAmount);
|
||||
if (isRingCT) {
|
||||
// for RCT we don't presently care about dustiness so add entire change amount
|
||||
console.log(
|
||||
"Sending change of " +
|
||||
monero_utils.formatMoneySymbol(changeAmount) +
|
||||
" to " +
|
||||
wallet__public_address,
|
||||
);
|
||||
fundTransferDescriptions.push({
|
||||
address: wallet__public_address,
|
||||
amount: changeAmount,
|
||||
});
|
||||
} else {
|
||||
// pre-ringct
|
||||
// do not give ourselves change < dust threshold
|
||||
var changeAmountDivRem = changeAmount.divRem(
|
||||
monero_config.dustThreshold,
|
||||
);
|
||||
console.log("💬 changeAmountDivRem", changeAmountDivRem);
|
||||
if (changeAmountDivRem[1].toString() !== "0") {
|
||||
// miners will add dusty change to fee
|
||||
console.log(
|
||||
"💬 Miners will add change of " +
|
||||
monero_utils.formatMoneyFullSymbol(
|
||||
changeAmountDivRem[1],
|
||||
) +
|
||||
" to transaction fee (below dust threshold)",
|
||||
);
|
||||
}
|
||||
if (changeAmountDivRem[0].toString() !== "0") {
|
||||
// send non-dusty change to our address
|
||||
var usableChange = changeAmountDivRem[0].multiply(
|
||||
monero_config.dustThreshold,
|
||||
);
|
||||
console.log(
|
||||
"💬 Sending change of " +
|
||||
monero_utils.formatMoneySymbol(usableChange) +
|
||||
" to " +
|
||||
wallet__public_address,
|
||||
);
|
||||
fundTransferDescriptions.push({
|
||||
address: wallet__public_address,
|
||||
amount: usableChange,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (usingOutsAmount_comparedTo_totalAmount == 0) {
|
||||
// this should always fire when sweeping
|
||||
if (isRingCT) {
|
||||
// then create random destination to keep 2 outputs always in case of 0 change
|
||||
var fakeAddress = monero_utils.create_address(
|
||||
monero_utils.random_scalar(),
|
||||
nettype,
|
||||
).public_addr;
|
||||
console.log(
|
||||
"Sending 0 XMR to a fake address to keep tx uniform (no change exists): " +
|
||||
fakeAddress,
|
||||
);
|
||||
fundTransferDescriptions.push({
|
||||
address: fakeAddress,
|
||||
amount: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
"fundTransferDescriptions so far",
|
||||
fundTransferDescriptions,
|
||||
);
|
||||
if (mixin < 0 || isNaN(mixin)) {
|
||||
__trampolineFor_err_withStr("Invalid mixin");
|
||||
return;
|
||||
}
|
||||
if (mixin > 0) {
|
||||
// first, grab RandomOuts, then enter __createTx
|
||||
preSuccess_nonTerminal_statusUpdate_fn(
|
||||
SendFunds_ProcessStep_Code.fetchingDecoyOutputs,
|
||||
);
|
||||
hostedMoneroAPIClient.RandomOuts(usingOuts, mixin, function(
|
||||
err,
|
||||
amount_outs,
|
||||
) {
|
||||
if (err) {
|
||||
__trampolineFor_err_withErr(err);
|
||||
return;
|
||||
}
|
||||
__createTxAndAttemptToSend(amount_outs);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// mixin === 0: -- PSNOTE: is that even allowed?
|
||||
__createTxAndAttemptToSend();
|
||||
}
|
||||
function __createTxAndAttemptToSend(mix_outs) {
|
||||
preSuccess_nonTerminal_statusUpdate_fn(
|
||||
SendFunds_ProcessStep_Code.constructingTransaction,
|
||||
);
|
||||
var signedTx;
|
||||
try {
|
||||
console.log("Destinations: ");
|
||||
monero_utils.printDsts(fundTransferDescriptions);
|
||||
//
|
||||
var realDestViewKey; // need to get viewkey for encrypting here, because of splitting and sorting
|
||||
if (final__pid_encrypt) {
|
||||
realDestViewKey = monero_utils.decode_address(
|
||||
moneroReady_targetDescription_address,
|
||||
nettype,
|
||||
).view;
|
||||
console.log("got realDestViewKey", realDestViewKey);
|
||||
}
|
||||
var splitDestinations = monero_utils.decompose_tx_destinations(
|
||||
fundTransferDescriptions,
|
||||
isRingCT,
|
||||
);
|
||||
console.log("Decomposed destinations:");
|
||||
monero_utils.printDsts(splitDestinations);
|
||||
//
|
||||
signedTx = monero_utils.create_transaction(
|
||||
wallet__public_keys,
|
||||
wallet__private_keys,
|
||||
splitDestinations,
|
||||
usingOuts,
|
||||
mix_outs,
|
||||
mixin,
|
||||
attemptAt_network_minimumFee,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
realDestViewKey,
|
||||
0,
|
||||
isRingCT,
|
||||
nettype,
|
||||
);
|
||||
} catch (e) {
|
||||
var errStr;
|
||||
if (e) {
|
||||
errStr = typeof e == "string" ? e : e.toString();
|
||||
} else {
|
||||
errStr = "Failed to create transaction with unknown error.";
|
||||
}
|
||||
__trampolineFor_err_withStr(errStr);
|
||||
return;
|
||||
}
|
||||
console.log("signed tx: ", JSON.stringify(signedTx));
|
||||
//
|
||||
var serialized_signedTx;
|
||||
var tx_hash;
|
||||
if (signedTx.version === 1) {
|
||||
serialized_signedTx = monero_utils.serialize_tx(signedTx);
|
||||
tx_hash = monero_utils.cn_fast_hash(serialized_signedTx);
|
||||
} else {
|
||||
const raw_tx_and_hash = monero_utils.serialize_rct_tx_with_hash(
|
||||
signedTx,
|
||||
);
|
||||
serialized_signedTx = raw_tx_and_hash.raw;
|
||||
tx_hash = raw_tx_and_hash.hash;
|
||||
}
|
||||
console.log("tx serialized: " + serialized_signedTx);
|
||||
console.log("Tx hash: " + tx_hash);
|
||||
//
|
||||
// work out per-kb fee for transaction and verify that it's enough
|
||||
var txBlobBytes = serialized_signedTx.length / 2;
|
||||
var numKB = Math.floor(txBlobBytes / 1024);
|
||||
if (txBlobBytes % 1024) {
|
||||
numKB++;
|
||||
}
|
||||
console.log(
|
||||
txBlobBytes +
|
||||
" bytes <= " +
|
||||
numKB +
|
||||
" KB (current fee: " +
|
||||
monero_utils.formatMoneyFull(attemptAt_network_minimumFee) +
|
||||
")",
|
||||
);
|
||||
const feeActuallyNeededByNetwork = calculate_fee__kb(
|
||||
feePerKB_JSBigInt,
|
||||
numKB,
|
||||
fee_multiplier_for_priority(simple_priority),
|
||||
);
|
||||
// if we need a higher fee
|
||||
if (
|
||||
feeActuallyNeededByNetwork.compare(
|
||||
attemptAt_network_minimumFee,
|
||||
) > 0
|
||||
) {
|
||||
console.log(
|
||||
"💬 Need to reconstruct the tx with enough of a network fee. Previous fee: " +
|
||||
monero_utils.formatMoneyFull(
|
||||
attemptAt_network_minimumFee,
|
||||
) +
|
||||
" New fee: " +
|
||||
monero_utils.formatMoneyFull(
|
||||
feeActuallyNeededByNetwork,
|
||||
),
|
||||
);
|
||||
// this will update status back to .calculatingFee
|
||||
__reenterable_constructFundTransferListAndSendFunds_findingLowestNetworkFee(
|
||||
moneroReady_targetDescription_address,
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
final__payment_id,
|
||||
final__pid_encrypt,
|
||||
unusedOuts,
|
||||
feePerKB_JSBigInt,
|
||||
feeActuallyNeededByNetwork, // we are re-entering this codepath after changing this feeActuallyNeededByNetwork
|
||||
);
|
||||
//
|
||||
return;
|
||||
}
|
||||
//
|
||||
// generated with correct per-kb fee
|
||||
const final_networkFee = attemptAt_network_minimumFee; // just to make things clear
|
||||
console.log(
|
||||
"💬 Successful tx generation, submitting tx. Going with final_networkFee of ",
|
||||
monero_utils.formatMoney(final_networkFee),
|
||||
);
|
||||
// status: submitting…
|
||||
preSuccess_nonTerminal_statusUpdate_fn(
|
||||
SendFunds_ProcessStep_Code.submittingTransaction,
|
||||
);
|
||||
hostedMoneroAPIClient.SubmitSerializedSignedTransaction(
|
||||
wallet__public_address,
|
||||
wallet__private_keys.view,
|
||||
serialized_signedTx,
|
||||
function(err) {
|
||||
if (err) {
|
||||
__trampolineFor_err_withStr(
|
||||
"Something unexpected occurred when submitting your transaction: " +
|
||||
err,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const tx_fee = final_networkFee; /*.add(hostingService_chargeAmount) NOTE: Service charge removed to reduce bloat for now */
|
||||
__trampolineFor_success(
|
||||
moneroReady_targetDescription_address,
|
||||
sweeping
|
||||
? parseFloat(
|
||||
monero_utils.formatMoneyFull(
|
||||
totalAmountWithoutFee_JSBigInt,
|
||||
),
|
||||
)
|
||||
: amount,
|
||||
final__payment_id,
|
||||
tx_hash,
|
||||
tx_fee,
|
||||
); // 🎉
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.SendFunds = SendFunds;
|
||||
//
|
||||
function new_moneroReadyTargetDescriptions_fromTargetDescriptions(
|
||||
monero_openalias_utils,
|
||||
targetDescriptions,
|
||||
nettype,
|
||||
fn, // fn: (err, moneroReady_targetDescriptions) -> Void
|
||||
) {
|
||||
// parse & normalize the target descriptions by mapping them to currency (Monero)-ready addresses & amounts
|
||||
// some pure function declarations for the map we'll do on targetDescriptions
|
||||
async.mapSeries(
|
||||
targetDescriptions,
|
||||
function(targetDescription, cb) {
|
||||
if (!targetDescription.address && !targetDescription.amount) {
|
||||
// PSNote: is this check rigorous enough?
|
||||
const errStr =
|
||||
"Please supply a target address and a target amount.";
|
||||
const err = new Error(errStr);
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
const targetDescription_address = targetDescription.address;
|
||||
const targetDescription_amount = "" + targetDescription.amount; // we are converting it to a string here because parseMoney expects a string
|
||||
// now verify/parse address and amount
|
||||
if (
|
||||
monero_openalias_utils.DoesStringContainPeriodChar_excludingAsXMRAddress_qualifyingAsPossibleOAAddress(
|
||||
targetDescription_address,
|
||||
) == true
|
||||
) {
|
||||
throw "You must resolve this OA address to a Monero address before calling SendFunds";
|
||||
}
|
||||
// otherwise this should be a normal, single Monero public address
|
||||
try {
|
||||
monero_utils.decode_address(targetDescription_address, nettype); // verify that the address is valid
|
||||
} catch (e) {
|
||||
const errStr =
|
||||
"Couldn't decode address " +
|
||||
targetDescription_address +
|
||||
": " +
|
||||
e;
|
||||
const err = new Error(errStr);
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
// amount:
|
||||
var moneroReady_amountToSend; // possibly need this ; here for the JS parser
|
||||
try {
|
||||
moneroReady_amountToSend = monero_utils.parseMoney(
|
||||
targetDescription_amount,
|
||||
);
|
||||
} catch (e) {
|
||||
const errStr =
|
||||
"Couldn't parse amount " +
|
||||
targetDescription_amount +
|
||||
": " +
|
||||
e;
|
||||
const err = new Error(errStr);
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
cb(null, {
|
||||
address: targetDescription_address,
|
||||
amount: moneroReady_amountToSend,
|
||||
});
|
||||
},
|
||||
function(err, moneroReady_targetDescriptions) {
|
||||
fn(err, moneroReady_targetDescriptions);
|
||||
},
|
||||
);
|
||||
}
|
||||
//
|
||||
function __randomIndex(list) {
|
||||
return Math.floor(Math.random() * list.length);
|
||||
}
|
||||
function _popAndReturnRandomElementFromList(list) {
|
||||
var idx = __randomIndex(list);
|
||||
var val = list[idx];
|
||||
list.splice(idx, 1);
|
||||
//
|
||||
return val;
|
||||
}
|
||||
function _outputsAndAmountToUseForMixin(
|
||||
target_amount,
|
||||
unusedOuts,
|
||||
isRingCT,
|
||||
sweeping,
|
||||
) {
|
||||
console.log(
|
||||
"Selecting outputs to use. target: " +
|
||||
monero_utils.formatMoney(target_amount),
|
||||
);
|
||||
var toFinalize_usingOutsAmount = new JSBigInt(0);
|
||||
const toFinalize_usingOuts = [];
|
||||
const remaining_unusedOuts = unusedOuts.slice(); // take copy so as to prevent issue if we must re-enter tx building fn if fee too low after building
|
||||
while (
|
||||
toFinalize_usingOutsAmount.compare(target_amount) < 0 &&
|
||||
remaining_unusedOuts.length > 0
|
||||
) {
|
||||
var out = _popAndReturnRandomElementFromList(remaining_unusedOuts);
|
||||
if (!isRingCT && out.rct) {
|
||||
// out.rct is set by the server
|
||||
continue; // skip rct outputs if not creating rct tx
|
||||
}
|
||||
const out_amount_JSBigInt = new JSBigInt(out.amount);
|
||||
if (out_amount_JSBigInt.compare(monero_config.dustThreshold) < 0) {
|
||||
// amount is dusty..
|
||||
if (sweeping == false) {
|
||||
console.log(
|
||||
"Not sweeping, and found a dusty (though maybe mixable) output... skipping it!",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (!out.rct || typeof out.rct === "undefined") {
|
||||
console.log(
|
||||
"Sweeping, and found a dusty but unmixable (non-rct) output... skipping it!",
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
console.log(
|
||||
"Sweeping and found a dusty but mixable (rct) amount... keeping it!",
|
||||
);
|
||||
}
|
||||
}
|
||||
toFinalize_usingOuts.push(out);
|
||||
toFinalize_usingOutsAmount = toFinalize_usingOutsAmount.add(
|
||||
out_amount_JSBigInt,
|
||||
);
|
||||
console.log(
|
||||
"Using output: " +
|
||||
monero_utils.formatMoney(out_amount_JSBigInt) +
|
||||
" - " +
|
||||
JSON.stringify(out),
|
||||
);
|
||||
}
|
||||
return {
|
||||
usingOuts: toFinalize_usingOuts,
|
||||
usingOutsAmount: toFinalize_usingOutsAmount,
|
||||
remaining_unusedOuts: remaining_unusedOuts,
|
||||
};
|
||||
}
|
@ -1,50 +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";
|
||||
//
|
||||
const monero_config = require("./monero_config");
|
||||
const monero_utils = require("./monero_cryptonote_utils_instance");
|
||||
//
|
||||
function IsTransactionConfirmed(tx, blockchain_height) {
|
||||
return blockchain_height - tx.height > monero_config.txMinConfirms;
|
||||
}
|
||||
exports.IsTransactionConfirmed = IsTransactionConfirmed;
|
||||
//
|
||||
function IsTransactionUnlocked(tx, blockchain_height) {
|
||||
return monero_utils.is_tx_unlocked(tx.unlock_time || 0, blockchain_height);
|
||||
}
|
||||
exports.IsTransactionUnlocked = IsTransactionUnlocked;
|
||||
//
|
||||
function TransactionLockedReason(tx, blockchain_height) {
|
||||
return monero_utils.tx_locked_reason(
|
||||
tx.unlock_time || 0,
|
||||
blockchain_height,
|
||||
);
|
||||
}
|
||||
exports.TransactionLockedReason = TransactionLockedReason;
|
@ -1,377 +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";
|
||||
//
|
||||
const mnemonic = require("../cryptonote_utils/mnemonic");
|
||||
const monero_utils = require("./monero_cryptonote_utils_instance");
|
||||
const monero_config = require("./monero_config");
|
||||
//
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Mnemonic wordset utilities - Exposing available names
|
||||
//
|
||||
const wordsetNamesByWordsetName = {};
|
||||
const allWordsetNames = Object.keys(mnemonic.mn_words);
|
||||
for (let wordsetName of allWordsetNames) {
|
||||
wordsetNamesByWordsetName[wordsetName] = wordsetName;
|
||||
}
|
||||
exports.WordsetNamesByWordsetName = wordsetNamesByWordsetName;
|
||||
exports.AllWordsetNames = allWordsetNames;
|
||||
//
|
||||
//
|
||||
// Mnemonic wordset utilities - Comparison
|
||||
// TODO: perhaps move this to mnemonic.js
|
||||
function AreEqualMnemonics(a, b, a__wordsetName, b__wordsetName) {
|
||||
if (a__wordsetName !== b__wordsetName) {
|
||||
return false;
|
||||
}
|
||||
const wordsetName = a__wordsetName;
|
||||
const wordset = mnemonic.mn_words[wordsetName];
|
||||
const prefix_len = wordset.prefix_len;
|
||||
// since mnemonics can be entered with only the first N letters, we must check equality of mnemonics by prefix
|
||||
let a__mnemonicString_words = a.split(" ");
|
||||
let b__mnemonicString_words = b.split(" ");
|
||||
if (a__mnemonicString_words.length != b__mnemonicString_words.length) {
|
||||
return false;
|
||||
}
|
||||
let numberOf_mnemonicString_words = a__mnemonicString_words.length;
|
||||
for (var i = 0; i < numberOf_mnemonicString_words; i++) {
|
||||
let a__word = a__mnemonicString_words[i];
|
||||
let b__word = b__mnemonicString_words[i];
|
||||
// ... We're assuming that a and b are already valid mneminics
|
||||
const a_prefix = a__word.slice(0, prefix_len);
|
||||
const b_prefix = b__word.slice(0, prefix_len);
|
||||
if (a_prefix !== b_prefix) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
exports.AreEqualMnemonics = AreEqualMnemonics;
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Mnemonic wordset utilities - Wordset name detection by mnemonic contents
|
||||
// TODO: perhaps move this to mnemonic.js
|
||||
function WordsetNameAccordingToMnemonicString(
|
||||
mnemonicString, // throws
|
||||
) {
|
||||
const mnemonicString_words = mnemonicString.split(" ");
|
||||
if (mnemonicString_words.length == 0) {
|
||||
throw "Invalid mnemonic";
|
||||
}
|
||||
var wholeMnemonicSuspectedAsWordsetNamed = null; // to derive
|
||||
for (let mnemonicString_word of mnemonicString_words) {
|
||||
var thisWordIsInWordsetNamed = null; // to derive
|
||||
for (let wordsetName of allWordsetNames) {
|
||||
if (wordsetName === "electrum") {
|
||||
continue; // skip because it conflicts with 'english'
|
||||
}
|
||||
const wordset = mnemonic.mn_words[wordsetName];
|
||||
const prefix_len = wordset.prefix_len;
|
||||
if (mnemonicString_word.length < prefix_len) {
|
||||
throw "Please enter more than " +
|
||||
(prefix_len - 1) +
|
||||
" letters per word";
|
||||
}
|
||||
const wordsetWords = wordset.words;
|
||||
for (let wordsetWord of wordsetWords) {
|
||||
if (wordsetWord.indexOf(mnemonicString_word) == 0) {
|
||||
// we can safely check prefix b/c we've checked mnemonicString_word is of at least min length
|
||||
thisWordIsInWordsetNamed = wordsetName;
|
||||
break; // done looking; exit interior then exterior loops
|
||||
}
|
||||
}
|
||||
if (thisWordIsInWordsetNamed != null) {
|
||||
// just found
|
||||
break; // also exit
|
||||
}
|
||||
// haven't found it yet; keep looking
|
||||
}
|
||||
if (thisWordIsInWordsetNamed === null) {
|
||||
// didn't find this word in any of the mnemonic wordsets
|
||||
throw "Unrecognized mnemonic language";
|
||||
}
|
||||
if (wholeMnemonicSuspectedAsWordsetNamed === null) {
|
||||
// haven't found it yet
|
||||
wholeMnemonicSuspectedAsWordsetNamed = thisWordIsInWordsetNamed;
|
||||
} else if (
|
||||
thisWordIsInWordsetNamed !== wholeMnemonicSuspectedAsWordsetNamed
|
||||
) {
|
||||
throw "Ambiguous mnemonic language"; // multiple wordset names detected
|
||||
} else {
|
||||
// nothing to do but keep verifying the rest of the words that it's the same suspsected wordset
|
||||
}
|
||||
}
|
||||
if (wholeMnemonicSuspectedAsWordsetNamed === null) {
|
||||
// this might be redundant, but for logical rigor……
|
||||
throw "Unrecognized mnemonic language";
|
||||
}
|
||||
//
|
||||
return wholeMnemonicSuspectedAsWordsetNamed;
|
||||
}
|
||||
exports.WordsetNameAccordingToMnemonicString = WordsetNameAccordingToMnemonicString;
|
||||
//
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Mnemonic wordset utilities - By locale
|
||||
//
|
||||
const mnemonicWordsetNamesByAppLocaleNames = {
|
||||
English: "english",
|
||||
Japanese: "japanese",
|
||||
Spanish: "spanish",
|
||||
Portuguese: "portuguese",
|
||||
// NOTE: no support for 'electrum' wordset here
|
||||
};
|
||||
exports.MnemonicWordsetNamesByAppLocaleNames = mnemonicWordsetNamesByAppLocaleNames;
|
||||
//
|
||||
exports.DefaultWalletMnemonicWordsetName =
|
||||
mnemonicWordsetNamesByAppLocaleNames.English;
|
||||
//
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallet creation:
|
||||
//
|
||||
function NewlyCreatedWallet(mnemonic_wordsetName, nettype) {
|
||||
const seed = monero_utils.random_scalar(); // to generate a 32-byte (25-word) but reduced seed
|
||||
const mnemonicString = mnemonic.mn_encode(seed, mnemonic_wordsetName);
|
||||
const keys = monero_utils.create_address(seed, nettype);
|
||||
//
|
||||
return {
|
||||
seed: seed,
|
||||
mnemonicString: mnemonicString,
|
||||
keys: keys,
|
||||
};
|
||||
}
|
||||
exports.NewlyCreatedWallet = NewlyCreatedWallet;
|
||||
//
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallet login:
|
||||
//
|
||||
function MnemonicStringFromSeed(account_seed, mnemonic_wordsetName) {
|
||||
const mnemonicString = mnemonic.mn_encode(
|
||||
account_seed,
|
||||
mnemonic_wordsetName,
|
||||
);
|
||||
//
|
||||
return mnemonicString;
|
||||
}
|
||||
exports.MnemonicStringFromSeed = MnemonicStringFromSeed;
|
||||
//
|
||||
function SeedAndKeysFromMnemonic_sync(
|
||||
mnemonicString,
|
||||
mnemonic_wordsetName,
|
||||
nettype,
|
||||
) {
|
||||
// -> {err_str?, seed?, keys?}
|
||||
mnemonicString = mnemonicString.toLowerCase() || "";
|
||||
try {
|
||||
var seed = null;
|
||||
var keys = null;
|
||||
switch (mnemonic_wordsetName) {
|
||||
case "english":
|
||||
try {
|
||||
seed = mnemonic.mn_decode(mnemonicString);
|
||||
} catch (e) {
|
||||
// Try decoding as an electrum seed, on failure throw the original exception
|
||||
try {
|
||||
seed = mnemonic.mn_decode(mnemonicString, "electrum");
|
||||
} catch (ee) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
seed = mnemonic.mn_decode(mnemonicString, mnemonic_wordsetName);
|
||||
break;
|
||||
}
|
||||
if (seed === null) {
|
||||
return { err_str: "Unable to derive seed", seed: null, keys: null };
|
||||
}
|
||||
keys = monero_utils.create_address(seed, nettype);
|
||||
if (keys === null) {
|
||||
return {
|
||||
err_str: "Unable to derive keys from seed",
|
||||
seed: seed,
|
||||
keys: null,
|
||||
};
|
||||
}
|
||||
return { err_str: null, seed: seed, keys: keys };
|
||||
} catch (e) {
|
||||
console.error("Invalid mnemonic!");
|
||||
return {
|
||||
err_str: typeof e === "string" ? e : "" + e,
|
||||
seed: null,
|
||||
keys: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.SeedAndKeysFromMnemonic_sync = SeedAndKeysFromMnemonic_sync;
|
||||
|
||||
function SeedAndKeysFromMnemonic(
|
||||
mnemonicString,
|
||||
mnemonic_wordsetName,
|
||||
nettype,
|
||||
fn, // made available via callback not because it's async but for convenience
|
||||
) {
|
||||
// fn: (err?, seed?, keys?)
|
||||
const payload = SeedAndKeysFromMnemonic_sync(
|
||||
mnemonicString,
|
||||
mnemonic_wordsetName,
|
||||
nettype,
|
||||
);
|
||||
const err = payload.err_str ? new Error(payload.err_str) : null;
|
||||
const seed = payload.seed;
|
||||
const keys = payload.keys;
|
||||
fn(err, seed, keys);
|
||||
}
|
||||
exports.SeedAndKeysFromMnemonic = SeedAndKeysFromMnemonic;
|
||||
//
|
||||
function VerifiedComponentsForLogIn_sync(
|
||||
address,
|
||||
nettype,
|
||||
view_key,
|
||||
spend_key_orUndefinedForViewOnly,
|
||||
seed_orUndefined,
|
||||
wasAGeneratedWallet,
|
||||
) {
|
||||
var spend_key =
|
||||
typeof spend_key_orUndefinedForViewOnly !== "undefined" &&
|
||||
spend_key_orUndefinedForViewOnly != null &&
|
||||
spend_key_orUndefinedForViewOnly != ""
|
||||
? spend_key_orUndefinedForViewOnly
|
||||
: null;
|
||||
var isInViewOnlyMode = spend_key == null;
|
||||
if (
|
||||
!view_key ||
|
||||
view_key.length !== 64 ||
|
||||
(isInViewOnlyMode ? false : spend_key.length !== 64)
|
||||
) {
|
||||
return { err_str: "invalid secret key length" };
|
||||
}
|
||||
if (
|
||||
!monero_utils.valid_hex(view_key) ||
|
||||
(isInViewOnlyMode ? false : !monero_utils.valid_hex(spend_key))
|
||||
) {
|
||||
return { err_str: "invalid hex formatting" };
|
||||
}
|
||||
var public_keys;
|
||||
try {
|
||||
public_keys = monero_utils.decode_address(address, nettype);
|
||||
} catch (e) {
|
||||
return { err_str: "invalid address" };
|
||||
}
|
||||
var expected_view_pub;
|
||||
try {
|
||||
expected_view_pub = monero_utils.sec_key_to_pub(view_key);
|
||||
} catch (e) {
|
||||
return { err_str: "invalid view key" };
|
||||
}
|
||||
var expected_spend_pub;
|
||||
if (spend_key.length === 64) {
|
||||
try {
|
||||
expected_spend_pub = monero_utils.sec_key_to_pub(spend_key);
|
||||
} catch (e) {
|
||||
return { err_str: "invalid spend key" };
|
||||
}
|
||||
}
|
||||
if (public_keys.view !== expected_view_pub) {
|
||||
return { err_str: "invalid view key" };
|
||||
}
|
||||
if (!isInViewOnlyMode && public_keys.spend !== expected_spend_pub) {
|
||||
return { err_str: "invalid spend key" };
|
||||
}
|
||||
const private_keys = {
|
||||
view: view_key,
|
||||
spend: spend_key,
|
||||
};
|
||||
var account_seed = null; // default
|
||||
if (
|
||||
typeof seed_orUndefined !== "undefined" &&
|
||||
seed_orUndefined &&
|
||||
seed_orUndefined.length != 0
|
||||
) {
|
||||
var expected_account;
|
||||
try {
|
||||
expected_account = monero_utils.create_address(
|
||||
seed_orUndefined,
|
||||
nettype,
|
||||
);
|
||||
} catch (e) {
|
||||
return { err_str: "invalid seed" };
|
||||
}
|
||||
if (
|
||||
expected_account.view.sec !== view_key ||
|
||||
expected_account.spend.sec !== spend_key ||
|
||||
expected_account.public_addr !== address
|
||||
) {
|
||||
return { err_str: "invalid seed" };
|
||||
}
|
||||
account_seed = seed_orUndefined;
|
||||
}
|
||||
const payload = {
|
||||
err_str: null, // err
|
||||
address: address,
|
||||
account_seed: account_seed !== "" ? account_seed : null,
|
||||
public_keys: public_keys,
|
||||
private_keys: private_keys,
|
||||
isInViewOnlyMode: isInViewOnlyMode,
|
||||
};
|
||||
return payload;
|
||||
}
|
||||
exports.VerifiedComponentsForLogIn_sync = VerifiedComponentsForLogIn_sync;
|
||||
//
|
||||
function VerifiedComponentsForLogIn(
|
||||
address,
|
||||
nettype,
|
||||
view_key,
|
||||
spend_key_orUndefinedForViewOnly,
|
||||
seed_orUndefined,
|
||||
wasAGeneratedWallet,
|
||||
fn,
|
||||
) {
|
||||
// fn: (err?, address, account_seed, public_keys, private_keys, isInViewOnlyMode) -> Void
|
||||
const payload = VerifiedComponentsForLogIn_sync(
|
||||
address,
|
||||
nettype,
|
||||
view_key,
|
||||
spend_key_orUndefinedForViewOnly,
|
||||
seed_orUndefined,
|
||||
wasAGeneratedWallet,
|
||||
);
|
||||
fn(
|
||||
payload.err_str ? new Error(payload.err_str) : null,
|
||||
payload.address,
|
||||
payload.account_seed,
|
||||
payload.public_keys,
|
||||
payload.private_keys,
|
||||
payload.isInViewOnlyMode,
|
||||
);
|
||||
}
|
||||
exports.VerifiedComponentsForLogIn = VerifiedComponentsForLogIn;
|
@ -0,0 +1,615 @@
|
||||
declare namespace BigInteger {
|
||||
type ParsableValues =
|
||||
| BigInteger.BigInteger
|
||||
| number
|
||||
| string
|
||||
| Buffer
|
||||
| (number)[];
|
||||
|
||||
class BigInteger {
|
||||
/**
|
||||
* @description Constant: ZERO
|
||||
* <BigInteger> 0.
|
||||
* @static
|
||||
* @type {BigInteger}
|
||||
*/
|
||||
public static ZERO: BigInteger;
|
||||
|
||||
/**
|
||||
* @description Constant: ONE
|
||||
* <BigInteger> 1.
|
||||
*
|
||||
* @static
|
||||
* @type {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static ONE: BigInteger;
|
||||
|
||||
/**
|
||||
* @description Constant: M_ONE
|
||||
*<BigInteger> -1.
|
||||
*
|
||||
* @static
|
||||
* @type {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static M_ONE: BigInteger;
|
||||
/**
|
||||
* @description Constant: _0
|
||||
* Shortcut for <ZERO>.
|
||||
*
|
||||
* @static
|
||||
* @type {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static _0: BigInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Constant: _1
|
||||
* Shortcut for <ONE>.
|
||||
* @static
|
||||
* @type {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static _1: BigInteger;
|
||||
|
||||
/**
|
||||
* @description Constant: small
|
||||
* Array of <BigIntegers> from 0 to 36.
|
||||
* These are used internally for parsing, but useful when you need a "small"
|
||||
* <BigInteger>.
|
||||
* @see <ZERO>, <ONE>, <_0>, <_1>
|
||||
* @static
|
||||
* @type {[
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger,
|
||||
* BigInteger ]}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static small: [
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
/* Assuming BigInteger_base > 36 */
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger,
|
||||
BigInteger
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Used for parsing/radix conversion
|
||||
* @static
|
||||
* @type {[
|
||||
* "0",
|
||||
* "1",
|
||||
* "2",
|
||||
* "3",
|
||||
* "4",
|
||||
* "5",
|
||||
* "6",
|
||||
* "7",
|
||||
* "8",
|
||||
* "9",
|
||||
* "A",
|
||||
* "B",
|
||||
* "C",
|
||||
* "D",
|
||||
* "E",
|
||||
* "F",
|
||||
* "G",
|
||||
* "H",
|
||||
* "I",
|
||||
* "J",
|
||||
* "K",
|
||||
* "L",
|
||||
* "M",
|
||||
* "N",
|
||||
* "O",
|
||||
* "P",
|
||||
* "Q",
|
||||
* "R",
|
||||
* "S",
|
||||
* "T",
|
||||
* "U",
|
||||
* "V",
|
||||
* "W",
|
||||
* "X",
|
||||
* "Y",
|
||||
* "Z"
|
||||
* ]}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static digits: [
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
];
|
||||
/**
|
||||
* @description
|
||||
* Convert a value to a <BigInteger>.
|
||||
*
|
||||
* Although <BigInteger()> is the constructor for <BigInteger> objects, it is
|
||||
* best not to call it as a constructor. If *n* is a <BigInteger> object, it is
|
||||
* simply returned as-is. Otherwise, <BigInteger()> is equivalent to <parse>
|
||||
* without a radix argument.
|
||||
*
|
||||
* > var n0 = BigInteger(); // Same as <BigInteger.ZERO>
|
||||
* > var n1 = BigInteger("123"); // Create a new <BigInteger> with value 123
|
||||
* > var n2 = BigInteger(123); // Create a new <BigInteger> with value 123
|
||||
* > var n3 = BigInteger(n2); // Return n2, unchanged
|
||||
*
|
||||
* The constructor form only takes an array and a sign. *n* must be an
|
||||
* array of numbers in little-endian order, where each digit is between 0
|
||||
* and BigInteger.base. The second parameter sets the sign: -1 for
|
||||
* negative, +1 for positive, or 0 for zero. The array is *not copied and
|
||||
* may be modified*. If the array contains only zeros, the sign parameter
|
||||
* is ignored and is forced to zero.
|
||||
*
|
||||
* > new BigInteger([5], -1): create a new BigInteger with value -5
|
||||
* @param {ParsableValues} n Value to convert to a <BigInteger>.
|
||||
* @see parse, BigInteger
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
constructor(n: ParsableValues, sign?: 0 | -1 | 1);
|
||||
|
||||
/**
|
||||
* @description Convert a <BigInteger> to a string.
|
||||
*
|
||||
* When *base* is greater than 10, letters are upper case.
|
||||
* @param {number} [base] Optional base to represent the number in (default is base 10). Must be between 2 and 36 inclusive, or an Error will be thrown
|
||||
* @returns {string} The string representation of the <BigInteger>.
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
toString(base?: number): string;
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Function: parse
|
||||
* Parse a string into a <BigInteger>.
|
||||
*
|
||||
* *base* is optional but, if provided, must be from 2 to 36 inclusive. If
|
||||
* *base* is not provided, it will be guessed based on the leading characters
|
||||
* of *s* as follows:
|
||||
*
|
||||
* - "0x" or "0X": *base* = 16
|
||||
* - "0c" or "0C": *base* = 8
|
||||
* - "0b" or "0B": *base* = 2
|
||||
* - else: *base* = 10
|
||||
*
|
||||
* If no base is provided, or *base* is 10, the number can be in exponential
|
||||
* form. For example, these are all valid:
|
||||
*
|
||||
* > BigInteger.parse("1e9"); // Same as "1000000000"
|
||||
* > BigInteger.parse("1.234*10^3"); // Same as 1234
|
||||
* > BigInteger.parse("56789 * 10 ** -2"); // Same as 567
|
||||
*
|
||||
* If any characters fall outside the range defined by the radix, an exception
|
||||
* will be thrown.
|
||||
*
|
||||
* @param {string} s the string to parse.
|
||||
* @param {number} [base] Optional radix (default is to guess based on *s*).
|
||||
* @returns {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
static parse(s: string, base?: number): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Add two <BigIntegers>.
|
||||
* @param {ParsableValues} n The number to add to *this*. Will be converted to a <BigInteger>.
|
||||
* @returns {BigInteger} The numbers added together.
|
||||
* @see <subtract>,<multiply>,<quotient>,<next>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
add(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get the additive inverse of a <BigInteger>.
|
||||
* @returns {BigInteger} A <BigInteger> with the same magnatude, but with the opposite sign.
|
||||
* @see <abs>
|
||||
* @memberof BigInteger
|
||||
*
|
||||
*/
|
||||
negate(): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Get the absolute value of a <BigInteger>.
|
||||
* @returns {BigInteger} A <BigInteger> with the same magnatude, but always positive (or zero).
|
||||
* @see <negate>
|
||||
* @memberof BigInteger
|
||||
*
|
||||
*/
|
||||
abs(): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Subtract two <BigIntegers>.
|
||||
*
|
||||
* @param {ParsableValues} n The number to subtract from *this*. Will be converted to a <BigInteger>.
|
||||
* @returns {BigInteger} The *n* subtracted from *this*.
|
||||
* @see <add>, <multiply>, <quotient>, <prev>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
subtract(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Get the next <BigInteger> (add one).
|
||||
*
|
||||
* @returns {BigInteger} *this* + 1.
|
||||
* @see <add>, <prev>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
next(): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Get the previous <BigInteger> (subtract one).
|
||||
*
|
||||
* @returns {BigInteger} *this* - 1.
|
||||
* @see <next>, <subtract>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
prev(): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Compare the absolute value of two <BigIntegers>.
|
||||
*
|
||||
* Calling <compareAbs> is faster than calling <abs> twice, then <compare>.
|
||||
* @param {ParsableValues} n The number to compare to *this*. Will be converted to a <BigInteger>.
|
||||
* @returns {number} -1, 0, or +1 if *|this|* is less than, equal to, or greater than *|n|*.
|
||||
* @see <compare>, <abs>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
compareAbs(n: ParsableValues): 1 | 0 | -1;
|
||||
|
||||
/**
|
||||
* @description Compare two <BigIntegers>.
|
||||
*
|
||||
* @param {ParsableValues} n The number to compare to *this*. Will be converted to a <BigInteger>.
|
||||
* @returns {(1 | 0 | -1)} -1, 0, or +1 if *this* is less than, equal to, or greater than *n*.
|
||||
* @see <compareAbs>, <isPositive>, <isNegative>, <isUnit>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
compare(n: ParsableValues): 1 | 0 | -1;
|
||||
|
||||
/**
|
||||
* @description Return true iff *this* is either 1 or -1.
|
||||
*
|
||||
* @returns {boolean} true if *this* compares equal to <BigInteger.ONE> or <BigInteger.M_ONE>.
|
||||
* @see <isZero>, <isNegative>, <isPositive>, <compareAbs>, <compare>,
|
||||
* <BigInteger.ONE>, <BigInteger.M_ONE>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
isUnit(): boolean;
|
||||
|
||||
/**
|
||||
* @description Multiply two <BigIntegers>.
|
||||
*
|
||||
* @param {ParsableValues} n The number to multiply *this* by. Will be converted to a <BigInteger>.
|
||||
* @returns {BigInteger} The numbers multiplied together.
|
||||
* @see <add>, <subtract>, <quotient>, <square>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
multiply(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Multiply a <BigInteger> by itself.
|
||||
* This is slightly faster than regular multiplication, since it removes the
|
||||
* duplicated multiplcations.
|
||||
* @returns {BigInteger} > this.multiply(this)
|
||||
* @see <multiply>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
square(): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Divide two <BigIntegers> and truncate towards zero.
|
||||
*
|
||||
* <quotient> throws an exception if *n* is zero.
|
||||
* @param {ParsableValues} n The number to divide *this* by. Will be converted to a <BigInteger>.
|
||||
* @returns {BigInteger} The *this* / *n*, truncated to an integer.
|
||||
* @see <add>, <subtract>, <multiply>, <divRem>, <remainder>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
quotient(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Deprecated synonym for <quotient>.
|
||||
* @param {ParsableValues} n
|
||||
* @returns {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
divide(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Calculate the remainder of two <BigIntegers>.
|
||||
*
|
||||
* <remainder> throws an exception if *n* is zero.
|
||||
*
|
||||
* @param {ParsableValues} n The remainder after *this* is divided *this* by *n*. Will be converted to a <BigInteger>.
|
||||
* @returns {BigInteger}*this* % *n*.
|
||||
* @see <divRem>, <quotient>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
remainder(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Calculate the integer quotient and remainder of two <BigIntegers>.
|
||||
*
|
||||
* <divRem> throws an exception if *n* is zero.
|
||||
*
|
||||
* @param {ParsableValues} n The number to divide *this* by. Will be converted to a <BigInteger>.
|
||||
* @returns {[BigInteger, BigInteger]} A two-element array containing the quotient and the remainder.
|
||||
*
|
||||
* > a.divRem(b)
|
||||
*
|
||||
* is exactly equivalent to
|
||||
*
|
||||
* > [a.quotient(b), a.remainder(b)]
|
||||
*
|
||||
* except it is faster, because they are calculated at the same time.
|
||||
* @see <quotient>, <remainder>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
divRem(n: ParsableValues): [BigInteger, BigInteger];
|
||||
|
||||
/**
|
||||
* @description Return true iff *this* is divisible by two.
|
||||
*
|
||||
* Note that <BigInteger.ZERO> is even.
|
||||
* @returns {boolean} true if *this* is even, false otherwise.
|
||||
* @see <isOdd>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
isEven(): boolean;
|
||||
|
||||
/**
|
||||
* @description Return true iff *this* is not divisible by two.
|
||||
*
|
||||
* @returns {boolean} true if *this* is odd, false otherwise.
|
||||
* @see <isEven>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
isOdd(): boolean;
|
||||
|
||||
/**
|
||||
* @description Get the sign of a <BigInteger>.
|
||||
*
|
||||
* @returns {(-1 | 0 | 1)} * -1 if *this* < 0
|
||||
* * 0 if *this* == 0
|
||||
* * +1 if *this* > 0
|
||||
* @see <isZero>, <isPositive>, <isNegative>, <compare>, <BigInteger.ZERO>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
sign(): -1 | 0 | 1;
|
||||
|
||||
/**
|
||||
* @description Return true iff *this* > 0.
|
||||
*
|
||||
* @returns {boolean} true if *this*.compare(<BigInteger.ZERO>) == 1.
|
||||
* @see <sign>, <isZero>, <isNegative>, <isUnit>, <compare>, <BigInteger.ZERO>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
isPositive(): boolean;
|
||||
|
||||
/**
|
||||
* @description Return true iff *this* < 0.
|
||||
*
|
||||
* @returns {boolean} true if *this*.compare(<BigInteger.ZERO>) == -1.
|
||||
* @see <sign>, <isPositive>, <isZero>, <isUnit>, <compare>, <BigInteger.ZERO>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
isNegative(): boolean;
|
||||
|
||||
/**
|
||||
* @description Return true iff *this* == 0.
|
||||
*
|
||||
* @returns {boolean} true if *this*.compare(<BigInteger.ZERO>) == 0.
|
||||
* @see <sign>, <isPositive>, <isNegative>, <isUnit>, <BigInteger.ZERO>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
isZero(): boolean;
|
||||
|
||||
/**
|
||||
* @description Multiply a <BigInteger> by a power of 10.
|
||||
*
|
||||
* This is equivalent to, but faster than
|
||||
*
|
||||
* > if (n >= 0) {
|
||||
* > return this.multiply(BigInteger("1e" + n));
|
||||
* > }
|
||||
* > else { // n <= 0
|
||||
* > return this.quotient(BigInteger("1e" + -n));
|
||||
* > }
|
||||
*
|
||||
* @param {ParsableValues} n The power of 10 to multiply *this* by. *n* is converted to a
|
||||
javascipt number and must be no greater than <BigInteger.MAX_EXP>
|
||||
(0x7FFFFFFF), or an exception will be thrown.
|
||||
* @returns {BigInteger} *this* * (10 ** *n*), truncated to an integer if necessary.
|
||||
* @see <pow>, <multiply>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
exp10(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Raise a <BigInteger> to a power.
|
||||
*
|
||||
* In this implementation, 0**0 is 1.
|
||||
*
|
||||
* @param {ParsableValues} n The exponent to raise *this* by. *n* must be no greater than
|
||||
* <BigInteger.MAX_EXP> (0x7FFFFFFF), or an exception will be thrown.
|
||||
* @returns {BigInteger} *this* raised to the *nth* power.
|
||||
* @see <modPow>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
pow(n: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Raise a <BigInteger> to a power (mod m).
|
||||
*
|
||||
* Because it is reduced by a modulus, <modPow> is not limited by
|
||||
* <BigInteger.MAX_EXP> like <pow>.
|
||||
*
|
||||
* @param {BigInteger} exponent The exponent to raise *this* by. Must be positive.
|
||||
* @param {ParsableValues} modulus The modulus.
|
||||
* @returns {BigInteger} *this* ^ *exponent* (mod *modulus*).
|
||||
* @see <pow>, <mod>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
modPow(exponent: BigInteger, modulus: ParsableValues): BigInteger;
|
||||
|
||||
/**
|
||||
* @description Get the natural logarithm of a <BigInteger> as a native JavaScript number.
|
||||
*
|
||||
* This is equivalent to
|
||||
*
|
||||
* > Math.log(this.toJSValue())
|
||||
*
|
||||
* but handles values outside of the native number range.
|
||||
*
|
||||
* @returns {number} log( *this* )
|
||||
* @see <toJSValue>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
log(): number;
|
||||
|
||||
/**
|
||||
* @description Convert a <BigInteger> to a native JavaScript integer.
|
||||
*
|
||||
* This is called automatically by JavaScipt to convert a <BigInteger> to a
|
||||
* native value.
|
||||
* @returns {number} > parseInt(this.toString(), 10)
|
||||
* @see <toString>, <toJSValue>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
valueOf(): number;
|
||||
|
||||
/**
|
||||
* @description Convert a <BigInteger> to a native JavaScript integer.
|
||||
*
|
||||
* This is the same as valueOf, but more explicitly named.
|
||||
* @returns {number} > parseInt(this.toString(), 10)
|
||||
* @see <toString>, <valueOf>
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
toJSValue(): number;
|
||||
|
||||
/**
|
||||
* @description Get the first byte of the Biginteger
|
||||
*
|
||||
* @returns {number} the first byte of the Biginteger
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
lowVal(): number;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Constant: MAX_EXP The largest exponent allowed in <pow> and <exp10> (0x7FFFFFFF or 2147483647).
|
||||
* @static
|
||||
* @type {BigInteger}
|
||||
* @memberof BigInteger
|
||||
*/
|
||||
public static MAX_EXP: BigInteger;
|
||||
}
|
||||
}
|
||||
|
||||
export = BigInteger;
|
@ -0,0 +1,4 @@
|
||||
import BigInteger = require("./biginteger");
|
||||
|
||||
export const BigInt = BigInteger.BigInteger;
|
||||
export type BigInt = BigInteger.BigInteger;
|
@ -0,0 +1,21 @@
|
||||
declare module "keccakjs" {
|
||||
type Message = Buffer | string;
|
||||
|
||||
class Hasher {
|
||||
constructor(bitlength: number);
|
||||
|
||||
/**
|
||||
* Update hash
|
||||
*
|
||||
* @param message The message you want to hash.
|
||||
*/
|
||||
update(message: Message): Hasher;
|
||||
|
||||
/**
|
||||
* Return hash in integer array.
|
||||
*/
|
||||
digest(encoding?: "hex" | "binary"): string;
|
||||
}
|
||||
|
||||
export = Hasher;
|
||||
}
|
@ -0,0 +1 @@
|
||||
export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
@ -0,0 +1,123 @@
|
||||
import { encode_varint } from "xmr-varint";
|
||||
import { cn_fast_hash } from "xmr-fast-hash";
|
||||
import { sc_reduce32 } from "xmr-crypto-ops/primitive_ops";
|
||||
import {
|
||||
cryptonoteBase58PrefixForSubAddressOn,
|
||||
cryptonoteBase58PrefixForIntegratedAddressOn,
|
||||
cryptonoteBase58PrefixForStandardAddressOn,
|
||||
} from "xmr-nettype-address-prefixes";
|
||||
import { cnBase58 } from "xmr-b58/xmr-base58";
|
||||
import { generate_keys, pubkeys_to_string } from "xmr-key-utils";
|
||||
import {
|
||||
INTEGRATED_ID_SIZE,
|
||||
ADDRESS_CHECKSUM_SIZE,
|
||||
} from "xmr-constants/address";
|
||||
import { Account } from "./types";
|
||||
import { NetType } from "xmr-types";
|
||||
|
||||
export function is_subaddress(addr: string, nettype: NetType) {
|
||||
const decoded = cnBase58.decode(addr);
|
||||
const subaddressPrefix = encode_varint(
|
||||
cryptonoteBase58PrefixForSubAddressOn(nettype),
|
||||
);
|
||||
const prefix = decoded.slice(0, subaddressPrefix.length);
|
||||
return prefix === subaddressPrefix;
|
||||
}
|
||||
|
||||
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(
|
||||
cryptonoteBase58PrefixForStandardAddressOn(nettype),
|
||||
);
|
||||
const expectedPrefixInt = encode_varint(
|
||||
cryptonoteBase58PrefixForIntegratedAddressOn(nettype),
|
||||
);
|
||||
const expectedPrefixSub = encode_varint(
|
||||
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 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(
|
||||
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);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { KeyPair } from "xmr-types";
|
||||
|
||||
export interface Account {
|
||||
spend: KeyPair;
|
||||
view: KeyPair;
|
||||
public_addr: string;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import { cnBase58 } from "./xmr-base58";
|
||||
const { decode, encode } = cnBase58;
|
||||
export { decode, encode };
|
@ -0,0 +1,21 @@
|
||||
declare namespace cn58 {
|
||||
namespace cnBase58 {
|
||||
/**
|
||||
*
|
||||
* @description Encodes a hex string into base 58 string
|
||||
* @param {string} hexStr hex string
|
||||
* @returns {string} base 58 string
|
||||
*/
|
||||
function encode(hexStr: string): string;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Decodes a base58 string to a hex string
|
||||
* @param {string} str base58 string
|
||||
* @returns {string} hex string
|
||||
*/
|
||||
function decode(str: string): string;
|
||||
}
|
||||
}
|
||||
|
||||
export = cn58;
|
@ -0,0 +1,2 @@
|
||||
export const ADDRESS_CHECKSUM_SIZE = 4;
|
||||
export const INTEGRATED_ID_SIZE = 8;
|
@ -0,0 +1,103 @@
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
export 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
|
||||
};
|
||||
|
||||
export const KEY_SIZE = 32;
|
||||
|
||||
export const HASH_SIZE = 32;
|
||||
|
||||
//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",
|
||||
];
|
@ -0,0 +1,88 @@
|
||||
import CNCrypto = require("xmr-vendor/cn_crypto");
|
||||
import nacl = require("xmr-vendor/fast_cn");
|
||||
import { STRUCT_SIZES, KEY_SIZE } from "./constants";
|
||||
import { encode_varint } from "xmr-varint";
|
||||
import { hash_to_scalar } from "./hash_ops";
|
||||
import { ge_scalarmult, ge_scalarmult_base, ge_sub } from "./primitive_ops";
|
||||
import { d2s } from "xmr-str-utils/integer-strings";
|
||||
import { hextobin, bintohex } from "xmr-str-utils/hex-strings";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export 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);
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
import CNCrypto = require("xmr-vendor/cn_crypto");
|
||||
import { cn_fast_hash } from "xmr-fast-hash";
|
||||
import { STRUCT_SIZES, KEY_SIZE, HASH_SIZE } from "./constants";
|
||||
import { bintohex, hextobin } from "xmr-str-utils/hex-strings";
|
||||
import { sc_reduce32 } from "./primitive_ops";
|
||||
|
||||
export function hash_to_scalar(buf: string) {
|
||||
const hash = cn_fast_hash(buf);
|
||||
const scalar = sc_reduce32(hash);
|
||||
return scalar;
|
||||
}
|
||||
|
||||
export 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;
|
||||
export 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 array_hash_to_scalar(array: string[]) {
|
||||
let buf = "";
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
buf += array[i];
|
||||
}
|
||||
return hash_to_scalar(buf);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import * as constants from "./constants";
|
||||
import * as derivation from "./derivation";
|
||||
import * as hash_ops from "./hash_ops";
|
||||
import * as key_image from "./key_image";
|
||||
import * as primitive_ops from "./primitive_ops";
|
||||
import * as rctOps from "./rct";
|
||||
|
||||
export { constants, derivation, hash_ops, key_image, primitive_ops, rctOps };
|
@ -0,0 +1,127 @@
|
||||
import {
|
||||
generate_key_derivation,
|
||||
derive_public_key,
|
||||
derive_secret_key,
|
||||
derivation_to_scalar,
|
||||
} from "./derivation";
|
||||
import CNCrypto = require("xmr-vendor/cn_crypto");
|
||||
import { KEY_SIZE, STRUCT_SIZES, I } from "./constants";
|
||||
import { hextobin, bintohex } from "xmr-str-utils/hex-strings";
|
||||
import { hash_to_ec, hash_to_scalar } from "./hash_ops";
|
||||
import { sc_sub } from "./primitive_ops";
|
||||
import { Keys } from "xmr-types";
|
||||
|
||||
export function derive_key_image_from_tx(
|
||||
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(ephemeral_pub, ephemeral_sec);
|
||||
return {
|
||||
ephemeral_pub: ephemeral_pub,
|
||||
key_image: k_image,
|
||||
};
|
||||
}
|
||||
|
||||
export function generate_key_image(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_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(ephemeral_pub, ephemeral_sec);
|
||||
return {
|
||||
in_ephemeral: {
|
||||
pub: ephemeral_pub,
|
||||
sec: ephemeral_sec,
|
||||
mask: mask,
|
||||
},
|
||||
key_image,
|
||||
};
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
import CNCrypto = require("xmr-vendor/cn_crypto");
|
||||
import nacl = require("xmr-vendor/fast_cn");
|
||||
import { bintohex, hextobin, valid_hex } from "xmr-str-utils/hex-strings";
|
||||
import { STRUCT_SIZES, KEY_SIZE } from "./constants";
|
||||
import { hash_to_ec_2 } from "./hash_ops";
|
||||
|
||||
//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
|
||||
export 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
|
||||
export 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
|
||||
export 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);
|
||||
}
|
||||
|
||||
export 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function ge_scalarmult_base(sec: string) {
|
||||
if (sec.length !== 64) {
|
||||
throw Error("Invalid sec length");
|
||||
}
|
||||
return bintohex(nacl.ge_scalarmult_base(hextobin(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)));
|
||||
}
|
||||
|
||||
export 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);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import {
|
||||
ge_double_scalarmult_base_vartime,
|
||||
sc_sub,
|
||||
sc_add,
|
||||
} from "./primitive_ops";
|
||||
import { H, I } from "./constants";
|
||||
import { valid_hex } from "xmr-str-utils/hex-strings";
|
||||
import { hash_to_scalar } from "./hash_ops";
|
||||
import { Commit } from "xmr-types";
|
||||
|
||||
//creates a Pedersen commitment from an amount (in scalar form) and a mask
|
||||
//C = bG + aH where b = mask, a = amount
|
||||
export 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;
|
||||
}
|
||||
|
||||
export function zeroCommit(amount: string) {
|
||||
return commit(amount, I);
|
||||
}
|
||||
|
||||
export function decode_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_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),
|
||||
};
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
import {
|
||||
Device,
|
||||
IAccountKeys,
|
||||
PublicAddress,
|
||||
DeviceMode,
|
||||
PublicKey,
|
||||
KeyDerivation,
|
||||
ISubaddressIndex,
|
||||
PublicSpendKey,
|
||||
SecretKey,
|
||||
Key,
|
||||
EcScalar,
|
||||
Hash8,
|
||||
KeyV,
|
||||
CtKeyV,
|
||||
} from "./types";
|
||||
import * as crypto from "xmr-crypto-ops";
|
||||
import {
|
||||
secret_key_to_public_key,
|
||||
generate_keys,
|
||||
random_keypair,
|
||||
} from "xmr-key-utils";
|
||||
import { KeyPair, Commit } from "xmr-types";
|
||||
import { encrypt_payment_id } from "xmr-pid";
|
||||
import { encode_ecdh, decode_ecdh } from "xmr-crypto-ops/rct";
|
||||
import { cn_fast_hash } from "xmr-fast-hash";
|
||||
import { sc_mulsub } from "xmr-crypto-ops/primitive_ops";
|
||||
|
||||
export class DefaultDevice implements Device {
|
||||
private name: string;
|
||||
|
||||
constructor() {
|
||||
this.name = "";
|
||||
}
|
||||
|
||||
/* ======================================================================= */
|
||||
/* SETUP/TEARDOWN */
|
||||
/* ======================================================================= */
|
||||
// #region SETUP/TEARDOWN
|
||||
|
||||
public set_name(name: string) {
|
||||
this.name = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
public get_name() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public async set_mode(_mode: DeviceMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// #endregion SETUP/TEARDOWN
|
||||
|
||||
/* ======================================================================= */
|
||||
/* WALLET & ADDRESS */
|
||||
/* ======================================================================= */
|
||||
// #region WALLET & ADDRESS
|
||||
|
||||
public async get_public_address(): Promise<PublicAddress> {
|
||||
return this.notSupported();
|
||||
}
|
||||
|
||||
public async get_secret_keys() {
|
||||
return this.notSupported();
|
||||
}
|
||||
|
||||
public async generate_chacha_key(_keys: IAccountKeys) {
|
||||
return this.notSupported();
|
||||
}
|
||||
|
||||
// #endregion WALLET & ADDRESS
|
||||
/* ======================================================================= */
|
||||
/* SUB ADDRESS */
|
||||
/* ======================================================================= */
|
||||
// #region SUB ADDRESS
|
||||
public async derive_subaddress_public_key(
|
||||
out_key: PublicKey,
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
): Promise<PublicKey> {
|
||||
return crypto.derivation.derive_subaddress_public_key(
|
||||
out_key,
|
||||
derivation,
|
||||
output_index,
|
||||
);
|
||||
}
|
||||
|
||||
public async get_subaddress_spend_public_key(
|
||||
keys: IAccountKeys,
|
||||
index: ISubaddressIndex,
|
||||
): Promise<PublicKey> {
|
||||
if (index.isZero()) {
|
||||
return keys.m_account_address.spend_public_key;
|
||||
}
|
||||
return this.notSupported();
|
||||
}
|
||||
|
||||
public async get_subaddress_spend_public_keys(
|
||||
_keys: IAccountKeys,
|
||||
_account: number,
|
||||
_begin: number,
|
||||
_end: number,
|
||||
): Promise<PublicSpendKey[]> {
|
||||
return this.notSupported();
|
||||
}
|
||||
|
||||
public async get_subaddress(
|
||||
keys: IAccountKeys,
|
||||
index: ISubaddressIndex,
|
||||
): Promise<PublicAddress> {
|
||||
if (index.isZero()) {
|
||||
return keys.m_account_address;
|
||||
}
|
||||
|
||||
return this.notSupported();
|
||||
}
|
||||
|
||||
public async get_subaddress_secret_key(
|
||||
_sec: SecretKey,
|
||||
_index: ISubaddressIndex,
|
||||
): Promise<SecretKey> {
|
||||
return this.notSupported();
|
||||
}
|
||||
/* ======================================================================= */
|
||||
/* DERIVATION & KEY */
|
||||
/* ======================================================================= */
|
||||
// #region DERIVATION & KEY
|
||||
|
||||
public async verify_keys(
|
||||
secretKey: SecretKey,
|
||||
publicKey: PublicKey,
|
||||
): Promise<boolean> {
|
||||
const calculatedPubKey = secret_key_to_public_key(secretKey);
|
||||
return calculatedPubKey === publicKey;
|
||||
}
|
||||
|
||||
public async scalarmultKey(P: Key, a: Key): Promise<Key> {
|
||||
return crypto.primitive_ops.ge_scalarmult(P, a);
|
||||
}
|
||||
|
||||
public async scalarmultBase(a: Key): Promise<Key> {
|
||||
return crypto.primitive_ops.ge_scalarmult_base(a);
|
||||
}
|
||||
|
||||
public async sc_secret_add(a: SecretKey, b: SecretKey): Promise<SecretKey> {
|
||||
return crypto.primitive_ops.sc_add(a, b);
|
||||
}
|
||||
|
||||
public async generate_keys(
|
||||
recoveryKey: SecretKey | undefined,
|
||||
): Promise<KeyPair> {
|
||||
if (recoveryKey) {
|
||||
return generate_keys(recoveryKey);
|
||||
}
|
||||
return random_keypair();
|
||||
}
|
||||
|
||||
public async generate_key_derivation(
|
||||
pub: PublicKey,
|
||||
sec: SecretKey,
|
||||
): Promise<KeyDerivation> {
|
||||
return crypto.derivation.generate_key_derivation(pub, sec);
|
||||
}
|
||||
|
||||
public async conceal_derivation(
|
||||
derivation: KeyDerivation,
|
||||
_tx_pub_key: PublicKey,
|
||||
_additional_tx_pub_keys: PublicKey[],
|
||||
_main_derivation: KeyDerivation,
|
||||
_additional_derivations: KeyDerivation[],
|
||||
): Promise<PublicKey> {
|
||||
return derivation;
|
||||
}
|
||||
|
||||
public async derivation_to_scalar(
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
): Promise<EcScalar> {
|
||||
return crypto.derivation.derivation_to_scalar(derivation, output_index);
|
||||
}
|
||||
|
||||
public async derive_secret_key(
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
sec: SecretKey,
|
||||
): Promise<SecretKey> {
|
||||
return crypto.derivation.derive_secret_key(
|
||||
derivation,
|
||||
output_index,
|
||||
sec,
|
||||
);
|
||||
}
|
||||
|
||||
public async derive_public_key(
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
pub: PublicKey,
|
||||
): Promise<PublicKey> {
|
||||
return crypto.derivation.derive_public_key(
|
||||
derivation,
|
||||
output_index,
|
||||
pub,
|
||||
);
|
||||
}
|
||||
|
||||
public async generate_key_image(
|
||||
pub: PublicKey,
|
||||
sec: SecretKey,
|
||||
): Promise<PublicKey> {
|
||||
return crypto.key_image.generate_key_image(pub, sec);
|
||||
}
|
||||
|
||||
public async secret_key_to_public_key(sec: SecretKey): Promise<PublicKey> {
|
||||
return secret_key_to_public_key(sec);
|
||||
}
|
||||
|
||||
/* ======================================================================= */
|
||||
/* TRANSACTION */
|
||||
/* ======================================================================= */
|
||||
// #region TRANSACTION
|
||||
|
||||
public async open_tx(): Promise<SecretKey> {
|
||||
const { sec } = random_keypair();
|
||||
return sec;
|
||||
}
|
||||
|
||||
public async encrypt_payment_id(
|
||||
paymentId: string,
|
||||
public_key: string,
|
||||
secret_key: string,
|
||||
): Promise<Hash8> {
|
||||
return encrypt_payment_id(paymentId, public_key, secret_key);
|
||||
}
|
||||
|
||||
public async decrypt_payment_id(
|
||||
paymentId: string,
|
||||
public_key: string,
|
||||
secret_key: string,
|
||||
): Promise<Hash8> {
|
||||
return this.encrypt_payment_id(paymentId, public_key, secret_key);
|
||||
}
|
||||
|
||||
public async ecdhEncode(
|
||||
unmasked: Commit,
|
||||
sharedSec: SecretKey,
|
||||
): Promise<Commit> {
|
||||
return encode_ecdh(unmasked, sharedSec);
|
||||
}
|
||||
|
||||
public async ecdhDecode(
|
||||
masked: Commit,
|
||||
sharedSec: SecretKey,
|
||||
): Promise<Commit> {
|
||||
return decode_ecdh(masked, sharedSec);
|
||||
}
|
||||
|
||||
public add_output_key_mapping(
|
||||
_Aout: PublicKey,
|
||||
_Bout: PublicKey,
|
||||
_is_subaddress: boolean,
|
||||
_real_output_index: number,
|
||||
_amount_key: Key,
|
||||
_out_eph_public_key: PublicKey,
|
||||
): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async mlsag_prehash(
|
||||
_blob: string,
|
||||
_inputs_size: number,
|
||||
_outputs_size: number,
|
||||
hashes: KeyV,
|
||||
_outPk: CtKeyV,
|
||||
): Promise<Key> {
|
||||
return cn_fast_hash(hashes.join(""));
|
||||
}
|
||||
|
||||
public async mlsag_prepare(
|
||||
H: Key,
|
||||
xx: Key,
|
||||
): Promise<{ a: Key; aG: Key; aHP: Key; II: Key }>;
|
||||
|
||||
public async mlsag_prepare(): Promise<{ a: Key; aG: Key }>;
|
||||
|
||||
public async mlsag_prepare(H?: Key, xx?: Key) {
|
||||
const { sec: a, pub: aG } = random_keypair();
|
||||
|
||||
if (H && xx) {
|
||||
const aHP = await this.scalarmultKey(H, a);
|
||||
const II = await this.scalarmultKey(H, xx);
|
||||
return { a, aG, aHP, II };
|
||||
} else {
|
||||
return { a, aG };
|
||||
}
|
||||
}
|
||||
|
||||
public async mlsag_hash(toHash: KeyV): Promise<Key> {
|
||||
return cn_fast_hash(toHash.join(""));
|
||||
}
|
||||
|
||||
public async mlsag_sign(
|
||||
c: Key,
|
||||
xx: KeyV,
|
||||
alpha: KeyV,
|
||||
rows: number,
|
||||
dsRows: number,
|
||||
ss: KeyV,
|
||||
): Promise<KeyV> {
|
||||
if (dsRows > rows) {
|
||||
throw Error("dsRows greater than rows");
|
||||
}
|
||||
if (xx.length !== rows) {
|
||||
throw Error("xx size does not match rows");
|
||||
}
|
||||
if (alpha.length !== rows) {
|
||||
throw Error("alpha size does not match rows");
|
||||
}
|
||||
if (ss.length !== rows) {
|
||||
throw Error("ss size does not match rows");
|
||||
}
|
||||
|
||||
for (let j = 0; j < rows; j++) {
|
||||
ss[j] = sc_mulsub(c, xx[j], alpha[j]);
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
public async close_tx(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private notSupported(): any {
|
||||
throw Error("This device function is not supported");
|
||||
}
|
||||
}
|
@ -0,0 +1,487 @@
|
||||
import { KeyPair, Commit } from "xmr-types";
|
||||
|
||||
export enum DeviceMode {
|
||||
TRANSACTION_CREATE_REAL,
|
||||
TRANSACTION_CREATE_FAKE,
|
||||
TRANSACTION_PARSE,
|
||||
NONE,
|
||||
}
|
||||
|
||||
// to later be converted to opaque types
|
||||
export type EcScalar = string;
|
||||
export type EcPoint = string;
|
||||
|
||||
export type SecretKey = EcScalar;
|
||||
export type PublicKey = EcPoint;
|
||||
|
||||
type SecretKeys = [SecretKey, SecretKey];
|
||||
|
||||
export type KeyDerivation = EcPoint;
|
||||
|
||||
type ChachaKey = string;
|
||||
export type PublicSpendKey = PublicKey;
|
||||
|
||||
export interface PublicAddress {
|
||||
view_public_key: PublicKey;
|
||||
spend_public_key: PublicKey;
|
||||
}
|
||||
|
||||
interface CtKey {
|
||||
dest: string;
|
||||
mask: string;
|
||||
}
|
||||
|
||||
export type CtKeyV = CtKey[];
|
||||
|
||||
export type Key = PublicKey | SecretKey;
|
||||
export type KeyV = Key[];
|
||||
|
||||
export type Hash8 = string; // 8 bytes payment id
|
||||
// cryptonote_basic
|
||||
export interface IAccountKeys {
|
||||
m_account_address: PublicAddress;
|
||||
m_spend_secret_key: string;
|
||||
m_view_secret_key: string;
|
||||
m_device: Device;
|
||||
}
|
||||
|
||||
export interface ISubaddressIndex {
|
||||
major: number; // 32 bits
|
||||
minor: number; // 32 bits
|
||||
isZero(): boolean;
|
||||
}
|
||||
|
||||
// device.hpp
|
||||
|
||||
export interface Device {
|
||||
/* ======================================================================= */
|
||||
/* SETUP/TEARDOWN */
|
||||
/* ======================================================================= */
|
||||
set_name(name: string): boolean;
|
||||
get_name(): string;
|
||||
set_mode(mode: DeviceMode): Promise<boolean>;
|
||||
|
||||
/* ======================================================================= */
|
||||
/* WALLET & ADDRESS */
|
||||
/* ======================================================================= */
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get the public address (Kv + Ks) of an account
|
||||
* @returns {Promise<PublicAddress>}
|
||||
* @memberof Device
|
||||
*/
|
||||
get_public_address(): Promise<PublicAddress>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get secret keys [kv, ks] of an account
|
||||
* @returns {Promise<SecretKeys>}
|
||||
* @memberof Device
|
||||
*/
|
||||
get_secret_keys(): Promise<SecretKeys>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Generate chacha key from kv and ks
|
||||
* @returns {Promise<ChachaKey>}
|
||||
* @memberof Device
|
||||
*/
|
||||
generate_chacha_key(keys: IAccountKeys): Promise<ChachaKey>;
|
||||
|
||||
/* ======================================================================= */
|
||||
/* SUB ADDRESS */
|
||||
/* ======================================================================= */
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Derives a subaddress public key
|
||||
* @param {PublicKey} pub K0
|
||||
* @param {KeyDerivation} derivation rKv
|
||||
* @param {number} output_index t
|
||||
* @returns {Promise<PublicKey>} K0 - derivation_to_scalar(rkv,t).G
|
||||
* @memberof Device
|
||||
*/
|
||||
derive_subaddress_public_key(
|
||||
pub: PublicKey,
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
): Promise<PublicKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {SecretKeys} keys keypair [Ks, kv]
|
||||
* @param {ISubaddressIndex} index i
|
||||
* @returns {Promise<string>} Ks,i
|
||||
* @memberof Device
|
||||
*/
|
||||
get_subaddress_spend_public_key(
|
||||
keys: IAccountKeys,
|
||||
index: ISubaddressIndex,
|
||||
): Promise<PublicKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get an array of public subaddress spend keys Ks,i[]
|
||||
* @param {IAccountKeys} keys
|
||||
* @param {number} account 32 bits
|
||||
* @param {number} begin 32 bits
|
||||
* @param {number} end 32 bits
|
||||
* @returns {Promise<PublicSpendKey[]>}
|
||||
* @memberof Device
|
||||
*/
|
||||
get_subaddress_spend_public_keys(
|
||||
keys: IAccountKeys,
|
||||
account: number,
|
||||
begin: number,
|
||||
end: number,
|
||||
): Promise<PublicSpendKey[]>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get a subaddress (Kv,i + Ks,i)
|
||||
* @param {IAccountKeys} keys
|
||||
* @param {ISubaddressIndex} index i
|
||||
* @returns {Promise<PublicAddress>}
|
||||
* @memberof Device
|
||||
*/
|
||||
get_subaddress(
|
||||
keys: IAccountKeys,
|
||||
index: ISubaddressIndex,
|
||||
): Promise<PublicAddress>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get a subaddress secret key `Hn(kv, i)`
|
||||
* @param {SecretKey} sec The secret key to derive the sub secret key from
|
||||
* @param {number} index
|
||||
* @returns {Promise<SecretKey>}
|
||||
* @memberof Device
|
||||
*/
|
||||
get_subaddress_secret_key(
|
||||
sec: SecretKey,
|
||||
index: ISubaddressIndex,
|
||||
): Promise<SecretKey>;
|
||||
|
||||
/* ======================================================================= */
|
||||
/* DERIVATION & KEY */
|
||||
/* ======================================================================= */
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Verifies that a keypair [k, K] are valid
|
||||
* @param {SecretKey} secretKey
|
||||
* @param {PublicKey} publicKey
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof Device
|
||||
*/
|
||||
verify_keys(secretKey: SecretKey, publicKey: PublicKey): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* @description Variable-base scalar multiplications for some integer a, and point P: aP (VBSM)
|
||||
* @param {Key} P
|
||||
* @param {Key} a
|
||||
* @returns {Promise<Key>} aP
|
||||
* @memberof Device
|
||||
*/
|
||||
scalarmultKey(P: Key, a: Key): Promise<Key>;
|
||||
|
||||
/**
|
||||
* @description Known-base scalar multiplications for some integer a: aG (KBSM)
|
||||
* @param {Key} a
|
||||
* @returns {Promise<Key>} aG
|
||||
* @memberof Device
|
||||
*/
|
||||
scalarmultBase(a: Key): Promise<Key>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Scalar addition (each private key is a scalar) a + b = r
|
||||
* @param {SecretKey} a
|
||||
* @param {SecretKey} b
|
||||
* @returns {Promise<string>} r
|
||||
* @memberof Device
|
||||
*/
|
||||
sc_secret_add(a: SecretKey, b: SecretKey): Promise<SecretKey>;
|
||||
|
||||
/**
|
||||
* @description Generates a keypair (R, r), leave recovery key undefined for a random key pair
|
||||
* @param {SecretKey} recoveryKey
|
||||
* @returns {Promise<KeyPair>}
|
||||
* @memberof Device
|
||||
*/
|
||||
generate_keys(recoveryKey: SecretKey | undefined): Promise<KeyPair>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Generates a key derivation,
|
||||
* can be used to generate ephemeral_(pub|sec) which is the one-time keypair
|
||||
* @param {PublicKey} pub Ex. rG, a transaction public key
|
||||
* @param {SecretKey} sec Ex. kv, a secret view key
|
||||
* @returns {Promise<KeyDerivation>} Derived Key Ex. rG.kv -> rKv
|
||||
* @memberof Device
|
||||
*/
|
||||
generate_key_derivation(
|
||||
pub: PublicKey,
|
||||
sec: SecretKey,
|
||||
): Promise<KeyDerivation>;
|
||||
|
||||
/**
|
||||
* @description Conceals a derivation, used when a clear text derivation needs to be encrypted so it can
|
||||
* later be used by other device methods since they only allow encrypted derivations as input
|
||||
* If the main derivation matches the derivation, then the concealed derivation of the tx_pub_key is returned,
|
||||
* otherwise additional_derivations is scanned through for a matching derivation, then the matching index is used to return the concealed
|
||||
* additional_tx_pub_keys at the matching index
|
||||
* @link https://github.com/monero-project/monero/pull/3591
|
||||
* @see 5.3.1 Zero-to-monero
|
||||
* Used when scanning txs to see if any txs are directed towards the users address
|
||||
* @ignore subaddresses
|
||||
* @param {KeyDerivation} derivation e.g rKv
|
||||
* @param {PublicKey} tx_pub_key
|
||||
* @param {PublicKey[]} additional_tx_pub_keys used for multi-destination transfers involving one or more subaddresses
|
||||
* @param {KeyDerivation} main_derivation
|
||||
* @param {KeyDerivation[]} additional_derivations used for multi-destination transfers involving one or more subaddresses
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @memberof Device
|
||||
*/
|
||||
conceal_derivation(
|
||||
derivation: KeyDerivation,
|
||||
tx_pub_key: PublicKey,
|
||||
additional_tx_pub_keys: PublicKey[],
|
||||
main_derivation: KeyDerivation,
|
||||
additional_derivations: KeyDerivation[],
|
||||
): Promise<PublicKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Transforms a derivation to a scalar based on an index
|
||||
* Used for multi output transactions and subaddresses
|
||||
* @param {KeyDerivation} derivation e.g rKv
|
||||
* @param {number} output_index t
|
||||
* @returns {Promise<EcScalar>} e.g Hn(rKvt, t)
|
||||
* @memberof Device
|
||||
*/
|
||||
derivation_to_scalar(
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
): Promise<EcScalar>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Derive a secret key
|
||||
* Used to derive an emphemeral (one-time) secret key at index t which then can be used to spend an output or, generate a key image (kHp(K)) when combined
|
||||
* when the corresponding public key
|
||||
* @see 5.2.1 Zero-to-monero
|
||||
* @param {KeyDerivation} derivation e.g rKv
|
||||
* @param {number} output_index e.g t
|
||||
* @param {SecretKey} sec e.g ks, a private spend key
|
||||
* @returns {Promise<SecretKey>} e.g k0, where k0 = Hn(rKv,t) + ks
|
||||
* @memberof Device
|
||||
*/
|
||||
derive_secret_key(
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
sec: SecretKey,
|
||||
): Promise<SecretKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Derive a public key
|
||||
* Used to derive an emphemeral (one-time) public key at index t which then can be used check if a transaction belongs to
|
||||
* the public key, or generate a key image (kHp(K)) when combined with the corresponding private key
|
||||
* @see 5.2.1 Zero-to-monero
|
||||
* @param {KeyDerivation} derivation e.g rKv
|
||||
* @param {number} output_index e.g t
|
||||
* @param {SecretKey} pub e.g Ks, a public spend key
|
||||
* @returns {Promise<SecretKey>} e.g k0, where k0 = Hn(rKv,t) + Ks
|
||||
* @memberof Device
|
||||
*/
|
||||
derive_public_key(
|
||||
derivation: KeyDerivation,
|
||||
output_index: number,
|
||||
pub: PublicKey,
|
||||
): Promise<PublicKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Generates a public key from a secret key
|
||||
* @param {SecretKey} sec e.g k
|
||||
* @returns {Promise<PublicKey>} e.g K where K = kG
|
||||
* @memberof Device
|
||||
*/
|
||||
secret_key_to_public_key(sec: SecretKey): Promise<PublicKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Generates key image kHp(K)
|
||||
* @param {PublicKey} pub K
|
||||
* @param {SecretKey} sec k
|
||||
* @returns {Promise<PublicKey>} kHp(K)
|
||||
* @memberof Device
|
||||
*/
|
||||
generate_key_image(pub: PublicKey, sec: SecretKey): Promise<PublicKey>;
|
||||
|
||||
/* ======================================================================= */
|
||||
/* TRANSACTION */
|
||||
/* ======================================================================= */
|
||||
|
||||
/**
|
||||
*
|
||||
* @description First step of creating a transaction
|
||||
* @returns {Promise<SecretKey>} A randomly generated spk
|
||||
* @memberof Device
|
||||
*/
|
||||
open_tx(): Promise<SecretKey>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Encrypt payment id
|
||||
* @param {string} paymentId
|
||||
* @param {string} public_key Kv
|
||||
* @param {string} secret_key r
|
||||
* @returns {Promise<Hash8>} encrypted payment id = XOR (Hn( generate_key_derivation(r, Kv) , ENCRYPTED_PAYMENT_ID_TAIL), paymentId)
|
||||
* @memberof Device
|
||||
*/
|
||||
encrypt_payment_id(
|
||||
paymentId: string,
|
||||
public_key: string,
|
||||
secret_key: string,
|
||||
): Promise<Hash8>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Decrypt payment id
|
||||
* @param {string} paymentId
|
||||
* @param {string} public_key
|
||||
* @param {string} secret_key
|
||||
* @returns {Promise<Hash8>} Decrypted payment id = encrypt_payment_id(payment_id, public_key, secret_key) since its a XOR operation
|
||||
* @memberof Device
|
||||
*/
|
||||
decrypt_payment_id(
|
||||
paymentId: string,
|
||||
public_key: string,
|
||||
secret_key: string,
|
||||
): Promise<Hash8>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Elliptic Curve Diffie Helman: encodes the amount b and mask a
|
||||
* where C= aG + bH
|
||||
* @param {Commit} unmasked The unmasked ecdh tuple to encode using the shared secret
|
||||
* @param {string} sharedSec e.g sharedSec = derivation_to_scalar(rKv,t) where Kv is the recipients
|
||||
* public view key
|
||||
* @returns {Promise<Commit>}
|
||||
* @memberof Device
|
||||
*/
|
||||
ecdhEncode(unmasked: Commit, sharedSec: SecretKey): Promise<Commit>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Elliptic Curve Diffie Helman: decodes the amount b and mask a
|
||||
* where C= aG + bH
|
||||
* @param {Commit} masked The masked ecdh tuple to decude using the shared secret
|
||||
* @param {SecretKey} sharedSec e.g sharedSec = derivation_to_scalar(rKv | rG.kv ,t)
|
||||
* @returns {Promise<Commit>}
|
||||
* @memberof Device
|
||||
*/
|
||||
ecdhDecode(masked: Commit, sharedSec: SecretKey): Promise<Commit>;
|
||||
|
||||
/**
|
||||
* @description store keys during construct_tx_with_tx_key to be later used during genRct -> mlsag_prehash
|
||||
* @param {PublicKey} Aout
|
||||
* @param {PublicKey} Bout
|
||||
* @param {boolean} is_subaddress
|
||||
* @param {number} real_output_index
|
||||
* @param {Key} amount_key
|
||||
* @param {PublicKey} out_eph_public_key
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof Device
|
||||
*/
|
||||
add_output_key_mapping(
|
||||
Aout: PublicKey,
|
||||
Bout: PublicKey,
|
||||
is_subaddress: boolean,
|
||||
real_output_index: number,
|
||||
amount_key: Key,
|
||||
out_eph_public_key: PublicKey,
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Compute the mlsag prehash, also known as the message to be signed
|
||||
* @param {string} blob
|
||||
* @param {number} inputs_size
|
||||
* @param {number} outputs_size
|
||||
* @param {KeyV} hashes
|
||||
* @param {CtKeyV} outPk
|
||||
* @returns {Promise<Key>} mlsag prehash
|
||||
* @memberof Device
|
||||
*/
|
||||
mlsag_prehash(
|
||||
blob: string,
|
||||
inputs_size: number,
|
||||
outputs_size: number,
|
||||
hashes: KeyV,
|
||||
outPk: CtKeyV,
|
||||
): Promise<Key>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Generate the matrix ring parameters
|
||||
* @param {Key} H
|
||||
* @param {Key} xx
|
||||
* @returns {Promise<{ a: Key, aG: Key, aHP: Key, II: Key }>}
|
||||
* @memberof Device
|
||||
*/
|
||||
mlsag_prepare(
|
||||
H: Key,
|
||||
xx: Key,
|
||||
): Promise<{ a: Key; aG: Key; aHP: Key; II: Key }>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Generate the matrix ring parameters
|
||||
* @returns {Promise<{ a: Key, aG: Key }>}
|
||||
* @memberof Device
|
||||
*/
|
||||
mlsag_prepare(): Promise<{ a: Key; aG: Key }>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description To be filled in
|
||||
* @param {KeyV} toHash
|
||||
* @returns {Promise<Key>} c
|
||||
* @memberof Device
|
||||
*/
|
||||
mlsag_hash(toHash: KeyV): Promise<Key>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description To be filled in
|
||||
* @param {key} c
|
||||
* @param {KeyV} xx
|
||||
* @param {KeyV} alpha
|
||||
* @param {number} rows
|
||||
* @param {number} dsRows
|
||||
* @param {KeyV} ss
|
||||
* @returns {Promise<KeyV>} ss
|
||||
* @memberof Device
|
||||
*/
|
||||
mlsag_sign(
|
||||
c: Key,
|
||||
xx: KeyV,
|
||||
alpha: KeyV,
|
||||
rows: number,
|
||||
dsRows: number,
|
||||
ss: KeyV,
|
||||
): Promise<KeyV>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Finalize tx on device
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof Device
|
||||
*/
|
||||
close_tx(): Promise<boolean>;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { hexUtils } from "xmr-str-utils";
|
||||
import SHA3 = require("keccakjs");
|
||||
|
||||
const { hextobin, valid_hex } = hexUtils;
|
||||
|
||||
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");
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
import { cryptonoteBase58PrefixForStandardAddressOn } from "xmr-nettype-address-prefixes";
|
||||
import { encode_varint } from "xmr-varint";
|
||||
import { ge_scalarmult_base, sc_reduce32 } from "xmr-crypto-ops/primitive_ops";
|
||||
import { cn_fast_hash } from "xmr-fast-hash";
|
||||
import { ADDRESS_CHECKSUM_SIZE } from "xmr-constants/address";
|
||||
import { cnBase58 } from "xmr-b58/xmr-base58";
|
||||
import { rand_32, random_scalar } from "xmr-rand";
|
||||
import { NetType, KeyPair } from "xmr-types";
|
||||
|
||||
export function secret_key_to_public_key(sec: string) {
|
||||
return ge_scalarmult_base(sec);
|
||||
}
|
||||
|
||||
export function pubkeys_to_string(
|
||||
spend: string,
|
||||
view: string,
|
||||
nettype: NetType,
|
||||
) {
|
||||
const prefix = encode_varint(
|
||||
cryptonoteBase58PrefixForStandardAddressOn(nettype),
|
||||
);
|
||||
const data = prefix + spend + view;
|
||||
const checksum = cn_fast_hash(data);
|
||||
return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2));
|
||||
}
|
||||
|
||||
// Generate keypair from seed
|
||||
export function generate_keys(seed: string): KeyPair {
|
||||
if (seed.length !== 64) throw Error("Invalid input length!");
|
||||
const sec = sc_reduce32(seed);
|
||||
const pub = secret_key_to_public_key(sec);
|
||||
return {
|
||||
sec,
|
||||
pub,
|
||||
};
|
||||
}
|
||||
|
||||
export function valid_keys(
|
||||
view_pub: string,
|
||||
view_sec: string,
|
||||
spend_pub: string,
|
||||
spend_sec: string,
|
||||
) {
|
||||
const expected_view_pub = secret_key_to_public_key(view_sec);
|
||||
const expected_spend_pub = secret_key_to_public_key(spend_sec);
|
||||
return expected_spend_pub === spend_pub && expected_view_pub === view_pub;
|
||||
}
|
||||
|
||||
export function random_keypair() {
|
||||
return generate_keys(rand_32());
|
||||
}
|
||||
|
||||
// alias
|
||||
export const skGen = random_scalar;
|
@ -0,0 +1,129 @@
|
||||
import { derive_key_image_from_tx } from "xmr-crypto-ops/key_image";
|
||||
|
||||
// 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.
|
||||
|
||||
// Managed caches - Can be used by apps which can't send a mutable_keyImagesByCacheKey
|
||||
|
||||
export type KeyImageCache = { [cacheIndex: string]: string };
|
||||
export type KeyImageCacheMap = { [address: string]: KeyImageCache };
|
||||
|
||||
const keyImagesByWalletId: KeyImageCacheMap = {};
|
||||
|
||||
/**
|
||||
* @description Performs a memoized computation of a key image
|
||||
* @param {KeyImageCache} keyImageCache
|
||||
* @param {string} txPubKey
|
||||
* @param {number} outIndex
|
||||
* @param {string} address
|
||||
* @param {string} privViewKey
|
||||
* @param {string} pubSpendKey
|
||||
* @param {string} privSpendKey
|
||||
* @returns
|
||||
*/
|
||||
export function genKeyImage(
|
||||
keyImageCache: KeyImageCache,
|
||||
txPubKey: string,
|
||||
outIndex: number,
|
||||
address: string,
|
||||
privViewKey: string,
|
||||
pubSpendKey: string,
|
||||
privSpendKey: string,
|
||||
) {
|
||||
const cacheIndex = `${txPubKey}:${address}:${outIndex}`;
|
||||
const cachedKeyImage = keyImageCache[cacheIndex];
|
||||
|
||||
if (cachedKeyImage) {
|
||||
return cachedKeyImage;
|
||||
}
|
||||
|
||||
const { key_image } = derive_key_image_from_tx(
|
||||
txPubKey,
|
||||
privViewKey,
|
||||
pubSpendKey,
|
||||
privSpendKey,
|
||||
outIndex,
|
||||
);
|
||||
|
||||
// cache the computed key image
|
||||
keyImageCache[cacheIndex] = key_image;
|
||||
|
||||
return key_image;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Get a key image cache, that's mapped by address
|
||||
* @export
|
||||
* @param {string} address
|
||||
*/
|
||||
export function getKeyImageCache(address: string) {
|
||||
const cacheId = parseAddress(address);
|
||||
|
||||
let cache = keyImagesByWalletId[cacheId];
|
||||
if (!cache) {
|
||||
cache = {};
|
||||
keyImagesByWalletId[cacheId] = cache;
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Clears a key image cache that's mapped by the users address
|
||||
*
|
||||
*
|
||||
* IMPORTANT: Ensure you call this method when you want to clear your wallet from
|
||||
* memory or delete it, or else you could leak key images and public addresses.
|
||||
* @export
|
||||
* @param {string} address
|
||||
*/
|
||||
export function clearKeyImageCache(address: string) {
|
||||
const cacheId = parseAddress(address);
|
||||
|
||||
delete keyImagesByWalletId[cacheId];
|
||||
|
||||
const cache = keyImagesByWalletId[cacheId];
|
||||
|
||||
if (cache) {
|
||||
throw Error("Key image cache still exists after deletion");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Normalize an address before using it to access the key image cache map as a key
|
||||
* @param {string} address
|
||||
*/
|
||||
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 Error("Address does not exist");
|
||||
}
|
||||
|
||||
return address.toString();
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { trimRight, padLeft } from "xmr-str-utils/std-strings";
|
||||
import { BigInt } from "biginteger";
|
||||
import { config } from "xmr-constants/coin-config";
|
||||
import { ParsedTarget } from "xmr-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;
|
||||
}
|
||||
|
||||
export function printDsts(dsts: ParsedTarget[]) {
|
||||
for (let i = 0; i < dsts.length; i++) {
|
||||
console.log(dsts[i].address + ": " + formatMoneyFull(dsts[i].amount));
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import { config } from "xmr-constants/coin-config";
|
||||
import { BigInt } from "biginteger";
|
||||
import { ParsedTarget } from "xmr-types";
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 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 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;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export * from "./request-utils";
|
||||
export * from "./response-parsers";
|
@ -0,0 +1,2 @@
|
||||
export * from "./response-parsers";
|
||||
export * from "./types";
|
@ -0,0 +1,272 @@
|
||||
// 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.
|
||||
|
||||
import { BigInt } from "biginteger";
|
||||
import {
|
||||
genKeyImage,
|
||||
KeyImageCache,
|
||||
getKeyImageCache,
|
||||
} from "xmr-keyimg-memoized";
|
||||
import {
|
||||
normalizeAddressTransactions,
|
||||
normalizeTransaction,
|
||||
isKeyImageEqual,
|
||||
zeroTransactionAmount,
|
||||
calculateTransactionAmount,
|
||||
estimateTransactionAmount,
|
||||
sortTransactions,
|
||||
removeEncryptedPaymentIDs,
|
||||
normalizeAddressInfo,
|
||||
validateUnspentOutput,
|
||||
validateSpendKeyImages,
|
||||
validUnspentOutput,
|
||||
validTxPubKey,
|
||||
} from "./utils";
|
||||
import {
|
||||
AddressTransactions,
|
||||
AddressInfo,
|
||||
UnspentOuts,
|
||||
NormalizedTransaction,
|
||||
} from "./types";
|
||||
|
||||
export function parseAddressInfo(
|
||||
address: string,
|
||||
keyImageCache: KeyImageCache = getKeyImageCache(address),
|
||||
data: AddressInfo,
|
||||
privViewKey: string,
|
||||
pubSpendKey: string,
|
||||
privSpendKey: string,
|
||||
) {
|
||||
const normalizedData = normalizeAddressInfo(data);
|
||||
let { total_sent } = normalizedData;
|
||||
const {
|
||||
total_received,
|
||||
locked_funds: locked_balance,
|
||||
start_height: account_scan_start_height,
|
||||
scanned_block_height: account_scanned_tx_height,
|
||||
scanned_height: account_scanned_block_height,
|
||||
transaction_height,
|
||||
blockchain_height,
|
||||
rates: ratesBySymbol,
|
||||
spent_outputs,
|
||||
} = normalizedData;
|
||||
|
||||
for (const spent_output of spent_outputs) {
|
||||
const key_image = genKeyImage(
|
||||
keyImageCache,
|
||||
spent_output.tx_pub_key,
|
||||
spent_output.out_index,
|
||||
address,
|
||||
privViewKey,
|
||||
pubSpendKey,
|
||||
privSpendKey,
|
||||
);
|
||||
|
||||
if (!isKeyImageEqual(spent_output, key_image)) {
|
||||
total_sent = new BigInt(total_sent)
|
||||
.subtract(spent_output.amount)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total_received: new BigInt(total_received),
|
||||
locked_balance: new BigInt(locked_balance),
|
||||
total_sent: new BigInt(total_sent),
|
||||
|
||||
spent_outputs,
|
||||
account_scanned_tx_height,
|
||||
account_scanned_block_height,
|
||||
account_scan_start_height,
|
||||
transaction_height,
|
||||
blockchain_height,
|
||||
|
||||
ratesBySymbol,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseAddressTransactions(
|
||||
address: string,
|
||||
keyImgCache: KeyImageCache = getKeyImageCache(address),
|
||||
data: AddressTransactions,
|
||||
privViewKey: string,
|
||||
pubSpendKey: string,
|
||||
privSpendKey: string,
|
||||
) {
|
||||
const {
|
||||
blockchain_height,
|
||||
scanned_block_height: account_scanned_block_height,
|
||||
scanned_height: account_scanned_height,
|
||||
start_height: account_scan_start_height,
|
||||
/*total_received*/
|
||||
transaction_height,
|
||||
transactions,
|
||||
} = normalizeAddressTransactions(data);
|
||||
|
||||
const normalizedTransactions: NormalizedTransaction[] = [];
|
||||
|
||||
for (let i = 0; i < transactions.length; i++) {
|
||||
const transaction = normalizeTransaction(transactions[i]);
|
||||
|
||||
for (let j = 0; j < transaction.spent_outputs.length; j++) {
|
||||
const keyImage = genKeyImage(
|
||||
keyImgCache,
|
||||
transaction.spent_outputs[j].tx_pub_key,
|
||||
transaction.spent_outputs[j].out_index,
|
||||
address,
|
||||
privViewKey,
|
||||
pubSpendKey,
|
||||
privSpendKey,
|
||||
);
|
||||
|
||||
if (!isKeyImageEqual(transaction.spent_outputs[j], keyImage)) {
|
||||
transaction.total_sent = new BigInt(
|
||||
transaction.total_sent,
|
||||
).subtract(transaction.spent_outputs[j].amount);
|
||||
|
||||
transaction.spent_outputs.splice(j, 1);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
if (zeroTransactionAmount(transaction)) {
|
||||
transactions.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
transaction.amount = calculateTransactionAmount(transaction);
|
||||
|
||||
transaction.approx_float_amount = estimateTransactionAmount(
|
||||
transaction,
|
||||
);
|
||||
|
||||
removeEncryptedPaymentIDs(transaction);
|
||||
|
||||
normalizedTransactions.push(transaction);
|
||||
}
|
||||
|
||||
sortTransactions(normalizedTransactions);
|
||||
|
||||
// on the other side, we convert transactions timestamp to Date obj
|
||||
|
||||
return {
|
||||
account_scanned_height,
|
||||
account_scanned_block_height,
|
||||
account_scan_start_height,
|
||||
transaction_height,
|
||||
blockchain_height,
|
||||
transactions: normalizedTransactions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Go through each (possibly) unspent out and remove ones that have been spent before
|
||||
* by computing a key image per unspent output and checking if they match any spend_key_images
|
||||
* @param {string} address
|
||||
* @param {KeyImageCache} [keyImageCache=getKeyImageCache(address)]
|
||||
* @param {UnspentOuts} data
|
||||
* @param {string} privViewKey
|
||||
* @param {string} pubSpendKey
|
||||
* @param {string} privSpendKey
|
||||
*/
|
||||
export function parseUnspentOuts(
|
||||
address: string,
|
||||
keyImageCache: KeyImageCache = getKeyImageCache(address),
|
||||
data: UnspentOuts,
|
||||
privViewKey: string,
|
||||
pubSpendKey: string,
|
||||
privSpendKey: string,
|
||||
) {
|
||||
const { outputs, per_kb_fee } = data;
|
||||
|
||||
if (!per_kb_fee) {
|
||||
throw Error("Unexpected / missing per_kb_fee");
|
||||
}
|
||||
|
||||
const unspentOuts = outputs || [];
|
||||
|
||||
for (let i = 0; i < unspentOuts.length; i++) {
|
||||
const unspentOut = unspentOuts[i];
|
||||
validateUnspentOutput(unspentOut, i);
|
||||
|
||||
const { spend_key_images } = unspentOut;
|
||||
validateSpendKeyImages(spend_key_images, i);
|
||||
|
||||
for (let j = 0; j < spend_key_images.length; j++) {
|
||||
const beforeSplice = unspentOuts[i];
|
||||
|
||||
if (!validUnspentOutput(beforeSplice, i)) {
|
||||
// NOTE: Looks like the i-- code below should exit earlier if this is necessary
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!validTxPubKey(beforeSplice)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const keyImage = genKeyImage(
|
||||
keyImageCache,
|
||||
beforeSplice.tx_pub_key,
|
||||
beforeSplice.index,
|
||||
address,
|
||||
privViewKey,
|
||||
pubSpendKey,
|
||||
privSpendKey,
|
||||
);
|
||||
|
||||
if (keyImage === beforeSplice.spend_key_images[j]) {
|
||||
// Remove output from list
|
||||
unspentOuts.splice(i, 1);
|
||||
|
||||
const afterSplice = unspentOuts[i];
|
||||
|
||||
// skip rest of spend_key_images
|
||||
if (afterSplice) {
|
||||
j = afterSplice.spend_key_images.length;
|
||||
}
|
||||
|
||||
i--;
|
||||
} else {
|
||||
console.log(
|
||||
`💬 Output used as mixin (${keyImage} / ${
|
||||
unspentOuts[i].spend_key_images[j]
|
||||
})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Unspent outs: " + JSON.stringify(unspentOuts));
|
||||
|
||||
return {
|
||||
unspentOuts,
|
||||
unusedOuts: [...unspentOuts],
|
||||
per_kb_fee: new BigInt(per_kb_fee),
|
||||
};
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
import { Omit } from "types";
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
export interface SpentOutput {
|
||||
amount: string;
|
||||
key_image: string;
|
||||
tx_pub_key: string;
|
||||
out_index: number;
|
||||
mixin?: number;
|
||||
}
|
||||
|
||||
export interface AddressTransactionsTx {
|
||||
id: number;
|
||||
hash?: string;
|
||||
timestamp: string;
|
||||
total_received?: string;
|
||||
total_sent?: string;
|
||||
unlock_time?: number;
|
||||
height?: number;
|
||||
coinbase?: boolean;
|
||||
mempool?: boolean;
|
||||
mixin?: number;
|
||||
spent_outputs?: SpentOutput[];
|
||||
payment_id?: string;
|
||||
}
|
||||
|
||||
export interface AddressTransactions {
|
||||
total_received?: string;
|
||||
scanned_height?: number;
|
||||
scanned_block_height?: number;
|
||||
start_height?: number;
|
||||
transaction_height?: number;
|
||||
blockchain_height?: number;
|
||||
transactions?: AddressTransactionsTx[];
|
||||
}
|
||||
|
||||
export interface NormalizedTransaction
|
||||
extends Required<Omit<AddressTransactionsTx, "total_sent" | "timestamp">> {
|
||||
total_sent: BigInt;
|
||||
amount: BigInt;
|
||||
approx_float_amount: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface Rates {
|
||||
[currencySymbol: string]: number;
|
||||
}
|
||||
|
||||
export interface AddressInfo {
|
||||
locked_funds?: string;
|
||||
total_received?: string;
|
||||
total_sent?: string;
|
||||
scanned_height?: number;
|
||||
scanned_block_height?: number;
|
||||
start_height?: number;
|
||||
transaction_height?: number;
|
||||
blockchain_height?: number;
|
||||
spent_outputs?: SpentOutput[];
|
||||
rates?: Rates;
|
||||
}
|
||||
|
||||
export interface UnspentOutput {
|
||||
amount: string;
|
||||
public_key: string;
|
||||
index: number;
|
||||
global_index: number;
|
||||
rct: string;
|
||||
tx_id: number;
|
||||
tx_hash: string;
|
||||
tx_pub_key: string;
|
||||
tx_prefix_hash: string;
|
||||
spend_key_images: string[];
|
||||
timestamp: string;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface UnspentOuts {
|
||||
per_kb_fee: number;
|
||||
amount: string;
|
||||
outputs?: UnspentOutput[];
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
import {
|
||||
AddressTransactions,
|
||||
AddressTransactionsTx,
|
||||
NormalizedTransaction,
|
||||
SpentOutput,
|
||||
AddressInfo,
|
||||
UnspentOutput,
|
||||
} from "./types";
|
||||
import { Omit } from "types";
|
||||
import { BigInt } from "biginteger";
|
||||
import { formatMoney } from "xmr-money/formatters";
|
||||
|
||||
export function isKeyImageEqual({ key_image }: SpentOutput, keyImage: string) {
|
||||
return key_image === keyImage;
|
||||
}
|
||||
|
||||
//#region addressInfo
|
||||
|
||||
export function normalizeAddressInfo(data: AddressInfo): Required<AddressInfo> {
|
||||
const defaultObj: Required<AddressInfo> = {
|
||||
total_sent: "0",
|
||||
total_received: "0",
|
||||
locked_funds: "0",
|
||||
|
||||
start_height: 0,
|
||||
scanned_block_height: 0,
|
||||
scanned_height: 0,
|
||||
transaction_height: 0,
|
||||
blockchain_height: 0,
|
||||
|
||||
rates: {},
|
||||
|
||||
spent_outputs: [] as SpentOutput[],
|
||||
};
|
||||
return { ...defaultObj, ...data };
|
||||
}
|
||||
//#endregion addressInfo
|
||||
|
||||
//#region parseAddressTransactions
|
||||
|
||||
export function normalizeAddressTransactions(
|
||||
data: AddressTransactions,
|
||||
): Required<AddressTransactions> {
|
||||
const defaultObj: Required<AddressTransactions> = {
|
||||
scanned_height: 0,
|
||||
scanned_block_height: 0,
|
||||
start_height: 0,
|
||||
transaction_height: 0,
|
||||
blockchain_height: 0,
|
||||
transactions: [] as AddressTransactionsTx[],
|
||||
total_received: "0",
|
||||
};
|
||||
return { ...defaultObj, ...data };
|
||||
}
|
||||
|
||||
export function normalizeTransaction(
|
||||
tx: AddressTransactionsTx,
|
||||
): NormalizedTransaction {
|
||||
const defaultObj: Omit<NormalizedTransaction, "timestamp"> = {
|
||||
amount: new BigInt(0),
|
||||
approx_float_amount: 0,
|
||||
hash: "",
|
||||
height: 0,
|
||||
id: 0,
|
||||
mempool: false,
|
||||
coinbase: false,
|
||||
mixin: 0,
|
||||
spent_outputs: [] as SpentOutput[],
|
||||
total_received: "0",
|
||||
total_sent: new BigInt(0),
|
||||
unlock_time: 0,
|
||||
payment_id: "",
|
||||
};
|
||||
|
||||
const mergedObj = {
|
||||
...defaultObj,
|
||||
...tx,
|
||||
total_sent: tx.total_sent
|
||||
? new BigInt(tx.total_sent)
|
||||
: defaultObj.total_sent,
|
||||
timestamp: new Date(tx.timestamp),
|
||||
};
|
||||
|
||||
return mergedObj;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Validates that the sum of total received and total sent is greater than 1
|
||||
* @param {NormalizedTransaction} { total_received, total_sent}
|
||||
*/
|
||||
export function zeroTransactionAmount({
|
||||
total_received,
|
||||
total_sent,
|
||||
}: NormalizedTransaction) {
|
||||
return new BigInt(total_received).add(total_sent).compare(0) <= 0;
|
||||
}
|
||||
|
||||
export function calculateTransactionAmount({
|
||||
total_received,
|
||||
total_sent,
|
||||
}: NormalizedTransaction) {
|
||||
return new BigInt(total_received).subtract(total_sent);
|
||||
}
|
||||
|
||||
export function estimateTransactionAmount({ amount }: NormalizedTransaction) {
|
||||
return parseFloat(formatMoney(amount));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description If the transaction is:
|
||||
* 1. An outgoing transaction, e.g. it is sending moneroj to an address rather than receiving it
|
||||
* 2. And contains an encrypted (8 byte) payment id
|
||||
*
|
||||
* Then, it is removed from the transaction object, because the server (MyMonero) can't filter out short (encrypted) pids on outgoing txs
|
||||
* @export
|
||||
* @param {NormalizedTransaction} tx
|
||||
*/
|
||||
export function removeEncryptedPaymentIDs(tx: NormalizedTransaction) {
|
||||
const { payment_id, approx_float_amount } = tx;
|
||||
const outgoingTxWithEncPid =
|
||||
payment_id && payment_id.length === 16 && approx_float_amount < 0;
|
||||
|
||||
if (outgoingTxWithEncPid) {
|
||||
delete tx.payment_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sort transactions based on the following cases where higher priorities
|
||||
* gain a lower index value:
|
||||
*
|
||||
* 1. Transactions that are in the mempool gain priority
|
||||
* 2. Otherwise, sort by id, where the higher ID has higher priority
|
||||
*
|
||||
* @export
|
||||
* @param {AddressTransactionsTx[]} transactions
|
||||
* @returns
|
||||
*/
|
||||
export function sortTransactions(transactions: NormalizedTransaction[]) {
|
||||
return transactions.sort((a, b) => {
|
||||
if (a.mempool && !b.mempool) {
|
||||
return -1; // a first
|
||||
} else if (b.mempool) {
|
||||
return 1; // b first
|
||||
}
|
||||
|
||||
// both mempool - fall back to .id compare
|
||||
return b.id - a.id;
|
||||
});
|
||||
}
|
||||
|
||||
//#endregion parseAddressTransactions
|
||||
|
||||
//#region parseUnspentOuts
|
||||
|
||||
export function validateUnspentOutput(out: UnspentOutput, index: number) {
|
||||
if (!out) {
|
||||
throw Error(`unspent_output at index ${index} was null`);
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSpendKeyImages(keyImgs: string[], index: number) {
|
||||
if (!keyImgs) {
|
||||
throw Error(
|
||||
`spend_key_images of unspent_output at index ${index} was null`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function validUnspentOutput(out: UnspentOutput, index: number) {
|
||||
if (out) {
|
||||
return true;
|
||||
} else {
|
||||
console.warn(
|
||||
`This unspent output at i ${index} was undefined! Skipping.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function validTxPubKey(out: UnspentOutput) {
|
||||
if (out.tx_pub_key) {
|
||||
return true;
|
||||
} else {
|
||||
console.warn(
|
||||
"This unspent out was missing a tx_pub_key! Skipping.",
|
||||
out,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion parseUnspentOuts
|
@ -0,0 +1,3 @@
|
||||
export * from "./mixin_utils";
|
||||
export * from "./sending_funds";
|
||||
export * from "./status_update_constants";
|
@ -0,0 +1,32 @@
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
/**
|
||||
* @description Gets a starting total amount.
|
||||
*
|
||||
* In the case of a sweeping transaction, the maximum possible amount (based on 64 bits unsigned integer) is chosen,
|
||||
* since all of the user's outputs will be used regardless.
|
||||
*
|
||||
* Otherwise, the feeless total (also known as the amount the sender actually wants to send to the given target address)
|
||||
* is summed with the network fee to give the total amount returned
|
||||
*
|
||||
* @export
|
||||
* @param {boolean} isSweeping
|
||||
* @param {BigInt} feelessTotal
|
||||
* @param {BigInt} networkFee
|
||||
* @returns
|
||||
*/
|
||||
export function getBaseTotalAmount(
|
||||
isSweeping: boolean,
|
||||
feelessTotal: BigInt,
|
||||
networkFee: BigInt,
|
||||
) {
|
||||
// const hostingService_chargeAmount = hostedMoneroAPIClient.HostingServiceChargeFor_transactionWithNetworkFee(attemptAt_network_minimumFee)
|
||||
|
||||
if (isSweeping) {
|
||||
return new BigInt("18450000000000000000"); //~uint64 max
|
||||
} else {
|
||||
return feelessTotal.add(
|
||||
networkFee,
|
||||
); /*.add(hostingService_chargeAmount) NOTE service fee removed for now */
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export function popRandElement<T>(list: T[]) {
|
||||
const idx = Math.floor(Math.random() * list.length);
|
||||
const val = list[idx];
|
||||
list.splice(idx, 1);
|
||||
return val;
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import { ViewSendKeys, Output, AmountOutput } from "xmr-types";
|
||||
import { Log } from "./logger";
|
||||
import { ERR } from "./errors";
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
export class WrappedNodeApi {
|
||||
private api: any;
|
||||
constructor(api: any) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public unspentOuts(
|
||||
address: string,
|
||||
privateKeys: ViewSendKeys,
|
||||
publicKeys: ViewSendKeys,
|
||||
mixin: number,
|
||||
isSweeping: boolean,
|
||||
) {
|
||||
type ResolveVal = {
|
||||
unusedOuts: Output[];
|
||||
dynamicFeePerKB: BigInt;
|
||||
};
|
||||
|
||||
return new Promise<ResolveVal>((resolve, reject) => {
|
||||
const { spend: xSpend, view: xView } = privateKeys;
|
||||
const { spend: pubSend } = publicKeys;
|
||||
const handler = (
|
||||
err: Error,
|
||||
_: Output[], // unspent outs, the original copy of unusedOuts
|
||||
unusedOuts: Output[],
|
||||
dynamicFeePerKB: BigInt,
|
||||
) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
Log.Fee.dynPerKB(dynamicFeePerKB);
|
||||
return resolve({
|
||||
unusedOuts,
|
||||
dynamicFeePerKB,
|
||||
});
|
||||
};
|
||||
|
||||
this.api.UnspentOuts(
|
||||
address,
|
||||
xView,
|
||||
pubSend,
|
||||
xSpend,
|
||||
mixin,
|
||||
isSweeping,
|
||||
handler,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public randomOuts(usingOuts: Output[], mixin: number) {
|
||||
return new Promise<{ mixOuts: AmountOutput[] }>((resolve, reject) => {
|
||||
this.api.RandomOuts(
|
||||
usingOuts,
|
||||
mixin,
|
||||
(err: Error, mixOuts: AmountOutput[]) =>
|
||||
err ? reject(err) : resolve({ mixOuts }),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public submitSerializedSignedTransaction(
|
||||
address: string,
|
||||
privateKeys: ViewSendKeys,
|
||||
serializedSignedTx: string,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.api.SubmitSerializedSignedTransaction(
|
||||
address,
|
||||
privateKeys.view,
|
||||
serializedSignedTx,
|
||||
(err: Error) =>
|
||||
err ? reject(ERR.TX.submitUnknown(err)) : resolve(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
import { sendFundStatus } from "../../status_update_constants";
|
||||
import { selectOutputsAndAmountForMixin } from "../output_selection";
|
||||
import { multiplyFeePriority, calculateFeeKb } from "../fee_utils";
|
||||
import { ERR } from "../errors";
|
||||
import { Log } from "../logger";
|
||||
import {
|
||||
constructTx,
|
||||
totalAmtAndEstFee,
|
||||
validateAndConstructFundTargets,
|
||||
} from "../tx_utils/tx_utils";
|
||||
import { getBaseTotalAmount } from "../amt_utils";
|
||||
import {
|
||||
CreateTxAndAttemptToSendParams,
|
||||
GetFundTargetsAndFeeParams,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* 1. Recalculates the fee and total amount needed for the transaction to be sent. RCT + non sweeping transactions will have their
|
||||
* network fee increased if fee calculation based on the number of outputs needed is higher than the passed-in fee. RCT+ sweeping transactions
|
||||
* are just checked if they have enough balance to proceed with the transaction. Non-RCT transactions will have no fee recalculation done on them.
|
||||
*
|
||||
*
|
||||
* 2. The resulting return values from step 1 will then be validated so that the sender has sufficient balances to proceed with sending the transaction.
|
||||
* Then, a list of sending targets will be constructed, always consisting of the target address and amount they want to send to, and possibly a change address,
|
||||
* if the sum of outs is greater than the amount sent + fee needed, and possibly a fake address + 0 amount to keep output uniformity if no change address
|
||||
* was generated.
|
||||
*
|
||||
*
|
||||
* 3. Finally, a list of random outputs is fetched from API to be mixed into the transaction (for generation of the ring signature) to provide anonymity for the sender.
|
||||
*
|
||||
*
|
||||
* NOTE: This function may be called more than once (although I believe two times is the maximum) if the recalculated fee is lower than the
|
||||
* actual transaction fee needed when the final fee is calculated from the size of the transaction itself. In the case that the previously mentioned
|
||||
* condition is true, then this function will be re-called with the updated higher fee based on the transaction size in kb.
|
||||
* @export
|
||||
* @param {GetFundTargetsAndFeeParams} params
|
||||
*/
|
||||
export async function getRestOfTxData(params: GetFundTargetsAndFeeParams) {
|
||||
const {
|
||||
senderAddress,
|
||||
|
||||
targetAddress,
|
||||
|
||||
mixin,
|
||||
unusedOuts,
|
||||
|
||||
simplePriority,
|
||||
feelessTotal,
|
||||
feePerKB, // obtained from server, so passed in
|
||||
networkFee,
|
||||
|
||||
isRingCT,
|
||||
isSweeping,
|
||||
|
||||
updateStatus,
|
||||
api,
|
||||
nettype,
|
||||
} = params;
|
||||
|
||||
// Now we need to establish some values for balance validation and to construct the transaction
|
||||
updateStatus(sendFundStatus.calculatingFee);
|
||||
|
||||
const baseTotalAmount = getBaseTotalAmount(
|
||||
isSweeping,
|
||||
feelessTotal,
|
||||
networkFee,
|
||||
);
|
||||
|
||||
Log.Balance.requiredBase(baseTotalAmount, isSweeping);
|
||||
|
||||
const {
|
||||
remainingUnusedOuts, // this is a copy of the pre-mutation usingOuts
|
||||
usingOuts,
|
||||
usingOutsAmount,
|
||||
} = selectOutputsAndAmountForMixin(
|
||||
baseTotalAmount,
|
||||
unusedOuts,
|
||||
isRingCT,
|
||||
isSweeping,
|
||||
);
|
||||
|
||||
// v-- now if RingCT compute fee as closely as possible before hand
|
||||
|
||||
const { newFee, totalAmount } = totalAmtAndEstFee({
|
||||
baseTotalAmount,
|
||||
feelessTotal,
|
||||
feePerKB,
|
||||
isRingCT,
|
||||
isSweeping,
|
||||
mixin,
|
||||
networkFee,
|
||||
remainingUnusedOuts,
|
||||
simplePriority,
|
||||
usingOuts,
|
||||
usingOutsAmount,
|
||||
});
|
||||
Log.Balance.requiredPostRct(totalAmount);
|
||||
|
||||
const { fundTargets } = validateAndConstructFundTargets({
|
||||
senderAddress,
|
||||
targetAddress,
|
||||
|
||||
feelessTotal,
|
||||
totalAmount,
|
||||
usingOutsAmount,
|
||||
|
||||
isRingCT,
|
||||
isSweeping,
|
||||
nettype,
|
||||
});
|
||||
Log.Target.display(fundTargets);
|
||||
|
||||
// check for invalid mixin level
|
||||
if (mixin < 0 || isNaN(mixin)) {
|
||||
throw ERR.MIXIN.INVAL;
|
||||
}
|
||||
|
||||
// if we want to have mixin for anonyminity
|
||||
if (mixin > 0) {
|
||||
updateStatus(sendFundStatus.fetchingDecoyOutputs);
|
||||
|
||||
// grab random outputs to make a ring signature with
|
||||
const { mixOuts } = await api.randomOuts(usingOuts, mixin);
|
||||
|
||||
return { mixOuts, fundTargets, newFee, usingOuts };
|
||||
}
|
||||
|
||||
// mixin === 0: -- PSNOTE: is that even allowed?
|
||||
return { mixOuts: undefined, fundTargets, newFee, usingOuts };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Creates the transaction blob and attempts to send it.
|
||||
*
|
||||
*
|
||||
* The transaction blob will be not sent if the resulting fee calculated based on the blobs size
|
||||
* is higher than the provided fee to the function, instead itll return a failure result, along
|
||||
* with the fee based on the transaction blob.
|
||||
*
|
||||
*
|
||||
* Otherwise, the serialized transaction blob will be sent to the API endpoint, along with
|
||||
* a success return value with the fee + transaction blobs' hash
|
||||
*
|
||||
* @export
|
||||
* @param {CreateTxAndAttemptToSendParams} params
|
||||
*/
|
||||
export async function createTxAndAttemptToSend(
|
||||
params: CreateTxAndAttemptToSendParams,
|
||||
) {
|
||||
const {
|
||||
senderAddress,
|
||||
senderPrivateKeys,
|
||||
|
||||
simplePriority,
|
||||
|
||||
feePerKB,
|
||||
networkFee,
|
||||
|
||||
updateStatus,
|
||||
api,
|
||||
} = params;
|
||||
|
||||
updateStatus(sendFundStatus.constructingTransaction);
|
||||
|
||||
const { numOfKB, serializedSignedTx, txHash } = constructTx(params);
|
||||
|
||||
const txFee = calculateFeeKb(
|
||||
feePerKB,
|
||||
numOfKB,
|
||||
multiplyFeePriority(simplePriority),
|
||||
);
|
||||
// if we need a higher fee
|
||||
if (txFee.compare(networkFee) > 0) {
|
||||
Log.Fee.estLowerThanReal(networkFee, txFee);
|
||||
return { success: false, txFee, txHash };
|
||||
}
|
||||
|
||||
// generated with correct per-kb fee
|
||||
Log.Fee.successfulTx(networkFee);
|
||||
updateStatus(sendFundStatus.submittingTransaction);
|
||||
|
||||
await api.submitSerializedSignedTransaction(
|
||||
senderAddress,
|
||||
senderPrivateKeys,
|
||||
serializedSignedTx,
|
||||
);
|
||||
|
||||
return { success: true, txFee: networkFee, txHash };
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from "./construct_tx_and_send";
|
@ -0,0 +1,65 @@
|
||||
import { WrappedNodeApi } from "../async_node_api";
|
||||
import {
|
||||
NetType,
|
||||
ViewSendKeys,
|
||||
ParsedTarget,
|
||||
Pid,
|
||||
Output,
|
||||
AmountOutput,
|
||||
} from "xmr-types";
|
||||
import { Status } from "../../status_update_constants";
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
export type GetFundTargetsAndFeeParams = {
|
||||
senderAddress: string;
|
||||
senderPublicKeys: ViewSendKeys;
|
||||
senderPrivateKeys: ViewSendKeys;
|
||||
|
||||
targetAddress: string;
|
||||
targetAmount: number;
|
||||
|
||||
mixin: number;
|
||||
unusedOuts: Output[];
|
||||
|
||||
simplePriority: number;
|
||||
feelessTotal: BigInt;
|
||||
feePerKB: BigInt; // obtained from server, so passed in
|
||||
networkFee: BigInt;
|
||||
|
||||
isSweeping: boolean;
|
||||
isRingCT: boolean;
|
||||
|
||||
updateStatus: (status: Status) => void;
|
||||
api: WrappedNodeApi;
|
||||
nettype: NetType;
|
||||
};
|
||||
|
||||
export type CreateTxAndAttemptToSendParams = {
|
||||
targetAddress: string;
|
||||
targetAmount: number;
|
||||
|
||||
senderAddress: string;
|
||||
senderPublicKeys: ViewSendKeys;
|
||||
senderPrivateKeys: ViewSendKeys;
|
||||
|
||||
fundTargets: ParsedTarget[];
|
||||
|
||||
pid: Pid; // unused
|
||||
encryptPid: boolean;
|
||||
|
||||
mixOuts?: AmountOutput[];
|
||||
mixin: number;
|
||||
usingOuts: Output[];
|
||||
|
||||
simplePriority: number;
|
||||
feelessTotal: BigInt;
|
||||
feePerKB: BigInt; // obtained from server, so passed in
|
||||
networkFee: BigInt;
|
||||
|
||||
isSweeping: boolean;
|
||||
isRingCT: boolean;
|
||||
|
||||
updateStatus: (status: Status) => void;
|
||||
api: WrappedNodeApi;
|
||||
nettype: NetType;
|
||||
};
|
@ -0,0 +1,79 @@
|
||||
import { BigInt } from "biginteger";
|
||||
import { formatMoney } from "xmr-money/formatters";
|
||||
import { config } from "xmr-constants/coin-config";
|
||||
|
||||
export namespace ERR {
|
||||
export namespace RING {
|
||||
export const INSUFF = Error("Ringsize is below the minimum.");
|
||||
}
|
||||
|
||||
export namespace MIXIN {
|
||||
export const INVAL = Error("Invalid mixin");
|
||||
}
|
||||
export namespace DEST {
|
||||
export const INVAL = Error("You need to enter a valid destination");
|
||||
}
|
||||
|
||||
export namespace AMT {
|
||||
export const INSUFF = Error("The amount you've entered is too low");
|
||||
}
|
||||
|
||||
export namespace PID {
|
||||
export const NO_INTEG_ADDR = Error(
|
||||
"Payment ID must be blank when using an Integrated Address",
|
||||
);
|
||||
export const NO_SUB_ADDR = Error(
|
||||
"Payment ID must be blank when using a Subaddress",
|
||||
);
|
||||
export const INVAL = Error("Invalid payment ID.");
|
||||
}
|
||||
|
||||
export namespace BAL {
|
||||
export function insuff(amtAvail: BigInt, requiredAmt: BigInt) {
|
||||
const { coinSymbol } = config;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace SWEEP {
|
||||
export const TOTAL_NEQ_OUTS = Error(
|
||||
"The sum of all outputs should be equal to the total amount for sweeping transactions",
|
||||
);
|
||||
}
|
||||
|
||||
export namespace TX {
|
||||
export function failure(err?: Error) {
|
||||
const errStr = err
|
||||
? err.toString()
|
||||
: "Failed to create transaction with unknown error.";
|
||||
|
||||
return Error(errStr);
|
||||
}
|
||||
export function submitUnknown(err: Error) {
|
||||
return Error(
|
||||
"Something unexpected occurred when submitting your transaction: " +
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace PARSE_TRGT {
|
||||
export const EMPTY = Error(
|
||||
"Please supply a target address and a target amount.",
|
||||
);
|
||||
export const OA_RES = Error(
|
||||
"You must resolve this OpenAlias address to a Monero address before calling SendFunds",
|
||||
);
|
||||
|
||||
export function decodeAddress(targetAddress: string, err: Error) {
|
||||
return Error(`Couldn't decode address ${targetAddress} : ${err}`);
|
||||
}
|
||||
|
||||
export function amount(targetAmount: string, err: Error) {
|
||||
return Error(`Couldn't parse amount ${targetAmount} : ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
export const DEFAULT_FEE_PRIORITY = 1;
|
||||
|
||||
export function calculateFee(
|
||||
feePerKB: BigInt,
|
||||
numOfBytes: number,
|
||||
feeMultiplier: number,
|
||||
) {
|
||||
const numberOf_kB = new BigInt((numOfBytes + 1023.0) / 1024.0); // i.e. ceil
|
||||
|
||||
return calculateFeeKb(feePerKB, numberOf_kB, feeMultiplier);
|
||||
}
|
||||
|
||||
export function calculateFeeKb(
|
||||
feePerKB: BigInt,
|
||||
numOfBytes: BigInt | number,
|
||||
feeMultiplier: number,
|
||||
) {
|
||||
const numberOf_kB = new BigInt(numOfBytes);
|
||||
const fee = feePerKB.multiply(feeMultiplier).multiply(numberOf_kB);
|
||||
|
||||
return fee;
|
||||
}
|
||||
|
||||
export function multiplyFeePriority(prio: number) {
|
||||
const fee_multiplier = [1, 4, 20, 166];
|
||||
|
||||
const priority = prio || DEFAULT_FEE_PRIORITY;
|
||||
|
||||
if (priority <= 0 || priority > fee_multiplier.length) {
|
||||
throw Error(
|
||||
"fee_multiplier_for_priority: simple_priority out of bounds",
|
||||
);
|
||||
}
|
||||
const priority_idx = priority - 1;
|
||||
return fee_multiplier[priority_idx];
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { NetType } from "xmr-types";
|
||||
import { Log } from "./logger";
|
||||
import { decode_address } from "xmr-address-utils";
|
||||
|
||||
export function getTargetPubViewKey(
|
||||
encPid: boolean,
|
||||
targetAddress: string,
|
||||
nettype: NetType,
|
||||
): string | undefined {
|
||||
if (encPid) {
|
||||
const key = decode_address(targetAddress, nettype).view;
|
||||
|
||||
Log.Target.viewKey(key);
|
||||
return key;
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
import { BigInt } from "biginteger";
|
||||
import {
|
||||
formatMoneySymbol,
|
||||
formatMoneyFullSymbol,
|
||||
formatMoney,
|
||||
formatMoneyFull,
|
||||
printDsts,
|
||||
} from "xmr-money/formatters";
|
||||
import { ParsedTarget, Output, SignedTransaction } from "xmr-types";
|
||||
|
||||
export namespace Log {
|
||||
export namespace Amount {
|
||||
export function beforeFee(feelessTotal: BigInt, isSweeping: boolean) {
|
||||
const feeless_total = isSweeping
|
||||
? "all"
|
||||
: formatMoney(feelessTotal);
|
||||
console.log(`💬 Total to send, before fee: ${feeless_total}`);
|
||||
}
|
||||
|
||||
export function change(changeAmount: BigInt) {
|
||||
console.log("changeAmount", changeAmount);
|
||||
}
|
||||
|
||||
export function changeAmountDivRem(amt: [BigInt, BigInt]) {
|
||||
console.log("💬 changeAmountDivRem", amt);
|
||||
}
|
||||
|
||||
export function toSelf(changeAmount: BigInt, selfAddress: string) {
|
||||
console.log(
|
||||
"Sending change of " +
|
||||
formatMoneySymbol(changeAmount) +
|
||||
" to " +
|
||||
selfAddress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Fee {
|
||||
export function dynPerKB(dynFeePerKB: BigInt) {
|
||||
console.log(
|
||||
"Received dynamic per kb fee",
|
||||
formatMoneySymbol(dynFeePerKB),
|
||||
);
|
||||
}
|
||||
export function basedOnInputs(
|
||||
newNeededFee: BigInt,
|
||||
usingOuts: Output[],
|
||||
) {
|
||||
console.log(
|
||||
"New fee: " +
|
||||
formatMoneySymbol(newNeededFee) +
|
||||
" for " +
|
||||
usingOuts.length +
|
||||
" inputs",
|
||||
);
|
||||
}
|
||||
export function belowDustThreshold(changeDivDustRemainder: BigInt) {
|
||||
console.log(
|
||||
"💬 Miners will add change of " +
|
||||
formatMoneyFullSymbol(changeDivDustRemainder) +
|
||||
" to transaction fee (below dust threshold)",
|
||||
);
|
||||
}
|
||||
|
||||
export function estLowerThanReal(
|
||||
estMinNetworkFee: BigInt,
|
||||
feeActuallyNeededByNetwork: BigInt,
|
||||
) {
|
||||
console.log(
|
||||
"💬 Need to reconstruct the tx with enough of a network fee. Previous fee: " +
|
||||
formatMoneyFull(estMinNetworkFee) +
|
||||
" New fee: " +
|
||||
formatMoneyFull(feeActuallyNeededByNetwork),
|
||||
);
|
||||
console.log("Reconstructing tx....");
|
||||
}
|
||||
|
||||
export function txKB(
|
||||
txBlobBytes: number,
|
||||
numOfKB: number,
|
||||
estMinNetworkFee: BigInt,
|
||||
) {
|
||||
console.log(
|
||||
txBlobBytes +
|
||||
" bytes <= " +
|
||||
numOfKB +
|
||||
" KB (current fee: " +
|
||||
formatMoneyFull(estMinNetworkFee) +
|
||||
")",
|
||||
);
|
||||
}
|
||||
|
||||
export function successfulTx(finalNetworkFee: BigInt) {
|
||||
console.log(
|
||||
"💬 Successful tx generation, submitting tx. Going with final_networkFee of ",
|
||||
formatMoney(finalNetworkFee),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Balance {
|
||||
export function requiredBase(totalAmount: BigInt, isSweeping: boolean) {
|
||||
if (isSweeping) {
|
||||
console.log("Balance required: all");
|
||||
} else {
|
||||
console.log(
|
||||
"Balance required: " + formatMoneySymbol(totalAmount),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function requiredPostRct(totalAmount: BigInt) {
|
||||
console.log(
|
||||
"~ Balance required: " + formatMoneySymbol(totalAmount),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Output {
|
||||
export function uniformity(fakeAddress: string) {
|
||||
console.log(
|
||||
"Sending 0 XMR to a fake address to keep tx uniform (no change exists): " +
|
||||
fakeAddress,
|
||||
);
|
||||
}
|
||||
|
||||
export function display(out: Output) {
|
||||
console.log(
|
||||
"Using output: " +
|
||||
formatMoney(out.amount) +
|
||||
" - " +
|
||||
JSON.stringify(out),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Target {
|
||||
export function display(fundTargets: ParsedTarget[]) {
|
||||
console.log("fundTransferDescriptions so far", fundTargets);
|
||||
}
|
||||
|
||||
export function fullDisplay(fundTargets: ParsedTarget[]) {
|
||||
console.log("Destinations: ");
|
||||
printDsts(fundTargets);
|
||||
}
|
||||
|
||||
export function displayDecomposed(splitDestinations: ParsedTarget[]) {
|
||||
console.log("Decomposed destinations:");
|
||||
printDsts(splitDestinations);
|
||||
}
|
||||
|
||||
export function viewKey(viewKey: string) {
|
||||
console.log("got target address's view key", viewKey);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Transaction {
|
||||
export function signed(signedTx: SignedTransaction) {
|
||||
console.log("signed tx: ", JSON.stringify(signedTx));
|
||||
}
|
||||
export function serializedAndHash(
|
||||
serializedTx: string,
|
||||
txHash: string,
|
||||
) {
|
||||
console.log("tx serialized: " + serializedTx);
|
||||
console.log("Tx hash: " + txHash);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace SelectOutsAndAmtForMix {
|
||||
export function target(targetAmount: BigInt) {
|
||||
console.log(
|
||||
"Selecting outputs to use. target: " +
|
||||
formatMoney(targetAmount),
|
||||
);
|
||||
}
|
||||
|
||||
export namespace Dusty {
|
||||
export function notSweeping() {
|
||||
console.log(
|
||||
"Not sweeping, and found a dusty (though maybe mixable) output... skipping it!",
|
||||
);
|
||||
}
|
||||
export function nonRct() {
|
||||
console.log(
|
||||
"Sweeping, and found a dusty but unmixable (non-rct) output... skipping it!",
|
||||
);
|
||||
}
|
||||
export function rct() {
|
||||
console.log(
|
||||
"Sweeping and found a dusty but mixable (rct) amount... keeping it!",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function usingOut(outAmount: BigInt, out: Output) {
|
||||
console.log(
|
||||
`Using output: ${formatMoney(outAmount)} - ${JSON.stringify(
|
||||
out,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export function possibleOAAddress(address: string) {
|
||||
return address.includes(".");
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { popRandElement } from "./arr_utils";
|
||||
import { Log } from "./logger";
|
||||
import { BigInt } from "biginteger";
|
||||
import { config } from "xmr-constants/coin-config";
|
||||
import { Output } from "xmr-types";
|
||||
|
||||
export function selectOutputsAndAmountForMixin(
|
||||
targetAmount: BigInt,
|
||||
unusedOuts: Output[],
|
||||
isRingCT: boolean,
|
||||
sweeping: boolean,
|
||||
) {
|
||||
Log.SelectOutsAndAmtForMix.target(targetAmount);
|
||||
|
||||
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 (
|
||||
usingOutsAmount.compare(targetAmount) < 0 &&
|
||||
remainingUnusedOuts.length > 0
|
||||
) {
|
||||
const out = popRandElement(remainingUnusedOuts);
|
||||
if (!isRingCT && out.rct) {
|
||||
// out.rct is set by the server
|
||||
continue; // skip rct outputs if not creating rct tx
|
||||
}
|
||||
const outAmount = new BigInt(out.amount);
|
||||
if (outAmount.compare(config.dustThreshold) < 0) {
|
||||
// amount is dusty..
|
||||
if (!sweeping) {
|
||||
Log.SelectOutsAndAmtForMix.Dusty.notSweeping();
|
||||
continue;
|
||||
}
|
||||
if (!out.rct) {
|
||||
Log.SelectOutsAndAmtForMix.Dusty.rct();
|
||||
continue;
|
||||
} else {
|
||||
Log.SelectOutsAndAmtForMix.Dusty.nonRct();
|
||||
}
|
||||
}
|
||||
usingOuts.push(out);
|
||||
usingOutsAmount = usingOutsAmount.add(outAmount);
|
||||
|
||||
Log.SelectOutsAndAmtForMix.usingOut(outAmount, out);
|
||||
}
|
||||
return {
|
||||
usingOuts,
|
||||
usingOutsAmount,
|
||||
remainingUnusedOuts,
|
||||
};
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { ParsedTarget, RawTarget, NetType } from "xmr-types";
|
||||
import { ERR } from "./errors";
|
||||
import { possibleOAAddress } from "./open_alias_lite";
|
||||
import { decode_address } from "xmr-address-utils";
|
||||
import { parseMoney } from "xmr-money/parsers";
|
||||
|
||||
/**
|
||||
* @description Map through the provided targets and normalize each address/amount pair
|
||||
*
|
||||
* Addresses are checked to see if they may belong to an OpenAlias address, and rejected if so.
|
||||
* Then they are validated by attempting to decode them.
|
||||
*
|
||||
* Amounts are attempted to be parsed from string value to BigInt value
|
||||
*
|
||||
* The validated address / parsed amount pairs are then returned
|
||||
*
|
||||
* @export
|
||||
* @param {RawTarget[]} targetsToParse
|
||||
* @param {NetType} nettype
|
||||
* @returns {ParsedTarget[]}
|
||||
*/
|
||||
export function parseTargets(
|
||||
targetsToParse: RawTarget[],
|
||||
nettype: NetType,
|
||||
): ParsedTarget[] {
|
||||
return targetsToParse.map(({ address, amount }) => {
|
||||
if (!address && !amount) {
|
||||
throw ERR.PARSE_TRGT.EMPTY;
|
||||
}
|
||||
|
||||
if (possibleOAAddress(address)) {
|
||||
throw ERR.PARSE_TRGT.OA_RES;
|
||||
}
|
||||
const amountStr = amount.toString();
|
||||
|
||||
try {
|
||||
decode_address(address, nettype);
|
||||
} catch (e) {
|
||||
throw ERR.PARSE_TRGT.decodeAddress(address, e);
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedAmount = parseMoney(amountStr);
|
||||
return { address, amount: parsedAmount };
|
||||
} catch (e) {
|
||||
throw ERR.PARSE_TRGT.amount(amountStr, e);
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import { isValidOrNoPaymentID } from "xmr-pid";
|
||||
import { ERR } from "./errors";
|
||||
import { decode_address, is_subaddress } from "xmr-address-utils";
|
||||
import { NetType } from "xmr-types";
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* Attempts to decode the provided address based on its nettype to break it down into its components
|
||||
* {pubSend, pubView, integratedPaymentId}
|
||||
*
|
||||
* Then based on the decoded values, determines if the payment ID (if supplied) should be encrypted or not.
|
||||
*
|
||||
* If a payment ID is not supplied, it may be grabbed from the integratedPaymentId component of the decoded
|
||||
* address if provided.
|
||||
*
|
||||
* At each step, invariants are enforced to prevent the following scenarios.
|
||||
*
|
||||
*
|
||||
* 1. Supplied PID + Integrated PID
|
||||
* 2. Supplied PID + Sending to subaddress
|
||||
* 3. Invalid supplied PID
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @param {string} address
|
||||
* @param {NetType} nettype
|
||||
* @param {(string | null)} pid
|
||||
*/
|
||||
export function checkAddressAndPidValidity(
|
||||
address: string,
|
||||
nettype: NetType,
|
||||
pid: string | null,
|
||||
) {
|
||||
let retPid = pid;
|
||||
let encryptPid = false;
|
||||
|
||||
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 (is_subaddress(address, nettype)) {
|
||||
throw ERR.PID.NO_SUB_ADDR;
|
||||
}
|
||||
}
|
||||
|
||||
// if the target address is integrated
|
||||
// then encrypt the payment id
|
||||
// and make sure its also valid
|
||||
if (decodedAddress.intPaymentId) {
|
||||
retPid = decodedAddress.intPaymentId;
|
||||
encryptPid = true;
|
||||
} else if (!isValidOrNoPaymentID(retPid)) {
|
||||
throw ERR.PID.INVAL;
|
||||
}
|
||||
|
||||
return { pid: retPid, encryptPid };
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from "./tx_utils";
|
@ -0,0 +1,414 @@
|
||||
import { getTargetPubViewKey } from "../key_utils";
|
||||
import {
|
||||
TotalAmtAndEstFeeParams,
|
||||
ConstructTxParams,
|
||||
EstRctFeeAndAmtParams,
|
||||
ConstructFundTargetsParams,
|
||||
} from "./types";
|
||||
import { ERR } from "../errors";
|
||||
import { Log } from "../logger";
|
||||
import { popRandElement } from "../arr_utils";
|
||||
import { calculateFee, multiplyFeePriority } from "../fee_utils";
|
||||
import { BigInt } from "biginteger";
|
||||
import {
|
||||
serialize_rct_tx_with_hash,
|
||||
estimateRctSize,
|
||||
} from "xmr-transaction/libs/ringct";
|
||||
import { serialize_non_rct_tx } from "xmr-transaction/libs/non-ringct/serialization";
|
||||
import { cn_fast_hash } from "xmr-fast-hash";
|
||||
import { SignedTransaction, ParsedTarget } from "xmr-types";
|
||||
import { create_transaction } from "xmr-transaction";
|
||||
import { create_address } from "xmr-address-utils";
|
||||
import { random_scalar } from "xmr-rand";
|
||||
import { config } from "xmr-constants/coin-config";
|
||||
import { decompose_tx_destinations } from "xmr-money/parsers";
|
||||
|
||||
// #region totalAmtAndEstFee
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Recalculates the fee and total amount needed for the transaction to be sent. RCT + non sweeping transactions will have their
|
||||
* network fee increased if fee calculation based on the number of outputs needed is higher than the passed-in fee. RCT+ sweeping transactions
|
||||
* are just checked if they have enough balance to proceed with the transaction. Non-RCT transactions will have no fee recalculation done on them.
|
||||
|
||||
* @export
|
||||
* @param {TotalAmtAndEstFeeParams} params
|
||||
* @returns
|
||||
*/
|
||||
export function totalAmtAndEstFee(params: TotalAmtAndEstFeeParams) {
|
||||
const {
|
||||
baseTotalAmount,
|
||||
|
||||
networkFee,
|
||||
|
||||
isRingCT,
|
||||
|
||||
usingOuts,
|
||||
} = params;
|
||||
|
||||
if (!isRingCT) {
|
||||
return { newFee: networkFee, totalAmount: baseTotalAmount };
|
||||
}
|
||||
|
||||
/* if (usingOuts.length > 1 && isRingCT )*/
|
||||
const { newFee, totalAmount } = estRctFeeAndAmt(params);
|
||||
|
||||
Log.Fee.basedOnInputs(newFee, usingOuts);
|
||||
|
||||
return { newFee, totalAmount };
|
||||
}
|
||||
|
||||
function estRctFeeAndAmt(params: EstRctFeeAndAmtParams) {
|
||||
const {
|
||||
mixin,
|
||||
|
||||
usingOuts,
|
||||
usingOutsAmount,
|
||||
|
||||
simplePriority,
|
||||
|
||||
feePerKB, // obtained from server, so passed in
|
||||
networkFee,
|
||||
isSweeping,
|
||||
} = params;
|
||||
|
||||
let feeBasedOnOuts = calculateFee(
|
||||
feePerKB,
|
||||
estimateRctSize(usingOuts.length, mixin, 2),
|
||||
multiplyFeePriority(simplePriority),
|
||||
);
|
||||
|
||||
// if newNeededFee < neededFee, use neededFee instead
|
||||
//(should only happen on the 2nd or later times through(due to estimated fee being too low))
|
||||
if (feeBasedOnOuts.compare(networkFee) < 0) {
|
||||
feeBasedOnOuts = networkFee;
|
||||
}
|
||||
|
||||
const [totalAmount, newFee] = isSweeping
|
||||
? estRctSwpingAmt(usingOutsAmount, feeBasedOnOuts)
|
||||
: estRctNonSwpAmt(params, feeBasedOnOuts);
|
||||
|
||||
return { totalAmount, newFee };
|
||||
}
|
||||
|
||||
function estRctSwpingAmt(usingOutsAmount: BigInt, fee: BigInt) {
|
||||
/*
|
||||
// When/if sending to multiple destinations supported, uncomment and port this:
|
||||
if (dsts.length !== 1) {
|
||||
deferred.reject("Sweeping to multiple accounts is not allowed");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
// feeless total is equivalent to all outputs (since its a sweeping tx)
|
||||
// subtracted from the newNeededFee (either from min tx cost or calculated cost based on outputs)
|
||||
const _feelessTotal = usingOutsAmount.subtract(fee);
|
||||
|
||||
// if the feeless total is less than 0 (so sum of all outputs is still less than network fee)
|
||||
// then reject tx
|
||||
if (_feelessTotal.compare(0) < 1) {
|
||||
throw ERR.BAL.insuff(usingOutsAmount, fee);
|
||||
}
|
||||
|
||||
// otherwise make the total amount the feeless total + the new fee
|
||||
const totalAmount = _feelessTotal.add(fee);
|
||||
|
||||
return [totalAmount, fee];
|
||||
}
|
||||
|
||||
function estRctNonSwpAmt(params: EstRctFeeAndAmtParams, fee: BigInt) {
|
||||
const {
|
||||
mixin,
|
||||
remainingUnusedOuts,
|
||||
usingOuts,
|
||||
usingOutsAmount,
|
||||
|
||||
simplePriority,
|
||||
feelessTotal,
|
||||
feePerKB, // obtained from server, so passed in
|
||||
} = params;
|
||||
|
||||
// make the current total amount equivalent to the feeless total and the new needed fee
|
||||
let currTotalAmount = feelessTotal.add(fee);
|
||||
|
||||
// add outputs 1 at a time till we either have them all or can meet the fee
|
||||
|
||||
// this case can happen when the fee calculated via outs size
|
||||
// is greater than the minimum tx fee size,
|
||||
// requiring a higher fee, so more outputs (if available)
|
||||
// need to be selected to fufill the difference
|
||||
|
||||
let newFee = fee;
|
||||
while (
|
||||
usingOutsAmount.compare(currTotalAmount) < 0 &&
|
||||
remainingUnusedOuts.length > 0
|
||||
) {
|
||||
const out = popRandElement(remainingUnusedOuts);
|
||||
|
||||
Log.Output.display(out);
|
||||
|
||||
// and recalculate invalidated values
|
||||
newFee = calculateFee(
|
||||
feePerKB,
|
||||
estimateRctSize(usingOuts.length, mixin, 2),
|
||||
multiplyFeePriority(simplePriority),
|
||||
);
|
||||
currTotalAmount = feelessTotal.add(newFee);
|
||||
}
|
||||
|
||||
const totalAmount = currTotalAmount;
|
||||
return [totalAmount, newFee];
|
||||
}
|
||||
|
||||
// #endregion totalAmtAndEstFee
|
||||
|
||||
// #region validateAndConstructFundTargets
|
||||
|
||||
/**
|
||||
* @description
|
||||
* 1. Validates the total amount needed for the tx against the available amounts via the sum of all outputs
|
||||
* to see if the sender has sufficient funds.
|
||||
*
|
||||
* 2. Then, a list of sending targets will be constructed, always consisting of the target address and amount they want to send to, and possibly a change address,
|
||||
* if the sum of outs is greater than the amount sent + fee needed, and possibly a fake address + 0 amount to keep output uniformity if no change address
|
||||
* was generated.
|
||||
*
|
||||
* @export
|
||||
* @param {ConstructFundTargetsParams} params
|
||||
* @returns
|
||||
*/
|
||||
export function validateAndConstructFundTargets(
|
||||
params: ConstructFundTargetsParams,
|
||||
) {
|
||||
const {
|
||||
senderAddress,
|
||||
targetAddress,
|
||||
|
||||
feelessTotal,
|
||||
totalAmount,
|
||||
usingOutsAmount,
|
||||
|
||||
isSweeping,
|
||||
isRingCT,
|
||||
|
||||
nettype,
|
||||
} = params;
|
||||
|
||||
// Now we can validate available balance with usingOutsAmount (TODO? maybe this check can be done before selecting outputs?)
|
||||
const outsCmpToTotalAmounts = usingOutsAmount.compare(totalAmount);
|
||||
const outsLessThanTotal = outsCmpToTotalAmounts < 0;
|
||||
const outsGreaterThanTotal = outsCmpToTotalAmounts > 0;
|
||||
const outsEqualToTotal = outsCmpToTotalAmounts === 0;
|
||||
|
||||
// what follows is comparision of the sum of outs amounts
|
||||
// vs the total amount actually needed
|
||||
// while also building up a list of addresses to send to
|
||||
// along with the amounts
|
||||
if (outsLessThanTotal) {
|
||||
throw ERR.BAL.insuff(usingOutsAmount, totalAmount);
|
||||
}
|
||||
|
||||
// Now we can put together the list of fund transfers we need to perform
|
||||
// excluding the tx fee
|
||||
// since that is included in the tx in its own field
|
||||
const fundTargets: ParsedTarget[] = []; // to build…
|
||||
// I. the actual transaction the user is asking to do
|
||||
fundTargets.push({
|
||||
address: targetAddress,
|
||||
amount: feelessTotal,
|
||||
});
|
||||
|
||||
// the fee that the hosting provider charges
|
||||
// NOTE: The fee has been removed for RCT until a later date
|
||||
// fundTransferDescriptions.push({
|
||||
// address: hostedMoneroAPIClient.HostingServiceFeeDepositAddress(),
|
||||
// amount: hostingService_chargeAmount
|
||||
// })
|
||||
|
||||
// some amount of the total outputs will likely need to be returned to the user as "change":
|
||||
if (outsGreaterThanTotal) {
|
||||
if (isSweeping) {
|
||||
throw ERR.SWEEP.TOTAL_NEQ_OUTS;
|
||||
}
|
||||
// where the change amount is whats left after sending to other addresses + fee
|
||||
const changeAmount = usingOutsAmount.subtract(totalAmount);
|
||||
|
||||
Log.Amount.change(changeAmount);
|
||||
|
||||
if (isRingCT) {
|
||||
// for RCT we don't presently care about dustiness so add entire change amount
|
||||
Log.Amount.toSelf(changeAmount, senderAddress);
|
||||
|
||||
fundTargets.push({
|
||||
address: senderAddress,
|
||||
amount: changeAmount,
|
||||
});
|
||||
} else {
|
||||
// pre-ringct
|
||||
// do not give ourselves change < dust threshold
|
||||
const [quotient, remainder] = changeAmount.divRem(
|
||||
config.dustThreshold,
|
||||
);
|
||||
|
||||
Log.Amount.changeAmountDivRem([quotient, remainder]);
|
||||
|
||||
if (!remainder.isZero()) {
|
||||
// miners will add dusty change to fee
|
||||
Log.Fee.belowDustThreshold(remainder);
|
||||
}
|
||||
|
||||
if (!quotient.isZero()) {
|
||||
// send non-dusty change to our address
|
||||
const usableChange = quotient.multiply(config.dustThreshold);
|
||||
|
||||
Log.Amount.toSelf(usableChange, senderAddress);
|
||||
|
||||
fundTargets.push({
|
||||
address: senderAddress,
|
||||
amount: usableChange,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (outsEqualToTotal) {
|
||||
// if outputs are equivalent to the total amount
|
||||
// this should always fire when sweeping
|
||||
// since we want to spend all outputs anyway
|
||||
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 = create_address(random_scalar(), nettype)
|
||||
.public_addr;
|
||||
|
||||
Log.Output.uniformity(fakeAddress);
|
||||
|
||||
fundTargets.push({
|
||||
address: fakeAddress,
|
||||
amount: BigInt.ZERO,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { fundTargets };
|
||||
}
|
||||
|
||||
// #endregion validateAndConstructFundTargets
|
||||
|
||||
//#region constructTx
|
||||
|
||||
export function constructTx(params: ConstructTxParams) {
|
||||
const { signedTx } = makeSignedTx(params);
|
||||
const { serializedSignedTx, txHash } = getSerializedTxAndHash(signedTx);
|
||||
const { numOfKB } = getTxSize(serializedSignedTx, params.networkFee);
|
||||
|
||||
return { numOfKB, txHash, serializedSignedTx };
|
||||
}
|
||||
|
||||
function makeSignedTx(params: ConstructTxParams) {
|
||||
try {
|
||||
const {
|
||||
senderPublicKeys,
|
||||
senderPrivateKeys,
|
||||
|
||||
targetAddress,
|
||||
fundTargets,
|
||||
|
||||
pid,
|
||||
encryptPid,
|
||||
|
||||
mixOuts,
|
||||
mixin,
|
||||
usingOuts,
|
||||
|
||||
networkFee,
|
||||
|
||||
isRingCT,
|
||||
|
||||
nettype,
|
||||
} = params;
|
||||
|
||||
Log.Target.fullDisplay(fundTargets);
|
||||
|
||||
const targetViewKey = getTargetPubViewKey(
|
||||
encryptPid,
|
||||
targetAddress,
|
||||
nettype,
|
||||
);
|
||||
|
||||
const splitDestinations: ParsedTarget[] = decompose_tx_destinations(
|
||||
fundTargets,
|
||||
isRingCT,
|
||||
);
|
||||
Log.Target.displayDecomposed(splitDestinations);
|
||||
|
||||
const signedTx = create_transaction(
|
||||
senderPublicKeys,
|
||||
senderPrivateKeys,
|
||||
splitDestinations,
|
||||
usingOuts,
|
||||
mixOuts,
|
||||
mixin,
|
||||
networkFee,
|
||||
pid,
|
||||
encryptPid,
|
||||
targetViewKey,
|
||||
0,
|
||||
isRingCT,
|
||||
nettype,
|
||||
);
|
||||
|
||||
Log.Transaction.signed(signedTx);
|
||||
|
||||
return { signedTx };
|
||||
} catch (e) {
|
||||
throw ERR.TX.failure(e);
|
||||
}
|
||||
}
|
||||
|
||||
function getSerializedTxAndHash(signedTx: SignedTransaction) {
|
||||
type ReturnVal = {
|
||||
serializedSignedTx: string;
|
||||
txHash: string;
|
||||
};
|
||||
|
||||
// pre rct
|
||||
if (signedTx.version === 1) {
|
||||
const serializedSignedTx = serialize_non_rct_tx(signedTx);
|
||||
const txHash = cn_fast_hash(serializedSignedTx);
|
||||
|
||||
const ret: ReturnVal = {
|
||||
serializedSignedTx,
|
||||
txHash,
|
||||
};
|
||||
|
||||
Log.Transaction.serializedAndHash(serializedSignedTx, txHash);
|
||||
|
||||
return ret;
|
||||
}
|
||||
// rct
|
||||
else {
|
||||
const { raw, hash } = serialize_rct_tx_with_hash(signedTx);
|
||||
|
||||
const ret: ReturnVal = {
|
||||
serializedSignedTx: raw,
|
||||
txHash: hash,
|
||||
};
|
||||
|
||||
Log.Transaction.serializedAndHash(raw, hash);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (txBlobBytes % 1024) {
|
||||
numOfKB++;
|
||||
}
|
||||
|
||||
Log.Fee.txKB(txBlobBytes, numOfKB, estMinNetworkFee);
|
||||
return { numOfKB };
|
||||
}
|
||||
|
||||
// #endregion constructTx
|
@ -0,0 +1,75 @@
|
||||
import {
|
||||
NetType,
|
||||
ParsedTarget,
|
||||
Pid,
|
||||
ViewSendKeys,
|
||||
Output,
|
||||
AmountOutput,
|
||||
} from "xmr-types";
|
||||
import { BigInt } from "biginteger";
|
||||
|
||||
export type ConstructTxParams = {
|
||||
senderPublicKeys: ViewSendKeys;
|
||||
senderPrivateKeys: ViewSendKeys;
|
||||
|
||||
targetAddress: string;
|
||||
fundTargets: ParsedTarget[];
|
||||
|
||||
pid: Pid;
|
||||
encryptPid: boolean;
|
||||
|
||||
mixOuts?: AmountOutput[];
|
||||
mixin: number;
|
||||
usingOuts: Output[];
|
||||
|
||||
networkFee: BigInt;
|
||||
|
||||
isRingCT: boolean;
|
||||
|
||||
nettype: NetType;
|
||||
};
|
||||
|
||||
export type TotalAmtAndEstFeeParams = {
|
||||
usingOutsAmount: BigInt;
|
||||
baseTotalAmount: BigInt;
|
||||
|
||||
mixin: number;
|
||||
remainingUnusedOuts: Output[];
|
||||
usingOuts: Output[];
|
||||
|
||||
simplePriority: number;
|
||||
feelessTotal: BigInt;
|
||||
feePerKB: BigInt; // obtained from server, so passed in
|
||||
networkFee: BigInt;
|
||||
|
||||
isSweeping: boolean;
|
||||
isRingCT: boolean;
|
||||
};
|
||||
|
||||
export type EstRctFeeAndAmtParams = {
|
||||
mixin: number;
|
||||
usingOutsAmount: BigInt;
|
||||
remainingUnusedOuts: Output[];
|
||||
usingOuts: Output[];
|
||||
|
||||
simplePriority: number;
|
||||
feelessTotal: BigInt;
|
||||
feePerKB: BigInt; // obtained from server, so passed in
|
||||
networkFee: BigInt;
|
||||
|
||||
isSweeping: boolean;
|
||||
};
|
||||
|
||||
export type ConstructFundTargetsParams = {
|
||||
senderAddress: string;
|
||||
targetAddress: string;
|
||||
|
||||
feelessTotal: BigInt;
|
||||
totalAmount: BigInt;
|
||||
usingOutsAmount: BigInt;
|
||||
|
||||
isSweeping: boolean;
|
||||
isRingCT: boolean;
|
||||
|
||||
nettype: NetType;
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
export const V7_MIN_MIXIN = 6;
|
||||
|
||||
function _mixinToRingsize(mixin: number) {
|
||||
return mixin + 1;
|
||||
}
|
||||
|
||||
export function minMixin() {
|
||||
return V7_MIN_MIXIN;
|
||||
}
|
||||
export function minRingSize() {
|
||||
return _mixinToRingsize(minMixin());
|
||||
}
|
||||
|
||||
export function fixedMixin() {
|
||||
return minMixin(); /* using the monero app default to remove MM user identifiers */
|
||||
}
|
||||
export function fixedRingsize() {
|
||||
return _mixinToRingsize(fixedMixin());
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
import {
|
||||
calculateFee,
|
||||
multiplyFeePriority,
|
||||
calculateFeeKb,
|
||||
} from "./internal_libs/fee_utils";
|
||||
import { minMixin } from "./mixin_utils";
|
||||
import { Status, sendFundStatus } from "./status_update_constants";
|
||||
import { ERR } from "./internal_libs/errors";
|
||||
import { Log } from "./internal_libs/logger";
|
||||
import { parseTargets } from "./internal_libs/parse_target";
|
||||
import { checkAddressAndPidValidity } from "./internal_libs/pid_utils";
|
||||
import { WrappedNodeApi } from "./internal_libs/async_node_api";
|
||||
import {
|
||||
getRestOfTxData,
|
||||
createTxAndAttemptToSend,
|
||||
} from "./internal_libs/construct_tx_and_send";
|
||||
import { BigInt } from "biginteger";
|
||||
import { estimateRctSize } from "xmr-transaction/libs/ringct";
|
||||
import { formatMoneyFull } from "xmr-money/formatters";
|
||||
import { NetType, RawTarget, Pid, ViewSendKeys } from "xmr-types";
|
||||
|
||||
export function estimatedTransactionNetworkFee(
|
||||
nonZeroMixin: number,
|
||||
feePerKB: BigInt,
|
||||
simplePriority: number,
|
||||
) {
|
||||
const numOfInputs = 2; // this might change -- might select inputs
|
||||
const numOfOutputs =
|
||||
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 = estimateRctSize(
|
||||
numOfInputs,
|
||||
nonZeroMixin,
|
||||
numOfOutputs,
|
||||
);
|
||||
const estFee = calculateFee(
|
||||
feePerKB,
|
||||
estimatedTxSize,
|
||||
multiplyFeePriority(simplePriority),
|
||||
);
|
||||
|
||||
return estFee;
|
||||
}
|
||||
|
||||
export type SendFundsRet = {
|
||||
targetAddress: string;
|
||||
sentAmount: number;
|
||||
pid: Pid;
|
||||
txHash: string;
|
||||
txFee: BigInt;
|
||||
};
|
||||
|
||||
export async function SendFunds(
|
||||
targetAddress: string, // currency-ready wallet address, but not an OpenAlias address (resolve before calling)
|
||||
nettype: NetType,
|
||||
amountorZeroWhenSweep: number, // n value will be ignored for sweep
|
||||
isSweeping: boolean, // send true to sweep - amountorZeroWhenSweep will be ignored
|
||||
senderAddress: string,
|
||||
senderPrivateKeys: ViewSendKeys,
|
||||
senderPublicKeys: ViewSendKeys,
|
||||
nodeAPI: any, // TODO: possibly factor this dependency
|
||||
pidToParse: Pid,
|
||||
mixin: number,
|
||||
simplePriority: number,
|
||||
updateStatus: (status: Status) => void,
|
||||
): Promise<SendFundsRet> {
|
||||
const api = new WrappedNodeApi(nodeAPI);
|
||||
const isRingCT = true;
|
||||
|
||||
if (mixin < minMixin()) {
|
||||
throw ERR.RING.INSUFF;
|
||||
}
|
||||
|
||||
// parse & normalize the target descriptions by mapping them to Monero addresses & amounts
|
||||
const targetAmount = isSweeping ? 0 : amountorZeroWhenSweep;
|
||||
const target: RawTarget = {
|
||||
address: targetAddress,
|
||||
amount: targetAmount,
|
||||
};
|
||||
const [singleTarget] = parseTargets(
|
||||
[target], // requires a list of descriptions - but SendFunds was
|
||||
// not written with multiple target support as MyMonero does not yet support it
|
||||
nettype,
|
||||
);
|
||||
|
||||
if (!singleTarget) {
|
||||
throw ERR.DEST.INVAL;
|
||||
}
|
||||
|
||||
const { address, amount } = singleTarget;
|
||||
const feelessTotal = new BigInt(amount);
|
||||
|
||||
Log.Amount.beforeFee(feelessTotal, isSweeping);
|
||||
|
||||
if (!isSweeping && feelessTotal.compare(0) <= 0) {
|
||||
throw ERR.AMT.INSUFF;
|
||||
}
|
||||
|
||||
const pidData = checkAddressAndPidValidity(address, nettype, pidToParse);
|
||||
|
||||
updateStatus(sendFundStatus.fetchingLatestBalance);
|
||||
|
||||
const { dynamicFeePerKB, unusedOuts } = await api.unspentOuts(
|
||||
senderAddress,
|
||||
senderPrivateKeys,
|
||||
senderPublicKeys,
|
||||
|
||||
mixin,
|
||||
|
||||
isSweeping,
|
||||
);
|
||||
|
||||
const feePerKB = dynamicFeePerKB;
|
||||
// Transaction will need at least 1KB fee (or 13KB for RingCT)
|
||||
const minNetworkTxSizeKb = /*isRingCT ? */ 13; /* : 1*/
|
||||
const estMinNetworkFee = calculateFeeKb(
|
||||
feePerKB,
|
||||
minNetworkTxSizeKb,
|
||||
multiplyFeePriority(simplePriority),
|
||||
);
|
||||
|
||||
// construct commonly used parameters
|
||||
const senderkeys = {
|
||||
senderAddress,
|
||||
senderPublicKeys,
|
||||
senderPrivateKeys,
|
||||
};
|
||||
|
||||
const targetData = {
|
||||
targetAddress,
|
||||
targetAmount,
|
||||
};
|
||||
|
||||
const feeMeta = {
|
||||
simplePriority,
|
||||
feelessTotal,
|
||||
feePerKB, // obtained from server, so passed in
|
||||
};
|
||||
|
||||
const txMeta = {
|
||||
isRingCT,
|
||||
isSweeping,
|
||||
nettype,
|
||||
};
|
||||
|
||||
const externApis = {
|
||||
updateStatus,
|
||||
api,
|
||||
};
|
||||
|
||||
// begin the network fee with the smallest fee possible
|
||||
let networkFee = estMinNetworkFee;
|
||||
|
||||
// this loop should only execute at most twice
|
||||
// 1st execution to generate the inital transaction
|
||||
// 2nd execution if the initial transaction's fee is greater than
|
||||
// what the predicted tx fee would be
|
||||
while (true) {
|
||||
// now we're going to try using this minimum fee but the function will be called again
|
||||
// if we find after constructing the whole tx that it is larger in kb than
|
||||
// the minimum fee we're attempting to send it off with
|
||||
const {
|
||||
mixOuts,
|
||||
fundTargets,
|
||||
newFee,
|
||||
usingOuts,
|
||||
} = await getRestOfTxData({
|
||||
...senderkeys,
|
||||
...targetData,
|
||||
|
||||
mixin,
|
||||
unusedOuts,
|
||||
|
||||
...feeMeta,
|
||||
networkFee,
|
||||
|
||||
...txMeta,
|
||||
...externApis,
|
||||
});
|
||||
networkFee = newFee; // reassign network fee to the new fee returned
|
||||
|
||||
const { txFee, txHash, success } = await createTxAndAttemptToSend({
|
||||
...senderkeys,
|
||||
...targetData,
|
||||
fundTargets,
|
||||
...pidData,
|
||||
|
||||
mixin,
|
||||
mixOuts,
|
||||
usingOuts,
|
||||
|
||||
...feeMeta,
|
||||
networkFee,
|
||||
|
||||
...txMeta,
|
||||
...externApis,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
const sentAmount = isSweeping
|
||||
? parseFloat(formatMoneyFull(feelessTotal))
|
||||
: targetAmount;
|
||||
|
||||
return {
|
||||
pid: pidData.pid,
|
||||
sentAmount,
|
||||
targetAddress,
|
||||
txFee,
|
||||
txHash,
|
||||
};
|
||||
} else {
|
||||
// if the function call failed
|
||||
// means that we need a higher fee that was returned
|
||||
// so reassign network fee to it
|
||||
networkFee = txFee;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
export const sendFundStatus = {
|
||||
fetchingLatestBalance: 1,
|
||||
calculatingFee: 2,
|
||||
fetchingDecoyOutputs: 3, // may get skipped if 0 mixin
|
||||
constructingTransaction: 4, // may go back to .calculatingFee
|
||||
submittingTransaction: 5,
|
||||
};
|
||||
|
||||
export const sendFundsStatusToMessage = {
|
||||
1: "Fetching latest balance.",
|
||||
2: "Calculating fee.",
|
||||
3: "Fetching decoy outputs.",
|
||||
4: "Constructing transaction.", // may go back to .calculatingFee
|
||||
5: "Submitting transaction.",
|
||||
};
|
||||
|
||||
export type Status = typeof sendFundStatus[keyof typeof sendFundStatus];
|
@ -0,0 +1,170 @@
|
||||
import { config } from "xmr-constants/coin-config";
|
||||
import { NetType } from "xmr-types";
|
||||
import { possibleOAAddress } from "../mymonero-send-tx/internal_libs/open_alias_lite";
|
||||
import { decode_address } from "xmr-address-utils";
|
||||
import { Omit } from "types";
|
||||
|
||||
// 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.
|
||||
|
||||
export enum URITypes {
|
||||
addressAsFirstPathComponent = 1,
|
||||
addressAsAuthority = 2,
|
||||
}
|
||||
|
||||
// we can stricten this typing using
|
||||
// a discriminate union later
|
||||
// since the TURITypes determines the nullity of the value
|
||||
type FundRequestPayload = {
|
||||
address: string;
|
||||
payment_id?: string | null;
|
||||
amount?: string | null;
|
||||
amountCcySymbol?: string | null;
|
||||
description?: string | null;
|
||||
message?: string | null;
|
||||
uriType: URITypes;
|
||||
};
|
||||
|
||||
export function encodeFundRequest(args: FundRequestPayload) {
|
||||
const address = args.address;
|
||||
if (!address) {
|
||||
throw Error("missing address");
|
||||
}
|
||||
|
||||
let mutable_uri = config.coinUriPrefix;
|
||||
|
||||
const uriType = args.uriType;
|
||||
if (uriType === URITypes.addressAsAuthority) {
|
||||
mutable_uri += "//"; // use for inserting a // so data detectors pick it up…
|
||||
} else if (uriType === URITypes.addressAsFirstPathComponent) {
|
||||
// nothing to do
|
||||
} else {
|
||||
throw Error("Illegal args.uriType");
|
||||
}
|
||||
|
||||
mutable_uri += address;
|
||||
let queryParamStart = true;
|
||||
|
||||
type ParamName =
|
||||
| "tx_amount"
|
||||
| "tx_amount_ccy"
|
||||
| "tx_description"
|
||||
| "tx_payment_id"
|
||||
| "tx_message";
|
||||
|
||||
function addParam(name: ParamName, value?: string | null) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (queryParamStart) {
|
||||
queryParamStart = false;
|
||||
}
|
||||
|
||||
mutable_uri += queryParamStart ? "?" : "&";
|
||||
mutable_uri += name + "=" + encodeURIComponent(value);
|
||||
}
|
||||
|
||||
addParam("tx_amount", args.amount);
|
||||
|
||||
const shouldAddCcySym =
|
||||
(args.amountCcySymbol || "").toLowerCase() !==
|
||||
config.coinSymbol.toLowerCase();
|
||||
if (shouldAddCcySym) {
|
||||
addParam("tx_amount_ccy", args.amountCcySymbol);
|
||||
}
|
||||
|
||||
addParam("tx_description", args.description);
|
||||
addParam("tx_payment_id", args.payment_id);
|
||||
addParam("tx_message", args.message);
|
||||
|
||||
return mutable_uri;
|
||||
}
|
||||
|
||||
type DecodeFundRequestPayload = Omit<FundRequestPayload, "uriType">;
|
||||
|
||||
export function decodeFundRequest(
|
||||
str: string,
|
||||
nettype: NetType,
|
||||
): DecodeFundRequestPayload {
|
||||
// detect no-scheme moneroAddr and possible OA addr - if has no monero: prefix
|
||||
|
||||
if (!str.startsWith(config.coinUriPrefix)) {
|
||||
if (str.includes("?")) {
|
||||
// fairly sure this is correct.. (just an extra failsafe/filter)
|
||||
throw Error("Unrecognized URI format");
|
||||
}
|
||||
|
||||
if (possibleOAAddress(str)) {
|
||||
return {
|
||||
address: str,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
decode_address(str, nettype);
|
||||
} catch (e) {
|
||||
throw Error("No Monero request info");
|
||||
}
|
||||
|
||||
// then it looks like a monero address
|
||||
return {
|
||||
address: str,
|
||||
};
|
||||
}
|
||||
|
||||
const url = new window.URL(str);
|
||||
|
||||
const protocol = url.protocol;
|
||||
if (protocol !== config.coinUriPrefix) {
|
||||
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
|
||||
let target_address = url.pathname || url.host || url.hostname;
|
||||
|
||||
if (target_address.startsWith("//")) {
|
||||
target_address = target_address.slice("//".length); // strip prefixing "//" in case URL had protocol:// instead of protocol:
|
||||
}
|
||||
|
||||
const searchParams = url.searchParams;
|
||||
|
||||
const payload: DecodeFundRequestPayload = {
|
||||
address: target_address,
|
||||
};
|
||||
|
||||
const keyPrefixToTrim = "tx_";
|
||||
(searchParams as any).forEach((value: string, key: string) => {
|
||||
const index = key.startsWith(keyPrefixToTrim)
|
||||
? key.slice(keyPrefixToTrim.length, key.length)
|
||||
: key;
|
||||
|
||||
payload[index as keyof DecodeFundRequestPayload] = value;
|
||||
});
|
||||
|
||||
return payload;
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import { rand_8 } from "xmr-rand";
|
||||
import { cn_fast_hash } from "xmr-fast-hash";
|
||||
import { INTEGRATED_ID_SIZE } from "xmr-constants/address";
|
||||
import { generate_key_derivation } from "xmr-crypto-ops/derivation";
|
||||
import { hex_xor } from "xmr-str-utils/hex-strings";
|
||||
|
||||
// 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.
|
||||
|
||||
export function makePaymentID() {
|
||||
return rand_8();
|
||||
}
|
||||
|
||||
export function encrypt_payment_id(
|
||||
payment_id: string,
|
||||
public_key: string,
|
||||
secret_key: string,
|
||||
) {
|
||||
// get the derivation from our passed viewkey, then hash that + tail to get encryption key
|
||||
const INTEGRATED_ID_SIZE_BYTES = INTEGRATED_ID_SIZE * 2;
|
||||
const ENCRYPTED_PAYMENT_ID_TAIL_BYTE = "8d";
|
||||
|
||||
const derivation = generate_key_derivation(public_key, secret_key);
|
||||
const data = `${derivation}${ENCRYPTED_PAYMENT_ID_TAIL_BYTE}`;
|
||||
const pid_key = cn_fast_hash(data).slice(0, INTEGRATED_ID_SIZE_BYTES);
|
||||
|
||||
const encryptedPid = hex_xor(payment_id, pid_key);
|
||||
|
||||
return encryptedPid;
|
||||
}
|
||||
|
||||
export function isValidOrNoPaymentID(pid?: string | null) {
|
||||
if (!pid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isValidShortPaymentID(pid) || isValidLongPaymentID(pid);
|
||||
}
|
||||
|
||||
export function isValidShortPaymentID(payment_id: string) {
|
||||
return isValidPaymentID(payment_id, 16);
|
||||
}
|
||||
|
||||
export function isValidLongPaymentID(payment_id: string) {
|
||||
console.warn("[WARN]: Long payment (plaintext) ids are deprecated");
|
||||
return isValidPaymentID(payment_id, 64);
|
||||
}
|
||||
|
||||
function isValidPaymentID(payment_id: string, length: 16 | 64) {
|
||||
const pattern = RegExp("^[0-9a-fA-F]{" + length + "}$");
|
||||
return pattern.test(payment_id);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { sc_reduce32 } from "xmr-crypto-ops/primitive_ops";
|
||||
|
||||
// Generate a 256-bit / 64-char / 32-byte crypto random
|
||||
export 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");
|
||||
}
|
||||
|
||||
// Random 32-byte ec scalar
|
||||
export function random_scalar() {
|
||||
return sc_reduce32(rand_32());
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
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
|
||||
export 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);
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
|
||||
export 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("");
|
||||
}
|
||||
|
||||
//switch byte order for hex string
|
||||
export 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;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import * as hexUtils from "./hex-strings";
|
||||
import * as intStrUtils from "./integer-strings";
|
||||
import * as stdUtils from "./std-strings";
|
||||
|
||||
export { hexUtils, intStrUtils, stdUtils };
|
@ -0,0 +1,42 @@
|
||||
import { BigInt } from "biginteger";
|
||||
import { swapEndian } from "./hex-strings";
|
||||
|
||||
//switch byte order charwise
|
||||
export 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
|
||||
export 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
|
||||
export 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));
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export 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;
|
||||
}
|
@ -0,0 +1,500 @@
|
||||
import {
|
||||
ParsedTarget,
|
||||
ViewSendKeys,
|
||||
Output,
|
||||
AmountOutput,
|
||||
Pid,
|
||||
NetType,
|
||||
SignedTransaction,
|
||||
SecretCommitment,
|
||||
RingMember,
|
||||
TransactionOutput,
|
||||
Keys,
|
||||
} from "xmr-types";
|
||||
import { BigInt } from "biginteger";
|
||||
import { valid_keys, random_keypair } from "xmr-key-utils";
|
||||
import { zeroCommit } from "xmr-crypto-ops/rct";
|
||||
import { d2s } from "xmr-str-utils/integer-strings";
|
||||
import { formatMoney, formatMoneyFull } from "xmr-money/formatters";
|
||||
import { INTEGRATED_ID_SIZE } from "xmr-constants/address";
|
||||
import {
|
||||
generate_key_derivation,
|
||||
derive_public_key,
|
||||
derivation_to_scalar,
|
||||
} from "xmr-crypto-ops/derivation";
|
||||
import { I } from "xmr-crypto-ops/constants";
|
||||
import { generate_key_image_helper_rct } from "xmr-crypto-ops/key_image";
|
||||
import { decode_address, is_subaddress } from "xmr-address-utils";
|
||||
import { ge_scalarmult } from "xmr-crypto-ops/primitive_ops";
|
||||
|
||||
import {
|
||||
get_payment_id_nonce,
|
||||
add_nonce_to_extra,
|
||||
abs_to_rel_offsets,
|
||||
add_pub_key_to_extra,
|
||||
get_tx_prefix_hash,
|
||||
} from "./libs/utils";
|
||||
import { generate_ring_signature } from "./libs/non-ringct";
|
||||
import { genRct } from "./libs/ringct";
|
||||
import { encrypt_payment_id } from "xmr-pid";
|
||||
|
||||
const UINT64_MAX = new BigInt(2).pow(64);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
payment_id = encrypt_payment_id(
|
||||
payment_id,
|
||||
realDestViewKey,
|
||||
txkey.sec,
|
||||
);
|
||||
}
|
||||
|
||||
const nonce = get_payment_id_nonce(payment_id, pid_encrypt);
|
||||
console.log("Extra nonce: " + nonce);
|
||||
extra = add_nonce_to_extra(extra, nonce);
|
||||
}
|
||||
|
||||
const CURRENT_TX_VERSION = 2;
|
||||
const OLD_TX_VERSION = 1;
|
||||
|
||||
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<BigInt>(
|
||||
(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<BigInt>(
|
||||
(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<Ret>(
|
||||
({ 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: RingMember[][] = [];
|
||||
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;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export * from "./create-tx";
|
||||
export * from "./tx-status";
|
@ -0,0 +1,191 @@
|
||||
import { STRUCT_SIZES, KEY_SIZE, HASH_SIZE } from "xmr-crypto-ops/constants";
|
||||
import { valid_hex, hextobin, bintohex } from "xmr-str-utils/hex-strings";
|
||||
import CNCrypto = require("xmr-vendor/cn_crypto");
|
||||
import { random_scalar } from "xmr-rand";
|
||||
import { hash_to_ec, hash_to_scalar } from "xmr-crypto-ops/hash_ops";
|
||||
|
||||
export 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;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue