mirror of
				https://git.wownero.com/wownero/wownero.git
				synced 2024-08-15 01:03:23 +00:00 
			
		
		
		
	wallet-rpc: restore from multisig seed
This commit is contained in:
		
							parent
							
								
									ab826008d6
								
							
						
					
					
						commit
						78348bcddd
					
				
					 7 changed files with 217 additions and 35 deletions
				
			
		| 
						 | 
				
			
			@ -29,6 +29,7 @@
 | 
			
		|||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
"""Test multisig transfers
 | 
			
		||||
"""
 | 
			
		||||
| 
						 | 
				
			
			@ -36,42 +37,74 @@ from __future__ import print_function
 | 
			
		|||
from framework.daemon import Daemon
 | 
			
		||||
from framework.wallet import Wallet
 | 
			
		||||
 | 
			
		||||
MULTISIG_PUB_ADDRS = [
 | 
			
		||||
  '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', # 2/2
 | 
			
		||||
  '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', # 2/3
 | 
			
		||||
  '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', # 3/3
 | 
			
		||||
  '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', # 3/4
 | 
			
		||||
  '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', # 2/4
 | 
			
		||||
  '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB'  # 1/2
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
class MultisigTest():
 | 
			
		||||
    def run_test(self):
 | 
			
		||||
        self.reset()
 | 
			
		||||
        self.mine('45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', 5)
 | 
			
		||||
        self.mine('44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', 5)
 | 
			
		||||
        self.mine('41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', 5)
 | 
			
		||||
        self.mine('44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', 5)
 | 
			
		||||
        self.mine('47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', 5)
 | 
			
		||||
        for pub_addr in MULTISIG_PUB_ADDRS:
 | 
			
		||||
          self.mine(pub_addr, 4)
 | 
			
		||||
        self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
 | 
			
		||||
 | 
			
		||||
        self.test_states()
 | 
			
		||||
 | 
			
		||||
        self.fund_addrs_with_normal_wallet(MULTISIG_PUB_ADDRS)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG')
 | 
			
		||||
        self.import_multisig_info([1, 0], 5)
 | 
			
		||||
        txid = self.transfer([1, 0])
 | 
			
		||||
        self.import_multisig_info([0, 1], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.remake_some_multisig_wallets_by_multsig_seed(2)
 | 
			
		||||
        self.import_multisig_info([0, 1], 6) # six outputs, same as before
 | 
			
		||||
        txid = self.transfer([0, 1])
 | 
			
		||||
        self.import_multisig_info([0, 1], 7) # seven outputs b/c we're dest plus change
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i')
 | 
			
		||||
        self.import_multisig_info([0, 2], 5)
 | 
			
		||||
        txid = self.transfer([0, 2])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.remake_some_multisig_wallets_by_multsig_seed(2)
 | 
			
		||||
        self.import_multisig_info([0, 2], 6) # six outputs, same as before
 | 
			
		||||
        txid = self.transfer([0, 2])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2], 7) # seven outputs b/c we're dest plus change
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP')
 | 
			
		||||
        self.import_multisig_info([2, 0, 1], 5)
 | 
			
		||||
        txid = self.transfer([2, 1, 0])
 | 
			
		||||
        self.import_multisig_info([0, 2, 1], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.remake_some_multisig_wallets_by_multsig_seed(3)
 | 
			
		||||
        self.import_multisig_info([2, 0, 1], 6) # six outputs, same as before
 | 
			
		||||
        txid = self.transfer([2, 1, 0])
 | 
			
		||||
        self.import_multisig_info([0, 2, 1], 7) # seven outputs b/c we're dest plus change
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff')
 | 
			
		||||
        self.import_multisig_info([0, 2, 3], 5)
 | 
			
		||||
        txid = self.transfer([0, 2, 3])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2, 3], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.remake_some_multisig_wallets_by_multsig_seed(3)
 | 
			
		||||
        self.import_multisig_info([0, 2, 3], 6) # six outputs, same as before
 | 
			
		||||
        txid = self.transfer([0, 2, 3])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2, 3], 7) # seven outputs b/c we're dest plus change
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U')
 | 
			
		||||
        self.import_multisig_info([1, 2], 5)
 | 
			
		||||
        txid = self.transfer([1, 2])
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +114,24 @@ class MultisigTest():
 | 
			
		|||
        self.import_multisig_info([0, 1, 2, 3], 7)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.remake_some_multisig_wallets_by_multsig_seed(2)
 | 
			
		||||
        self.import_multisig_info([0, 1, 2, 3], 6) # six outputs, same as before
 | 
			
		||||
        txid = self.transfer([2, 3])
 | 
			
		||||
        self.import_multisig_info([0, 1, 2, 3], 7) # seven outputs b/c we're dest plus change
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.create_multisig_wallets(1, 2, '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB')
 | 
			
		||||
        self.import_multisig_info([0, 1], 5)
 | 
			
		||||
        txid = self.transfer([0])
 | 
			
		||||
        self.import_multisig_info([0, 1], 6)
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
        self.remake_some_multisig_wallets_by_multsig_seed(1)
 | 
			
		||||
        self.import_multisig_info([0, 1], 6) # six outputs, same as before
 | 
			
		||||
        txid = self.transfer([1])
 | 
			
		||||
        self.import_multisig_info([0, 1], 7) # seven outputs b/c we're dest plus change
 | 
			
		||||
        self.check_transaction(txid)
 | 
			
		||||
 | 
			
		||||
    def reset(self):
 | 
			
		||||
        print('Resetting blockchain')
 | 
			
		||||
        daemon = Daemon()
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +144,11 @@ class MultisigTest():
 | 
			
		|||
        daemon = Daemon()
 | 
			
		||||
        daemon.generateblocks(address, blocks)
 | 
			
		||||
 | 
			
		||||
    # This method sets up N_total wallets with a threshold of M_threshold doing the following steps:
 | 
			
		||||
    #   * restore_deterministic_wallet(w/ hardcoded seeds)
 | 
			
		||||
    #   * prepare_multisig(enable_multisig_experimental = True)
 | 
			
		||||
    #   * make_multisig()
 | 
			
		||||
    #   * exchange_multisig_keys()
 | 
			
		||||
    def create_multisig_wallets(self, M_threshold, N_total, expected_address):
 | 
			
		||||
      print('Creating ' + str(M_threshold) + '/' + str(N_total) + ' multisig wallet')
 | 
			
		||||
      seeds = [
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +159,8 @@ class MultisigTest():
 | 
			
		|||
      ]
 | 
			
		||||
      assert M_threshold <= N_total
 | 
			
		||||
      assert N_total <= len(seeds)
 | 
			
		||||
 | 
			
		||||
      # restore_deterministic_wallet() & prepare_multisig()
 | 
			
		||||
      self.wallet = [None] * N_total
 | 
			
		||||
      info = []
 | 
			
		||||
      for i in range(N_total):
 | 
			
		||||
| 
						 | 
				
			
			@ -114,10 +172,12 @@ class MultisigTest():
 | 
			
		|||
        assert len(res.multisig_info) > 0
 | 
			
		||||
        info.append(res.multisig_info)
 | 
			
		||||
 | 
			
		||||
      # Assert that all wallets are multisig
 | 
			
		||||
      for i in range(N_total):
 | 
			
		||||
        res = self.wallet[i].is_multisig()
 | 
			
		||||
        assert res.multisig == False
 | 
			
		||||
 | 
			
		||||
      # make_multisig() with each other's info
 | 
			
		||||
      addresses = []
 | 
			
		||||
      next_stage = []
 | 
			
		||||
      for i in range(N_total):
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +185,7 @@ class MultisigTest():
 | 
			
		|||
        addresses.append(res.address)
 | 
			
		||||
        next_stage.append(res.multisig_info)
 | 
			
		||||
 | 
			
		||||
      # Assert multisig paramaters M/N for each wallet
 | 
			
		||||
      for i in range(N_total):
 | 
			
		||||
        res = self.wallet[i].is_multisig()
 | 
			
		||||
        assert res.multisig == True
 | 
			
		||||
| 
						 | 
				
			
			@ -132,13 +193,15 @@ class MultisigTest():
 | 
			
		|||
        assert res.threshold == M_threshold
 | 
			
		||||
        assert res.total == N_total
 | 
			
		||||
 | 
			
		||||
      while True:
 | 
			
		||||
      # exchange_multisig_keys()
 | 
			
		||||
      num_exchange_multisig_keys_stages = 0
 | 
			
		||||
      while True: # while not all wallets are ready
 | 
			
		||||
        n_ready = 0
 | 
			
		||||
        for i in range(N_total):
 | 
			
		||||
          res = self.wallet[i].is_multisig()
 | 
			
		||||
          if res.ready == True:
 | 
			
		||||
            n_ready += 1
 | 
			
		||||
        assert n_ready == 0 or n_ready == N_total
 | 
			
		||||
        assert n_ready == 0 or n_ready == N_total # No partial readiness
 | 
			
		||||
        if n_ready == N_total:
 | 
			
		||||
          break
 | 
			
		||||
        info = next_stage
 | 
			
		||||
| 
						 | 
				
			
			@ -148,10 +211,17 @@ class MultisigTest():
 | 
			
		|||
          res = self.wallet[i].exchange_multisig_keys(info)
 | 
			
		||||
          next_stage.append(res.multisig_info)
 | 
			
		||||
          addresses.append(res.address)
 | 
			
		||||
        num_exchange_multisig_keys_stages += 1
 | 
			
		||||
 | 
			
		||||
      # We should only need N - M + 1 key exchange rounds
 | 
			
		||||
      assert num_exchange_multisig_keys_stages == N_total - M_threshold + 1
 | 
			
		||||
 | 
			
		||||
      # Assert that the all wallets have expected public address
 | 
			
		||||
      for i in range(N_total):
 | 
			
		||||
        assert addresses[i] == expected_address
 | 
			
		||||
        assert addresses[i] == expected_address, addresses[i]
 | 
			
		||||
      self.wallet_address = expected_address
 | 
			
		||||
 | 
			
		||||
      # Assert multisig paramaters M/N and "ready" for each wallet
 | 
			
		||||
      for i in range(N_total):
 | 
			
		||||
        res = self.wallet[i].is_multisig()
 | 
			
		||||
        assert res.multisig == True
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +229,73 @@ class MultisigTest():
 | 
			
		|||
        assert res.threshold == M_threshold
 | 
			
		||||
        assert res.total == N_total
 | 
			
		||||
 | 
			
		||||
    # We want to test if multisig wallets can receive normal transfers as well and mining transfers
 | 
			
		||||
    def fund_addrs_with_normal_wallet(self, addrs):
 | 
			
		||||
      print("Funding multisig wallets with normal wallet-to-wallet transfers")
 | 
			
		||||
 | 
			
		||||
      # Generate normal deterministic wallet
 | 
			
		||||
      normal_seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
 | 
			
		||||
      assert not hasattr(self, 'wallet') or not self.wallet
 | 
			
		||||
      self.wallet = [Wallet(idx = 0)]
 | 
			
		||||
      res = self.wallet[0].restore_deterministic_wallet(seed = normal_seed)
 | 
			
		||||
      assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
 | 
			
		||||
 | 
			
		||||
      self.wallet[0].refresh()
 | 
			
		||||
 | 
			
		||||
      # Check that we own enough spendable enotes
 | 
			
		||||
      res = self.wallet[0].incoming_transfers(transfer_type = 'available')
 | 
			
		||||
      assert 'transfers' in res
 | 
			
		||||
      num_outs_spendable = 0
 | 
			
		||||
      min_out_amount = None
 | 
			
		||||
      for t in res.transfers:
 | 
			
		||||
          if not t.spent:
 | 
			
		||||
            num_outs_spendable += 1
 | 
			
		||||
            min_out_amount = min(min_out_amount, t.amount) if min_out_amount is not None else t.amount
 | 
			
		||||
      assert num_outs_spendable >= 2 * len(addrs)
 | 
			
		||||
 | 
			
		||||
      # Transfer to addrs and mine to confirm tx
 | 
			
		||||
      dsts = [{'address': addr, 'amount': int(min_out_amount * 0.95)} for addr in addrs]
 | 
			
		||||
      res = self.wallet[0].transfer(dsts, get_tx_metadata = True)
 | 
			
		||||
      tx_hex = res.tx_metadata
 | 
			
		||||
      res = self.wallet[0].relay_tx(tx_hex)
 | 
			
		||||
      self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 10)
 | 
			
		||||
 | 
			
		||||
    def remake_some_multisig_wallets_by_multsig_seed(self, threshold):
 | 
			
		||||
      N = len(self.wallet)
 | 
			
		||||
      signers_to_remake = set()
 | 
			
		||||
      num_signers_to_remake = random.randint(1, N) # Do at least one
 | 
			
		||||
      while len(signers_to_remake) < num_signers_to_remake:
 | 
			
		||||
        signers_to_remake.add(random.randint(0, N - 1))
 | 
			
		||||
 | 
			
		||||
      for i in signers_to_remake:
 | 
			
		||||
        print("Remaking {}/{} multsig wallet from multisig seed: #{}".format(threshold, N, i+1))
 | 
			
		||||
 | 
			
		||||
        otherwise_unused_seed = \
 | 
			
		||||
          'factual wiggle awakened maul sash biscuit pause reinvest fonts sleepless knowledge tossed jewels request gusts dagger gumball onward dotted amended powder cynical strained topic request'
 | 
			
		||||
 | 
			
		||||
        # Get information about wallet, will compare against later
 | 
			
		||||
        old_viewkey = self.wallet[i].query_key('view_key').key
 | 
			
		||||
        old_spendkey = self.wallet[i].query_key('spend_key').key
 | 
			
		||||
        old_multisig_seed = self.wallet[i].query_key('mnemonic').key
 | 
			
		||||
 | 
			
		||||
        # Close old wallet and restore w/ random seed so we know that restoring actually did something
 | 
			
		||||
        self.wallet[i].close_wallet()
 | 
			
		||||
        self.wallet[i].restore_deterministic_wallet(seed=otherwise_unused_seed)
 | 
			
		||||
        mid_viewkey = self.wallet[i].query_key('view_key').key
 | 
			
		||||
        assert mid_viewkey != old_viewkey
 | 
			
		||||
 | 
			
		||||
        # Now restore w/ old multisig seed and check against original
 | 
			
		||||
        self.wallet[i].close_wallet()
 | 
			
		||||
        self.wallet[i].restore_deterministic_wallet(seed=old_multisig_seed, enable_multisig_experimental=True)
 | 
			
		||||
        new_viewkey = self.wallet[i].query_key('view_key').key
 | 
			
		||||
        new_spendkey = self.wallet[i].query_key('spend_key').key
 | 
			
		||||
        new_multisig_seed = self.wallet[i].query_key('mnemonic').key
 | 
			
		||||
        assert new_viewkey == old_viewkey
 | 
			
		||||
        assert new_spendkey == old_spendkey
 | 
			
		||||
        assert new_multisig_seed == old_multisig_seed
 | 
			
		||||
 | 
			
		||||
        self.wallet[i].refresh()
 | 
			
		||||
 | 
			
		||||
    def test_states(self):
 | 
			
		||||
        print('Testing multisig states')
 | 
			
		||||
        seeds = [
 | 
			
		||||
| 
						 | 
				
			
			@ -251,7 +388,7 @@ class MultisigTest():
 | 
			
		|||
          assert res.n_outputs == expected_outputs
 | 
			
		||||
 | 
			
		||||
    def transfer(self, signers):
 | 
			
		||||
        assert len(signers) >= 2
 | 
			
		||||
        assert len(signers) >= 1
 | 
			
		||||
 | 
			
		||||
        daemon = Daemon()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue