def _do_multisig_script(self, sigs, message, current_script_sig, redeem_script, hash_type): # If the current script is empty or None, create it sig_script = None if current_script_sig is None or not str(current_script_sig): sig_bytes = [s["signature"].to_der() + pack_compact_int(hash_type) for s in sigs] sig_script = Script.build_multisig_sig(sigs=sig_bytes, redeem_script=redeem_script) else: # Need to extract all the sigs already present multisig_params = redeem_script.extract_multisig_redeem_info() sig_info = current_script_sig.extract_multisig_sig_info() # Do a few quick sanity checks if str(sig_info["redeem_script"]) != str(redeem_script): raise ValueError("Redeem script in signature script does not match redeem_script!") if len(sig_info["signatures"]) == multisig_params["n"]: # Already max number of signatures return current_script_sig # Go through the signatures and match them up to the public keys # in the redeem script pub_keys = [] for pk in multisig_params["public_keys"]: pub_keys.append(crypto.PublicKey.from_bytes(pk)) existing_sigs = [] for s in sig_info["signatures"]: s1, h = s[:-1], s[-1] # Last byte is hash_type existing_sigs.append(crypto.Signature.from_der(s1)) if h != hash_type: raise ValueError("hash_type does not match that of the existing signatures.") # Match them up existing_sig_indices = self._match_sigs_to_pub_keys(existing_sigs, pub_keys, message) sig_indices = {s["index"]: s["signature"] for s in sigs} # Make sure there are no dups all_indices = set(list(existing_sig_indices.keys()) + list(sig_indices.keys())) if len(all_indices) < len(existing_sig_indices) + len(sig_indices): raise ValueError("At least one signature matches an existing signature.") if len(all_indices) > multisig_params["n"]: raise ValueError("There are too many signatures.") all_sigs = [] for i in sorted(all_indices): if i in existing_sig_indices: all_sigs.append(existing_sig_indices[i]) elif i in sig_indices: all_sigs.append(sig_indices[i]) all_sigs_bytes = [s.to_der() + pack_compact_int(hash_type) for s in all_sigs] sig_script = Script.build_multisig_sig(all_sigs_bytes, redeem_script) return sig_script
def __bytes__(self): """ Serializes the object into a byte stream. Returns: b (bytes): The serialized transaction. """ return (pack_u32(self.version) + # Version pack_compact_int(self.num_inputs) + # Input count b''.join([bytes(i) for i in self.inputs]) + # Inputs pack_compact_int(self.num_outputs) + # Output count b''.join([bytes(o) for o in self.outputs]) + # Outputs pack_u32(self.lock_time) # Lock time )
def __bytes__(self): """ Serializes the object into a byte stream. Returns: b (bytes): The serialized transaction. """ return ( pack_u32(self.version) + pack_compact_int(self.num_inputs) # Version + b"".join([bytes(i) for i in self.inputs]) # Input count + pack_compact_int(self.num_outputs) # Inputs + b"".join([bytes(o) for o in self.outputs]) # Output count + pack_u32(self.lock_time) # Outputs # Lock time )
def sign_half_signed_payment(self, payment_tx, redeem_script): """Sign a half-signed payment transaction. Args: payment_tx (two1.lib.bitcoin.txn.Transaction): an object that contains a transaction from a customer, whether for a refund or general payment, to be signed by the merchant. Returns: signed_tx (two1.lib.bitcoin.txn.Transaction): an object that contains a transaction that has been signed by both the customer and the merchant. """ # Verify that the deposit spend has only one input if len(payment_tx.inputs) != 1: raise InvalidPaymentError('Transaction should have one input.') # Get the public and private keys associated with this transaction merchant_public_key = redeem_script.merchant_public_key private_key = self._wallet.get_private_for_public(merchant_public_key) # Sign the first (and only) input in the transaction sig = payment_tx.get_signature_for_input(0, Transaction.SIG_HASH_ALL, private_key, redeem_script)[0] # Update input script sig payment_tx.inputs[0].script.insert( 1, sig.to_der() + utils.pack_compact_int(Transaction.SIG_HASH_ALL)) # Return a Transaction containing the fully-signed transaction. return payment_tx
def sign_half_signed_payment(self, payment_tx, redeem_script): """Sign a half-signed payment transaction. Args: payment_tx (two1.lib.bitcoin.txn.Transaction): an object that contains a transaction from a customer, whether for a refund or general payment, to be signed by the merchant. Returns: signed_tx (two1.lib.bitcoin.txn.Transaction): an object that contains a transaction that has been signed by both the customer and the merchant. """ # Verify that the deposit spend has only one input if len(payment_tx.inputs) != 1: raise InvalidPaymentError('Transaction should have one input.') # Get the public and private keys associated with this transaction merchant_public_key = redeem_script.merchant_public_key private_key = self._wallet.get_private_for_public(merchant_public_key) # Sign the first (and only) input in the transaction sig = payment_tx.get_signature_for_input(0, Transaction.SIG_HASH_ALL, private_key, redeem_script)[0] # Update input script sig payment_tx.inputs[0].script.insert(1, sig.to_der() + utils.pack_compact_int(Transaction.SIG_HASH_ALL)) # Return a Transaction containing the fully-signed transaction. return payment_tx
def __bytes__(self): """ Serializes the Block object into a byte stream. Returns: b (bytes): The serialized byte stream. """ return (bytes(self.block_header) + pack_compact_int(len(self.txns)) + b''.join([bytes(t) for t in self.txns]))
def sign_input(self, input_index, hash_type, private_key, sub_script): """ Signs an input. Args: input_index (int): The index of the input to sign. hash_type (int): What kind of signature hash to do. private_key (crypto.PrivateKey): private key with which to sign the transaction. sub_script (Script): the scriptPubKey of the corresponding utxo being spent if the outpoint is P2PKH or the redeem script if the outpoint is P2SH. """ if input_index < 0 or input_index >= len(self.inputs): raise ValueError("Invalid input index.") inp = self.inputs[input_index] multisig = False if sub_script.is_multisig_redeem(): multisig = True elif not sub_script.is_p2pkh(): raise TypeError("Signing arbitrary redeem scripts is not currently supported.") tmp_script = sub_script.remove_op("OP_CODESEPARATOR") # Before signing we should verify that the address in the # sub_script corresponds to that of the private key m = self._match_public_key(private_key, tmp_script) if not m['match']: if multisig: msg = "Public key derived from private key does not match any of the public keys in redeem script." else: msg = "Address derived from private key does not match sub_script!" raise ValueError(msg) sig, signed_message = self.get_signature_for_input( input_index, hash_type, private_key, sub_script) if multisig: # For multisig, we need to determine if there are already # signatures and if so, where we insert this signature inp.script = self._do_multisig_script( [dict(index=m['info']['multisig_key_index'], signature=sig)], signed_message, inp.script, tmp_script, hash_type) else: pub_key_bytes = self._get_public_key_bytes(private_key, m['info']['compressed']) inp.script = Script([sig.to_der() + pack_compact_int(hash_type), pub_key_bytes]) return True
def __bytes__(self): """ Serializes the Block object into a byte stream. Returns: b (bytes): The serialized byte stream. """ return ( bytes(self.block_header) + pack_compact_int(len(self.txns)) + b''.join([bytes(t) for t in self.txns]) )
def sign_input(self, input_index, hash_type, private_key, sub_script): """ Signs an input. Args: input_index (int): The index of the input to sign. hash_type (int): What kind of signature hash to do. private_key (crypto.PrivateKey): private key with which to sign the transaction. sub_script (Script): the scriptPubKey of the corresponding utxo being spent if the outpoint is P2PKH or the redeem script if the outpoint is P2SH. """ if input_index < 0 or input_index >= len(self.inputs): raise ValueError("Invalid input index.") inp = self.inputs[input_index] multisig = False if sub_script.is_multisig_redeem(): multisig = True elif not sub_script.is_p2pkh(): raise TypeError( "Signing arbitrary redeem scripts is not currently supported.") tmp_script = sub_script.remove_op("OP_CODESEPARATOR") # Before signing we should verify that the address in the # sub_script corresponds to that of the private key m = self._match_public_key(private_key, tmp_script) if not m['match']: if multisig: msg = "Public key derived from private key does not match any of the public keys in redeem script." else: msg = "Address derived from private key does not match sub_script!" raise ValueError(msg) sig, signed_message = self.get_signature_for_input( input_index, hash_type, private_key, sub_script) if multisig: # For multisig, we need to determine if there are already # signatures and if so, where we insert this signature inp.script = self._do_multisig_script( [dict(index=m['info']['multisig_key_index'], signature=sig)], signed_message, inp.script, tmp_script, hash_type) else: pub_key_bytes = self._get_public_key_bytes(private_key, m['info']['compressed']) inp.script = Script( [sig.to_der() + pack_compact_int(hash_type), pub_key_bytes]) return True
def _do_multisig_script(self, sigs, message, current_script_sig, redeem_script, hash_type): # If the current script is empty or None, create it sig_script = None if current_script_sig is None or not str(current_script_sig): sig_bytes = [ s['signature'].to_der() + pack_compact_int(hash_type) for s in sigs ] sig_script = Script.build_multisig_sig(sigs=sig_bytes, redeem_script=redeem_script) else: # Need to extract all the sigs already present multisig_params = redeem_script.extract_multisig_redeem_info() sig_info = current_script_sig.extract_multisig_sig_info() # Do a few quick sanity checks if str(sig_info['redeem_script']) != str(redeem_script): raise ValueError( "Redeem script in signature script does not match redeem_script!" ) if len(sig_info['signatures']) == multisig_params['n']: # Already max number of signatures return current_script_sig # Go through the signatures and match them up to the public keys # in the redeem script pub_keys = [] for pk in multisig_params['public_keys']: pub_keys.append(crypto.PublicKey.from_bytes(pk)) existing_sigs = [] for s in sig_info['signatures']: s1, h = s[:-1], s[-1] # Last byte is hash_type existing_sigs.append(crypto.Signature.from_der(s1)) if h != hash_type: raise ValueError( "hash_type does not match that of the existing signatures." ) # Match them up existing_sig_indices = self._match_sigs_to_pub_keys( existing_sigs, pub_keys, message) sig_indices = {s['index']: s['signature'] for s in sigs} # Make sure there are no dups all_indices = set(list(existing_sig_indices.keys()) + \ list(sig_indices.keys())) if len(all_indices) < len(existing_sig_indices) + len(sig_indices): raise ValueError( "At least one signature matches an existing signature.") if len(all_indices) > multisig_params['n']: raise ValueError("There are too many signatures.") all_sigs = [] for i in sorted(all_indices): if i in existing_sig_indices: all_sigs.append(existing_sig_indices[i]) elif i in sig_indices: all_sigs.append(sig_indices[i]) all_sigs_bytes = [ s.to_der() + pack_compact_int(hash_type) for s in all_sigs ] sig_script = Script.build_multisig_sig(all_sigs_bytes, redeem_script) return sig_script
def sign_input(self, input_index, hash_type, private_key, sub_script): """ Signs an input. Args: input_index (int): The index of the input to sign. hash_type (int): What kind of signature hash to do. private_key (crypto.PrivateKey): private key with which to sign the transaction. sub_script (Script): the scriptPubKey of the corresponding utxo being spent if the outpoint is P2PKH or the redeem script if the outpoint is P2SH. """ if input_index < 0 or input_index >= len(self.inputs): raise ValueError("Invalid input index.") inp = self.inputs[input_index] curr_script_sig = inp.script multisig = False multisig_params = None multisig_key_index = -1 if sub_script.is_multisig_redeem(): multisig = True multisig_params = sub_script.extract_multisig_redeem_info() elif not sub_script.is_p2pkh(): raise TypeError("Signing arbitrary redeem scripts is not currently supported.") tmp_script = sub_script.remove_op("OP_CODESEPARATOR") compressed = False if hash_type & 0x1f == self.SIG_HASH_SINGLE and len(self.inputs) > len(self.outputs): # This is to deal with the bug where specifying an index # that is out of range (wrt outputs) results in a # signature hash of 0x1 (little-endian) msg_to_sign = 0x1.to_bytes(32, 'little') else: txn_copy = self._copy_for_sig(input_index, hash_type, tmp_script) if multisig: # Determine which of the public keys this private key # corresponds to. public_keys = multisig_params['public_keys'] pub_key_full = self._get_public_key_bytes(private_key, False) pub_key_comp = self._get_public_key_bytes(private_key, True) for i, p in enumerate(public_keys): if pub_key_full == p or pub_key_comp == p: multisig_key_index = i break if multisig_key_index == -1: raise ValueError( "Public key derived from private key does not match any of the public keys in redeem script.") else: # Before signing we should verify that the address in the # sub_script corresponds to that of the private key script_pub_key_h160_hex = tmp_script.get_hash160() if script_pub_key_h160_hex is None: raise ValueError("Couldn't find public key hash in sub_script!") # first try uncompressed key h160 = None for compressed in [True, False]: h160 = private_key.public_key.hash160(compressed) if h160 != bytes.fromhex(script_pub_key_h160_hex[2:]): h160 = None else: break if h160 is None: raise ValueError("Address derived from private key does not match sub_script!") msg_to_sign = bytes(Hash.dhash(bytes(txn_copy) + pack_u32(hash_type))) sig = private_key.sign(msg_to_sign, False) if multisig: # For multisig, we need to determine if there are already # signatures and if so, where we insert this signature inp.script = self._do_multisig_script([dict(index=multisig_key_index, signature=sig)], msg_to_sign, curr_script_sig, tmp_script, hash_type) else: pub_key_bytes = self._get_public_key_bytes(private_key, compressed) pub_key_str = pack_var_str(pub_key_bytes) script_sig = pack_var_str( sig.to_der() + pack_compact_int(hash_type)) + pub_key_str inp.script = Script(script_sig) return True