Skip to content

Commit

Permalink
initial support to send to a subaddress added
Browse files Browse the repository at this point in the history
  • Loading branch information
moneroexamples committed Nov 13, 2017
1 parent 18310de commit 23ca56f
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 13 deletions.
9 changes: 3 additions & 6 deletions README.md
Expand Up @@ -32,15 +32,12 @@ to MyMonero. They include:
- legacy address + payment_id system replaced with integrated addresses for better privacy.
- free, time based imports of recent transactions added.
- new transaction details window.
- sending xmr to a subaddress (not receiving nor generating subaddresses for now).

## Testnet version

- [http://139.162.32.245:81](http://139.162.32.245:81)

or alternatively, owning to [Gingeropolous](https://github.com/Gingeropolous):

- [http://testwallet.moneroworld.com](http://testwallet.moneroworld.com)

This is Open Monero running on testnet network. You can use it to play around with it.
Since this is testnet version, frequent changes and database resets are expected. Also,
it is running on cheap vps, which may result in some lag.
Expand Down Expand Up @@ -91,7 +88,7 @@ For other Linux operating systems, the instructions are analogical.

#### Monero download and compilation

Download and compile recent Monero realease into your home folder:
Download and compile recent Monero release into your home folder:

```bash
# first install monero dependecines
Expand All @@ -109,7 +106,7 @@ git clone https://github.com/monero-project/monero
cd monero/

# checkout last monero version
git checkout -b last_release v0.11.0.0
git checkout -b last_release v0.11.1.0

This comment has been minimized.

Copy link
@kenshi84

kenshi84 Nov 13, 2017

Contributor

Note that the subaddress PR wasn't included in the v0.11.1 release branch; it's only in the master branch at the moment.

This comment has been minimized.

Copy link
@moneroexamples

moneroexamples Nov 13, 2017

Author Owner

You right. For now these changes are in sending_to_subaddress branch of open monero.


make
```
Expand Down
61 changes: 55 additions & 6 deletions html/js/cn_util.js
Expand Up @@ -47,11 +47,15 @@ var cnUtil = (function(initConfig) {
var ENCRYPTED_PAYMENT_ID_TAIL = 141;
var CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = config.addressPrefix;
var CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = config.integratedAddressPrefix;
var CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = config.subAddressPrefix;

if (config.testnet === true)
{
CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = config.addressPrefixTestnet;
CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = config.integratedAddressPrefixTestnet;
CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = config.subAddressPrefixTestnet;
}

var UINT64_MAX = new JSBigInt(2).pow(64);
var CURRENT_TX_VERSION = 2;
var OLD_TX_VERSION = 1;
Expand Down Expand Up @@ -507,13 +511,21 @@ var cnUtil = (function(initConfig) {
var prefix = this.encode_varint(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX);
return cnBase58.encode(prefix + spend.pub).slice(0, 44);
};

this.is_subaddress = function(address) {
var dec = cnBase58.decode(address);
var expectedPrefixSub = this.encode_varint(CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX);
var prefix = dec.slice(0, expectedPrefixSub.length);
return (prefix === expectedPrefixSub);
}

this.decode_address = function(address) {
var dec = cnBase58.decode(address);
var expectedPrefix = this.encode_varint(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX);
var expectedPrefixInt = this.encode_varint(CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX);
var expectedPrefixSub = this.encode_varint(CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX);
var prefix = dec.slice(0, expectedPrefix.length);
if (prefix !== expectedPrefix && prefix !== expectedPrefixInt) {
if (prefix !== expectedPrefix && prefix !== expectedPrefixInt && prefix !== expectedPrefixSub) {
throw "Invalid address prefix";
}
dec = dec.slice(expectedPrefix.length);
Expand All @@ -523,7 +535,11 @@ var cnUtil = (function(initConfig) {
var intPaymentId = dec.slice(128, 128 + (INTEGRATED_ID_SIZE * 2));
var checksum = dec.slice(128 + (INTEGRATED_ID_SIZE * 2), 128 + (INTEGRATED_ID_SIZE * 2) + (ADDRESS_CHECKSUM_SIZE * 2));
var expectedChecksum = this.cn_fast_hash(prefix + spend + view + intPaymentId).slice(0, ADDRESS_CHECKSUM_SIZE * 2);
} else if (prefix === expectedPrefix) {
var checksum = dec.slice(128, 128 + (ADDRESS_CHECKSUM_SIZE * 2));
var expectedChecksum = this.cn_fast_hash(prefix + spend + view).slice(0, ADDRESS_CHECKSUM_SIZE * 2);
} else {
// if its not regular address, nor integrated, than it must be subaddress
var checksum = dec.slice(128, 128 + (ADDRESS_CHECKSUM_SIZE * 2));
var expectedChecksum = this.cn_fast_hash(prefix + spend + view).slice(0, ADDRESS_CHECKSUM_SIZE * 2);
}
Expand Down Expand Up @@ -1629,7 +1645,7 @@ var cnUtil = (function(initConfig) {
return sigs;
};

this.construct_tx = function(keys, sources, dsts, fee_amount, payment_id, pid_encrypt, realDestViewKey, unlock_time, rct) {
this.construct_tx = function(keys, sources, dsts, fee_amount, payment_id, pid_encrypt, realDestViewKey, unlock_time, rct, changeAddress) {
//we move payment ID stuff here, because we need txkey to encrypt
var txkey = this.random_keypair();
console.log(txkey);
Expand All @@ -1648,6 +1664,12 @@ var cnUtil = (function(initConfig) {
console.log("Extra nonce: " + nonce);
extra = this.add_nonce_to_extra(extra, nonce);
}

// for sending to a subaddress, need to generate new tx public key

//var sub_addr_decoded = this.decode_address(dsts[0].address); // for example dest[0] is subaddress
//txkey.pub = ge_scalarmult(sub_addr_decoded.spend, txkey.sec);

var tx = {
unlock_time: unlock_time,
version: rct ? CURRENT_TX_VERSION : OLD_TX_VERSION,
Expand All @@ -1661,7 +1683,8 @@ var cnUtil = (function(initConfig) {
} else {
tx.signatures = [];
}
tx.extra = this.add_pub_key_to_extra(tx.extra, txkey.pub);

//tx.extra = this.add_pub_key_to_extra(tx.extra, txkey.pub);
tx.prvkey = txkey.sec;

var in_contexts = [];
Expand Down Expand Up @@ -1712,12 +1735,33 @@ var cnUtil = (function(initConfig) {
var outputs_money = JSBigInt.ZERO;
var out_index = 0;
var amountKeys = []; //rct only

// keep generated tx public key, just in case when sending to a subaddress.
// when sending to a subaddress, new txkey.pub is generated, but txkey.sec stays same
var orginal_tx_pub_key = txkey.pub;

for (i = 0; i < dsts.length; ++i) {
if (new JSBigInt(dsts[i].amount).compare(0) < 0) {
throw "dst.amount < 0"; //amount can be zero if no change
}
dsts[i].keys = this.decode_address(dsts[i].address);
var out_derivation = this.generate_key_derivation(dsts[i].keys.view, txkey.sec);

if (this.is_subaddress(dsts[i].address))
{
txkey.pub = ge_scalarmult(dsts[i].keys.spend, txkey.sec);
}

var out_derivation;

if (dsts[i].address === changeAddress.address)
{
out_derivation = this.generate_key_derivation(txkey.pub, changeAddress.view_key);

This comment has been minimized.

Copy link
@kenshi84

kenshi84 Nov 13, 2017

Contributor

Probably you should use keys.view.sec instead of changeAddress.view_key because changeAddress could be a subaddress when the user is on a different account with index>0. Although receiving funds at subaddresses isn't supported yet, I think the implementation should be prepared to pass an appropriate subaddress as the change address when the user is on an account with index>0; i.e. the change address shouldn't be given as var changeAddress = AccountService.getAddressAndViewKey();

This comment has been minimized.

Copy link
@moneroexamples

moneroexamples Nov 13, 2017

Author Owner

Thanks will take the comments into account. Still its only initial support. More tests and tweaks are needed to make sure it works properly.

}
else
{
out_derivation = this.generate_key_derivation(dsts[i].keys.view, txkey.sec);
}

if (rct) {
amountKeys.push(this.derivation_to_scalar(out_derivation, out_index));
}
Expand All @@ -1734,9 +1778,14 @@ var cnUtil = (function(initConfig) {
++out_index;
outputs_money = outputs_money.add(dsts[i].amount);
}

tx.extra = this.add_pub_key_to_extra(tx.extra, txkey.pub);

if (outputs_money.add(fee_amount).compare(inputs_money) > 0) {
throw "outputs money (" + this.formatMoneyFull(outputs_money) + ") + fee (" + this.formatMoneyFull(fee_amount) + ") > inputs money (" + this.formatMoneyFull(inputs_money) + ")";
}


if (!rct) {
for (i = 0; i < sources.length; ++i) {
var src_keys = [];
Expand Down Expand Up @@ -1795,7 +1844,7 @@ var cnUtil = (function(initConfig) {
console.log(tx);
return tx;
};
this.create_transaction = function(pub_keys, sec_keys, dsts, outputs, mix_outs, fake_outputs_count, fee_amount, payment_id, pid_encrypt, realDestViewKey, unlock_time, rct) {
this.create_transaction = function(pub_keys, sec_keys, dsts, outputs, mix_outs, fake_outputs_count, fee_amount, payment_id, pid_encrypt, realDestViewKey, unlock_time, rct, changeAddress) {
unlock_time = unlock_time || 0;
mix_outs = mix_outs || [];
var i, j;
Expand Down Expand Up @@ -1915,7 +1964,7 @@ var cnUtil = (function(initConfig) {
} else if (cmp > 0) {
throw "Need more money than found! (have: " + cnUtil.formatMoney(found_money) + " need: " + cnUtil.formatMoney(needed_money) + ")";
}
return this.construct_tx(keys, sources, dsts, fee_amount, payment_id, pid_encrypt, realDestViewKey, unlock_time, rct);
return this.construct_tx(keys, sources, dsts, fee_amount, payment_id, pid_encrypt, realDestViewKey, unlock_time, rct, changeAddress);
};

this.estimateRctSize = function(inputs, mixin, outputs) {
Expand Down
2 changes: 2 additions & 0 deletions html/js/config.js
Expand Up @@ -12,8 +12,10 @@ var config = {
coinUriPrefix: 'monero:',
addressPrefix: 18,
integratedAddressPrefix: 19,
subAddressPrefix: 42,
addressPrefixTestnet: 53,
integratedAddressPrefixTestnet: 54,
subAddressPrefixTestnet: 63,
feePerKB: new JSBigInt('2000000000'),//20^10 - for testnet its not used, as fee is dynamic.
dustThreshold: new JSBigInt('1000000000'),//10^10 used for choosing outputs/change - we decompose all the way down if the receiver wants now regardless of threshold
txChargeRatio: 0.5,
Expand Down
6 changes: 5 additions & 1 deletion html/js/controllers/send_coins.js
Expand Up @@ -45,6 +45,9 @@ thinwalletCtrls.controller('SendCoinsCtrl', function($scope, $http, $q,

var view_only = AccountService.isViewOnly();

// our address and associated prv view key
var changeAddress = AccountService.getAddressAndViewKey();

var explorerUrl = config.testnet ? config.testnetExplorerUrl : config.mainnetExplorerUrl;

// few multiplayers based on uint64_t wallet2::get_fee_multiplier
Expand Down Expand Up @@ -649,7 +652,8 @@ thinwalletCtrls.controller('SendCoinsCtrl', function($scope, $http, $q,
splittedDsts, using_outs,
mix_outs, mixin, neededFee,
payment_id, pid_encrypt,
realDestViewKey, 0, rct);
realDestViewKey, 0, rct,
changeAddress);

} catch (e) {
deferred.reject("Failed to create transaction: " + e);
Expand Down

0 comments on commit 23ca56f

Please sign in to comment.