Exemple #1
0
async def wallet(request: Request) -> Wallet:
    provider: Provider = request.scope['provider']
    pub_key: str = request.path_params['pub_key']

    # noinspection PyShadowingNames
    wallet = await provider.get_wallet(pub_key)

    message = '|'.join([
        request.method,
        request.url.path if not request.url.query else f'{request.url.path}?{request.url.query}',
        (await request.body()).decode('utf-8', errors='replace') or '{}',
    ]).encode('utf-8')

    message_hash = double_sha256(message)
    key = Key(pub_key)

    x_signature = request.headers.get('x-signature', '')
    signature = base64.b16decode(x_signature, casefold=True)

    if not signature:
        raise Exception('Signature must exist')

    if not verify(message_hash, signature, key):
        raise Exception('Verify signature failure')

    yield wallet
Exemple #2
0
    def SignRemoteHTLCToUs(self, request, context):
        """BOLT #3 - HTLC Outputs, phase 1 Sign an htlc-success tx on a
        remote HTLC output offered to us, assuming we know the preimage,
        at force-close time
        """
        node_id = request.node_id.data
        node = self.nodes.get(node_id)
        logger.debug("SignRemoteHTLCToUs node:%s" % node_id.hex())

        channel_nonce = request.channel_nonce.data
        channel = node.channels.get(channel_nonce)

        remote_per_commit_point = request.remote_per_commit_point.data

        tx = request.tx
        amount = tx.input_descs[0].prev_output.value_sat
        redeemscript = tx.input_descs[0].redeem_script

        reply = remotesigner_pb2.SignatureReply()

        htlc_key = Key(
            import_key=channel.basepoints.keys.htlc_basepoint_secret,
            is_private=True)
        htlc_basepoint = htlc_key.public_compressed_byte

        local_priv_key = derive_priv_key(
            remote_per_commit_point, htlc_basepoint,
            channel.basepoints.keys.htlc_basepoint_secret)

        local_key = Key(import_key=local_priv_key, is_private=True)
        keys = [local_key]

        tx = Transaction.import_raw(tx.raw_tx_bytes, self.network)
        tx.inputs[0].script_type = "p2wsh"
        tx.inputs[0].witness_type = 'segwit'
        tx.inputs[0].redeemscript = redeemscript
        tx.inputs[0].value = amount
        tx.witness_type = 'segwit'
        tx.sign(keys=keys, tid=0)

        logger.debug("remote_per_commit_point %s" %
                     remote_per_commit_point.hex())
        logger.debug("channel.basepoints.funding_pk %s" %
                     channel.basepoints.funding_pk.hex())
        reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded(
        ) + b'\x01'
        return reply
Exemple #3
0
    def SignRemoteCommitmentTx(self, request, context):
        """BOLT #3 - Commitment Transaction, phase 1
        Sign the remote commitment tx, at commitment time
        """
        node_id = request.node_id.data
        node = self.nodes.get(node_id)
        logger.debug("SignRemoteCommitmentTx node:%s" % node_id.hex())

        channel_nonce = request.channel_nonce.data
        channel = node.channels.get(channel_nonce)

        tx = request.tx
        amount = tx.input_descs[0].prev_output.value_sat

        reply = remotesigner_pb2.SignatureReply()

        local_key = Key(import_key=channel.funding_privkey, is_private=True)
        remote_pub = channel.remote_basepoints.funding_pubkey
        remote_pubkey = Key(
            import_key=channel.remote_basepoints.funding_pubkey)

        pubs = sorted([remote_pub, local_key.public_compressed_byte])
        if remote_pub == pubs[0]:
            keys = [remote_pubkey, local_key]
        else:
            keys = [local_key, remote_pubkey]

        tx = Transaction.import_raw(tx.raw_tx_bytes, self.network)
        tx.inputs[0].script_type = "p2wsh"
        tx.inputs[0].witness_type = 'segwit'
        tx.inputs[0].sigs_required = 2
        tx.inputs[0].value = amount
        tx.witness_type = 'segwit'
        tx.sign(keys=keys, tid=0)

        logger.debug("channel.basepoints.funding_pk %s" %
                     channel.basepoints.funding_pk.hex())
        reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded(
        ) + b'\x01'
        return reply
Exemple #4
0
    def SignDelayedPaymentToUs(self, request, context):
        """BOLT #5 - Unilateral Close Handling, phase 1
        Sign a delayed to-local output - either from the commitment tx or
        from an HTLC, at force-close time
        """
        node_id = request.node_id.data
        node = self.nodes.get(node_id)
        logger.debug("SignDelayedPaymentToUs node:%s" % node_id.hex())

        channel_nonce = request.channel_nonce.data
        channel = node.channels.get(channel_nonce)

        n = request.n

        tx = request.tx
        amount = tx.input_descs[0].prev_output.value_sat
        redeemscript = tx.input_descs[0].redeem_script

        reply = remotesigner_pb2.SignatureReply()

        delayed_payment_basepoint = channel.basepoints.delayed_payment

        element = shachain_derive(channel.shaelement, START_INDEX - n)
        per_commitment_point_prv = coincurve.PrivateKey(
            secret=bytes(element.secret))
        local_per_commit_point = per_commitment_point_prv.public_key.format()

        local_priv_key = derive_priv_key(
            local_per_commit_point, delayed_payment_basepoint,
            channel.basepoints.keys.delayed_payment_basepoint_secret)

        local_key = Key(import_key=local_priv_key, is_private=True)
        keys = [local_key]

        tx = Transaction.import_raw(tx.raw_tx_bytes, self.network)
        tx.inputs[0].script_type = "p2wsh"
        tx.inputs[0].witness_type = 'segwit'
        tx.inputs[0].redeemscript = redeemscript
        tx.inputs[0].value = amount
        tx.witness_type = 'segwit'
        tx.sign(keys=keys, tid=0)

        reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded(
        ) + b'\x01'
        return reply
Exemple #5
0
    def SignLocalHTLCTx(self, request, context):
        """BOLT #3 - HTLC Outputs, phase 1
        Sign an htlc-success tx spending a local HTLC output, assuming we
        know the preimage, at force-close time
        """
        node_id = request.node_id.data
        node = self.nodes.get(node_id)
        logger.debug("SignLocalHTLCTx node:%s" % node_id.hex())

        channel_nonce = request.channel_nonce.data
        channel = node.channels.get(channel_nonce)

        n = request.n

        tx = request.tx
        amount = tx.input_descs[0].prev_output.value_sat
        redeemscript = tx.input_descs[0].redeem_script

        reply = remotesigner_pb2.SignatureReply()

        htlc_basepoint = channel.basepoints.htlc

        element = shachain_derive(channel.shaelement, START_INDEX - n)
        per_commitment_point_prv = coincurve.PrivateKey(
            secret=bytes(element.secret))
        local_per_commit_point = per_commitment_point_prv.public_key.format()

        local_priv_key = derive_priv_key(
            local_per_commit_point, htlc_basepoint,
            channel.basepoints.keys.htlc_basepoint_secret)

        local_key = Key(import_key=local_priv_key, is_private=True)
        keys = [local_key]

        tx = Transaction.import_raw(tx.raw_tx_bytes, self.network)
        tx.inputs[0].script_type = "p2wsh"
        tx.inputs[0].witness_type = 'segwit'
        tx.inputs[0].redeemscript = redeemscript
        tx.inputs[0].value = amount
        tx.witness_type = 'segwit'
        tx.sign(keys=keys, tid=0)

        reply.signature.data = tx.inputs[0].signatures[0].as_der_encoded(
        ) + b'\x01'
        return reply
Exemple #6
0
def serialize_multisig_redeemscript(key_list,
                                    n_required=None,
                                    compressed=True):
    """
    Create a multisig redeemscript used in a p2sh.

    Contains the number of signatures, followed by the list of public keys and the OP-code for the number of signatures required.

    :param key_list: List of public keys
    :type key_list: Key, list
    :param n_required: Number of required signatures
    :type n_required: int
    :param compressed: Use compressed public keys?
    :type compressed: bool

    :return bytes: A multisig redeemscript
    """
    if not key_list:
        return b''
    if not isinstance(key_list, list):
        raise TransactionError("Argument public_key_list must be of type list")
    public_key_list = []
    for k in key_list:
        if isinstance(k, Key):
            if compressed:
                public_key_list.append(k.public_byte)
            else:
                public_key_list.append(k.public_uncompressed_byte)
        elif len(k) == 65 and k[0:1] == b'\x04' or len(k) == 33 and k[0:1] in [
                b'\x02', b'\x03'
        ]:
            public_key_list.append(k)
        else:
            try:
                kobj = Key(k)
                if compressed:
                    public_key_list.append(kobj.public_byte)
                else:
                    public_key_list.append(kobj.public_uncompressed_byte)
            except:
                raise TransactionError(
                    "Unknown key %s, please specify Key object, public or private key string"
                )

    return _serialize_multisig_redeemscript(public_key_list, n_required)
Exemple #7
0
    def SignPenaltyToUs(self, request, context):
        """BOLT #5 - Unilateral Close Handling, phase 1
        Sign a penalty tx to us - either sweeping the to-local commitment
        tx output or sweeping an HTLC tx output, after the remote
        broadcast a revoked commitment transaction.
        """
        node_id = request.node_id.data
        node = self.nodes.get(node_id)
        logger.debug("SignPenaltyToUs node:%s" % node_id.hex())

        channel_nonce = request.channel_nonce.data
        channel = node.channels.get(channel_nonce)

        revocation_secret = request.revocation_secret.data

        tx = request.tx
        amount = tx.input_descs[0].prev_output.value_sat
        redeemscript = tx.input_descs[0].redeem_script
        index = request.input

        revocationprivkey = derive_blinded_privkey(
            revocation_secret,
            channel.basepoints.keys.revocation_basepoint_secret)

        local_key = Key(import_key=revocationprivkey, is_private=True)
        keys = [local_key]

        tx = Transaction.import_raw(tx.raw_tx_bytes, self.network)
        tx.inputs[index].script_type = "p2wsh"
        tx.inputs[index].witness_type = 'segwit'
        tx.inputs[index].redeemscript = redeemscript
        tx.inputs[index].value = amount
        tx.witness_type = 'segwit'
        tx.sign(keys=keys, tid=index)

        reply = remotesigner_pb2.SignatureReply()
        reply.signature.data = tx.inputs[index].signatures[0].as_der_encoded(
        ) + b'\x01'

        return reply
Exemple #8
0
    def create_transaction(self, wallet_address_list,
                           private_input_address_list, output_address,
                           change_address, send_amount, fee_per_kb, network):
        try:

            print(
                'Create and sign  Transaction with Multiple INPUTS using keys from Wallet class '
            )
            charge_back_amount = 0
            print('Dummy transaction with Outputs ')
            transaction_outputs = [
                Output(send_amount, address=output_address, network=network),
                Output(charge_back_amount,
                       address=change_address,
                       network=network)
            ]
            print('transaction_outputs', transaction_outputs)
            print('created dummy txn for fee calc')
            t = Transaction(outputs=transaction_outputs,
                            network=network,
                            fee_per_kb=fee_per_kb)
            print('Fee with o/p  ', t.calculate_fee())
            fee = t.calculate_fee()
            charge_back_amount = send_amount - fee
            '''
            if charge_back_amount < 0:
                raise ServiceException('Insufficient fund to continue transaction')
            '''
            print('created real txn for fee calc')
            transaction_outputs = [
                Output(send_amount, address=output_address, network=network),
                Output(charge_back_amount,
                       address=change_address,
                       network=network)
            ]
            print('transaction_outputs', transaction_outputs)
            transaction_inputs = []
            for input in wallet_address_list:
                transaction_inputs.append(
                    (input['txid'], int(input['vout']),
                     self.json_get(private_input_address_list,
                                   input['address'])))
            print('transaction_inputs', transaction_inputs)
            t.fee = t.calculate_fee()
            print('Fee with ip/ op ', t.fee)

            for ti in transaction_inputs:
                ki = Key(ti[2], network=network)
                t.add_input(prev_hash=ti[0], output_n=ti[1], keys=ki.public())
            icount = 0
            for ti in transaction_inputs:
                ki = Key(ti[2], network=network)
                t.sign(ki.private_byte, icount)
                icount += 1

            print('\nRaw Signed Transaction %s' % binascii.hexlify(t.raw()))
            print('\nVerified %s' % t.verify())

            print('------------info-----------')
            t.info()
            print('------------end-----------')
        except Exception as e:
            raise e

        return t
Exemple #9
0
#
#    BitcoinLib - Python Cryptocurrency Library
#
#    EXAMPLES - Key and HDKey Class
#
#    © 2017 September - 1200 Web Development <http://1200wd.com/>
#

from pprint import pprint

from bitcoinlib.keys import Address, HDKey, Key, deserialize_address

# Key Class Examples

print("\n=== Generate random key ===")
k = Key()
k.info()

print("\n=== Import Public key ===")
K = Key('025c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec')
K.info()

print("\n=== Import Private key as decimal ===")
pk = 45552833878247474734848656701264879218668934469350493760914973828870088122784
k = Key(import_key=pk, network='testnet')
k.info()

print("\n=== Import Private key as byte ===")
pk = b':\xbaAb\xc7%\x1c\x89\x12\x07\xb7G\x84\x05Q\xa7\x199\xb0\xde\x08\x1f\x85\xc4\xe4L\xf7\xc1>A\xda\xa6\x01'
k = Key(pk)
k.info()
Exemple #10
0
    def SignFundingTx(self, request, context):
        """BOLT #3 - Funding Transaction
        Sign the funding transaction
        TODO this must be done after we know the remote signature on the
        commitment tx - need an API call that provides this to the
        signer.
        """

        node_id = request.node_id.data
        node = self.nodes.get(node_id)
        logger.debug("SignFundingTx node:%s" % node_id)

        xpub = node.bip32_key.get_xpriv_from_path('m/0/0')
        onchain_bip32_key = BIP32.from_xpriv(xpub)

        tx = request.tx
        tx = Transaction.import_raw(tx.raw_tx_bytes, self.network)

        reply = remotesigner_pb2.SignFundingTxReply()
        witnesses = remotesigner_pb2.Witness()

        local_prv = None
        local_key = None
        logger.debug(request)
        logger.debug(request.tx.raw_tx_bytes.hex())
        for i, input_desc in enumerate(request.tx.input_descs):
            key_index = input_desc.key_loc.key_index
            spend_type = input_desc.spend_type
            amount = input_desc.prev_output.value_sat
            channel_nonce = input_desc.close_info.channel_nonce.data
            commitment_point = input_desc.close_info.commitment_point.data
            channel = node.channels.get(channel_nonce)

            if key_index != 0:
                local_prv = onchain_bip32_key.get_privkey_from_path("m/%d" %
                                                                    key_index)
                local_key = Key(import_key=local_prv, is_private=True)
            elif channel:
                if commitment_point:
                    local_priv_key = derive_priv_key(
                        commitment_point, payment_basepoint,
                        channel.basepoints.keys.payment_basepoint_secret)
                    local_key = Key(import_key=local_priv_key, is_private=True)
                else:
                    local_key = Key(import_key=channel.basepoints.keys.
                                    payment_basepoint_secret,
                                    is_private=True)
            else:
                witnesses.signature.data = b''
                witnesses.pubkey.data = b''
                reply.witnesses.extend([witnesses])
                continue

            spend_type = spend_type_to_string(spend_type)
            tx.inputs[i].script_type = spend_type
            logger.debug("funding signature: %s" % spend_type)
            tx.inputs[i].witness_type = "segwit"
            tx.inputs[i].sigs_required = 1
            tx.inputs[i].value = amount
            if spend_type == 'p2pkh':
                tx.witness_type = 'legacy'
            else:
                tx.witness_type = 'segwit'
            tx.sign(keys=[local_key], tid=i)

            witnesses.signature.data = tx.inputs[i].witnesses[0]
            witnesses.pubkey.data = tx.inputs[i].witnesses[1]

            logger.debug("funding signature: %s" %
                         tx.inputs[i].signatures[0].as_der_encoded().hex())
            logger.debug("funding public key %s" %
                         tx.inputs[i].as_dict()['public_keys'])
            reply.witnesses.extend([witnesses])
        logger.debug(reply)
        return reply
Exemple #11
0
    def sign(self, keys, tid=0, hash_type=SIGHASH_ALL):
        """
        Sign the transaction input with provided private key
        
        :param keys: A private key or list of private keys
        :type keys: HDKey, Key, bytes, list
        :param tid: Index of transaction input
        :type tid: int
        :param hash_type: Specific hash type, default is SIGHASH_ALL
        :type hash_type: int

        :return int: Return int with number of signatures added
        """

        n_signs = 0
        if not isinstance(keys, list):
            keys = [keys]

        if self.inputs[tid].script_type == 'coinbase':
            raise TransactionError("Can not sign coinbase transactions")
        tsig = hashlib.sha256(hashlib.sha256(self.raw(tid)).digest()).digest()

        for key in keys:
            if isinstance(key, (HDKey, Key)):
                priv_key = key.private_byte
                pub_key = key.public_byte
            else:
                ko = Key(key)
                priv_key = ko.private_byte
                pub_key = ko.public_byte
            if not priv_key:
                raise TransactionError(
                    "Please provide a valid private key to sign the transaction. "
                    "%s is not a private key" % priv_key)
            sk = ecdsa.SigningKey.from_string(priv_key, curve=ecdsa.SECP256k1)
            while True:
                sig_der = sk.sign_digest(tsig,
                                         sigencode=ecdsa.util.sigencode_der)
                # Test if signature has low S value, to prevent 'Non-canonical signature: High S Value' errors
                # TODO: Recalc 's' instead, see:
                #       https://github.com/richardkiss/pycoin/pull/24/files#diff-12d8832e97767321d1f3c40909be8b23
                signature = convert_der_sig(sig_der)
                s = int(signature[64:], 16)
                if s < ecdsa.SECP256k1.order / 2:
                    break
            newsig = {
                'sig_der': to_bytes(sig_der),
                'signature': to_bytes(signature),
                'priv_key': priv_key,
                'pub_key': pub_key,
                'transaction_id': tid
            }

            # Check if signature signs known key and is not already in list
            pub_key_list = [x.public_byte for x in self.inputs[tid].keys]
            pub_key_list_uncompressed = [
                x.public_uncompressed_byte for x in self.inputs[tid].keys
            ]
            if pub_key not in pub_key_list:
                raise TransactionError(
                    "This key does not sign any known key: %s" % pub_key)
            if pub_key in [x['pub_key'] for x in self.inputs[tid].signatures]:
                _logger.warning("Key %s already signed" % pub_key)
                break

            n_signs += 1
            # Insert newsig in correct place in list
            if self.inputs[tid].signatures:
                # 1. determine position for newsig according to key list
                newsig_pos = pub_key_list.index(pub_key)
                n_total_sigs = len(pub_key_list)

                # 2. assume signature list is in correct order then determine possible position of newsig
                sig_start_domain = [''] * n_total_sigs
                sig_start_domain[newsig_pos] = newsig
                sig_domains = []
                empty_slots = [
                    i for i, j in enumerate(sig_start_domain) if j == ''
                ]
                possible_sig_positions = combinations(
                    empty_slots, len(self.inputs[tid].signatures))
                for pp in possible_sig_positions:
                    sig_domain = deepcopy(sig_start_domain)
                    signatures = deepcopy(self.inputs[tid].signatures)[::-1]
                    for sig_pos in pp:
                        sig_domain[sig_pos] = signatures.pop()
                    sig_domains.append(sig_domain)

                # Verify sig domains
                for sig_domain in sig_domains:
                    signature_list = [s for s in sig_domain if s != '']
                    for sig in signature_list:
                        pos = sig_domain.index(sig)
                        if not verify_signature(
                                tsig, sig['signature'],
                                pub_key_list_uncompressed[pos]):
                            sig_domains.remove(sig_domain)
                            break
                            # TODO: Remove all domains with signature on this position
                if len(sig_domains):
                    self.inputs[tid].signatures = [
                        s for s in sig_domains[0] if s != ''
                    ]
                else:
                    raise TransactionError("Invalid signatures found")
                # TODO: Think about what will happen when 2 or more identical private keys are used
            else:
                self.inputs[tid].signatures.append(newsig)

        if self.inputs[tid].script_type == 'p2pkh':
            self.inputs[tid].unlocking_script = \
                varstr(self.inputs[tid].signatures[0]['sig_der'] + struct.pack('B', hash_type)) + \
                varstr(self.inputs[tid].keys[0].public_byte)
        elif self.inputs[tid].script_type == 'p2sh_multisig':
            n_tag = self.inputs[tid].redeemscript[0]
            if not isinstance(n_tag, int):
                n_tag = struct.unpack('B', n_tag)[0]
            n_required = n_tag - 80
            signatures = [
                s['sig_der'] for s in self.inputs[tid].signatures[:n_required]
            ]
            if b'' in signatures:
                raise TransactionError(
                    "Empty signature found in signature list when signing. "
                    "Is DER encoded version of signature defined?")
            self.inputs[tid].unlocking_script = \
                _p2sh_multisig_unlocking_script(signatures, self.inputs[tid].redeemscript, hash_type)
        else:
            raise TransactionError(
                "Script type %s not supported at the moment" %
                self.inputs[tid].script_type)
        return n_signs
Exemple #12
0
    def __init__(self,
                 amount,
                 address='',
                 public_key_hash=b'',
                 public_key=b'',
                 lock_script=b'',
                 network=DEFAULT_NETWORK):
        """
        Create a new transaction output
        
        An transaction outputs locks the specified amount to a public key. Anyone with the private key can unlock
        this output.
        
        The transaction output class contains an amount and the destination which can be provided either as address, 
        public key, public key hash or a locking script. Only one needs to be provided as the they all can be derived 
        from each other, but you can provide as much attributes as you know to improve speed.
        
        :param amount: Amount of output in smallest denominator of currency, for example satoshi's for bitcoins
        :type amount: int
        :param address: Destination address of output. Leave empty to derive from other attributes you provide.
        :type address: str
        :param public_key_hash: Hash of public key
        :type public_key_hash: bytes, str
        :param public_key: Destination public key
        :type public_key: bytes, str
        :param lock_script: Locking script of output. If not provided a default unlocking script will be provided with a public key hash.
        :type lock_script: bytes, str
        :param network: Network, leave empty for default
        :type network: str
        """
        if not (address or public_key_hash or public_key or lock_script):
            raise TransactionError(
                "Please specify address, lock_script, public key or public key hash when "
                "creating output")

        self.amount = amount
        self.lock_script = to_bytes(lock_script)
        self.public_key_hash = to_bytes(public_key_hash)
        self.address = address
        self.public_key = to_bytes(public_key)
        self.network = Network(network)
        self.compressed = True
        self.k = None
        self.versionbyte = self.network.prefix_address
        self.script_type = 'p2pkh'

        if self.public_key:
            self.k = Key(binascii.hexlify(self.public_key).decode('utf-8'),
                         network=network)
            self.address = self.k.address()
            self.compressed = self.k.compressed
        if self.public_key_hash and not self.address:
            self.address = pubkeyhash_to_addr(public_key_hash,
                                              versionbyte=self.versionbyte)
        if self.address:
            address_dict = deserialize_address(self.address)
            if address_dict['script_type']:
                self.script_type = address_dict['script_type']
            else:
                raise TransactionError(
                    "Could not determine script type of address %s" %
                    self.address)
            self.public_key_hash = address_dict['public_key_hash_bytes']
            if address_dict[
                    'network'] and self.network.network_name != address_dict[
                        'network']:
                raise TransactionError(
                    "Address (%s) is from different network then defined %s" %
                    (address_dict['network'], self.network.network_name))
        if not self.public_key_hash and self.k:
            self.public_key_hash = self.k.hash160()

        if self.lock_script and not self.public_key_hash:
            ss = script_deserialize(self.lock_script)
            self.script_type = ss['script_type']
            if self.script_type == 'p2sh':
                self.versionbyte = self.network.prefix_address_p2sh
            if self.script_type in ['p2pkh', 'p2sh']:
                self.public_key_hash = ss['signatures'][0]
                self.address = pubkeyhash_to_addr(self.public_key_hash,
                                                  versionbyte=self.versionbyte)
            else:
                _logger.warning("Script type %s not supported" %
                                self.script_type)

        if self.lock_script == b'':
            if self.script_type == 'p2pkh':
                self.lock_script = b'\x76\xa9\x14' + self.public_key_hash + b'\x88\xac'
            elif self.script_type == 'p2sh':
                self.lock_script = b'\xa9\x14' + self.public_key_hash + b'\x87'
            else:
                raise TransactionError(
                    "Unknown output script type %s, please provide own locking script"
                    % self.script_type)
Exemple #13
0
    def __init__(self,
                 prev_hash,
                 output_index,
                 keys=None,
                 signatures=None,
                 unlocking_script=b'',
                 script_type='p2pkh',
                 sequence=b'\xff\xff\xff\xff',
                 compressed=True,
                 sigs_required=None,
                 sort=False,
                 network=DEFAULT_NETWORK,
                 tid=0):
        """
        Create a new transaction input
        
        :param prev_hash: Transaction hash of the UTXO (previous output) which will be spent.
        :type prev_hash: bytes, hexstring
        :param output_index: Output number in previous transaction.
        :type output_index: bytes, int
        :param keys: A list of Key objects or public / private key string in various formats. If no list is provided but a bytes or string variable, a list with one item will be created. Optional
        :type keys: list (bytes, str)
        :param signatures: Specify optional signatures
        :type signatures: bytes, str
        :param unlocking_script: Unlocking script (scriptSig) to prove ownership. Optional
        :type unlocking_script: bytes, hexstring
        :param script_type: Type of unlocking script used, i.e. p2pkh or p2sh_multisig. Default is p2pkh
        :type script_type: str
        :param sequence: Sequence part of input, you normally do not have to touch this
        :type sequence: bytes
        :param compressed: Use compressed or uncompressed public keys. Default is compressed
        :type compressed: bool
        :param sigs_required: Number of signatures required for a p2sh_multisig unlocking script
        :param sigs_required: int
        :param sort: Sort public keys according to BIP0045 standard. Default is False to avoid unexpected change of key order.
        :type sort: boolean
        :param network: Network, leave empty for default
        :type network: str
        :param tid: Index of input in transaction. Used by Transaction class.
        :type tid: int
        """
        self.prev_hash = to_bytes(prev_hash)
        self.output_index = output_index
        if isinstance(output_index, numbers.Number):
            self.output_index_int = output_index
            self.output_index = struct.pack('>I', output_index)
        else:
            self.output_index_int = struct.unpack('I', output_index)[0]
            self.output_index = output_index
        if isinstance(keys, (bytes, str)):
            keys = [keys]
        self.unlocking_script = to_bytes(unlocking_script)
        self.unlocking_script_unsigned = b''
        self.script_type = script_type
        self.sequence = to_bytes(sequence)
        self.compressed = compressed
        self.network = Network(network)
        self.tid = tid
        if keys is None:
            keys = []
        self.keys = []
        if not isinstance(keys, list):
            keys = [keys]
        if not signatures:
            signatures = []
        if not isinstance(signatures, list):
            signatures = [signatures]
        # Sort according to BIP45 standard
        self.sort = sort
        if sort:
            self.keys.sort(key=lambda k: k.public_byte)
        self.address = ''
        self.signatures = []
        self.redeemscript = b''
        if not sigs_required:
            if script_type == 'p2sh_multisig':
                raise TransactionError(
                    "Please specify number of signatures required (sigs_required) parameter"
                )
            else:
                sigs_required = 1
        self.sigs_required = sigs_required
        self.script_type = script_type

        if prev_hash == b'\0' * 32:
            self.script_type = 'coinbase'

        # If unlocking script is specified extract keys, signatures, type from script
        if unlocking_script:
            us_dict = script_deserialize(unlocking_script)
            if not us_dict or us_dict['script_type'] in ['unknown', 'empty']:
                raise TransactionError(
                    "Could not parse unlocking script (%s)" %
                    binascii.hexlify(unlocking_script))
            self.script_type = us_dict['script_type']
            self.sigs_required = us_dict['number_of_sigs_n']
            self.redeemscript = us_dict['redeemscript']
            signatures += us_dict['signatures']
            keys += us_dict['keys']

        for key in keys:
            if not isinstance(key, Key):
                kobj = Key(key, network=network)
            else:
                kobj = key
            if kobj not in self.keys:
                self.keys.append(kobj)

        for sig in signatures:
            sig_der = ''
            if sig.startswith(b'\x30'):
                # If signature ends with Hashtype, remove hashtype and continue
                # TODO: support for other hashtypes
                if sig.endswith(b'\x01'):
                    _, junk = ecdsa.der.remove_sequence(sig)
                    if junk == b'\x01':
                        sig_der = sig[:-1]
                else:
                    sig_der = sig
                try:
                    sig = convert_der_sig(sig[:-1], as_hex=False)
                except:
                    pass
            self.signatures.append({
                'sig_der': sig_der,
                'signature': to_bytes(sig),
                'priv_key': '',
                'pub_key': ''
            })

        if self.script_type == 'sig_pubkey':
            self.script_type = 'p2pkh'
        if self.script_type == 'p2pkh':
            if self.keys:
                self.unlocking_script_unsigned = b'\x76\xa9\x14' + to_bytes(
                    self.keys[0].hash160()) + b'\x88\xac'
                self.address = self.keys[0].address()
        elif self.script_type == 'p2sh_multisig':
            if not self.keys:
                raise TransactionError(
                    "Please provide keys to append multisig transaction input")
            if not self.redeemscript:
                self.redeemscript = serialize_multisig_redeemscript(
                    self.keys,
                    n_required=self.sigs_required,
                    compressed=self.compressed)

            self.address = pubkeyhash_to_addr(
                script_to_pubkeyhash(self.redeemscript),
                versionbyte=self.network.prefix_address_p2sh)
            self.unlocking_script_unsigned = self.redeemscript
Exemple #14
0
    def sign(self, keys, tid=0, hash_type=SIGHASH_ALL):
        """
        Sign the transaction input with provided private key
        
        :param keys: A private key or list of private keys
        :type keys: HDKey, Key, bytes, list
        :param tid: Index of transaction input
        :type tid: int
        :param hash_type: Specific hash type, default is SIGHASH_ALL
        :type hash_type: int

        :return int: Return int with number of signatures added
        """

        n_signs = 0
        if not isinstance(keys, list):
            keys = [keys]

        if self.inputs[tid].script_type == 'coinbase':
            raise TransactionError("Can not sign coinbase transactions")
        tsig = hashlib.sha256(hashlib.sha256(self.raw(tid)).digest()).digest()

        pub_key_list = [x.public_byte for x in self.inputs[tid].keys]
        pub_key_list_uncompressed = [
            x.public_uncompressed_byte for x in self.inputs[tid].keys
        ]
        n_total_sigs = len(pub_key_list)
        sig_domain = [''] * n_total_sigs

        for key in keys:
            if isinstance(key, (HDKey, Key)):
                priv_key = key.private_byte
                pub_key = key.public_byte
            else:
                ko = Key(key, compressed=self.inputs[tid].compressed)
                priv_key = ko.private_byte
                pub_key = ko.public_byte
            if not priv_key:
                raise TransactionError(
                    "Please provide a valid private key to sign the transaction. "
                    "%s is not a private key" % priv_key)
            sk = ecdsa.SigningKey.from_string(priv_key, curve=ecdsa.SECP256k1)
            while True:
                sig_der = sk.sign_digest(tsig,
                                         sigencode=ecdsa.util.sigencode_der)
                # Test if signature has low S value, to prevent 'Non-canonical signature: High S Value' errors
                # TODO: Recalc 's' instead, see:
                #       https://github.com/richardkiss/pycoin/pull/24/files#diff-12d8832e97767321d1f3c40909be8b23
                signature = convert_der_sig(sig_der)
                s = int(signature[64:], 16)
                if s < ecdsa.SECP256k1.order / 2:
                    break
            newsig = {
                'sig_der': to_bytes(sig_der),
                'signature': to_bytes(signature),
                'priv_key': priv_key,
                'pub_key': pub_key,
                'transaction_id': tid
            }

            # Check if signature signs known key and is not already in list
            if pub_key not in pub_key_list:
                raise TransactionError(
                    "This key does not sign any known key: %s" % pub_key)
            if pub_key in [x['pub_key'] for x in self.inputs[tid].signatures]:
                _logger.warning("Key %s already signed" % pub_key)
                break

            newsig_pos = pub_key_list.index(pub_key)
            sig_domain[newsig_pos] = newsig
            n_signs += 1

        # Add already known signatures on correct position
        n_sigs_to_insert = len(self.inputs[tid].signatures)
        for sig in self.inputs[tid].signatures:
            free_positions = [i for i, s in enumerate(sig_domain) if s == '']
            for pos in free_positions:
                if verify_signature(tsig, sig['signature'],
                                    pub_key_list_uncompressed[pos]):
                    sig_domain[pos] = sig
                    n_sigs_to_insert -= 1
                    break
        if n_sigs_to_insert:
            _logger.info(
                "Some signatures are replaced with the signatures of the provided keys"
            )
        self.inputs[tid].signatures = [s for s in sig_domain if s != '']

        if self.inputs[tid].script_type == 'p2pkh':
            if len(self.inputs[tid].signatures):
                self.inputs[tid].unlocking_script = \
                    varstr(self.inputs[tid].signatures[0]['sig_der'] + struct.pack('B', hash_type)) + \
                    varstr(self.inputs[tid].keys[0].public_byte)
        elif self.inputs[tid].script_type == 'p2sh_multisig':
            n_tag = self.inputs[tid].redeemscript[0]
            if not isinstance(n_tag, int):
                n_tag = struct.unpack('B', n_tag)[0]
            n_required = n_tag - 80
            signatures = [
                s['sig_der'] for s in self.inputs[tid].signatures[:n_required]
            ]
            if b'' in signatures:
                raise TransactionError(
                    "Empty signature found in signature list when signing. "
                    "Is DER encoded version of signature defined?")
            self.inputs[tid].unlocking_script = \
                _p2sh_multisig_unlocking_script(signatures, self.inputs[tid].redeemscript, hash_type)
        else:
            raise TransactionError(
                "Script type %s not supported at the moment" %
                self.inputs[tid].script_type)
        return n_signs - n_sigs_to_insert
Exemple #15
0
from bitcoinlib.keys import HDKey, Key
from bitcoinlib.transactions import Input, Output, Transaction, script_deserialize

#
# Create transactions
#

print(
    "\n=== Create and sign transaction with add_input, add_output methods ===")
print(
    "(Based on http://bitcoin.stackexchange.com/questions/3374/how-to-redeem-a-basic-tx/24580)"
)
t = Transaction()
prev_tx = 'f2b3eb2deb76566e7324307cd47c35eeb88413f971d88519859b1834307ecfec'
ki = Key(0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725,
         compressed=False)
t.add_input(prev_hash=prev_tx,
            output_n=1,
            keys=ki.public_hex,
            compressed=False)
t.add_output(99900000, '1runeksijzfVxyrpiyCY2LCBvYsSiFsCm')
t.sign(ki.private_byte)
pprint(t.as_dict())
print("Raw:", binascii.hexlify(t.raw()))
print("Verified %s " % t.verify())
print(t.raw_hex())

print(
    "\n=== Create and sign transaction with transactions Input and Output objects ==="
)
print(
Exemple #16
0
    def parse_bytesio(cls,
                      script,
                      message=None,
                      tx_data=None,
                      strict=True,
                      _level=0):
        """
        Parse raw script and return Script object. Extracts script commands, keys, signatures and other data.

        :param script: Raw script to parse in bytes, BytesIO or hexadecimal string format
        :type script: BytesIO
        :param message: Signed message to verify, normally a transaction hash
        :type message: bytes
        :param tx_data: Dictionary with extra information needed to verify script. Such as 'redeemscript' for multisignature scripts and 'blockcount' for time locked scripts
        :type tx_data: dict
        :param strict: Raise exception when script is malformed, incomplete or not understood. Default is True
        :type strict: bool
        :param _level: Internal argument used to avoid recursive depth
        :type _level: int

        :return Script:
        """
        commands = []
        signatures = []
        keys = []
        blueprint = []
        redeemscript = b''
        sigs_required = None
        # hash_type = SIGHASH_ALL  # todo: check
        hash_type = None
        if not tx_data:
            tx_data = {}

        while script:
            chb = script.read(1)
            if not chb:
                break
            ch = int.from_bytes(chb, 'big')
            data = None
            data_length = 0
            if 1 <= ch <= 75:  # Data`
                data_length = ch
            elif ch == op.op_pushdata1:
                data_length = int.from_bytes(script.read(1), 'little')
            elif ch == op.op_pushdata2:
                data_length = int.from_bytes(script.read(2), 'little')
            if data_length:
                data = script.read(data_length)
                if len(data) != data_length:
                    msg = "Malformed script, not enough data found"
                    if strict:
                        raise ScriptError(msg)
                    else:
                        _logger.warning(msg)

            if data:
                data_type = get_data_type(data)
                commands.append(data)
                if data_type == 'signature':
                    sig = Signature.parse_bytes(data)
                    signatures.append(sig)
                    hash_type = sig.hash_type
                    blueprint.append('signature')
                elif data_type == 'signature_object':
                    signatures.append(data)
                    hash_type = data.hash_type
                    blueprint.append('signature')
                elif data_type == 'key':
                    keys.append(Key(data))
                    blueprint.append('key')
                elif data_type == 'key_object':
                    keys.append(data)
                    blueprint.append('key')
                elif data_type[:4] == 'data':
                    # FIXME: This is arbitrary
                    blueprint.append('data-%d' % len(data))
                elif len(commands) >= 2 and commands[-2] == op.op_return:
                    blueprint.append('data-%d' % len(data))
                else:
                    # FIXME: Only parse sub-scripts if script is expected
                    try:
                        if _level >= 1:
                            blueprint.append('data-%d' % len(data))
                        else:
                            s2 = Script.parse_bytes(data, _level=_level + 1)
                            commands.pop()
                            commands += s2.commands
                            blueprint += s2.blueprint
                            keys += s2.keys
                            signatures += s2.signatures
                            redeemscript = s2.redeemscript
                            sigs_required = s2.sigs_required
                    except (ScriptError, IndexError):
                        blueprint.append('data-%d' % len(data))
            else:  # Other opcode
                commands.append(ch)
                blueprint.append(ch)

        s = cls(commands,
                message,
                keys=keys,
                signatures=signatures,
                blueprint=blueprint,
                tx_data=tx_data,
                hash_type=hash_type)
        script.seek(0)
        s._raw = script.read()

        s.script_types = _get_script_types(blueprint)
        if 'unknown' in s.script_types:
            s.script_types = ['unknown']

        # Extract extra information from script data
        for st in s.script_types[:1]:
            if st == 'multisig':
                s.redeemscript = s.raw
                s.sigs_required = s.commands[0] - 80
                if s.sigs_required > len(keys):
                    raise ScriptError(
                        "Number of signatures required (%d) is higher then number of keys (%d)"
                        % (s.sigs_required, len(keys)))
                if len(s.keys) != s.commands[-2] - 80:
                    raise ScriptError("%d keys found but %d keys expected" %
                                      (len(s.keys), s.commands[-2] - 80))
            elif st in ['p2wpkh', 'p2wsh', 'p2sh'] and len(s.commands) > 1:
                s.public_hash = s.commands[1]
            elif st == 'p2pkh' and len(s.commands) > 2:
                s.public_hash = s.commands[2]
        s.redeemscript = redeemscript if redeemscript else s.redeemscript
        if s.redeemscript and 'redeemscript' not in s.tx_data:
            s.tx_data['redeemscript'] = s.redeemscript

        s.sigs_required = sigs_required if sigs_required else s.sigs_required

        return s