Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fidelity bond wallets #544

Merged
merged 16 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 271 additions & 0 deletions docs/fidelity-bonds.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions jmbitcoin/jmbitcoin/secp256k1_deterministic.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def bip32_deserialize(data):

def raw_bip32_privtopub(rawtuple):
vbytes, depth, fingerprint, i, chaincode, key = rawtuple
if vbytes in PUBLIC:
return rawtuple
newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC
return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key, False))

Expand Down
2 changes: 1 addition & 1 deletion jmbitcoin/jmbitcoin/secp256k1_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#Required only for PoDLE calculation:
N = 115792089237316195423570985008687907852837564279074904382605163141518161494337

BTC_P2PK_VBYTE = {"mainnet": b'\x00', "testnet": b'\x6f'}
BTC_P2PK_VBYTE = {"mainnet": b'\x00', "testnet": b'\x6f', "regtest": 100}
BTC_P2SH_VBYTE = {"mainnet": b'\x05', "testnet": b'\xc4'}

#Standard prefix for Bitcoin message signing.
Expand Down
85 changes: 64 additions & 21 deletions jmbitcoin/jmbitcoin/secp256k1_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import random
from jmbitcoin.secp256k1_main import *
from jmbitcoin.bech32 import *
import jmbitcoin as btc

P2PKH_PRE, P2PKH_POST = b'\x76\xa9\x14', b'\x88\xac'
P2SH_P2WPKH_PRE, P2SH_P2WPKH_POST = b'\xa9\x14', b'\x87'
Expand Down Expand Up @@ -534,23 +535,29 @@ def pubkey_to_p2wpkh_address(pub):
script = pubkey_to_p2wpkh_script(pub)
return script_to_address(script)

def pubkeys_to_p2wsh_script(pubs):
def pubkeys_to_p2wsh_multisig_script(pubs):
""" Given a list of N pubkeys, constructs an N of N
multisig scriptPubKey of type pay-to-witness-script-hash.
No other scripts than N-N multisig supported as of now.
chris-belcher marked this conversation as resolved.
Show resolved Hide resolved
"""
N = len(pubs)
script = mk_multisig_script(pubs, N)
return P2WSH_PRE + bin_sha256(binascii.unhexlify(script))
return redeem_script_to_p2wsh_script(script)

def pubkeys_to_p2wsh_address(pubs):
def pubkeys_to_p2wsh_multisig_address(pubs):
""" Given a list of N pubkeys, constructs an N of N
multisig address of type pay-to-witness-script-hash.
No other scripts than N-N multisig supported as of now.
"""
script = pubkeys_to_p2wsh_script(pubs)
script = pubkeys_to_p2wsh_multisig_script(pubs)
return script_to_address(script)

def redeem_script_to_p2wsh_script(redeem_script):
return P2WSH_PRE + bin_sha256(binascii.unhexlify(redeem_script))

def redeem_script_to_p2wsh_address(redeem_script, vbyte, witver=0):
return script_to_address(redeem_script_to_p2wsh_script(redeem_script), vbyte, witver)

def deserialize_script(scriptinp):
""" Note that this is not used internally, in
the jmbitcoin package, to deserialize() transactions;
Expand Down Expand Up @@ -603,10 +610,14 @@ def hex_string(scriptbytes, hexout):

def serialize_script_unit(unit):
if isinstance(unit, int):
if unit < 16:
if unit == 0:
return from_int_to_byte(unit)
elif unit < 16:
return from_int_to_byte(unit + 80)
else:
elif unit < 256:
return from_int_to_byte(unit)
else:
return b'\x04' + struct.pack(b"<I", unit)
elif unit is None:
return b'\x00'
else:
Expand Down Expand Up @@ -634,15 +645,37 @@ def serialize_script(script):
else:
return result


def mk_multisig_script(pubs, k):
""" Given a list of pubkeys and an integer k,
construct a multisig script for k of N, where N is
the length of the list `pubs`; script is returned
as hex string.
"""
return serialize_script([k] + pubs + [len(pubs)]) + 'ae'
return serialize_script([k] + pubs + [len(pubs)]) \
+ 'ae' #OP_CHECKMULTISIG
chris-belcher marked this conversation as resolved.
Show resolved Hide resolved

def mk_freeze_script(pub, locktime):
"""
Given a pubkey and locktime, create a script which can only be spent
after the locktime has passed using OP_CHECKLOCKTIMEVERIFY
"""
if not isinstance(locktime, int):
raise TypeError("locktime must be int")
chris-belcher marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(pub, bytes):
raise TypeError("pubkey must be in bytes")
usehex = False
if not is_valid_pubkey(pub, usehex, require_compressed=True):
raise ValueError("not a valid public key")
scr = [locktime, btc.OP_CHECKLOCKTIMEVERIFY, btc.OP_DROP, pub,
btc.OP_CHECKSIG]
return binascii.hexlify(serialize_script(scr)).decode()

def mk_burn_script(data):
if not isinstance(data, bytes):
raise TypeError("data must be in bytes")
data = binascii.hexlify(data).decode()
scr = [btc.OP_RETURN, data]
return serialize_script(scr)
chris-belcher marked this conversation as resolved.
Show resolved Hide resolved

# Signing and verifying

Expand Down Expand Up @@ -701,8 +734,8 @@ def sign(tx, i, priv, hashcode=SIGHASH_ALL, usenonce=None, amount=None,
`amount` flags whether segwit signing is to be done, and the field
`native` flags that native segwit p2wpkh signing is to be done. Note
that signing multisig is to be done with the alternative functions
multisign or p2wsh_multisign (and non N of N multisig scripthash
signing is not currently supported).
get_p2sh_signature or get_p2wsh_signature (and non N of N multisig
scripthash signing is not currently supported).
"""
if isinstance(tx, basestring) and not isinstance(tx, bytes):
tx = binascii.unhexlify(tx)
Expand Down Expand Up @@ -762,28 +795,28 @@ def signall(tx, priv):
tx = sign(tx, i, priv)
return tx


def multisign(tx, i, script, pk, amount=None, hashcode=SIGHASH_ALL):
""" Tx is assumed to be serialized. The script passed here is
the redeemscript, for example the output of mk_multisig_script.
def get_p2sh_signature(tx, i, redeem_script, pk, amount=None, hashcode=SIGHASH_ALL):
"""
Tx is assumed to be serialized. redeem_script is for example the
output of mk_multisig_script.
pk is the private key, and must be passed in hex.
If amount is not None, the output of p2wsh_multisign is returned.
If amount is not None, the output of get_p2wsh_signature is returned.
What is returned is a single signature.
"""
if isinstance(tx, str):
tx = binascii.unhexlify(tx)
if isinstance(script, str):
script = binascii.unhexlify(script)
if isinstance(redeem_script, str):
redeem_script = binascii.unhexlify(redeem_script)
if amount:
return p2wsh_multisign(tx, i, script, pk, amount, hashcode)
modtx = signature_form(tx, i, script, hashcode)
return get_p2wsh_signature(tx, i, redeem_script, pk, amount, hashcode)
modtx = signature_form(tx, i, redeem_script, hashcode)
return ecdsa_tx_sign(modtx, pk, hashcode)

def p2wsh_multisign(tx, i, script, pk, amount, hashcode=SIGHASH_ALL):
def get_p2wsh_signature(tx, i, redeem_script, pk, amount, hashcode=SIGHASH_ALL):
""" See note to multisign for the value to pass in as `script`.
Tx is assumed to be serialized.
"""
modtx = segwit_signature_form(deserialize(tx), i, script, amount,
modtx = segwit_signature_form(deserialize(tx), i, redeem_script, amount,
hashcode, decoder_func=lambda x: x)
return ecdsa_tx_sign(modtx, pk, hashcode)

Expand Down Expand Up @@ -819,6 +852,16 @@ def apply_multisignatures(*args):
txobj["ins"][i]["script"] = serialize_script([None] + sigs + [script])
return serialize(txobj)

def apply_freeze_signature(tx, i, redeem_script, sig):
if isinstance(redeem_script, str):
redeem_script = binascii.unhexlify(redeem_script)
if isinstance(sig, str):
sig = binascii.unhexlify(sig)
txobj = deserialize(tx)
txobj["ins"][i]["script"] = ""
txobj["ins"][i]["txinwitness"] = [sig, redeem_script]
return serialize(txobj)

def mktx(ins, outs, version=1, locktime=0):
""" Given a list of input dicts with key "output"
which are txid:n strings in hex, and a list of outputs
Expand Down
12 changes: 7 additions & 5 deletions jmclient/jmclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
from .taker import Taker, P2EPTaker
from .wallet import (Mnemonic, estimate_tx_fee, WalletError, BaseWallet, ImportWalletMixin,
BIP39WalletMixin, BIP32Wallet, BIP49Wallet, LegacyWallet,
SegwitWallet, SegwitLegacyWallet, UTXOManager,
WALLET_IMPLEMENTATIONS, compute_tx_locktime)
SegwitWallet, SegwitLegacyWallet, FidelityBondMixin,
FidelityBondWatchonlyWallet, SegwitLegacyWalletFidelityBonds,
UTXOManager, WALLET_IMPLEMENTATIONS, compute_tx_locktime)
from .storage import (Argon2Hash, Storage, StorageError, RetryableStorageError,
StoragePasswordError, VolatileStorage)
from .cryptoengine import BTCEngine, BTC_P2PKH, BTC_P2SH_P2WPKH, EngineError
from .configure import (load_test_config,
load_program_config, get_p2pk_vbyte, jm_single, get_network, update_persist_config,
validate_address, get_irc_mchannels, get_blockchain_interface_instance,
get_p2sh_vbyte, set_config, is_segwit_mode, is_native_segwit_mode)
validate_address, is_burn_destination, get_irc_mchannels,
get_blockchain_interface_instance, get_p2sh_vbyte, set_config, is_segwit_mode,
is_native_segwit_mode)
from .blockchaininterface import (BlockchainInterface,
RegtestBitcoinCoreInterface, BitcoinCoreInterface)
from .electruminterface import ElectrumInterface
Expand All @@ -48,7 +50,7 @@
from .wallet_utils import (
wallet_tool_main, wallet_generate_recover_bip39, open_wallet,
open_test_wallet_maybe, create_wallet, get_wallet_cls, get_wallet_path,
wallet_display, get_utxos_enabled_disabled)
wallet_display, get_utxos_enabled_disabled, wallet_gettimelockaddress)
from .wallet_service import WalletService
from .maker import Maker, P2EPMaker
from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain
Expand Down
25 changes: 25 additions & 0 deletions jmclient/jmclient/blockchaininterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import time
from decimal import Decimal
import binascii
from twisted.internet import reactor, task

import jmbitcoin as btc
Expand Down Expand Up @@ -406,6 +407,30 @@ def get_block_time(self, blockhash):
except JsonRpcError:
return self.rpc('getblock', [blockhash])['time']

def get_tx_merkle_branch(self, txid, blockhash=None):
if not blockhash:
tx = self.rpc("gettransaction", [txid])
if tx["confirmations"] < 1:
raise ValueError("Transaction not in block")
chris-belcher marked this conversation as resolved.
Show resolved Hide resolved
blockhash = tx["blockhash"]
try:
core_proof = self.rpc("gettxoutproof", [[txid], blockhash])
except JsonRpcError:
raise ValueError("Block containing transaction is pruned")
return self.core_proof_to_merkle_branch(core_proof)

def core_proof_to_merkle_branch(self, core_proof):
core_proof = binascii.unhexlify(core_proof)
#first 80 bytes of a proof given by core are just a block header
#so we can save space by replacing it with a 4-byte block height
return core_proof[80:]

def verify_tx_merkle_branch(self, txid, block_height, merkle_branch):
block_hash = self.rpc("getblockhash", [block_height])
core_proof = self.rpc("getblockheader", [block_hash, False]) + \
binascii.hexlify(merkle_branch).decode()
ret = self.rpc("verifytxoutproof", [core_proof])
return len(ret) == 1 and ret[0] == txid

class RegtestBitcoinCoreMixin():
"""
Expand Down
2 changes: 1 addition & 1 deletion jmclient/jmclient/cli_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def get_tumbler_parser():
def get_sendpayment_parser():
parser = OptionParser(
usage=
'usage: %prog [options] wallet_file amount destaddr\n' +
'usage: %prog [options] wallet_file amount destination\n' +
' %prog [options] wallet_file bitcoin_uri',
description='Sends a single payment from a given mixing depth of your '
+
Expand Down
5 changes: 5 additions & 0 deletions jmclient/jmclient/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ def validate_address(addr):
return True, 'address validated'


_BURN_DESTINATION = "BURN"

def is_burn_destination(destination):
return destination == _BURN_DESTINATION

def donation_address(reusable_donation_pubkey=None): #pragma: no cover
#Donation code currently disabled, so not tested.
if not reusable_donation_pubkey:
Expand Down
Loading