URI: 
       tadd android authenticator script - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit acc594e5d1a1a6c430dd0aa50d56bd1de6715958
   DIR parent c872a3c4202082772f05056bcaf89ef32f8707e9
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Sun,  1 Mar 2015 13:27:18 +0100
       
       add android authenticator script
       
       Diffstat:
         M contrib/make_android                |      17 +++++++++--------
         A scripts/authenticator.py            |     357 +++++++++++++++++++++++++++++++
       
       2 files changed, 366 insertions(+), 8 deletions(-)
       ---
   DIR diff --git a/contrib/make_android b/contrib/make_android
       t@@ -14,17 +14,18 @@ if __name__ == '__main__':
                print "The packages directory is missing."
                sys.exit()
        
       -    os.system('rm -rf dist/e4a-%s'%version)
       -    os.mkdir('dist/e4a-%s'%version)
       -    shutil.copyfile("electrum",'dist/e4a-%s/e4a.py'%version)
       +    target = 'dist/e4a-%s'%version
       +    os.system('rm -rf %s'%target)
       +    os.mkdir(target)
       +    shutil.copyfile('electrum', target + '/e4a.py')
       +    shutil.copyfile('scripts/authenticator.py', target + '/authenticator.py')
            shutil.copytree("packages",'dist/e4a-%s/packages'%version, ignore=shutil.ignore_patterns('*.pyc'))
            shutil.copytree("lib",'dist/e4a-%s/lib'%version, ignore=shutil.ignore_patterns('*.pyc'))
            # dns is not used by android app
       -    os.system('rm -rf dist/e4a-%s/packages/dns')
       -    os.mkdir('dist/e4a-%s/gui'%version)
       -    for n in ['android.py']:
       -        shutil.copy("gui/%s"%n,'dist/e4a-%s/gui'%version)
       -    open('dist/e4a-%s/gui/__init__.py'%version,'w').close()
       +    os.system('rm -rf %s/packages/dns'%target)
       +    os.mkdir(target + '/gui')
       +    shutil.copyfile('gui/android.py', target + '/gui/android.py')
       +    open(target + '/gui/__init__.py','w').close()
        
            os.chdir("dist")
            # create the zip file
   DIR diff --git a/scripts/authenticator.py b/scripts/authenticator.py
       t@@ -0,0 +1,357 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2015 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/>.
       +
       +
       +
       +
       +from __future__ import absolute_import
       +
       +import android
       +import sys
       +import os
       +import imp
       +import base64
       +
       +script_dir = os.path.dirname(os.path.realpath(__file__))
       +sys.path.insert(0, os.path.join(script_dir, 'packages'))
       +
       +import qrcode
       +
       +imp.load_module('electrum', *imp.find_module('lib'))
       +
       +from electrum import SimpleConfig, Wallet, WalletStorage, format_satoshis
       +from electrum import util
       +from electrum.transaction import Transaction
       +from electrum.bitcoin import base_encode, base_decode
       +
       +def modal_dialog(title, msg = None):
       +    droid.dialogCreateAlert(title,msg)
       +    droid.dialogSetPositiveButtonText('OK')
       +    droid.dialogShow()
       +    droid.dialogGetResponse()
       +    droid.dialogDismiss()
       +
       +def modal_input(title, msg, value = None, etype=None):
       +    droid.dialogCreateInput(title, msg, value, etype)
       +    droid.dialogSetPositiveButtonText('OK')
       +    droid.dialogSetNegativeButtonText('Cancel')
       +    droid.dialogShow()
       +    response = droid.dialogGetResponse()
       +    result = response.result
       +    droid.dialogDismiss()
       +
       +    if result is None:
       +        return modal_input(title, msg, value, etype)
       +
       +    if result.get('which') == 'positive':
       +        return result.get('value')
       +
       +def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
       +    droid.dialogCreateAlert(q, msg)
       +    droid.dialogSetPositiveButtonText(pos_text)
       +    droid.dialogSetNegativeButtonText(neg_text)
       +    droid.dialogShow()
       +    response = droid.dialogGetResponse()
       +    result = response.result
       +    droid.dialogDismiss()
       +
       +    if result is None:
       +        return modal_question(q, msg, pos_text, neg_text)
       +
       +    return result.get('which') == 'positive'
       +
       +
       +
       +
       +
       +def make_layout(s):
       +    content = """
       +
       +      <LinearLayout 
       +        android:id="@+id/zz"
       +        android:layout_width="match_parent"
       +        android:layout_height="wrap_content" 
       +        android:background="#ff222222">
       +
       +        <TextView
       +          android:id="@+id/textElectrum"
       +          android:text="Electrum Authenticator"
       +          android:textSize="7pt"
       +          android:textColor="#ff4444ff"
       +          android:gravity="left"
       +          android:layout_height="wrap_content"
       +          android:layout_width="match_parent"
       +        />
       +      </LinearLayout>
       +
       +        %s   """%s
       +
       +
       +    return """<?xml version="1.0" encoding="utf-8"?>
       +      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       +        android:id="@+id/background"
       +        android:orientation="vertical" 
       +        android:layout_width="match_parent"
       +        android:layout_height="match_parent" 
       +        android:background="#ff000022">
       +
       +      %s 
       +      </LinearLayout>"""%content
       +
       +
       +
       +
       +
       +
       +def qr_layout(title):
       +    title_view= """
       +    <TextView android:id="@+id/addrTextView"
       +    android:layout_width="match_parent"
       +    android:layout_height="50"
       +    android:text="%s"
       +    android:textAppearance="?android:attr/textAppearanceLarge"
       +    android:gravity="center_vertical|center_horizontal|center">
       +    </TextView>"""%title
       +
       +    image_view="""
       +    <ImageView
       +    android:id="@+id/qrView"
       +    android:gravity="center"
       +    android:layout_width="match_parent"
       +    android:antialias="false"
       +    android:src=""
       +    />
       +    """
       +    return make_layout(title_view + image_view)
       +
       +
       +
       +
       +
       +
       +
       +
       +
       +
       +def add_menu():
       +    droid.clearOptionsMenu()
       +    droid.addOptionsMenuItem("Seed", "seed", None,"")
       +    droid.addOptionsMenuItem("Public Key", "xpub", None,"")
       +    droid.addOptionsMenuItem("Transaction", "scan", None,"")
       +    droid.addOptionsMenuItem("Password", "password", None,"")
       +
       +
       +
       +def make_bitmap(data):
       +    # fixme: this is highly inefficient
       +    import qrcode
       +    from electrum import bmp
       +    qr = qrcode.QRCode()
       +    qr.add_data(data)
       +    bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
       +
       +
       +droid = android.Android()
       +wallet = None
       +
       +class Authenticator:
       +
       +    def __init__(self):
       +        global wallet
       +        self.qr_data = None
       +        storage = WalletStorage({'wallet_path':'/sdcard/electrum/authenticator'})
       +        if not storage.file_exists:
       +
       +            action = self.restore_or_create()
       +            if not action:
       +                exit()
       +            password = droid.dialogGetPassword('Choose a password').result
       +            if password:
       +                password2 = droid.dialogGetPassword('Confirm password').result
       +                if password != password2:
       +                    modal_dialog('Error', 'Passwords do not match')
       +                    exit()
       +            else:
       +                password = None
       +            if action == 'create':
       +                wallet = Wallet(storage)
       +                seed = wallet.make_seed()
       +                modal_dialog('Your seed is:', seed)
       +            elif action == 'import':
       +                seed = self.seed_dialog()
       +                if not seed:
       +                    exit()
       +                if not Wallet.is_seed(seed):
       +                    exit()
       +                wallet = Wallet.from_seed(seed, storage)
       +            else:
       +                exit()
       +
       +            wallet.add_seed(seed, password)
       +            wallet.create_master_keys(password)
       +            wallet.create_main_account(password)
       +        else:
       +            wallet = Wallet(storage)
       +
       +    def restore_or_create(self):
       +        droid.dialogCreateAlert("Seed not found", "Do you want to create a new seed, or to import it?")
       +        droid.dialogSetPositiveButtonText('Create')
       +        droid.dialogSetNeutralButtonText('Import')
       +        droid.dialogSetNegativeButtonText('Cancel')
       +        droid.dialogShow()
       +        response = droid.dialogGetResponse().result
       +        droid.dialogDismiss()
       +        if not response: return
       +        if response.get('which') == 'negative':
       +            return
       +        return 'import' if response.get('which') == 'neutral' else 'create'
       +
       +    def seed_dialog(self):
       +        if modal_question("Enter your seed", "Input method", 'QR Code', 'mnemonic'):
       +            code = droid.scanBarcode()
       +            r = code.result
       +            if r:
       +                seed = r['extras']['SCAN_RESULT']
       +            else:
       +                return
       +        else:
       +            seed = modal_input('Mnemonic', 'Please enter your seed phrase')
       +        return str(seed)
       +
       +    def show_qr(self, data):
       +        path = "/sdcard/sl4a/qrcode.bmp"
       +        if data:
       +            droid.dialogCreateSpinnerProgress("please wait")
       +            droid.dialogShow()
       +            try:
       +                make_bitmap(data)
       +            finally:
       +                droid.dialogDismiss()
       +        else:
       +            with open(path, 'w') as f: f.write('')
       +        droid.fullSetProperty("qrView", "src", 'file://'+path)
       +        self.qr_data = data
       +
       +    def show_title(self, title):
       +        droid.fullSetProperty("addrTextView","text", title)
       +
       +    def get_password(self):
       +        if wallet.use_encryption:
       +            password = droid.dialogGetPassword('Password').result
       +            try:
       +                wallet.check_password(password)
       +            except:
       +                return False
       +            return password
       +
       +    def main(self):
       +        add_menu()
       +        welcome = 'Use the menu to scan a transaction.'
       +        droid.fullShow(qr_layout(welcome))
       +        while True:
       +            event = droid.eventWait().result
       +            if not event:
       +                continue
       +            elif event["name"] == "key":
       +                if event["data"]["key"] == '4':
       +                    if self.qr_data:
       +                        self.show_qr(None)
       +                        self.show_title(welcome)
       +                    else:
       +                        break
       +
       +            elif event["name"] == "seed":
       +                password = self.get_password()
       +                if password is False:
       +                    modal_dialog('Error','incorrect password')
       +                    continue
       +                seed = wallet.get_mnemonic(password)
       +                modal_dialog('Your seed is', seed)
       +
       +            elif event["name"] == "password":
       +                self.change_password_dialog()
       +
       +            elif event["name"] == "xpub":
       +                mpk = wallet.get_master_public_key()
       +                self.show_qr(mpk)
       +                self.show_title('master public key')
       +
       +            elif event["name"] == "scan":
       +                r = droid.scanBarcode()
       +                r = r.result
       +                if not r:
       +                    continue
       +                data = r['extras']['SCAN_RESULT']
       +                data = base_decode(data.encode('utf8'), None, base=43)
       +                data = ''.join(chr(ord(b)) for b in data).encode('hex')
       +                tx = Transaction.deserialize(data)
       +                #except:
       +                #    modal_dialog('Error', 'Cannot parse transaction')
       +                #    continue
       +                if not wallet.can_sign(tx):
       +                    modal_dialog('Error', 'Cannot sign this transaction')
       +                    continue
       +                lines = map(lambda x: x[0] + u'\t\t' + format_satoshis(x[1]) if x[1] else x[0], tx.get_outputs())
       +                if not modal_question('Sign?', '\n'.join(lines)):
       +                    continue
       +                password = self.get_password()
       +                if password is False:
       +                    modal_dialog('Error','incorrect password')
       +                    continue
       +                droid.dialogCreateSpinnerProgress("Signing")
       +                droid.dialogShow()
       +                wallet.sign_transaction(tx, password)
       +                droid.dialogDismiss()
       +                data = base_encode(str(tx).decode('hex'), base=43)
       +                self.show_qr(data)
       +                self.show_title('Signed Transaction')
       +
       +        droid.makeToast("Bye!")
       +
       +
       +    def change_password_dialog(self):
       +        if wallet.use_encryption:
       +            password  = droid.dialogGetPassword('Your seed is encrypted').result
       +            if password is None:
       +                return
       +        else:
       +            password = None
       +        try:
       +            wallet.check_password(password)
       +        except Exception:
       +            modal_dialog('Error', 'Incorrect password')
       +            return
       +        new_password  = droid.dialogGetPassword('Choose a password').result
       +        if new_password == None:
       +            return
       +        if new_password != '':
       +            password2  = droid.dialogGetPassword('Confirm new password').result
       +            if new_password != password2:
       +                modal_dialog('Error', 'passwords do not match')
       +                return
       +        wallet.update_password(password, new_password)
       +        if new_password:
       +            modal_dialog('Password updated', 'Your seed is encrypted')
       +        else:
       +            modal_dialog('No password', 'Your seed is not encrypted')
       +
       +
       +
       +if __name__ == "__main__":
       +    a = Authenticator()
       +    a.main()