def __init__(self, height, raw_script, sequence=MAX_INT, block_version=3): self.height = height if block_version == 1: scr = raw_script else: scr = Script.build_push_int(self.height) + raw_script # Coinbase scripts are basically whatever, so we don't # try to create a script object from them. super().__init__(self.NULL_OUTPOINT, self.MAX_INT, scr, sequence)
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 _copy_for_sig(self, input_index, hash_type, sub_script): """ Returns a copy of this txn appropriate for signing, based on hash_type. """ new_txn = copy.deepcopy(self) # First deal w/the inputs # For the SIG_HASH_ANY case, we only care about # self.inputs[input_index] if hash_type == self.SIG_HASH_ANY: ti = new_txn.inputs[input_index] new_txn.inputs = [ti] else: for i, inp in enumerate(new_txn.inputs): inp.script = sub_script if i == input_index else Script("") if hash_type & 0x1f in [ self.SIG_HASH_NONE, self.SIG_HASH_SINGLE ] and input_index != i: # Sequence numbers (nSequence) must be set to 0 for all but # the input we care about. inp.sequence_num = 0 # Now deal with outputs if hash_type & 0x1f == self.SIG_HASH_NONE: new_txn.outputs = [] elif hash_type & 0x1f == self.SIG_HASH_SINGLE: # Resize output vector to input_index + 1 new_txn.outputs = new_txn.outputs[:input_index + 1] # All outputs except outputs[i] have a value of -1 (0xffffffff) # and a blank script for i, out in enumerate(new_txn.outputs): if i != input_index: out.script = Script("") out.value = 0xffffffff return new_txn
def from_bytes(b): """ Deserializes a byte stream into a TransactionOutput object. Args: b (bytes): byte-stream beginning with the value. Returns: tuple: First element of the tuple is a TransactionOutput, the second is the remainder of the byte stream. """ value, b0 = unpack_u64(b) script_len, b0 = unpack_compact_int(b0) return (TransactionOutput(value, Script(b0[:script_len])), b0[script_len:])
def _verify_input(self, input_index, sub_script, partial_multisig=False): p2sh = sub_script.is_p2sh() sig_script = self.inputs[input_index].script si = ScriptInterpreter(txn=self, input_index=input_index, sub_script=sub_script) try: si.run_script(sig_script) except ScriptInterpreterError: return False # This copy_stack and the restore_stack emulate the behavior # found in bitcoin core for evaluating P2SH scripts. See: # https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L1170 if p2sh: si.copy_stack() try: si.run_script(sub_script) except ScriptInterpreterError: return False rv = si.valid if p2sh: si.restore_stack() redeem_script = Script(si.stack.pop()) si._sub_script = redeem_script try: if sig_script.is_multisig_sig() and partial_multisig: # This is a hack for partial verification partial_script = copy.deepcopy(redeem_script) partial_script.ast[-1] = 'OP_CHECKPARTIALMULTISIG' sig_info = sig_script.extract_multisig_sig_info() si.run_script(partial_script) rv &= si.match_count > 0 and si.match_count <= len( sig_info['signatures']) else: si.run_script(redeem_script) rv &= si.valid except: rv = False return rv
def from_bytes(b): """ Deserializes a byte stream into a TransactionInput. Args: b (bytes): byte stream starting with the outpoint. Returns: tuple: First element of the tuple is the TransactionInput object and the second is the remaining byte stream. """ outpoint = b[0:32] outpoint_index, b1 = unpack_u32(b[32:]) script, b1 = Script.from_bytes(b1) sequence_num, b1 = unpack_u32(b1) return (TransactionInput(Hash(outpoint), outpoint_index, script, sequence_num), b1)
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