Skip to content

Commit

Permalink
RPA Implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
fyookball committed May 20, 2021
1 parent 7ca9066 commit e538369
Show file tree
Hide file tree
Showing 14 changed files with 1,284 additions and 71 deletions.
34 changes: 29 additions & 5 deletions electroncash/base_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from . import keystore
from . import mnemonic
from . import util
from .wallet import (ImportedAddressWallet, ImportedPrivkeyWallet,
from .wallet import (ImportedAddressWallet, ImportedPrivkeyWallet,RpaWallet,
Standard_Wallet, Multisig_Wallet, wallet_types)
from .i18n import _

Expand Down Expand Up @@ -86,6 +86,7 @@ def new(self):
('standard', _("Standard wallet")),
('multisig', _("Multi-signature wallet")),
('imported', _("Import Bitcoin Cash addresses or private keys")),
('rpa', _("Reusable payment address")),
]
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
Expand All @@ -98,6 +99,8 @@ def on_wallet_type(self, choice):
action = 'choose_multisig'
elif choice == 'imported':
action = 'import_addresses_or_keys'
elif choice == 'rpa':
action = 'on_rpa'
self.run(action)

def choose_multisig(self):
Expand All @@ -109,10 +112,17 @@ def on_multisig(m, n):
self.multisig_dialog(run_next=on_multisig)

def choose_keystore(self):
assert self.wallet_type in ['standard', 'multisig']
assert self.wallet_type in ['standard', 'multisig','rpa']
i = len(self.keystores)
title = _('Add cosigner') + ' (%d of %d)'%(i+1, self.n) if self.wallet_type=='multisig' else _('Keystore')
if self.wallet_type =='standard' or i==0:
if self.wallet_type == 'rpa':
message = _(
'Do you want to create a new seed, or to restore a wallet using an existing seed?')
choices = [
('create_standard_seed', _('Create a new seed')),
('restore_from_seed', _('I already have a seed')),
]
elif self.wallet_type =='standard' or i==0:
message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
choices = [
('create_standard_seed', _('Create a new seed')),
Expand Down Expand Up @@ -145,6 +155,11 @@ def bip38_prompt_for_pw(self, bip38_keys):
''' Implemented in Qt InstallWizard subclass '''
raise NotImplementedError('bip38_prompt_for_pw not implemented')

def on_rpa(self):
self.run('choose_keystore')
self.request_password(run_next=self.on_password)
self.terminate()

def on_import(self, text):
if keystore.is_address_list(text):
self.wallet = ImportedAddressWallet.from_text(self.storage, text)
Expand Down Expand Up @@ -355,7 +370,10 @@ def on_keystore(self, k):
if has_xpub:
from .bitcoin import xpub_type
t1 = xpub_type(k.xpub)
if self.wallet_type == 'standard':
if self.wallet_type == 'rpa':
keys = k.dump()
self.keystores.append(k)
elif self.wallet_type == 'standard':
if has_xpub and t1 not in ['standard']:
self.show_error(_('Wrong key type') + ' %s'%t1)
self.run('choose_keystore')
Expand Down Expand Up @@ -400,7 +418,13 @@ def on_password(self, password, encrypt):
for k in self.keystores:
if k.may_have_password():
k.update_password(None, password)
if self.wallet_type == 'standard':
if self.wallet_type == 'rpa':
keys = self.keystores[0].dump()
self.storage.put('keystore_rpa_aux', keys)
self.storage.put('seed_type', self.seed_type)
text = ""
self.wallet = RpaWallet.from_text(self.storage, text, password)
elif self.wallet_type == 'standard':
self.storage.put('seed_type', self.seed_type)
keys = self.keystores[0].dump()
self.storage.put('keystore', keys)
Expand Down
23 changes: 23 additions & 0 deletions electroncash/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from .transaction import tx_from_str
import argparse
import ast
import base64
Expand All @@ -36,6 +37,7 @@
from functools import wraps

from . import bitcoin
from . import rpa
from . import util
from .address import Address, AddressError
from .bitcoin import hash_160, COIN, TYPE_ADDRESS
Expand Down Expand Up @@ -546,6 +548,27 @@ def _mktx(self, outputs, fee=None, change_addr=None, domain=None, nocheck=False,
self.wallet.save_transactions()
return tx

@command('w')
def rpa_generate_paycode(self):
if self.wallet.wallet_type is not 'rpa':
return {'error': 'This command may only be used on an RPA wallet.'}
return rpa.paycode.generate_paycode(self.wallet)

@command('w')
def rpa_generate_transaction_from_paycode(self, amount, paycode):
# WARNING: Amount is in full Bitcoin Cash units
if self.wallet.wallet_type is not 'rpa':
return {'error': 'This command may only be used on an RPA wallet.'}

return rpa.paycode.generate_transaction_from_paycode(self.wallet, self.config, amount, paycode)

@command('wp')
def rpa_extract_private_key_from_transaction(self, raw_tx, password=None):
if self.wallet.wallet_type is not 'rpa':
return {'error': 'This command may only be used on an RPA wallet.'}

return rpa.paycode.extract_private_key_from_transaction(self.wallet, raw_tx, password)

@command('wp')
def payto(self, destination, amount, fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False, password=None, locktime=None,
op_return=None, op_return_raw=None, addtransaction=False):
Expand Down
14 changes: 12 additions & 2 deletions electroncash/keystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import inspect
from . import bitcoin
from .bitcoin import *

Expand Down Expand Up @@ -104,7 +105,7 @@ def decrypt_message(self, sequence, message, password):
decrypted = ec.decrypt_message(message)
return decrypted

def sign_transaction(self, tx, password, *, use_cache=False):
def sign_transaction(self, tx, password, *, use_cache=False, ndata=None, grind=None):
if self.is_watching_only():
return
# Raise if password is not correct.
Expand All @@ -115,7 +116,16 @@ def sign_transaction(self, tx, password, *, use_cache=False):
keypairs[k] = self.get_private_key(v, password)
# Sign
if keypairs:
tx.sign(keypairs, use_cache=use_cache)
if ndata is not None:
# If we have ndata, check that the Transaction class passed to us supports ndata.
# Some extant plugins such as Flipstarter inherit from Transaction (reimplementing `sign`) and do not
# support this argument.
if 'ndata' not in inspect.signature(tx.sign, follow_wrapped=True).parameters:
raise RuntimeError(f'Transaction subclass "{tx.__class__.__name__}" does not support the "ndata" kwarg')
tx.sign(keypairs, use_cache=use_cache, ndata=ndata)
else:
# Regular transaction sign (no ndata supported or no ndata specified)
tx.sign(keypairs, use_cache=use_cache,ndata=ndata)

This comment has been minimized.

Copy link
@cculianu

cculianu May 20, 2021

Collaborator

This line in this branch (no ndata) should be:

                # Regular transaction sign (no ndata supported or no ndata specified)
                tx.sign(keypairs, use_cache=use_cache)


class Imported_KeyStore(Software_KeyStore):
Expand Down
38 changes: 37 additions & 1 deletion electroncash/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,10 @@ def request_fee_estimates(self):
def get_status_value(self, key):
if key == 'status':
value = self.connection_status
elif key == 'import_rpa_tx':
value = (self.import_rpa_tx, self.rpawallet)
elif key == 'import_rpa_mempool_tx':
value = (self.import_rpa_mempool_tx, self.rpawallet)
elif key == 'banner':
value = self.banner
elif key == 'fee':
Expand All @@ -553,7 +557,12 @@ def get_status_value(self, key):
return value

def notify(self, key):
if key in ('updated',):
if key == 'import_rpa_tx' or key == 'import_rpa_mempool_tx':
rpa_data = self.get_status_value(key)
rpa_tx_data = rpa_data[0]
rpa_wallet = rpa_data[1]
self.trigger_callback(key, rpa_tx_data, rpa_wallet)
elif key in ('updated',):
# Legacy support. Will warn that updated is deprecated.
self.trigger_callback(key)
else:
Expand Down Expand Up @@ -786,6 +795,14 @@ def add_recent_server(self, server):
self.recent_servers = self.recent_servers[0:20]
self.save_recent_servers()

def process_rpa_transactions(self, interface, data, wallet):
self.import_rpa_tx = data
self.notify('import_rpa_tx')

def process_rpa_mempool_transactions(self, interface, data, wallet):
self.import_rpa_mempool_tx = data
self.notify('import_rpa_mempool_tx')

def process_response(self, interface, request, response, callbacks):
if self.debug:
self.print_error("<--", response)
Expand All @@ -805,6 +822,10 @@ def process_response(self, interface, request, response, callbacks):
if method == 'server.version':
if isinstance(result, list):
self.on_server_version(interface, result)
elif method == 'blockchain.reusable.get_history':
self.process_rpa_transactions(interface, result, self.rpawallet)
elif method == 'blockchain.reusable.get_mempool':
self.process_rpa_mempool_transactions(interface, result, self.rpawallet)
elif method == 'blockchain.headers.subscribe':
if error is None:
# on_notify_header below validates result is right type or format
Expand Down Expand Up @@ -1246,6 +1267,19 @@ def on_block_headers(self, interface, request, response):
interface.blockchain.catch_up = None
self.notify('blockchain_updated')

def request_rpa_mempool(self, byte_prefix_string, wallet):
params = [byte_prefix_string]
self.queue_request('blockchain.reusable.get_mempool', params)
self.rpawallet = wallet

This comment has been minimized.

Copy link
@cculianu

cculianu May 20, 2021

Collaborator

This is wrong. More than 1 wallet can be open. The network is a singleton that services multiple possible wallets at once. You cannot do this. If more than 1 rpa_wallet is open you will have a bad time.

return True

def request_rpa_txs(self, height, number_of_blocks,
byte_prefix_string, wallet):
params = [height, number_of_blocks, byte_prefix_string]
self.queue_request('blockchain.reusable.get_history', params)
self.rpawallet = wallet
return True

def request_header(self, interface, height):
"""
This works for all modes except for 'default'.
Expand All @@ -1265,6 +1299,8 @@ def request_header(self, interface, height):
params = [height, networks.net.VERIFICATION_BLOCK_HEIGHT]
self.queue_request('blockchain.block.header', params, interface)
return True



def on_header(self, interface, request, response):
"""Handle receiving a single block header"""
Expand Down
1 change: 1 addition & 0 deletions electroncash/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class MainNet(AbstractNet):
ADDRTYPE_P2SH = 5
ADDRTYPE_P2SH_BITPAY = 40
CASHADDR_PREFIX = "bitcoincash"
RPA_PREFIX = "paycode"
HEADERS_URL = "http://bitcoincash.com/files/blockchain_headers" # Unused
GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
Expand Down
9 changes: 9 additions & 0 deletions electroncash/rpa/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# -*- mode: python3 -*-
# This file (c) 2020 Calin Culianu
# Part of the Electron Cash SPV Wallet
# License: MIT

from . import addr
from . import paycode

0 comments on commit e538369

Please sign in to comment.