Skip to content

Commit

Permalink
timelocking and first ui
Browse files Browse the repository at this point in the history
  • Loading branch information
KarolTrzeszczkowski committed Feb 26, 2019
0 parents commit b6b04b4
Show file tree
Hide file tree
Showing 9 changed files with 1,322 additions and 0 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions README.md
@@ -0,0 +1,4 @@
# Electron Cash Coinsplitter plugin
This is [Mark's Lundeberg's coinsplitter](https://github.com/markblundeberg/coinsplitter) ported to the plugin architecture.

All of the user guides can be found in description of the oryginal coinsplitter. The main difference is that now coinsplitter is in the new tab, not a dialog in the menu. You also need a separate plugin ([BSV Plugin](https://github.com/KarolTrzeszczkowski/Electron-Cash-BSV-Plugin)) to access your funds on the BSV chain via Electron Cash. Make sure you read BSV Plugin README.md before using it. You can import your private keys to a different wallet app if you do't want to use BSV Plugin.
Binary file added last-will-plugin.zip
Binary file not shown.
5 changes: 5 additions & 0 deletions last-will-plugin/__init__.py
@@ -0,0 +1,5 @@
from electroncash.i18n import _

fullname = 'last-will-plugin'
description = _('Plugin last-will-plugin')
available_for = ['qt']
121 changes: 121 additions & 0 deletions last-will-plugin/last_will_contract.py
@@ -0,0 +1,121 @@
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
from electroncash.bitcoin import ser_to_point, point_to_ser
from electroncash.address import Address, Script, hash160, ScriptOutput
import hashlib
from .op_codes import OpCodes
import time
LOCKTIME_THRESHOLD = 500000000

def joinbytes(iterable):
"""Joins an iterable of bytes and/or integers into a single byte string"""
return b''.join((bytes((x,)) if isinstance(x,int) else x) for x in iterable)

class LastWillContract:
"""Contract for making coins that can only be spent on BCH chains supporting
OP_CHECKDATASIGVERIFY, with backup clause for recovering dust on non-supporting
chains."""
def __init__(self, master_privkey):
G = generator_secp256k1
order = G.order()
# hard derivation (irreversible):
x = int.from_bytes(hashlib.sha512(b'Split1' + master_privkey.to_bytes(32, 'big')).digest(), 'big')
self.priv1 = 1 + (x % (order-1))
self.pub1ser = point_to_ser(self.priv1 * G, True)
self.keypairs = {self.pub1ser.hex() : (self.priv1.to_bytes(32, 'big'), True)}



days=0.04
self.seconds= int(time.time()) + int(days * 24 * 60 * 60)
seconds_bytes=format_time(self.seconds)

self.redeemscript = joinbytes([
len(seconds_bytes), seconds_bytes, OpCodes.OP_CHECKLOCKTIMEVERIFY,
OpCodes.OP_DROP,
len(self.pub1ser), self.pub1ser,
OpCodes.OP_CHECKSIG ])

print(len(self.redeemscript))
# assert 76< len(self.redeemscript) <= 255 # simplify push in scriptsig; note len is around 200.
self.address = Address.from_multisig_script(self.redeemscript)
self.dummy_scriptsig_redeem = '01'*(74 + len(self.redeemscript)) # make dummy scripts of correct size for size estimation.


def makeinput(self, prevout_hash, prevout_n, value):
"""
Construct an unsigned input for adding to a transaction. scriptSig is
set to a dummy value, for size estimation.
(note: Transaction object will fail to broadcast until you sign and run `completetx`)
"""

scriptSig = self.dummy_scriptsig_redeem
pubkey = self.pub1ser

txin = dict(
prevout_hash = prevout_hash,
prevout_n = prevout_n,
sequence = 0,
scriptSig = scriptSig,

type = 'unknown',
address = self.address,
scriptCode = self.redeemscript.hex(),
num_sig = 1,
signatures = [None],
x_pubkeys = [pubkey.hex()],
value = value,
)
return txin

def signtx(self, tx):
"""generic tx signer for compressed pubkey"""
tx.sign(self.keypairs)

def completetx(self, tx):
"""
Completes transaction by creating scriptSig. You need to sign the
transaction before using this (see `signtx`). `secret` may be bytes
(if redeeming) or None (if refunding).
This works on multiple utxos if needed.
"""

for txin in tx.inputs():
# find matching inputs
if txin['address'] != self.address:
continue
sig = txin['signatures'][0]
if not sig:
continue
sig = bytes.fromhex(sig)
if txin['scriptSig'] == self.dummy_scriptsig_redeem:
script = [
len(sig), sig,
len(self.redeemscript), self.redeemscript, # Script shorter than 75 bits
]
txin['scriptSig'] = joinbytes(script).hex()
# need to update the raw, otherwise weird stuff happens.
tx.raw = tx.serialize()



def format_time(seconds):

print("Transaction Locktime in seconds: "+ str(seconds))
print("Current time: "+ str(time.time()))

assert seconds >= LOCKTIME_THRESHOLD
assert seconds < 0x8000000000

if seconds < 0x80000000:
# until year 2038 use 4 byte form
time_bytes = seconds.to_bytes(4, 'little')
else:
# from 2038 onwards our number cannot fit into 4 bytes since the high
# bit is used for sign, in bitcoin script.
time_bytes = seconds.to_bytes(5, 'little')

assert time_bytes[-1] != 0 and not time_bytes[-1] & 0x80
return time_bytes
# The transaction was rejected because it contians a non-mandatory script verify flag.
23 changes: 23 additions & 0 deletions last-will-plugin/op_codes.py
@@ -0,0 +1,23 @@
from electroncash.enum import Enumeration

OpCodes = Enumeration("OpCodes", [
("OP_0", 0), ("OP_PUSHDATA1",76), "OP_PUSHDATA2", "OP_PUSHDATA4", "OP_1NEGATE", "OP_RESERVED",
"OP_1", "OP_2", "OP_3", "OP_4", "OP_5", "OP_6", "OP_7",
"OP_8", "OP_9", "OP_10", "OP_11", "OP_12", "OP_13", "OP_14", "OP_15", "OP_16",
"OP_NOP", "OP_VER", "OP_IF", "OP_NOTIF", "OP_VERIF", "OP_VERNOTIF", "OP_ELSE", "OP_ENDIF", "OP_VERIFY",
"OP_RETURN", "OP_TOALTSTACK", "OP_FROMALTSTACK", "OP_2DROP", "OP_2DUP", "OP_3DUP", "OP_2OVER", "OP_2ROT", "OP_2SWAP",
"OP_IFDUP", "OP_DEPTH", "OP_DROP", "OP_DUP", "OP_NIP", "OP_OVER", "OP_PICK", "OP_ROLL", "OP_ROT",
"OP_SWAP", "OP_TUCK", "OP_CAT", "OP_SPLIT", "OP_NUM2BIN", "OP_BIN2NUM", "OP_SIZE", "OP_INVERT", "OP_AND",
"OP_OR", "OP_XOR", "OP_EQUAL", "OP_EQUALVERIFY", "OP_RESERVED1", "OP_RESERVED2", "OP_1ADD", "OP_1SUB", "OP_2MUL",
"OP_2DIV", "OP_NEGATE", "OP_ABS", "OP_NOT", "OP_0NOTEQUAL", "OP_ADD", "OP_SUB", "OP_MUL", "OP_DIV",
"OP_MOD", "OP_LSHIFT", "OP_RSHIFT", "OP_BOOLAND", "OP_BOOLOR",
"OP_NUMEQUAL", "OP_NUMEQUALVERIFY", "OP_NUMNOTEQUAL", "OP_LESSTHAN",
"OP_GREATERTHAN", "OP_LESSTHANOREQUAL", "OP_GREATERTHANOREQUAL", "OP_MIN", "OP_MAX",
"OP_WITHIN", "OP_RIPEMD160", "OP_SHA1", "OP_SHA256", "OP_HASH160",
"OP_HASH256", "OP_CODESEPARATOR", "OP_CHECKSIG", "OP_CHECKSIGVERIFY", "OP_CHECKMULTISIG",
"OP_CHECKMULTISIGVERIFY",
("OP_NOP1", 0xB0),
"OP_CHECKLOCKTIMEVERIFY", "OP_CHECKSEQUENCEVERIFY",
"OP_NOP4", "OP_NOP5", "OP_NOP6", "OP_NOP7", "OP_NOP8", "OP_NOP9", "OP_NOP10",
"OP_CHECKDATASIG", "OP_CHECKDATASIGVERIFY",
])
102 changes: 102 additions & 0 deletions last-will-plugin/qt.py
@@ -0,0 +1,102 @@
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import electroncash.version
from electroncash.i18n import _
from electroncash.plugins import BasePlugin, hook


class Plugin(BasePlugin):
electrumcash_qt_gui = None
# There's no real user-friendly way to enforce this. So for now, we just calculate it, and ignore it.
is_version_compatible = True

def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
self.network=None
self.wallet_windows = {}
self.lw_tabs = {}
self.lw_tab = {}
def fullname(self):
return 'Last Will'

def description(self):
return _("Plugin Last Will")

def is_available(self):
if self.is_version_compatible is None:
version = float(electroncash.version.PACKAGE_VERSION)
self.is_version_compatible = version >= MINIMUM_ELECTRON_CASH_VERSION
return True


def on_close(self):
"""
BasePlugin callback called when the wallet is disabled among other things.
"""
for window in list(self.wallet_windows.values()):
self.close_wallet(window.wallet)

@hook
def update_contact(self, address, new_entry, old_entry):
print("update_contact", address, new_entry, old_entry)

@hook
def delete_contacts(self, contact_entries):
print("delete_contacts", contact_entries)

@hook
def init_qt(self, qt_gui):
"""
Hook called when a plugin is loaded (or enabled).
"""
self.electrumcash_qt_gui = qt_gui
# We get this multiple times. Only handle it once, if unhandled.
if len(self.wallet_windows):
return
# These are per-wallet windows.
for window in self.electrumcash_qt_gui.windows:
self.load_wallet(window.wallet, window)

@hook
def load_wallet(self, wallet, window):
"""
Hook called when a wallet is loaded and a window opened for it.
"""
wallet_name = window.wallet.basename()
self.wallet_windows[wallet_name] = window
self.add_ui_for_wallet(wallet_name, window)
self.refresh_ui_for_wallet(wallet_name)


@hook
def close_wallet(self, wallet):

wallet_name = wallet.basename()
window = self.wallet_windows[wallet_name]
del self.wallet_windows[wallet_name]
self.remove_ui_for_wallet(wallet_name, window)


def add_ui_for_wallet(self, wallet_name, window):
from .ui import Intro
l = Intro(window, self, wallet_name, address=None)
tab = window.create_list_tab(l)
self.lw_tabs[wallet_name] = tab
self.lw_tab[wallet_name] = l
window.tabs.addTab(tab, QIcon(":icons/preferences.png"), _('Last Will'))

def remove_ui_for_wallet(self, wallet_name, window):
wallet_tab = self.lw_tabs.get(wallet_name, None)
if wallet_tab is not None:
del self.lw_tab[wallet_name]
del self.lw_tabs[wallet_name]
i = window.tabs.indexOf(wallet_tab)
window.tabs.removeTab(i)


def refresh_ui_for_wallet(self, wallet_name):
wallet_tab = self.lw_tabs[wallet_name]
wallet_tab.update()
wallet_tab = self.lw_tab[wallet_name]
wallet_tab.update()

0 comments on commit b6b04b4

Please sign in to comment.