pull/40/merge
HenryNguyen5 6 years ago committed by GitHub
commit 28f7addb62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

5
.gitignore vendored

@ -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

@ -1,3 +1,15 @@
import { skGen } from "xmr-key-utils";
import {
ge_scalarmult_base,
ge_scalarmult,
} from "xmr-crypto-ops/primitive_ops";
import { identity } from "xmr-crypto-ops/constants";
import { hashToPoint } from "xmr-crypto-ops/hash_ops";
import {
MLSAG_Gen,
MLSAG_ver,
} from "xmr-transaction/libs/ringct/components/prove_ringct_mg";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -26,21 +38,19 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const monero_utils = require("../").monero_utils;
it("MG_sigs", () => {
function skvGen(len) {
let skVec = [];
function skvGen(len: number) {
const skVec: string[] = [];
for (let i = 0; i < len; i++) {
skVec.push(monero_utils.skGen());
skVec.push(skGen());
}
return skVec;
}
//initializes a key matrix;
//first parameter is rows,
//second is columns
function keyMInit(rows, cols) {
let rv = [];
function keyMInit(cols: number) {
let rv: string[][] = [];
for (let i = 0; i < cols; i++) {
rv.push([]);
}
@ -53,7 +63,7 @@ it("MG_sigs", () => {
let N = 3; // cols
let R = 2; // rows
let xm = keyMInit(R, N); // = [[None]*N] #just used to generate test public keys
let xm = keyMInit(N); // = [[None]*N] #just used to generate test public keys
let sk = skvGen(R);
// [
@ -63,15 +73,15 @@ it("MG_sigs", () => {
// [pubkeyn, commitmentn]]
// // Gen creates a signature which proves that for some column in the keymatrix "pk"
// the signer knows a secret key for each row in that column
let P = keyMInit(R, N); // = keyM[[None]*N] #stores the public keys;
let P = keyMInit(N); // = keyM[[None]*N] #stores the public keys;
let ind = 2;
let i = 0;
for (j = 0; j < R; j++) {
for (i = 0; i < N; i++) {
xm[i][j] = monero_utils.skGen();
P[i][j] = monero_utils.ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit]
xm[i][j] = skGen();
P[i][j] = ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit]
}
}
@ -80,34 +90,27 @@ it("MG_sigs", () => {
sk[j] = xm[ind][j];
}
let message = monero_utils.identity();
let kimg = monero_utils.ge_scalarmult(
monero_utils.hashToPoint(P[ind][0]),
sk[0],
);
let rv = monero_utils.MLSAG_Gen(message, P, sk, kimg, ind);
let c = monero_utils.MLSAG_ver(message, P, rv, kimg);
let message = identity();
let kimg = ge_scalarmult(hashToPoint(P[ind][0]), sk[0]);
let rv = MLSAG_Gen(message, P, sk, kimg, ind);
let c = MLSAG_ver(message, P, rv, kimg);
expect(c).toEqual(true);
xtmp = skvGen(R);
xm = keyMInit(R, N); // = [[None]*N] #just used to generate test public keys
xm = keyMInit(N); // = [[None]*N] #just used to generate test public keys
sk = skvGen(R);
for (j = 0; j < R; j++) {
for (i = 0; i < N; i++) {
xm[i][j] = monero_utils.skGen();
P[i][j] = monero_utils.ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit]
xm[i][j] = skGen();
P[i][j] = ge_scalarmult_base(xm[i][j]); // generate fake [pubkey, commit]
}
}
sk[1] = skGen(); //assume we don't know one of the private keys..
kimg = monero_utils.ge_scalarmult(
monero_utils.hashToPoint(P[ind][0]),
sk[0],
);
rv = monero_utils.MLSAG_Gen(message, P, sk, kimg, ind);
c = monero_utils.MLSAG_ver(message, P, rv, kimg);
kimg = ge_scalarmult(hashToPoint(P[ind][0]), sk[0]);
rv = MLSAG_Gen(message, P, sk, kimg, ind);
c = MLSAG_ver(message, P, rv, kimg);
expect(c).toEqual(false);
});

@ -1,3 +1,9 @@
import { generate_parameters } from "./test_parameters";
import {
genBorromean,
verifyBorromean,
} from "xmr-transaction/libs/ringct/components/prove_range/borromean";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -26,13 +32,11 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const monero_utils = require("../../").monero_utils;
const { generate_parameters } = require("./test_parameters");
const { indi, P1v, P2v, xv, N } = generate_parameters();
it("borromean_3", () => {
// #true one
const bb = monero_utils.genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/
const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/
const bb = genBorromean(xv, [P1v, P2v], indi, N); /*?.*/
const valid = verifyBorromean(bb, P1v, P2v); /*?.*/
expect(valid).toBe(true);
});

@ -26,14 +26,18 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const monero_utils = require("../../").monero_utils;
const { generate_parameters } = require("./test_parameters");
import { generate_parameters } from "./test_parameters";
import {
genBorromean,
verifyBorromean,
} from "xmr-transaction/libs/ringct/components/prove_range/borromean";
const { indi, P1v, P2v, xv, N } = generate_parameters();
it("borromean_2", () => {
//#false one
indi[3] = `${(+indi[3] + 1) % 2}`;
const bb = monero_utils.genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/
const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/
const bb = genBorromean(xv, [P1v, P2v], indi, N); /*?.*/
const valid = verifyBorromean(bb, P1v, P2v); /*?.*/
expect(valid).toBe(false);
});

@ -26,8 +26,12 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const monero_utils = require("../../").monero_utils;
const { generate_parameters } = require("./test_parameters");
import { generate_parameters } from "./test_parameters";
import {
genBorromean,
verifyBorromean,
} from "xmr-transaction/libs/ringct/components/prove_range/borromean";
const { indi, P1v, P2v, xv, N } = generate_parameters();
it("borromean_3", () => {
@ -35,7 +39,7 @@ it("borromean_3", () => {
indi[3] = `${(+indi[3] + 1) % 2}`;
indi[3] = `${(+indi[3] + 1) % 2}`;
const bb = monero_utils.genBorromean(xv, [P1v, P2v], indi, 2, N); /*?.*/
const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/
const bb = genBorromean(xv, [P1v, P2v], indi, N); /*?.*/
const valid = verifyBorromean(bb, P1v, P2v); /*?.*/
expect(valid).toBe(true);
});

@ -26,13 +26,16 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const monero_utils = require("../../").monero_utils;
const { generate_parameters } = require("./test_parameters");
import {
genBorromean,
verifyBorromean,
} from "xmr-transaction/libs/ringct/components/prove_range/borromean";
import { generate_parameters } from "./test_parameters";
const { indi, P1v, P2v, xv, N } = generate_parameters();
it("borromean_4", () => {
// #false one
const bb = monero_utils.genBorromean(xv, [P2v, P1v], indi, 2, N); /*?.*/
const valid = monero_utils.verifyBorromean(bb, P1v, P2v); /*?.*/
const bb = genBorromean(xv, [P2v, P1v], indi, N); /*?.*/
const valid = verifyBorromean(bb, P1v, P2v); /*?.*/
expect(valid).toBe(false);
});

@ -26,36 +26,35 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const mnemonic = require("../../cryptonote_utils/mnemonic");
const monero_utils = require("../../").monero_utils;
import { randomBytes } from "crypto";
import { padLeft } from "xmr-str-utils/std-strings";
import { skGen } from "xmr-key-utils";
import {
ge_scalarmult_base,
ge_add,
ge_sub,
} from "xmr-crypto-ops/primitive_ops";
import { H2 } from "xmr-crypto-ops/constants";
function randomBit() {
// get random 32 bits in hex
const rand32bits = mnemonic.mn_random(32);
// get random 8 bits in hex
const rand8bits = randomBytes(1).toString("hex");
// take 4 bits "nibble" and convert to binary
// then take last index
return monero_utils.padLeft(
parseInt(rand32bits[0], 16).toString(2),
4,
0,
)[3];
return padLeft(parseInt(rand8bits[0], 16).toString(2), 4, "0")[3];
}
//Tests for Borromean signatures
//#boro true one, false one, C != sum Ci, and one out of the range..
const N = 64;
let xv = [], // vector of secret keys, 1 per ring (nrings)
P1v = [], //key64, arr of commitments Ci
P2v = [], //key64
indi = []; // vector of secret indexes, 1 per ring (nrings), can be a string
let indi_2 = [];
let indi_3 = [];
let indi_4 = [];
let xv: string[] = [], // vector of secret keys, 1 per ring (nrings)
P1v: string[] = [], //key64, arr of commitments Ci
P2v: string[] = [], //key64
indi: string[] = []; // vector of secret indexes, 1 per ring (nrings), can be a string
let generated = false;
function generate_parameters() {
export function generate_parameters() {
if (generated) {
const indiCopy = [...indi];
@ -64,20 +63,18 @@ function generate_parameters() {
for (let j = 0; j < N; j++) {
indi[j] = randomBit(); /*?.*/
xv[j] = monero_utils.skGen(); /*?.*/
xv[j] = skGen(); /*?.*/
if (+indi[j] === 0) {
P1v[j] = monero_utils.ge_scalarmult_base(xv[j]); /*?.*/
P1v[j] = ge_scalarmult_base(xv[j]); /*?.*/
} else {
P1v[j] = monero_utils.ge_scalarmult_base(xv[j]); // calculate aG = xv[j].G /*?.*/
P1v[j] = monero_utils.ge_add(P1v[j], monero_utils.H2[j]); // calculate aG + H2 /*?.*/
P1v[j] = ge_scalarmult_base(xv[j]); // calculate aG = xv[j].G /*?.*/
P1v[j] = ge_add(P1v[j], H2[j]); // calculate aG + H2 /*?.*/
}
P2v[j] = monero_utils.ge_sub(P1v[j], monero_utils.H2[j]); /*?.*/
P2v[j] = ge_sub(P1v[j], H2[j]); /*?.*/
}
generated = true;
return { xv, P1v, P2v, indi, N };
}
}
module.exports = { generate_parameters };

@ -1,3 +1,15 @@
import { valid_hex } from "xmr-str-utils/hex-strings";
import { NetType } from "xmr-types";
import { cn_fast_hash } from "xmr-fast-hash";
import {
generate_key_derivation,
derivation_to_scalar,
derive_public_key,
derive_subaddress_public_key,
} from "xmr-crypto-ops/derivation";
import { decode_address } from "xmr-address-utils";
import { hash_to_scalar } from "xmr-crypto-ops/hash_ops";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -26,122 +38,93 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"use strict";
const mymonero = require("../");
const assert = require("assert");
var public_key =
const public_key =
"904e49462268d771cc1649084c35aa1296bfb214880fe2e7f373620a3e2ba597";
var private_key =
const private_key =
"52aa4c69b93b780885c9d7f51e6fd5795904962c61a2e07437e130784846f70d";
var nettype = mymonero.nettype_utils.network_type.MAINNET;
const nettype = NetType.MAINNET;
describe("cryptonote_utils tests", function() {
it("is valid hex", function() {
var valid = mymonero.monero_utils.valid_hex(private_key);
assert.strictEqual(valid, true);
const valid = valid_hex(private_key);
expect(valid).toEqual(true);
});
it("fast hash / keccak-256", function() {
var hash = mymonero.monero_utils.cn_fast_hash(
private_key,
private_key.length,
);
assert.equal(
hash,
const hash = cn_fast_hash(private_key);
expect(hash).toEqual(
"64997ff54f0d82ee87d51e971a0329d4315481eaeb4ad2403c65d5843480c414",
);
});
it("generate key derivation", function() {
var derivation = mymonero.monero_utils.generate_key_derivation(
public_key,
private_key,
);
assert.equal(
derivation,
const derivation = generate_key_derivation(public_key, private_key);
expect(derivation).toEqual(
"591c749f1868c58f37ec3d2a9d2f08e7f98417ac4f8131e3a57c1fd71273ad00",
);
});
it("decode mainnet primary address", function() {
var decoded = mymonero.monero_utils.decode_address(
const decoded = decode_address(
"49qwWM9y7j1fvaBK684Y5sMbN8MZ3XwDLcSaqcKwjh5W9kn9qFigPBNBwzdq6TCAm2gKxQWrdZuEZQBMjQodi9cNRHuCbTr",
nettype,
);
var expected = {
const expected = {
spend:
"d8f1e81ecbe25ce8b596d426fb02fe7b1d4bb8d14c06b3d3e371a60eeea99534",
view:
"576f0e61e250d941746ed147f602b5eb1ea250ca385b028a935e166e18f74bd7",
};
assert.deepEqual(decoded, expected);
expect(decoded).toEqual(expected);
});
it("decode mainnet integrated address", function() {
var decoded = mymonero.monero_utils.decode_address(
const decoded = decode_address(
"4KYcX9yTizXfvaBK684Y5sMbN8MZ3XwDLcSaqcKwjh5W9kn9qFigPBNBwzdq6TCAm2gKxQWrdZuEZQBMjQodi9cNd3mZpgrjXBKMx9ee7c",
nettype,
);
var expected = {
const expected = {
spend:
"d8f1e81ecbe25ce8b596d426fb02fe7b1d4bb8d14c06b3d3e371a60eeea99534",
view:
"576f0e61e250d941746ed147f602b5eb1ea250ca385b028a935e166e18f74bd7",
intPaymentId: "83eab71fbee84eb9",
};
assert.deepEqual(decoded, expected);
expect(decoded).toEqual(expected);
});
it("hash_to_scalar", function() {
var scalar = mymonero.monero_utils.hash_to_scalar(private_key);
assert.equal(
scalar,
const scalar = hash_to_scalar(private_key);
expect(scalar).toEqual(
"77c5899835aa6f96b13827f43b094abf315481eaeb4ad2403c65d5843480c404",
);
});
it("derivation_to_scalar", function() {
var derivation = mymonero.monero_utils.generate_key_derivation(
public_key,
private_key,
);
var scalar = mymonero.monero_utils.derivation_to_scalar(derivation, 1);
assert.equal(
scalar,
const derivation = generate_key_derivation(public_key, private_key);
const scalar = derivation_to_scalar(derivation, 1);
expect(scalar).toEqual(
"201ce3c258e09eeb6132ec266d24ee1ca957828f384ce052d5bc217c2c55160d",
);
});
it("derive public key", function() {
var derivation = mymonero.monero_utils.generate_key_derivation(
public_key,
private_key,
);
var output_key = mymonero.monero_utils.derive_public_key(
derivation,
1,
public_key,
);
assert.equal(
output_key,
const derivation = generate_key_derivation(public_key, private_key);
const output_key = derive_public_key(derivation, 1, public_key);
expect(output_key).toEqual(
"da26518ddb54cde24ccfc59f36df13bbe9bdfcb4ef1b223d9ab7bef0a50c8be3",
);
});
it("derive subaddress public key", function() {
var derivation = mymonero.monero_utils.generate_key_derivation(
public_key,
private_key,
);
var subaddress_public_key = mymonero.monero_utils.derive_subaddress_public_key(
const derivation = generate_key_derivation(public_key, private_key);
const subaddress_public_key = derive_subaddress_public_key(
public_key,
derivation,
1,
);
assert.equal(
subaddress_public_key,
expect(subaddress_public_key).toEqual(
"dfc9e4a0039e913204c1c0f78e954a7ec7ce291d8ffe88265632f0da9d8de1be",
);
});

@ -1,3 +1,8 @@
import { BigInt } from "biginteger";
import { skGen } from "xmr-key-utils";
import { d2s } from "xmr-str-utils/integer-strings";
import { encode_ecdh, decode_ecdh } from "xmr-crypto-ops/rct";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -26,29 +31,26 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const JSBigInt = require("../cryptonote_utils/biginteger").BigInteger;
const monero_utils = require("../").monero_utils;
it("ecdh_roundtrip", () => {
const test_amounts = [
new JSBigInt(1),
new JSBigInt(1),
new JSBigInt(2),
new JSBigInt(3),
new JSBigInt(4),
new JSBigInt(5),
new JSBigInt(10000),
new BigInt(1),
new BigInt(1),
new BigInt(2),
new BigInt(3),
new BigInt(4),
new BigInt(5),
new BigInt(10000),
new JSBigInt("10000000000000000000"),
new JSBigInt("10203040506070809000"),
new BigInt("10000000000000000000"),
new BigInt("10203040506070809000"),
new JSBigInt("123456789123456789"),
new BigInt("123456789123456789"),
];
for (const amount of test_amounts) {
const k = monero_utils.skGen();
const scalar = monero_utils.skGen(); /*?*/
const amt = monero_utils.d2s(amount.toString());
const k = skGen();
const scalar = skGen(); /*?*/
const amt = d2s(amount.toString());
const t0 = {
mask: scalar,
amount: amt,
@ -57,9 +59,9 @@ it("ecdh_roundtrip", () => {
// both are strings so we can shallow copy
let t1 = { ...t0 };
t1 = monero_utils.encode_rct_ecdh(t1, k);
t1 = encode_ecdh(t1, k);
t1 = monero_utils.decode_rct_ecdh(t1, k);
t1 = decode_ecdh(t1, k);
expect(t1.mask).toEqual(t0.mask);
expect(t1.amount).toEqual(t0.amount);
}

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,3 +1,10 @@
import { BigInt } from "biginteger";
import { hash_to_scalar } from "xmr-crypto-ops/hash_ops";
import { Z } from "xmr-crypto-ops/constants";
import { generate_key_image } from "xmr-crypto-ops/key_image";
import { genRct, verRct, decodeRct } from "xmr-transaction/libs/ringct";
import { ctskpkGen, populateFromBlockchain } from "./test_utils";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -25,12 +32,6 @@
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const {
ctskpkGen,
populateFromBlockchain,
JSBigInt,
monero_utils,
} = require("./test_utils");
it("range_proofs", () => {
//Ring CT Stuff
@ -55,29 +56,29 @@ it("range_proofs", () => {
// key vector
let amount_keys = [];
amounts.push(new JSBigInt(500));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(500));
amount_keys.push(hash_to_scalar(Z));
amounts.push(new JSBigInt(4500));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(4500));
amount_keys.push(hash_to_scalar(Z));
amounts.push(new JSBigInt(500));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(500));
amount_keys.push(hash_to_scalar(Z));
amounts.push(new JSBigInt(500));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(500));
amount_keys.push(hash_to_scalar(Z));
//compute rct data with mixin 500
const { index, mixRing } = populateFromBlockchain(inPk, 3);
// generate kimg
const kimg = [monero_utils.generate_key_image_2(inPk[0].dest, inSk[0].x)];
const kimg = [generate_key_image(inPk[0].dest, inSk[0].x)];
let s = monero_utils.genRct(
monero_utils.Z,
let s = genRct(
Z,
inSk,
kimg,
[[]],
[],
amounts,
mixRing,
amount_keys,
@ -85,32 +86,22 @@ it("range_proofs", () => {
"0",
);
expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(true);
expect(verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(verRct(s, false, mixRing, kimg[0])).toEqual(true);
//decode received amount
monero_utils.decodeRct(s, amount_keys[1], 1);
decodeRct(s, amount_keys[1], 1);
// Ring CT with failing MG sig part should not verify!
// Since sum of inputs != outputs
amounts[1] = new JSBigInt(12501);
amounts[1] = new BigInt(12501);
s = monero_utils.genRct(
monero_utils.Z,
inSk,
kimg,
[[]],
amounts,
mixRing,
amount_keys,
[index],
"0",
);
s = genRct(Z, inSk, kimg, [], amounts, mixRing, amount_keys, [index], "0");
expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(false);
expect(verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(verRct(s, false, mixRing, kimg[0])).toEqual(false);
//decode received amount
monero_utils.decodeRct(s, amount_keys[1], 1);
decodeRct(s, amount_keys[1], 1);
});

@ -1,3 +1,11 @@
import { BigInt } from "biginteger";
import { ctskpkGen, populateFromBlockchain } from "./test_utils";
import { SecretCommitment, RingMember } from "xmr-types";
import { hash_to_scalar } from "xmr-crypto-ops/hash_ops";
import { Z } from "xmr-crypto-ops/constants";
import { generate_key_image } from "xmr-crypto-ops/key_image";
import { genRct, verRct, decodeRct } from "xmr-transaction/libs/ringct";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -25,19 +33,13 @@
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const {
ctskpkGen,
populateFromBlockchain,
JSBigInt,
monero_utils,
} = require("./test_utils");
it("range_proofs", () => {
//Ring CT Stuff
//ct range proofs
// ctkey vectors
let inSk = [],
inPk = [];
let inSk: SecretCommitment[] = [],
inPk: RingMember[] = [];
// ctkeys
// we test only a single input here since the current impl of
@ -55,29 +57,29 @@ it("range_proofs", () => {
// key vector
let amount_keys = [];
amounts.push(new JSBigInt(1000));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(1000));
amount_keys.push(hash_to_scalar(Z));
amounts.push(new JSBigInt(4000));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(4000));
amount_keys.push(hash_to_scalar(Z));
amounts.push(new JSBigInt(1000));
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
amounts.push(new BigInt(1000));
amount_keys.push(hash_to_scalar(Z));
//compute rct data with mixin 500
const { index, mixRing } = populateFromBlockchain(inPk, 2);
// generate kimg
const kimg = [monero_utils.generate_key_image_2(inPk[0].dest, inSk[0].x)];
const kimg = [generate_key_image(inPk[0].dest, inSk[0].x)];
// add fee of 1 NOTE: fee is passed in with its endian not swapped, hence no usage of d2s
const fee = "1";
let s = monero_utils.genRct(
monero_utils.Z,
let s = genRct(
Z,
inSk,
kimg,
[[]],
[],
amounts,
mixRing,
amount_keys,
@ -85,32 +87,22 @@ it("range_proofs", () => {
fee,
);
expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(true);
expect(verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(verRct(s, false, mixRing, kimg[0])).toEqual(true);
//decode received amount
monero_utils.decodeRct(s, amount_keys[1], 1);
decodeRct(s, amount_keys[1], 1);
// Ring CT with failing MG sig part should not verify!
// Since sum of inputs != outputs
amounts[1] = new JSBigInt(4501);
amounts[1] = new BigInt(4501);
s = monero_utils.genRct(
monero_utils.Z,
inSk,
kimg,
[[]],
amounts,
mixRing,
amount_keys,
[index],
fee,
);
s = genRct(Z, inSk, kimg, [], amounts, mixRing, amount_keys, [index], fee);
expect(monero_utils.verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(monero_utils.verRct(s, false, mixRing, kimg[0])).toEqual(false);
expect(verRct(s, true, mixRing, kimg[0])).toEqual(true);
expect(verRct(s, false, mixRing, kimg[0])).toEqual(false);
//decode received amount
monero_utils.decodeRct(s, amount_keys[1], 1);
decodeRct(s, amount_keys[1], 1);
});

@ -26,12 +26,17 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const {
ctskpkGen,
populateFromBlockchainSimple,
JSBigInt,
monero_utils,
} = require("./test_utils");
import { ctskpkGen, populateFromBlockchainSimple } from "./test_utils";
import { hash_to_scalar } from "xmr-crypto-ops/hash_ops";
import { Z } from "xmr-crypto-ops/constants";
import { generate_key_image } from "xmr-crypto-ops/key_image";
import {
genRct,
verRctSimple,
decodeRctSimple,
} from "xmr-transaction/libs/ringct";
import { BigInt } from "biginteger";
import { random_scalar } from "xmr-rand";
it("should test ringct simple transactions", () => {
//Ring CT Stuff
@ -50,7 +55,7 @@ it("should test ringct simple transactions", () => {
let [sctmp, pctmp] = ctskpkGen(3000);
inSk.push(sctmp);
inPk.push(pctmp);
inamounts.push(3000);
inamounts.push(new BigInt(3000));
}
//add fake input 3000
@ -60,16 +65,16 @@ it("should test ringct simple transactions", () => {
let [sctmp, pctmp] = ctskpkGen(3000);
inSk.push(sctmp);
inPk.push(pctmp);
inamounts.push(3000);
inamounts.push(new BigInt(3000));
}
outamounts.push(5000);
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
outamounts.push(new BigInt(5000));
amount_keys.push(hash_to_scalar(Z));
outamounts.push(999);
amount_keys.push(monero_utils.hash_to_scalar(monero_utils.Z));
outamounts.push(new BigInt(999));
amount_keys.push(hash_to_scalar(Z));
const message = monero_utils.random_scalar();
const message = random_scalar();
const txnFee = "1";
// generate mixin and indices
@ -84,11 +89,11 @@ it("should test ringct simple transactions", () => {
// generate kimg
const kimg = [
monero_utils.generate_key_image_2(inPk[0].dest, inSk[0].x),
monero_utils.generate_key_image_2(inPk[1].dest, inSk[1].x),
generate_key_image(inPk[0].dest, inSk[0].x),
generate_key_image(inPk[1].dest, inSk[1].x),
];
const s = monero_utils.genRct(
const s = genRct(
message,
inSk,
kimg,
@ -100,8 +105,8 @@ it("should test ringct simple transactions", () => {
txnFee,
);
expect(monero_utils.verRctSimple(s, true, mixRings, kimg)).toEqual(true);
expect(monero_utils.verRctSimple(s, false, mixRings, kimg)).toEqual(true);
expect(verRctSimple(s, true, mixRings, kimg)).toEqual(true);
expect(verRctSimple(s, false, mixRings, kimg)).toEqual(true);
monero_utils.decodeRctSimple(s, amount_keys[1], 1);
decodeRctSimple(s, amount_keys[1], 1);
});

@ -25,60 +25,65 @@
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const monero_utils = require("../../").monero_utils;
const JSBigInt = require("../../cryptonote_utils/biginteger").BigInteger;
const { randomBytes } = require("crypto");
import { randomBytes } from "crypto";
import { SecretCommitment, RingMember } from "xmr-types";
import { random_keypair } from "xmr-key-utils";
import { d2s } from "xmr-str-utils/integer-strings";
import { ge_scalarmult, ge_add } from "xmr-crypto-ops/primitive_ops";
import { H } from "xmr-crypto-ops/constants";
//generates a <secret , public> / Pedersen commitment to the amount
function ctskpkGen(amount) {
let sk = {},
pk = {};
const key_pair1 = monero_utils.random_keypair();
const key_pair2 = monero_utils.random_keypair();
export function ctskpkGen(amount: number): [SecretCommitment, RingMember] {
let sk = { x: "", a: "" },
pk = { dest: "", mask: "" };
const key_pair1 = random_keypair();
const key_pair2 = random_keypair();
sk.x = key_pair1.sec;
pk.dest = key_pair1.pub;
sk.a = key_pair2.sec;
pk.mask = key_pair2.pub;
const am = monero_utils.d2s(amount.toString());
const bH = monero_utils.ge_scalarmult(monero_utils.H, am);
const am = d2s(amount.toString());
const bH = ge_scalarmult(H, am);
pk.mask = monero_utils.ge_add(pk.mask, bH);
pk.mask = ge_add(pk.mask, bH);
return [sk, pk];
}
function randomNum(upperLimit) {
export function randomNum(upperLimit: number) {
return parseInt(randomBytes(1).toString("hex"), 16) % upperLimit;
}
//These functions get keys from blockchain
//replace these when connecting blockchain
//getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with
function getKeyFromBlockchain(reference_index) {
let a = {};
a.dest = monero_utils.random_keypair().pub;
a.mask = monero_utils.random_keypair().pub;
// These functions get keys from blockchain
// replace these when connecting blockchain
// getKeyFromBlockchain grabs a key from the blockchain at "reference_index" (unused param) to mix with
export function getKeyFromBlockchain() {
let a = { dest: "", mask: "" };
a.dest = random_keypair().pub;
a.mask = random_keypair().pub;
return a;
}
// populateFromBlockchain creates a keymatrix with "mixin" + 1 columns and one of the columns is inPk
// the return values are the key matrix, and the index where inPk was put (random).
function populateFromBlockchain(inPk, mixin) {
export function populateFromBlockchain(inPk: RingMember[], mixin: number) {
const rows = inPk.length;
const inPkCpy = [...inPk];
// ctkeyMatrix
const mixRing = [];
const mixRing: RingMember[][] = [];
const index = randomNum(mixin);
for (let i = 0; i < rows; i++) {
mixRing[i] = [];
for (let j = 0; j <= mixin; j++) {
if (j !== index) {
mixRing[i][j] = getKeyFromBlockchain(index); /*?*/
mixRing[i][j] = getKeyFromBlockchain(); /*?*/
} else {
mixRing[i][j] = inPkCpy.pop();
mixRing[i][j] = inPkCpy.pop() as RingMember;
}
}
}
@ -86,13 +91,13 @@ function populateFromBlockchain(inPk, mixin) {
return { mixRing, index };
}
function populateFromBlockchainSimple(inPk, mixin) {
export function populateFromBlockchainSimple(inPk: RingMember, mixin: number) {
const index = randomNum(mixin);
const mixRing = [];
for (let i = 0; i <= mixin; i++) {
if (i !== index) {
mixRing[i] = getKeyFromBlockchain(index);
mixRing[i] = getKeyFromBlockchain();
} else {
mixRing[i] = inPk;
}
@ -100,12 +105,3 @@ function populateFromBlockchainSimple(inPk, mixin) {
return { mixRing, index };
}
module.exports = {
ctskpkGen,
populateFromBlockchain,
populateFromBlockchainSimple,
getKeyFromBlockchain,
monero_utils,
JSBigInt,
};

@ -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;

@ -8,6 +8,7 @@
"url": "git+https://github.com/mymonero/mymonero-core-js.git"
},
"scripts": {
"build": "tsc",
"format": "find . -name '*.js*' | xargs prettier --write --config ./.prettierrc --config-precedence file-override",
"test": "jest",
"test:coverage": "jest --coverage"
@ -32,18 +33,32 @@
},
"homepage": "https://github.com/mymonero/mymonero-core-js#readme",
"dependencies": {
"async": "^2.6.0"
"keccakjs": "^0.2.1",
"moment": "^2.22.2"
},
"devDependencies": {
"jest": "^23.1.0"
"jest": "^23.1.0",
"ts-jest": "^23.0.1",
"ts-node": "^7.0.0",
"tsconfig-paths": "^3.4.2",
"typescript": "^2.9.2"
},
"jest" : {
"testEnvironment":"node",
"jest": {
"moduleFileExtensions": [
"js",
"jsx",
"json",
"ts",
"tsx"
],
"transform": {
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"modulePaths": ["<rootDir>/src" ],
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$",
"coveragePathIgnorePatterns": [
"node_modules",
"cryptonote_utils/biginteger.js",
"cryptonote_utils/nacl-fast-cn.js",
"cryptonote_utils/sha3.js",
"cryptonote_utils/cryptonote_crypto_EMSCRIPTEN.js"]
"/node_modules/",
"__test__"
]
}
}

@ -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;

@ -27,7 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// v--- These should maybe be injected into context
const JSBigInt = require("./biginteger").BigInteger;
const JSBigInt = require("../biginteger").BigInt;
var cnBase58 = (function() {
var b58 = {};
@ -47,7 +47,7 @@ var cnBase58 = (function() {
var UINT64_MAX = new JSBigInt(2).pow(64);
function hextobin(hex) {
if (hex.length % 2 !== 0) throw "Hex string has invalid length!";
if (hex.length % 2 !== 0) throw Error("Hex string has invalid length!");
var res = new Uint8Array(hex.length / 2);
for (var i = 0; i < hex.length / 2; ++i) {
res[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
@ -81,7 +81,7 @@ var cnBase58 = (function() {
function uint8_be_to_64(data) {
if (data.length < 1 || data.length > 8) {
throw "Invalid input length";
throw Error("Invalid input length");
}
var res = JSBigInt.ZERO;
var twopow8 = new JSBigInt(2).pow(8);
@ -105,7 +105,7 @@ var cnBase58 = (function() {
res = res.multiply(twopow8).add(data[i++]);
break;
default:
throw "Impossible condition";
throw Error("Impossible condition");
}
return res;
}
@ -113,7 +113,7 @@ var cnBase58 = (function() {
function uint64_to_8be(num, size) {
var res = new Uint8Array(size);
if (size < 1 || size > 8) {
throw "Invalid input length";
throw Error("Invalid input length");
}
var twopow8 = new JSBigInt(2).pow(8);
for (var i = size - 1; i >= 0; i--) {
@ -125,7 +125,7 @@ var cnBase58 = (function() {
b58.encode_block = function(data, buf, index) {
if (data.length < 1 || data.length > full_encoded_block_size) {
throw "Invalid block length: " + data.length;
throw Error("Invalid block length: " + data.length);
}
var num = uint8_be_to_64(data);
var i = encoded_block_sizes[data.length] - 1;
@ -183,24 +183,24 @@ var cnBase58 = (function() {
b58.decode_block = function(data, buf, index) {
if (data.length < 1 || data.length > full_encoded_block_size) {
throw "Invalid block length: " + data.length;
throw Error("Invalid block length: " + data.length);
}
var res_size = encoded_block_sizes.indexOf(data.length);
if (res_size <= 0) {
throw "Invalid block size";
throw Error("Invalid block size");
}
var res_num = new JSBigInt(0);
var order = new JSBigInt(1);
for (var i = data.length - 1; i >= 0; i--) {
var digit = alphabet.indexOf(data[i]);
if (digit < 0) {
throw "Invalid symbol";
throw Error("Invalid symbol");
}
var product = order.multiply(digit).add(res_num);
// if product > UINT64_MAX
if (product.compare(UINT64_MAX) === 1) {
throw "Overflow";
throw Error("Overflow");
}
res_num = product;
order = order.multiply(alphabet_size);
@ -209,7 +209,7 @@ var cnBase58 = (function() {
res_size < full_block_size &&
new JSBigInt(2).pow(8 * res_size).compare(res_num) <= 0
) {
throw "Overflow 2";
throw Error("Overflow 2");
}
buf.set(uint64_to_8be(res_num, res_size), index);
return buf;
@ -226,7 +226,7 @@ var cnBase58 = (function() {
last_block_size,
);
if (last_block_decoded_size < 0) {
throw "Invalid encoded length";
throw Error("Invalid encoded length");
}
var data_size =
full_block_count * full_block_size + last_block_decoded_size;

@ -0,0 +1,2 @@
export const ADDRESS_CHECKSUM_SIZE = 4;
export const INTEGRATED_ID_SIZE = 8;

@ -1,3 +1,5 @@
import { BigInt } from "biginteger";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -25,14 +27,30 @@
// 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;
//
module.exports = {
export interface XMRConfig {
readonly coinUnitPlaces: 12;
readonly coinUnits: BigInt;
readonly txMinConfirms: 10;
readonly coinSymbol: "XMR";
readonly openAliasPrefix: "xmr";
readonly coinName: "Monero";
readonly coinUriPrefix: "monero:";
readonly addressPrefix: 18;
readonly integratedAddressPrefix: 19;
readonly subaddressPrefix: 42;
readonly dustThreshold: BigInt;
readonly maxBlockNumber: 500000000;
readonly avgBlockTime: 60;
}
const coinUnitPlaces = 12;
export const config: XMRConfig = {
// Number of atomic units in one unit of currency. e.g. 12 => 10^12 = 1000000000000
coinUnitPlaces: 12,
coinUnitPlaces,
coinUnits: new BigInt(10).pow(coinUnitPlaces),
// Minimum number of confirmations for a transaction to show as confirmed
txMinConfirms: 10,
@ -56,7 +74,7 @@ module.exports = {
// Dust threshold in atomic units
// 2*10^9 used for choosing outputs/change - we decompose all the way down if the receiver wants now regardless of threshold
dustThreshold: new JSBigInt("2000000000"),
dustThreshold: new BigInt("2000000000"),
// Maximum block number, used for tx unlock time
maxBlockNumber: 500000000,

@ -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";

@ -25,25 +25,62 @@
// 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_wallet_utils = require("./monero_wallet_utils");
//
function MnemonicWordsetNameWithLocale(
currentLocale, // e.g. 'en'
export function addUserAgentParams(
params: { [parameterName: string]: string },
appUserAgentProduct: string,
appUserAgentVersion: string,
) {
const mnemonicWordsetNamesByAppLocaleNames =
monero_wallet_utils.MnemonicWordsetNamesByAppLocaleNames;
if (currentLocale.indexOf("en") === 0) {
return mnemonicWordsetNamesByAppLocaleNames.English;
} else if (currentLocale.indexOf("es") === 0) {
return mnemonicWordsetNamesByAppLocaleNames.Spanish;
} else if (currentLocale.indexOf("pt") === 0) {
return mnemonicWordsetNamesByAppLocaleNames.Portuguese;
} else if (currentLocale.indexOf("ja") === 0) {
return mnemonicWordsetNamesByAppLocaleNames.Japanese;
// 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
params["app_name"] = appUserAgentProduct;
params["app_version"] = appUserAgentVersion;
}
type Json = {
[key: string]: null | undefined | number | string | boolean | Json;
};
/**
*
*
* @export
* @param {string} hostName e.g api.mymonero.com
* @param {string} endPoint e.g login
* @param {Json} payload
* @returns
*/
export async function makeRequest(
hostName: string,
endPoint: string,
payload: Json,
) {
const url = `https://${hostName}/${endPoint}`;
const res = await window
.fetch(url, {
method: "post",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(payload),
})
.then(checkHttpStatus)
.then(parseJSON);
return res;
}
function checkHttpStatus(response: Response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
return new Error(response.statusText);
}
return monero_wallet_utils.DefaultWalletMnemonicWordsetName; // which would be .English
}
exports.MnemonicWordsetNameWithLocale = MnemonicWordsetNameWithLocale;
function parseJSON(response: Response) {
return response.json();
}

@ -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,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,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;
}

@ -1,3 +1,5 @@
import { NetType } from "xmr-types";
// Copyright (c) 2014-2018, MyMonero.com
//
// All rights reserved.
@ -26,67 +28,47 @@
// 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 __MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18;
const __MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19;
const __MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 42;
var network_type = {
MAINNET: 0,
TESTNET: 1,
STAGENET: 2,
};
exports.network_type = network_type;
//
var __MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18;
var __MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19;
var __MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 42;
//
var __TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53;
var __TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54;
var __TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 63;
//
var __STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 24;
var __STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 25;
var __STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 36;
//
function cryptonoteBase58PrefixForStandardAddressOn(nettype) {
if (nettype == null || typeof nettype === "undefined") {
console.warn("Unexpected nil nettype");
}
if (nettype == network_type.MAINNET) {
const __TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53;
const __TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54;
const __TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 63;
const __STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 24;
const __STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 25;
const __STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 36;
export function cryptonoteBase58PrefixForStandardAddressOn(nettype: NetType) {
if (nettype === NetType.MAINNET) {
return __MAINNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
} else if (nettype == network_type.TESTNET) {
} else if (nettype === NetType.TESTNET) {
return __TESTNET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
} else if (nettype == network_type.STAGENET) {
} else if (nettype === NetType.STAGENET) {
return __STAGENET_CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
}
throw "Illegal nettype";
throw Error("Illegal nettype");
}
function cryptonoteBase58PrefixForIntegratedAddressOn(nettype) {
if (nettype == null || typeof nettype === "undefined") {
console.warn("Unexpected nil nettype");
}
if (nettype == network_type.MAINNET) {
export function cryptonoteBase58PrefixForIntegratedAddressOn(nettype: NetType) {
if (nettype === NetType.MAINNET) {
return __MAINNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
} else if (nettype == network_type.TESTNET) {
} else if (nettype === NetType.TESTNET) {
return __TESTNET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
} else if (nettype == network_type.STAGENET) {
} else if (nettype === NetType.STAGENET) {
return __STAGENET_CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
}
throw "Illegal nettype";
throw Error("Illegal nettype");
}
function cryptonoteBase58PrefixForSubAddressOn(nettype) {
if (nettype == null || typeof nettype === "undefined") {
console.warn("Unexpected nil nettype");
}
if (nettype == network_type.MAINNET) {
export function cryptonoteBase58PrefixForSubAddressOn(nettype: NetType) {
if (nettype === NetType.MAINNET) {
return __MAINNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
} else if (nettype == network_type.TESTNET) {
} else if (nettype === NetType.TESTNET) {
return __TESTNET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
} else if (nettype == network_type.STAGENET) {
} else if (nettype === NetType.STAGENET) {
return __STAGENET_CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
}
throw "Illegal nettype";
throw Error("Illegal nettype");
}
//
exports.cryptonoteBase58PrefixForStandardAddressOn = cryptonoteBase58PrefixForStandardAddressOn;
exports.cryptonoteBase58PrefixForIntegratedAddressOn = cryptonoteBase58PrefixForIntegratedAddressOn;
exports.cryptonoteBase58PrefixForSubAddressOn = cryptonoteBase58PrefixForSubAddressOn;

@ -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…
Cancel
Save