Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b6b04b4
Showing
9 changed files
with
1,322 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from electroncash.i18n import _ | ||
|
||
fullname = 'last-will-plugin' | ||
description = _('Plugin last-will-plugin') | ||
available_for = ['qt'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
Oops, something went wrong.