wallet and fake addr for rct tx method integrations; moved estimatedRctSize; moved tx parsing fns out of cryptonote_utils

pull/41/head
Paul Shapiro 6 years ago
parent 772fc340f9
commit 84c8db7756

@ -127,6 +127,10 @@ var cnUtil = function(currencyConfig)
}
throw "ret_val_boolstring_to_bool given illegal input"
}
function api_safe_wordset_name(wordset_name)
{
return wordset_name.charAt(0).toUpperCase() + wordset_name.substr(1) // capitalizes first letter
}
//
var config = {}; // shallow copy of initConfig
for (var key in currencyConfig) {
@ -149,6 +153,50 @@ var cnUtil = function(currencyConfig)
return mnemonic.mn_random(64);
};
this.is_subaddress = function(addr, nettype) {
const args =
{
address: addr,
nettype_string: nettype_utils.nettype_to_API_string(nettype)
};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.is_subaddress(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return ret_val_boolstring_to_bool(ret.retVal);
};
this.is_integrated_address = function(addr, nettype) {
const args =
{
address: addr,
nettype_string: nettype_utils.nettype_to_API_string(nettype)
};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.is_integrated_address(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return ret_val_boolstring_to_bool(ret.retVal);
};
this.new_payment_id = function() {
const args = {};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.new_payment_id(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return ret.retVal;
};
this.new__int_addr_from_addr_and_short_pid = function(
address,
short_pid,
@ -174,9 +222,17 @@ var cnUtil = function(currencyConfig)
return ret.retVal;
};
this.create_address = function(seed, nettype)
{
// TODO:
this.new_fake_address_for_rct_tx = function(nettype)
{ // TODO: possibly support sending random_scalar from JS to emscripten to avoid emscripten random
const args = { nettype_string: nettype_utils.nettype_to_API_string(nettype) };
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.new_fake_address_for_rct_tx(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return ret.retVal;
};
this.decode_address = function(address, nettype)
@ -201,20 +257,106 @@ var cnUtil = function(currencyConfig)
}
};
this.is_subaddress = function(addr, nettype) {
this.newly_created_wallet = function(
wordset_name,
nettype
) {
const args =
{
address: addr,
wordset_name: api_safe_wordset_name(wordset_name),
nettype_string: nettype_utils.nettype_to_API_string(nettype)
};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.is_subaddress(args_str);
const ret_string = CNCrypto.newly_created_wallet(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return ret_val_boolstring_to_bool(ret.retVal)
return { // calling these out so as to provide a stable ret val interface
mnemonic_string: ret.mnemonic_string,
sec_seed_string: ret.sec_seed_string,
address_string: ret.address_string,
pub_viewKey_string: ret.pub_viewKey_string,
sec_viewKey_string: ret.sec_viewKey_string,
pub_spendKey_string: ret.pub_spendKey_string,
sec_spendKey_string: ret.sec_spendKey_string
};
};
this.mnemonic_from_seed = function(
seed_string,
wordset_name
) {
const args =
{
seed_string: seed_string,
wordset_name: api_safe_wordset_name(wordset_name)
};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.mnemonic_from_seed(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return ret.retVal;
};
this.seed_and_keys_from_mnemonic = function(
mnemonic_string,
wordset_name
) {
const args =
{
mnemonic_string: mnemonic_string,
wordset_name: api_safe_wordset_name(wordset_name)
};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.seed_and_keys_from_mnemonic(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return { // calling these out so as to provide a stable ret val interface
sec_seed_string: ret.sec_seed_string,
address_string: ret.address_string,
pub_viewKey_string: ret.pub_viewKey_string,
sec_viewKey_string: ret.sec_viewKey_string,
pub_spendKey_string: ret.pub_spendKey_string,
sec_spendKey_string: ret.sec_spendKey_string
};
};
this.validate_components_for_login = function(
address_string,
sec_viewKey_string,
sec_spendKey_string,
seed_string,
nettype
) {
const args =
{
address_string: address_string,
sec_viewKey_string: sec_viewKey_string,
sec_spendKey_string: sec_spendKey_string,
seed_string: seed_string,
nettype_string: nettype_utils.nettype_to_API_string(nettype)
};
const args_str = JSON.stringify(args);
const CNCrypto = loaded_CNCrypto();
const ret_string = CNCrypto.validate_components_for_login(args_str);
const ret = JSON.parse(ret_string);
if (typeof ret.err_msg !== 'undefined' && ret.err_msg) {
throw ret.err_msg // TODO: maybe return this somehow
}
return { // calling these out so as to provide a stable ret val interface
isValid: ret_val_boolstring_to_bool(ret.isValid),
isInViewOnlyMode: ret_val_boolstring_to_bool(ret.isInViewOnlyMode),
pub_viewKey_string: ret.pub_viewKey_string,
pub_spendKey_string: ret.pub_spendKey_string
};
};
this.generate_key_image = function(
@ -322,101 +464,6 @@ var cnUtil = function(currencyConfig)
throw "Not enough outputs to mix with";
}
}
// TODO
};
this.estimateRctSize = function(inputs, mixin, outputs, extra_size, bulletproof)
{
// keeping this in JS instead of C++ for now b/c it's much faster to access, and we don't have to make it asynchronous by waiting for the module to load
bulletproof = bulletproof == true ? true : false
extra_size = extra_size || 40
//
var size = 0;
// tx prefix
// first few bytes
size += 1 + 6;
size += inputs * (1 + 6 + (mixin + 1) * 2 + 32);
// vout
size += outputs * (6 + 32);
// extra
size += extra_size;
// rct signatures
// type
size += 1;
// rangeSigs
if (bulletproof)
size += ((2*6 + 4 + 5)*32 + 3) * outputs;
else
size += (2*64*32+32+64*32) * outputs;
// MGs
size += inputs * (64 * (mixin + 1) + 32);
// mixRing - not serialized, can be reconstructed
/* size += 2 * 32 * (mixin+1) * inputs; */
// pseudoOuts
size += 32 * inputs;
// ecdhInfo
size += 2 * 32 * outputs;
// outPk - only commitment is saved
size += 32 * outputs;
// txnFee
size += 4;
// const logStr = `estimated rct tx size for ${inputs} at mixin ${mixin} and ${outputs} : ${size} (${((32 * inputs/*+1*/) + 2 * 32 * (mixin+1) * inputs + 32 * outputs)}) saved)`
// console.log(logStr)
return size;
};
this.is_tx_unlocked = function(unlock_time, blockchain_height) {
if (!config.maxBlockNumber) {
throw "Max block number is not set in config!";
}
if (unlock_time < config.maxBlockNumber) {
// unlock time is block height
return blockchain_height >= unlock_time;
} else {
// unlock time is timestamp
var current_time = Math.round(new Date().getTime() / 1000);
return current_time >= unlock_time;
}
};
this.tx_locked_reason = function(unlock_time, blockchain_height) {
if (unlock_time < config.maxBlockNumber) {
// unlock time is block height
var numBlocks = unlock_time - blockchain_height;
if (numBlocks <= 0) {
return "Transaction is unlocked";
}
var unlock_prediction = moment().add(
numBlocks * config.avgBlockTime,
"seconds",
);
return (
"Will be unlocked in " +
numBlocks +
" blocks, ~" +
unlock_prediction.fromNow(true) +
", " +
unlock_prediction.calendar() +
""
);
} else {
// unlock time is timestamp
var current_time = Math.round(new Date().getTime() / 1000);
var time_difference = unlock_time - current_time;
if (time_difference <= 0) {
return "Transaction is unlocked";
}
var unlock_moment = moment(unlock_time * 1000);
return (
"Will be unlocked " +
unlock_moment.fromNow() +
", " +
unlock_moment.calendar()
);
}
};
function assert(stmt, val) {

@ -69,7 +69,46 @@ function default_priority() {
} // aka .low
exports.default_priority = default_priority;
//
//
function estimateRctSize(inputs, mixin, outputs, extra_size, bulletproof)
{
// keeping this in JS instead of C++ for now b/c it's much faster to access, and we don't have to make it asynchronous by waiting for the module to load
bulletproof = bulletproof == true ? true : false
extra_size = extra_size || 40
//
var size = 0;
// tx prefix
// first few bytes
size += 1 + 6;
size += inputs * (1 + 6 + (mixin + 1) * 2 + 32);
// vout
size += outputs * (6 + 32);
// extra
size += extra_size;
// rct signatures
// type
size += 1;
// rangeSigs
if (bulletproof)
size += ((2*6 + 4 + 5)*32 + 3) * outputs;
else
size += (2*64*32+32+64*32) * outputs;
// MGs
size += inputs * (64 * (mixin + 1) + 32);
// mixRing - not serialized, can be reconstructed
/* size += 2 * 32 * (mixin+1) * inputs; */
// pseudoOuts
size += 32 * inputs;
// ecdhInfo
size += 2 * 32 * outputs;
// outPk - only commitment is saved
size += 32 * outputs;
// txnFee
size += 4;
// const logStr = `estimated rct tx size for ${inputs} at mixin ${mixin} and ${outputs} : ${size} (${((32 * inputs/*+1*/) + 2 * 32 * (mixin+1) * inputs + 32 * outputs)}) saved)`
// console.log(logStr)
return size;
};
function calculate_fee(fee_per_kb_JSBigInt, numberOf_bytes, fee_multiplier) {
const numberOf_kB_JSBigInt = new JSBigInt(
(numberOf_bytes + 1023.0) / 1024.0,
@ -114,7 +153,7 @@ function EstimatedTransaction_networkFee(
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(
const estimated_txSize = estimateRctSize(
numberOf_inputs,
nonZero_mixin_int,
numberOf_outputs,
@ -422,7 +461,7 @@ function SendFunds(
if (/*usingOuts.length > 1 &&*/ isRingCT) {
var newNeededFee = calculate_fee(
feePerKB_JSBigInt,
monero_utils.estimateRctSize(usingOuts.length, mixin, 2),
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))
@ -475,7 +514,7 @@ function SendFunds(
// and recalculate invalidated values
newNeededFee = calculate_fee(
feePerKB_JSBigInt,
monero_utils.estimateRctSize(
estimateRctSize(
usingOuts.length,
mixin,
2,
@ -587,10 +626,9 @@ function SendFunds(
// 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(),
const fakeAddress = monero_utils.new_fake_address_for_rct_tx(
nettype,
).public_addr;
);
console.log(
"Sending 0 XMR to a fake address to keep tx uniform (no change exists): " +
fakeAddress,

@ -37,14 +37,57 @@ function IsTransactionConfirmed(tx, blockchain_height) {
exports.IsTransactionConfirmed = IsTransactionConfirmed;
//
function IsTransactionUnlocked(tx, blockchain_height) {
return monero_utils.is_tx_unlocked(tx.unlock_time || 0, blockchain_height);
const unlock_time = tx.unlock_time || 0;
if (!monero_config.maxBlockNumber) {
throw "Max block number is not set in config!";
}
if (unlock_time < monero_config.maxBlockNumber) {
// unlock time is block height
return blockchain_height >= unlock_time;
} else {
// unlock time is timestamp
var current_time = Math.round(new Date().getTime() / 1000);
return current_time >= unlock_time;
}
}
exports.IsTransactionUnlocked = IsTransactionUnlocked;
//
function TransactionLockedReason(tx, blockchain_height) {
return monero_utils.tx_locked_reason(
tx.unlock_time || 0,
blockchain_height,
);
function TransactionLockedReason(tx, blockchain_height)
{
const unlock_time = tx.unlock_time || 0;
if (unlock_time < monero_config.maxBlockNumber) {
// unlock time is block height
var numBlocks = unlock_time - blockchain_height;
if (numBlocks <= 0) {
return "Transaction is unlocked";
}
var unlock_prediction = moment().add(
numBlocks * monero_config.avgBlockTime,
"seconds"
);
return (
"Will be unlocked in " +
numBlocks +
" blocks, ~" +
unlock_prediction.fromNow(true) +
", " +
unlock_prediction.calendar() +
""
);
} else {
// unlock time is timestamp
var current_time = Math.round(new Date().getTime() / 1000);
var time_difference = unlock_time - current_time;
if (time_difference <= 0) {
return "Transaction is unlocked";
}
var unlock_moment = moment(unlock_time * 1000);
return (
"Will be unlocked " +
unlock_moment.fromNow() +
", " +
unlock_moment.calendar()
);
}
}
exports.TransactionLockedReason = TransactionLockedReason;

@ -158,14 +158,25 @@ exports.DefaultWalletMnemonicWordsetName =
// 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);
//
// TODO: possibly deprecate this function now that it's basically a passthrough (it existed so as to avoid modifying cryptonote_utils)
const ret = monero_utils.newly_created_wallet(
mnemonic_wordsetName,
nettype
);
return {
seed: seed,
mnemonicString: mnemonicString,
keys: keys,
seed: ret.sec_seed_string,
mnemonicString: ret.mnemonic_string,
keys: {
public_addr: ret.address_string,
view: {
sec: ret.sec_viewKey_string,
pub: ret.pub_viewKey_string
},
spend: {
sec: ret.sec_spendKey_string,
pub: ret.pub_spendKey_string
}
}
};
}
exports.NewlyCreatedWallet = NewlyCreatedWallet;
@ -175,12 +186,8 @@ exports.NewlyCreatedWallet = NewlyCreatedWallet;
// Wallet login:
//
function MnemonicStringFromSeed(account_seed, mnemonic_wordsetName) {
const mnemonicString = mnemonic.mn_encode(
account_seed,
mnemonic_wordsetName,
);
//
return mnemonicString;
// TODO: possibly deprecate this function as it now merely wraps another
return monero_utils.mnemonic_from_seed(account_seed, mnemonic_wordsetName);
}
exports.MnemonicStringFromSeed = MnemonicStringFromSeed;
//
@ -192,37 +199,25 @@ function SeedAndKeysFromMnemonic_sync(
// -> {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;
}
const ret = monero_utils.seed_and_keys_from_mnemonic(
mnemonicString,
mnemonic_wordsetName
);
return {
err_str: null,
seed: ret.sec_seed_string,
keys: {
public_addr: ret.address_string,
view: {
sec: ret.sec_viewKey_string,
pub: ret.pub_viewKey_string
},
spend: {
sec: ret.sec_spendKey_string,
pub: ret.pub_spendKey_string
}
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 {
@ -257,93 +252,37 @@ function VerifiedComponentsForLogIn_sync(
address,
nettype,
view_key,
spend_key_orUndefinedForViewOnly,
seed_orUndefined,
wasAGeneratedWallet,
spend_key__orZero,
seed__orZero,
) {
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;
var spend_key__orEmpty = spend_key__orZero || "";
var seed__orEmpty = seed__orZero || "";
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" };
const ret = monero_utils.validate_components_for_login(
address,
view_key,
spend_key__orEmpty,
seed__orEmpty,
nettype
);
if (ret.isValid == false) { // actually don't think we're expecting this..
return {
err_str: "Invalid input"
}
}
account_seed = seed_orUndefined;
return {
err_str: null,
public_keys: {
view: ret.pub_viewKey_string,
spend: ret.pub_spendKey_string
},
isInViewOnlyMode: ret.isInViewOnlyMode // should be true "if(spend_key__orZero)"
};
} catch (e) {
return {
err_str: typeof e === "string" ? e : "" + e
};
}
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;
//
@ -351,9 +290,8 @@ function VerifiedComponentsForLogIn(
address,
nettype,
view_key,
spend_key_orUndefinedForViewOnly,
seed_orUndefined,
wasAGeneratedWallet,
spend_key_orUndef,
seed_orUndef,
fn,
) {
// fn: (err?, address, account_seed, public_keys, private_keys, isInViewOnlyMode) -> Void
@ -361,16 +299,12 @@ function VerifiedComponentsForLogIn(
address,
nettype,
view_key,
spend_key_orUndefinedForViewOnly,
seed_orUndefined,
wasAGeneratedWallet,
spend_key_orUndef,
seed_orUndef
);
fn(
payload.err_str ? new Error(payload.err_str) : null,
payload.address,
payload.account_seed,
payload.public_keys,
payload.private_keys,
payload.isInViewOnlyMode,
);
}

Loading…
Cancel
Save