#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2014 Thomas Voegtlin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import socket
import threading
import time
import xmlrpclib
from PyQt4 . QtGui import *
from PyQt4 . QtCore import *
from electrum import bitcoin , util
from electrum import transaction
from electrum . plugins import BasePlugin , hook
from electrum . i18n import _
import sys
import traceback
PORT = 12344
HOST = ' ecdsa.net '
description = _ ( " This plugin facilitates the use of multi-signatures wallets. It sends and receives partially signed transactions from/to your cosigner wallet. Transactions are encrypted and stored on a remote server. " )
server = xmlrpclib . ServerProxy ( ' http:// %s : %d ' % ( HOST , PORT ) , allow_none = True )
class Listener ( threading . Thread ) :
def __init__ ( self , parent ) :
threading . Thread . __init__ ( self )
self . daemon = True
self . parent = parent
self . keyname = None
self . keyhash = None
self . is_running = False
self . message = None
def set_key ( self , keyname , keyhash ) :
self . keyname = keyname
self . keyhash = keyhash
def clear ( self ) :
server . delete ( self . keyhash )
self . message = None
def run ( self ) :
self . is_running = True
while self . is_running :
if not self . keyhash :
time . sleep ( 2 )
continue
if not self . message :
try :
self . message = server . get ( self . keyhash )
except Exception as e :
util . print_error ( " cannot contact cosigner pool " )
time . sleep ( 30 )
continue
if self . message :
self . parent . win . emit ( SIGNAL ( " cosigner:receive " ) )
# poll every 30 seconds
time . sleep ( 30 )
class Plugin ( BasePlugin ) :
wallet = None
listener = None
def fullname ( self ) :
return ' Cosigner Pool '
def description ( self ) :
return description
@hook
def init_qt ( self , gui ) :
self . win = gui . main_window
self . win . connect ( self . win , SIGNAL ( ' cosigner:receive ' ) , self . on_receive )
def enable ( self ) :
self . set_enabled ( True )
return True
def is_available ( self ) :
if self . wallet is None :
return True
return self . wallet . wallet_type in [ ' 2of2 ' , ' 2of3 ' ]
@hook
def load_wallet ( self , wallet ) :
self . wallet = wallet
if not self . is_available ( ) :
return
if self . listener is None :
self . listener = Listener ( self )
self . listener . start ( )
mpk = self . wallet . get_master_public_keys ( )
self . cosigner_list = [ ]
for key , xpub in mpk . items ( ) :
keyname = key + ' / ' # fixme
K = bitcoin . deserialize_xkey ( xpub ) [ - 1 ] . encode ( ' hex ' )
_hash = bitcoin . Hash ( K ) . encode ( ' hex ' )
if self . wallet . master_private_keys . get ( keyname ) :
self . listener . set_key ( keyname , _hash )
else :
self . cosigner_list . append ( ( xpub , K , _hash ) )
@hook
def transaction_dialog ( self , d ) :
self . send_button = b = QPushButton ( _ ( " Send to cosigner " ) )
b . clicked . connect ( lambda : self . do_send ( d . tx ) )
d . buttons . insertWidget ( 2 , b )
self . transaction_dialog_update ( d )
@hook
def transaction_dialog_update ( self , d ) :
if d . tx . is_complete ( ) :
self . send_button . hide ( )
return
for xpub , K , _hash in self . cosigner_list :
if self . cosigner_can_sign ( d . tx , xpub ) :
self . send_button . show ( )
break
else :
self . send_button . hide ( )
def cosigner_can_sign ( self , tx , cosigner_xpub ) :
from electrum . transaction import x_to_xpub
xpub_set = set ( [ ] )
for txin in tx . inputs :
for x_pubkey in txin [ ' x_pubkeys ' ] :
xpub = x_to_xpub ( x_pubkey )
if xpub :
xpub_set . add ( xpub )
return cosigner_xpub in xpub_set
def do_send ( self , tx ) :
for xpub , K , _hash in self . cosigner_list :
if not self . cosigner_can_sign ( tx , xpub ) :
continue
message = bitcoin . encrypt_message ( tx . raw , K )
try :
server . put ( _hash , message )
except Exception as e :
self . win . show_message ( str ( e ) )
return
self . win . show_message ( " Your transaction was sent to the cosigning pool. \n Open your cosigner wallet to retrieve it. " )
def on_receive ( self ) :
if self . wallet . use_encryption :
password = self . win . password_dialog ( ' An encrypted transaction was retrieved from cosigning pool. \n Please enter your password to decrypt it. ' )
if not password :
return
else :
password = None
if not self . win . question ( _ ( " An encrypted transaction was retrieved from cosigning pool. \n Do you want to open it now? " ) ) :
return
message = self . listener . message
key = self . listener . keyname
xprv = self . wallet . get_master_private_key ( key , password )
if not xprv :
return
try :
k = bitcoin . deserialize_xkey ( xprv ) [ - 1 ] . encode ( ' hex ' )
EC = bitcoin . EC_KEY ( k . decode ( ' hex ' ) )
message = EC . decrypt_message ( message )
except Exception as e :
traceback . print_exc ( file = sys . stdout )
self . win . show_message ( str ( e ) )
return
self . listener . clear ( )
tx = transaction . Transaction . deserialize ( message )
self . win . show_transaction ( tx )