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

fix(hd-wallet): enable/withdraw using any account'/change/address_index #1933

Merged
merged 27 commits into from
Aug 31, 2023

Conversation

shamardy
Copy link
Collaborator

@shamardy shamardy commented Aug 4, 2023

Intermediate PR part of #1838

  • Global enabling of an account'/change/address_index path for all coins using hd_account_id config parameter is replaced by enable_hd which is a bool that defaults to false. I couldn't remove this parameter all together and make enabling of Iguana or HD mode at the coin level since the Iguana seed phrases which is passed to config at initiation will not always be BIP39 compliant which will result in HD context to fail in initializing. I made enable_hd default to false to not break backward compatibility. c.c. @KomodoPlatform/qa

  • HD withdrawal from any account'/change/address_index path is implemented for UTXO, EVM and Tendermint coins for now, other coins will be added in next PRs.

  • path_to_address parameter is added to coins activation requests to set the default account'/change/address_index path that will be used for swaps. If not provided, the default will be 0'/0/0.

  • from parameter in withdraw can be used to withdraw from any account'/change/address_index using

    "from": {
        "account": integer,
        "is_change": bool,
        "address_index": integer
    }

    no need to document any of these changes for now since these parameters can be changed as this is is only an
    Intermediate PR. For instance from.address_id that is used in HW wallets can be used instead since I found they are
    the same, c.c. @KomodoPlatform/qa

  • Review process notes:

    • @rozhkovdmitrii Please review the adex-cli changes related to replacing hd_account_id with enable_hd, activation scheme data doesn't support HD wallet, I should update how HD wallet enabling work with adex-cli when all the HD fixes are done in next PRs, as activation scheme data doesn't support HD wallet and will not be able to support it with the current changes.
    • GUI authentication for EVM uses the activated key pair only (the ones used for swaps), I understand that this is not a problem for withdrawing using another keypair but I wanted to make sure that is the case @ozkanonur.
    • @laruh please review changes to NFT code.
    • @ozkanonur please review changes related to Tendermint.
    • @artemii235 please review changes related to zcoin.
    • @dimxy I added you to this review since it intersects with HW wallet task and signing RPC task too.
  • Todo in next PRs:

    • Tests for Tendermint token withdraws.
    • Withdraw from any account'/change/address_index for ARRR, NFTs, SLP and QRC20.
    • Opening lightning channels from any account'/change/address_index and also Solana support can be postponed until these features are planned.
    • Transaction history support.
    • Displaying HD addresses and balances using account_balance RPC.
    • Other SoW items.

@shamardy shamardy added the in progress Changes will be made from the author label Aug 4, 2023
@shamardy shamardy self-assigned this Aug 4, 2023
@shamardy shamardy added 1.0.7-beta under review and removed in progress Changes will be made from the author labels Aug 4, 2023
drop_mutability!(priv_key);

let secret = *priv_key.private_key().as_ref();
Ok(Secp256k1Secret::from(secret))
Copy link

@ca333 ca333 Aug 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't store sensitive data longer in memory than needed. could use e.g. https://crates.io/crates/zeroize or similar approach. We ult. need the key only in case of signing requirement. Will open separate issue ticket since this affects multiple layers - but figured, maybe we want to start here / with HD withdrawl layer, since critical layer?

EDIT: given workload, lets keep on radar / handle in one of the next iterations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could use e.g. https://crates.io/crates/zeroize or similar approach

Thanks for the suggestion. Using zeroize looks like a good approach to zeroing the memory region after the variable is dropped, we should also consider disabling swapping for this memory region to avoid the OS saving secrets to disk unintentionally. Please note that the master seed is kept in memory throughout the application runtime, we should probably add the option to keep it encrypted only in memory and decrypt it using the RPC password (the rpc password is also kept in memory, so maybe we should keep the hash of the password+salt instead to verify the password on each call) as part of this separate issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop itself is enough for this purpose

Copy link
Member

@onur-ozkan onur-ozkan Aug 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For certain values, we have to keep them in memory constantly to operate mm2 without always asking users. Which might not be good for GUI clients and bots. To protect against memory attacks, we can change the memory addresses dynamically while the program is running so we don't have the important values on the same address all the time while the program is running. However, we should also make sure passphrases(and other informations) are safe, as they're stored in a plain text file right know (MM_CONF_PATH). If someone gets into the mm2 device, they can quickly get the passphrases from the text file without needing doing complicated hardware attack / reverse engineering stuff.

We talked about this topic in a public Mattermost channel with @gcharang. We sort of figured out a quite good solution for the issue, but I can't recall all the details since our discussion was very detailed and it was months ago.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I know that komodod use MemoryPageLocker object for locking privkeys in memory, I guess, to prevent sending virtual pages with sensitive data to swap file. I wonder if we have the same feature in rust

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I know that komodod use MemoryPageLocker object for locking privkeys in memory, I guess, to prevent sending virtual pages with sensitive data to swap file. I wonder if we have the same feature in rust

We can use mlock from libc, I don't know if there is any better way for that.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems this would be platform-dependant as in komodod it is implemented like

bool MemoryPageLocker::Lock(const void* addr, size_t len)
{
#ifdef _WIN32
    return VirtualLock(const_cast<void*>(addr), len) != 0;
#else
    return mlock(addr, len) == 0;
#endif
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your input @dimxy @ozkanonur, I will look into it once the issue is opened and planned. We can discuss the details more then :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to use winapi for doing this on Windows as libc doesn't support windows. Both winapi and libc are already present in our dependency tree, so we can do it easily.

ca333
ca333 previously approved these changes Aug 18, 2023
Copy link

@ca333 ca333 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secure code reviewed

feedback given on key handling / secure mem zeroing

  • should additionally ensure we don't accidentally create multiple mem copies of "sensitive" data due to (de)serialization and other relevant data ops.

@smk762
Copy link

smk762 commented Aug 28, 2023

  • Can we please get an example of the new method structures (or a link to the same in code)?
  • I see this change touches a few places, so can you please confirm / add to the below so we can make sure we cover everything required.
  • Are all tests ready for testing in wasm, or some are some ready in in CLI only?
  • Global enabling of an "account'/change/address_index" path for all coins using "hd_account_id" config parameter is replaced by "enable_hd" which is a bool that defaults to false.
    • So enable_hd goes in MM2.json instead of hd_account_id?

@shamardy
Copy link
Collaborator Author

shamardy commented Aug 29, 2023

So enable_hd goes in MM2.json instead of hd_account_id?

Yes, it's better this way since we will not be tied to an address path (the account'/change/address_index part) for all coins when initializing mm2.

I see this change touches a few places, so can you please confirm / add to the below so we can make sure we cover everything required.
Tendermint activate / withdraw
NFT activate / withdraw
Trezor activate / withdraw
BIP39 HD without trezor activate / withdraw
zhtlc activate / withdraw
evm activate withdraw
utxo activate / withdraw
Or just #1933 (comment) ?

This is the most important #1933 (comment) as others can change. Everything else can change to make HD wallet feature better, but I will provide examples for everything else below:

Coins Activation:

  • path_to_address parameter is added to coins activation requests to set the default account'/change/address_index path that will be used for swaps. If not provided, the default will be 0'/0/0.
  • The below examples are for 7'/0/77 path, we can change the path_to_address as we want and enable different coins using different path_to_address which was the main purpose of this PR.
  • We can also disable a coin and re-enable it with different path_to_address, if a coin has ongoing swaps or matching orders it can't be disabled as before and if no matching orders, orders placed will be cancelled so it's ok to disable and enable with different path_to_address regarding swaps/orders.
  • The only problem is activating a coin after restarting mm2 with a different path_to_address than the last used one before stopping mm2 as swaps that were not finished will fail or worse things can happen. This problem will be solved in next PRs but if required, I can do a quick fix in this PR. GUIs can save the last used path_to_address for now and re-enable with it after restarting, but this is not the optimal solution as a seamless solution using API should be provided.

UTXO electrum:

{
    "userpass": "{{userpass}}",
    "method": "electrum",
    "coin": "RICK",
    "servers": [
        {
            "url": "electrum1.cipig.net:10017"
        },
        {
            "url": "electrum2.cipig.net:10017"
        },
        {
            "url": "electrum3.cipig.net:10017"
        }
    ],
    "path_to_address": {
        "account": 7,
        "is_change": false,
        "address_index": 77
    }
}

UTXO v2 (task::enable_utxo::init):

path_to_address is added to UtxoActivationParams

{
    "userpass": "{{userpass}}",
    "mmrpc": "2.0",
    "method": "task::enable_utxo::init",
    "params": {
        "ticker": "RICK",
        "activation_params": {
            "mode": {
                "rpc": "Electrum",
                "rpc_data": {
                    "servers": [
                        {
                            "url": "electrum1.cipig.net:10017"
                        },
                        {
                            "url": "electrum2.cipig.net:10017"
                        },
                        {
                            "url": "electrum3.cipig.net:10017"
                        }
                    ]
                }
            },
            "path_to_address": {
                "account": 7,
                "is_change": false,
                "address_index": 77
            }
        }
    }
}

EVM enable:

{
    "userpass": "{{userpass}}",
    "method": "enable",
    "coin": "ETH",
    "urls": [
        "http://eth1.cipig.net:8555",
        "http://eth2.cipig.net:8555",
        "http://eth3.cipig.net:8555"
    ],
    "swap_contract_address": "0x24ABE4c71FC658C91313b6552cd40cD808b3Ea80",
    "path_to_address": {
        "account": 7,
        "is_change": false,
        "address_index": 77
    }
}

EVM v2 (enable_eth_with_tokens/enable_erc20):

enable_erc20 is the same since it uses platform coin address and platform coin has to be activated for it to be used.

{
    "userpass": "{{userpass}}",
    "method": "enable_eth_with_tokens",
    "mmrpc": "2.0",
    "params": {
        "ticker": "ETH",
        "swap_contract_address": "0x24ABE4c71FC658C91313b6552cd40cD808b3Ea80",
        "fallback_swap_contract": "0x8500AFc0bc5214728082163326C2FF0C73f4a871",
        "nodes": [
            {
                "url": "http://eth1.cipig.net:8555",
                "gui_auth": false
            },
            {
                "url": "http://eth2.cipig.net:8555",
                "gui_auth": false
            },
            {
                "url": "http://eth3.cipig.net:8555",
                "gui_auth": false
            },
            {
                "url": "https://node.komodo.earth:8080/ethereum",
                "gui_auth": true
            }
        ],
        "erc20_tokens_requests": [
            {
                "ticker": "BUSD-ERC20"
            }
        ],
        "path_to_address": {
            "account": 7,
            "is_change": false,
            "address_index": 77
        }
    }
}

This comment is getting quite large so I will continue in separate comments :)

@shamardy
Copy link
Collaborator Author

Tendermint Activation (enable_tendermint_with_assets, enable_tendermint_token):

For enable_tendermint_token it's the same as enable_erc20 since it also uses platform coin address and platform coin has to be activated for it to be used.

{
    "userpass": "{{userpass}}",
    "method": "enable_tendermint_with_assets",
    "mmrpc": "2.0",
    "params": {
        "ticker": "IRIS",
        "tokens_params": [
            {
                "ticker": "ATOM-IBC_IRIS"
            }
        ],
        "rpc_urls": [
            "https://iris.komodo.earth/",
            "https://rpc.irishub-1.irisnet.org"
        ],
        "path_to_address": {
            "account": 7,
            "is_change": false,
            "address_index": 77
        }
    }
}

@shamardy
Copy link
Collaborator Author

zhtlc/zcoin Activation (enable_z_coin::init):

zcoin doesn't use path_to_address but uses only account field. ref #1933 (comment) we don't know yet if we will support multiple addresses per account or not in the future as I will open this for discussion when the time comes.

{
    "userpass": "{{userpass}}",
    "mmrpc": "2.0",
    "method": "task::enable_z_coin::init",
    "params": {
        "ticker": "ZOMBIE",
        "activation_params": {
            "mode": {
                "rpc": "Light",
                "rpc_data": {
                    "electrum_servers": [
                        {
                            "url": "zombie.sirseven.me:10033"
                        }
                    ],
                    "light_wallet_d_servers": [
                        "http://zombie.sirseven.me:443"
                    ]
                }
            },
            "account": 7
        }
    }
}

@shamardy
Copy link
Collaborator Author

NFT activate / withdraw:

If mm2 is started with enable_hd:true, NFT will use the default 0'/0/0 path for the activated address. HD wallet as in multiple accounts and addresses is not supported yet for NFT. It should be supported in future PRs.

Trezor activate / withdraw

This PR doesn't affect Trezor implementation at all and it should work as it has been before.

BIP39 HD without trezor activate / withdraw

@smk762 what do you mean with this? to use enable_hd:true, the passphrase has to be BIP39 compatible otherwise an error will be returned and mm2 will not be initialized.

@shamardy
Copy link
Collaborator Author

Withdraw methods (withdraw v1, withdraw v2, ibc_withdraw):

for the above methods from parameter can be used to withdraw from any account'/change/address_index using

"from": {
    "account": integer,
    "is_change": bool,
    "address_index": integer
}

This only works for UTXO,EVM and Tendermint for now, example for UTXO withdraw:

{
    "userpass": "{{userpass}}",
    "mmrpc": "2.0",
    "method": "withdraw",
    "params": {
        "coin": "RICK",
        "to": "RNBA756iaFCx2Uhya3pvCufbeyovAaknJL",
        "amount": 1.025,
        "from": {
            "account": 3,
            "is_change": 0,
            "address_index": 6
        }
    }
}

Unfortunately, there is no current way to see balances and addresses for an account to make sure withdraw worked. I used this https://iancoleman.io/bip39/ for addresses and checked chain explorers to make sure it worked fine. Account addresses and balances methods should be provided in the next PR but it might require some changes in activation and withdraw methods too.

@shamardy
Copy link
Collaborator Author

Zcoin withdraw:

We can't withdraw from any account for now, it will be added in next PRs too. Only enabled account with coin activation is used using task::withdraw::init.

Are all tests ready for testing in wasm, or some are some ready in in CLI only?

All are working for wasm except zcoin since it's not supported in wasm yet.

@smk762
Copy link

smk762 commented Aug 30, 2023

Thanks for the examples and clarifications

BIP39 HD without trezor activate / withdraw

@smk762 what do you mean with this? to use enable_hd:true, the passphrase has to be BIP39 compatible otherwise an error will be returned and mm2 will not be initialized.

Basically meaning to test the activation / withdraw without using a hardware wallet

@shamardy
Copy link
Collaborator Author

Basically meaning to test the activation / withdraw without using a hardware wallet

Yeah, this whole PR is for this. Not related to hardware wallet at all.

Copy link

@smk762 smk762 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks.
The only issues errors encountered are TODO in subsequent PRs, so approving this one.

For reference, these were the issues I encountered:

  • unsure of derivation paths for zhtlc, and the ones attempted returned an address which would not receive funds sent from a legacy desktop address.
  • SLP activates, but there was an infra issue with withdrawals unrelated to this PR "Internal error: Error PayloadTooShort on request to the url https://bchd.dragonhound.info/pb.bchrpc/GetSlpTrustedValidation" I will check the node.
  • Tendermint activates, but withdraw not tested as docs for [r2r] cosmos ibc transfer implementation #1636 have not yet been drafted. I will revisit these tests when drafting the docs and/or future related PRs.

@shamardy
Copy link
Collaborator Author

Thank you for the testing @smk762, I added this comment to the issue checklist #1838 (comment) to not forget about them :)

@shamardy shamardy merged commit 51c44f6 into dev Aug 31, 2023
21 of 30 checks passed
@shamardy shamardy deleted the fix-hd-acc-activation branch August 31, 2023 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants