Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sub-addresses, JS decoding/proving txs and stagenet #86

Merged
merged 113 commits into from Apr 4, 2018

Conversation

moneroexamples
Copy link
Owner

@moneroexamples moneroexamples commented Nov 8, 2017

The pull request adds support for sub-address, so that you can decode outputs sent to a sub-address and prove sending xmr to a sub-address

Also optional JavaScript based decoding and proving txs are added.

@stoffu
Copy link
Contributor

stoffu commented Nov 8, 2017

Your patch currently doesn't deal with the additional "per-output" tx keys scheme introduced for subaddresses; such additional tx secret keys are needed when sending to multiple destinations that include one or more subaddresses. Here's a patch I've quickly assembled that handles them:

diff --git a/src/page.h b/src/page.h
index 79d4fc4..6084cca 100644
--- a/src/page.h
+++ b/src/page.h
@@ -126,6 +126,7 @@ namespace xmreg
         crypto::hash hash;
         crypto::hash prefix_hash;
         crypto::public_key pk;
+        std::vector<crypto::public_key> additional_pks;
         uint64_t xmr_inputs;
         uint64_t xmr_outputs;
         uint64_t num_nonrct_inputs;
@@ -1469,11 +1470,21 @@ namespace xmreg
             // parse string representing given private key
             crypto::secret_key prv_view_key;
 
-            if (!xmreg::parse_str_secret_key(viewkey_str, prv_view_key))
+            std::vector<crypto::secret_key> multiple_tx_secret_keys;
+            if (!xmreg::parse_str_secret_key(viewkey_str, multiple_tx_secret_keys))
             {
                 cerr << "Cant parse the private key: " << viewkey_str << endl;
                 return string("Cant parse private key: " + viewkey_str);
             }
+            if (multiple_tx_secret_keys.size() == 1)
+            {
+                prv_view_key = multiple_tx_secret_keys[0];
+            }
+            else if (!tx_prove)
+            {
+                cerr << "Concatenated secret keys are only for tx proving!" << endl;
+                return string("Concatenated secret keys are only for tx proving!");
+            }
 
 
             // just to see how would having spend keys could worked
@@ -1648,12 +1659,17 @@ namespace xmreg
             // public transaction key is combined with our viewkey
             // to create, so called, derived key.
             key_derivation derivation;
+            std::vector<key_derivation> additional_derivations(txd.additional_pks.size());
+            if (tx_prove && multiple_tx_secret_keys.size() != txd.additional_pks.size() + 1)
+            {
+                return string("This transaction includes additional tx pubkeys whose size doesn't match with the provided tx secret keys");
+            }
 
             public_key pub_key = tx_prove ? address_info.address.m_view_public_key : txd.pk;
 
             //cout << "txd.pk: " << pod_to_hex(txd.pk) << endl;
 
-            if (!generate_key_derivation(pub_key, prv_view_key, derivation))
+            if (!generate_key_derivation(pub_key, tx_prove ? multiple_tx_secret_keys[0] : prv_view_key, derivation))
             {
                 cerr << "Cant get derived key for: "  << "\n"
                      << "pub_tx_key: " << pub_key << " and "
@@ -1661,6 +1677,17 @@ namespace xmreg
 
                 return string("Cant get key_derivation");
             }
+            for (size_t i = 0; i < txd.additional_pks.size(); ++i)
+            {
+                if (!generate_key_derivation(tx_prove ? pub_key : txd.additional_pks[i], tx_prove ? multiple_tx_secret_keys[i + 1] : prv_view_key, additional_derivations[i]))
+                {
+                    cerr << "Cant get derived key for: "  << "\n"
+                         << "pub_tx_key: " << txd.additional_pks[i] << " and "
+                         << "prv_view_key" << prv_view_key << endl;
+
+                    return string("Cant get key_derivation");
+                }
+            }
 
             // decrypt encrypted payment id, as used in integreated addresses
             crypto::hash8 decrypted_payment_id8 = txd.payment_id8;
@@ -1701,6 +1728,16 @@ namespace xmreg
 
                 // check if generated public key matches the current output's key
                 bool mine_output = (outp.first.key == tx_pubkey);
+                bool with_additional = false;
+                if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size())
+                {
+                    derive_public_key(additional_derivations[output_idx],
+                                      output_idx,
+                                      address_info.address.m_spend_public_key,
+                                      tx_pubkey);
+                    mine_output = (outp.first.key == tx_pubkey);
+                    with_additional = true;
+                }
 
                 // if mine output has RingCT, i.e., tx version is 2
                 if (mine_output && tx.version == 2)
@@ -1716,8 +1753,7 @@ namespace xmreg
                         bool r;
 
                         r = decode_ringct(tx.rct_signatures,
-                                          pub_key,
-                                          prv_view_key,
+                                          with_additional ? additional_derivations[output_idx] : derivation,
                                           output_idx,
                                           tx.rct_signatures.ecdhInfo[output_idx].mask,
                                           rct_amount);
@@ -1888,12 +1924,14 @@ namespace xmreg
 
                     public_key mixin_tx_pub_key
                             = xmreg::get_tx_pub_key_from_received_outs(mixin_tx);
+                    std::vector<public_key> mixin_additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(mixin_tx);
 
                     string mixin_tx_pub_key_str = pod_to_hex(mixin_tx_pub_key);
 
                     // public transaction key is combined with our viewkey
                     // to create, so called, derived key.
                     key_derivation derivation;
+                    std::vector<key_derivation> additional_derivations(mixin_additional_tx_pub_keys.size());
 
                     if (!generate_key_derivation(mixin_tx_pub_key, prv_view_key, derivation))
                     {
@@ -1903,6 +1941,17 @@ namespace xmreg
 
                         continue;
                     }
+                    for (size_t i = 0; i < mixin_additional_tx_pub_keys.size(); ++i)
+                    {
+                        if (!generate_key_derivation(mixin_additional_tx_pub_keys[i], prv_view_key, additional_derivations[i]))
+                        {
+                            cerr << "Cant get derived key for: "  << "\n"
+                                 << "pub_tx_key: " << mixin_additional_tx_pub_keys[i] << " and "
+                                 << "prv_view_key" << prv_view_key << endl;
+        
+                            continue;
+                        }
+                    }
 
                     //          <public_key  , amount  , out idx>
                     vector<tuple<txout_to_key, uint64_t, uint64_t>> output_pub_keys;
@@ -1953,6 +2002,16 @@ namespace xmreg
 
                         // check if generated public key matches the current output's key
                         bool mine_output = (txout_k.key == tx_pubkey_generated);
+                        bool with_additional = false;
+                        if (!mine_output && mixin_additional_tx_pub_keys.size() == output_pub_keys.size())
+                        {
+                            derive_public_key(additional_derivations[output_idx_in_tx],
+                                              output_idx_in_tx,
+                                              address_info.address.m_spend_public_key,
+                                              tx_pubkey_generated);
+                            mine_output = (txout_k.key == tx_pubkey_generated);
+                            with_additional = true;
+                        }
 
 
                         if (mine_output && mixin_tx.version == 2)
@@ -1967,8 +2026,7 @@ namespace xmreg
                                 bool r;
 
                                 r = decode_ringct(mixin_tx.rct_signatures,
-                                                  mixin_tx_pub_key,
-                                                  prv_view_key,
+                                                  with_additional ? additional_derivations[output_idx_in_tx] : derivation,
                                                   output_idx_in_tx,
                                                   mixin_tx.rct_signatures.ecdhInfo[output_idx_in_tx].mask,
                                                   rct_amount);
@@ -3308,6 +3366,7 @@ namespace xmreg
                     }
 
                     public_key tx_pub_key = xmreg::get_tx_pub_key_from_received_outs(tx);
+                    std::vector<public_key> additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(tx);
 
                     // cointbase txs have amounts in plain sight.
                     // so use amount from ringct, only for non-coinbase txs
@@ -3320,6 +3379,12 @@ namespace xmreg
                                                td.m_internal_output_index,
                                                tx.rct_signatures.ecdhInfo[td.m_internal_output_index].mask,
                                                xmr_amount);
+                        r = r || decode_ringct(tx.rct_signatures,
+                                               additional_tx_pub_keys[td.m_internal_output_index],
+                                               prv_view_key,
+                                               td.m_internal_output_index,
+                                               tx.rct_signatures.ecdhInfo[td.m_internal_output_index].mask,
+                                               xmr_amount);
 
                         if (!r)
                         {
@@ -4627,6 +4692,7 @@ namespace xmreg
             // public transaction key is combined with our viewkey
             // to create, so called, derived key.
             key_derivation derivation;
+            std::vector<key_derivation> additional_derivations(txd.additional_pks.size());
 
             public_key pub_key = tx_prove ? address_info.address.m_view_public_key : txd.pk;
 
@@ -4638,6 +4704,15 @@ namespace xmreg
                 j_response["message"] = "Cant calculate key_derivation";
                 return j_response;
             }
+            for (size_t i = 0; i < txd.additional_pks.size(); ++i)
+            {
+                if (!generate_key_derivation(txd.additional_pks[i], prv_view_key, additional_derivations[i]))
+                {
+                    j_response["status"]  = "error";
+                    j_response["message"] = "Cant calculate key_derivation";
+                    return j_response;
+                }
+            }
 
             uint64_t output_idx {0};
 
@@ -4661,6 +4736,16 @@ namespace xmreg
 
                 // check if generated public key matches the current output's key
                 bool mine_output = (outp.first.key == tx_pubkey);
+                bool with_additional = false;
+                if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size())
+                {
+                    derive_public_key(additional_derivations[output_idx],
+                                      output_idx,
+                                      address_info.address.m_spend_public_key,
+                                      tx_pubkey);
+                    mine_output = (outp.first.key == tx_pubkey);
+                    with_additional = true;
+                }
 
                 // if mine output has RingCT, i.e., tx version is 2
                 if (mine_output && tx.version == 2)
@@ -4676,8 +4761,7 @@ namespace xmreg
                         bool r;
 
                         r = decode_ringct(tx.rct_signatures,
-                                          pub_key,
-                                          prv_view_key,
+                                          with_additional ? additional_derivations[output_idx] : derivation,
                                           output_idx,
                                           tx.rct_signatures.ecdhInfo[output_idx].mask,
                                           rct_amount);
@@ -5081,6 +5165,16 @@ namespace xmreg
                     return false;
                 }
 
+                std::vector<key_derivation> additional_derivations(txd.additional_pks.size());
+                for (size_t i = 0; i < txd.additional_pks.size(); ++i)
+                {
+                    if (!generate_key_derivation(txd.additional_pks[i], prv_view_key, additional_derivations[i]))
+                    {
+                        error_msg = "Cant calculate key_derivation";
+                        return false;
+                    }
+                }
+
                 uint64_t output_idx{0};
 
                 std::vector<uint64_t> money_transfered(tx.vout.size(), 0);
@@ -5103,6 +5197,16 @@ namespace xmreg
 
                     // check if generated public key matches the current output's key
                     bool mine_output = (outp.first.key == tx_pubkey);
+                    bool with_additional = false;
+                    if (!mine_output && txd.additional_pks.size() == txd.output_pub_keys.size())
+                    {
+                        derive_public_key(additional_derivations[output_idx],
+                                          output_idx,
+                                          address.m_spend_public_key,
+                                          tx_pubkey);
+                        mine_output = (outp.first.key == tx_pubkey);
+                        with_additional = true;
+                    }
 
                     // if mine output has RingCT, i.e., tx version is 2
                     if (mine_output && tx.version == 2)
@@ -5120,8 +5224,7 @@ namespace xmreg
                             rct::key mask = tx.rct_signatures.ecdhInfo[output_idx].mask;
 
                             r = decode_ringct(tx.rct_signatures,
-                                              txd.pk,
-                                              prv_view_key,
+                                              with_additional ? additional_derivations[output_idx] : derivation,
                                               output_idx,
                                               mask,
                                               rct_amount);
@@ -5747,6 +5850,7 @@ namespace xmreg
             // due to previous bug with sining txs:
             // https://github.com/monero-project/monero/pull/1358/commits/7abfc5474c0f86e16c405f154570310468b635c2
             txd.pk = xmreg::get_tx_pub_key_from_received_outs(tx);
+            txd.additional_pks = cryptonote::get_additional_tx_pub_keys_from_extra(tx);
 
 
             // sum xmr in inputs and ouputs in the given tx
diff --git a/src/tools.cpp b/src/tools.cpp
index 1bb0501..8d7cda6 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -904,18 +904,28 @@ namespace xmreg
                   rct::key & mask,
                   uint64_t & amount)
     {
-        try
-        {
-            crypto::key_derivation derivation;
+        crypto::key_derivation derivation;
 
-            bool r = crypto::generate_key_derivation(pub, sec, derivation);
+        bool r = crypto::generate_key_derivation(pub, sec, derivation);
 
-            if (!r)
-            {
-                cerr <<"Failed to generate key derivation to decode rct output " << i << endl;
-                return false;
-            }
+        if (!r)
+        {
+            cerr <<"Failed to generate key derivation to decode rct output " << i << endl;
+            return false;
+        }
+
+        return decode_ringct(rv, derivation, i, mask, amount);
+    }
 
+    bool
+    decode_ringct(const rct::rctSig& rv,
+                  const crypto::key_derivation &derivation,
+                  unsigned int i,
+                  rct::key & mask,
+                  uint64_t & amount)
+    {
+        try
+        {
             crypto::secret_key scalar1;
 
             crypto::derivation_to_scalar(derivation, i, scalar1);
diff --git a/src/tools.h b/src/tools.h
index a22d314..1cc3431 100644
--- a/src/tools.h
+++ b/src/tools.h
@@ -72,6 +72,22 @@ namespace xmreg
     bool
     parse_str_secret_key(const string& key_str, T& secret_key);
 
+    template <typename T>
+    bool
+    parse_str_secret_key(const string& key_str, std::vector<T>& secret_keys)
+    {
+        const size_t num_keys = key_str.size() / 64;
+        if (num_keys * 64 != key_str.size())
+            return false;
+        secret_keys.resize(num_keys);
+        for (size_t i = 0; i < num_keys; ++i)
+        {
+            if (!parse_str_secret_key(key_str.substr(64*i, 64), secret_keys[i]))
+                return false;
+        }
+        return true;
+    }
+
 
     bool
     get_tx_pub_key_from_str_hash(Blockchain& core_storage,
@@ -227,6 +243,13 @@ namespace xmreg
                   rct::key & mask,
                   uint64_t & amount);
 
+    bool
+    decode_ringct(const rct::rctSig & rv,
+                  const crypto::key_derivation &derivation,
+                  unsigned int i,
+                  rct::key & mask,
+                  uint64_t & amount);
+
     bool
     url_decode(const std::string& in, std::string& out);

It seems to be working, but I haven't tested it thoroughly.

Also note that because of the introduction of the additional tx keys, the wallet now needs to print out the additional tx secret keys when applicable. Such a code is implemented in monero-project/monero#2487 where the command get_tx_key prints out one long string concatenating the normal tx secret key and all the additional tx secret keys.

@moneroexamples
Copy link
Owner Author

moneroexamples commented Nov 8, 2017

@stoffu

Thanks I will look into it. To clarify this is needed when you want to prove to someone that you send some xmr to their sub-address using your private tx key?

EDIT.
I see now. I applied your patch. It works well with Decoding outputs. Thanks lots. I didnt know about this issue.

But cant make it work with "prove sending". What do I put as "private tx key" in the first input form? Regular prv tx key from simplewallet obtained using get_tx_key is too short and multiple_tx_secret_keys length does not match txd.additional_pks.size() + 1.

@stoffu
Copy link
Contributor

stoffu commented Nov 8, 2017

@moneroexamples

But cant make it work with "prove sending". What do I put as "private tx key" in the first input form? Regular prv tx key from simplewallet obtained using get_tx_key is too short and multiple_tx_secret_keys length does not match txd.additional_pks.size() + 1.

As in my above comment, you need to use an updated version of get_tx_key implemented in monero-project/monero#2487.

@moneroexamples
Copy link
Owner Author

moneroexamples commented Nov 9, 2017

@stoffu

Thanks! I applied your get_tx_key patch to monero, recompiled and now private tx key is long, as expected and proving sending works, e.g.,

get_tx_key 6c67b215156d1419664e4a8ea2d76a4570a2c5efdd0e55938b88852bb2d7bead
Tx key: 53f393b1b10176458a1251219dca38d8d55aed6b65a829c6510a4bffc68ce009a081add5bd2c6bcae8b7d037db8046c7c7478d4f31f0a10117c1abc87f8d0504bedba450a852d8f610a4207d7ccb40110c6c31cdfd237080cca80e1ac959450b0441673e00a1b402de6a91920e32a5a85756308bddee9bd1e5ea77445bb3fb0d577757230f902cff5ee12652a87b3007f2fc46ef4c7201078ff8f5ddc0402805

@moneroexamples moneroexamples changed the title Add support for sub-addresses and JS decoding/proving txs Add support for sub-addresses, JS decoding/proving txs and stagenet Mar 15, 2018
@moneroexamples moneroexamples merged commit 53e50ed into master Apr 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants