示例#1
0
 def _validity_checking(self):
     '''A variety of tests to ensure this PSBT is valid'''
     # Check to make sure unsigned transaction is present
     if PSBT_GLOBAL_UNSIGNED_TX not in self.maps['global']:
         raise ValueError('Invalid PSBT, missing unsigned transaction')
     else:
         # Parse global unsigned tx for future checks
         tx_obj = Tx.parse(BytesIO(self.maps['global'][PSBT_GLOBAL_UNSIGNED_TX]))
     # If a scriptSig or scriptWitness is present, this is an signed transaction and is invalid
     for i in tx_obj.tx_ins:
         if len(i.script_sig.elements) >  0 or len(i.witness_program) > 1:
             raise ValueError('Invalid PSBT, transaction in global map has scriptSig or scriptWitness present, not unsigned')
     # Get number of inputs in unsigned tx and psbt maps
     global_ins_cnt = len(tx_obj.tx_ins)
     psbt_ins_cnt = len(self.maps['inputs'])
     # If unsigned tx has no inputs, invalid
     if global_ins_cnt == 0:
         raise ValueError('Invalid PSBT, unsigned transaction missing inputs')
     # If the psbt has no inputs, invalid
     elif psbt_ins_cnt == 0:
         raise ValueError('Invalid PSBT, no inputs')
     # If the counts do not match, invalid
     elif global_ins_cnt != psbt_ins_cnt:
         raise ValueError('Invalid PSBT, number of inputs in unsigned transaction and PSBT do not match')
     # Repeat for outputs
     global_outs_cnt = len(tx_obj.tx_outs)
     psbt_outs_cnt = len(self.maps['outputs'])
     if global_outs_cnt == 0:
         raise ValueError('Invalid PSBT, unsigned transaction missing outputs')
     elif psbt_outs_cnt == 0:
         raise ValueError('Invalid PSBT, no outputs')
     elif global_outs_cnt != psbt_outs_cnt:
         raise ValueError('Invalid PSBT, number of outputs in unsigned transaction and PSBT do not match')
示例#2
0
 def __init__(self, serialized_psbt):
     # Specify current role as string for default file name in make_file()
     self.role = 'Transaction_Extractor'
     self.psbt=psbt.parse(BytesIO(serialized_psbt))
     # Take the finalized scriptSig and scriptWitness data and complete the unsigned tx
     self.tx_obj = Tx.parse(BytesIO(self.psbt.maps['global'][PSBT_GLOBAL_UNSIGNED_TX]))
     # Iterate through psbt input key-value fields and input their finalized data into 
     # the transaction. Note this assumes PSBT inputs are ordered the same as they are
     # in the unsigned TX
     # TODO: Reconsider that assumption
     for i in range(len(self.psbt.maps['inputs'])):
         curr_input = self.psbt.maps['inputs'][i]
         if self._is_witness_input(curr_input):
             try:
                 # Insert final scriptWitness as witness program for this input
                 self.tx_obj.tx_ins[i].witness_program = curr_input[PSBT_IN_FINAL_SCRIPTWITNESS]
             except KeyError:
                 # If this is witness input, final scriptWitness should be present
                 raise ValueError('PSBT input is missing finalized scriptWitness')
             # If a final scriptSig is present, must be P2SH-wrapped segwit so scriptSig is required
             if PSBT_IN_FINAL_SCRIPTSIG in curr_input:
                 self.tx_obj.tx_ins[i].script_sig = Script.parse(curr_input[PSBT_IN_FINAL_SCRIPTSIG])
         # Else, this is a non-witness input
         else:
             try:
                 # Insert final scriptSig into input
                 self.tx_obj.tx_ins[i].script_sig = Script.parse(curr_input[PSBT_IN_FINAL_SCRIPTSIG])
             except KeyError:
                 # If not a witness input, final scriptSig should be present
                 raise ValueError('PSBT input is missing finalized scriptSig')  
示例#3
0
 def add_witness_utxo(self, input_index, utxo, utxo_index):
     '''Add a non-witness UTXO to it's corresponding input
     
     input_index - (int) index of the input being updated
     utxo - raw bytes of utxo being added
     '''
     tx_obj = Tx.parse(BytesIO(utxo))
     value = tx_obj.tx_outs[utxo_index].serialize()
     self.psbt.maps['inputs'][input_index][PSBT_IN_WITNESS_UTXO] = value
示例#4
0
 def __init__(self, inputs, outputs, tx_version=2, input_sequence=0xffffffff, locktime=0):
     '''Inputs should be a list of tuples in the form of (prev tx, prev index)
     Outputs should be a list of tuples in the form of (amount, scriptPubKey)'''
     # Specify current role as string for default file name in make_file()
     self.role = 'Creator'
     self.tx_inputs = []
     # outputs should be a list of tuples in the form of (amount, scriptPubKey), amount in satoshi
     self.tx_outputs = []
     # Initialize list of TxIn objects (Inputs)
     for i in inputs:
         self.tx_inputs.append(TxIn(prev_tx=i[0], prev_index=i[1], script_sig=b'', sequence=input_sequence))
     # Initialize list of TxOut objects (Outputs)
     for i in outputs:
         self.tx_outputs.append(TxOut(amount=i[0], script_pubkey=i[1]))
     self.tx_obj = Tx(version=tx_version, tx_ins=self.tx_inputs, tx_outs=self.tx_outputs, locktime=locktime)
     # Get a serialized version of the unsigned tx for the psbt
     serialized_tx = self.tx_obj.serialize()
     # Construct a serialized psbt manually
     new_psbt_serialized = MAGIC_BYTES + HEAD_SEPARATOR + psbt.serialize_map(key=PSBT_GLOBAL_UNSIGNED_TX, \
     value=serialized_tx)  + DATA_SEPARATOR + (DATA_SEPARATOR*len(self.tx_inputs)) + \
     (DATA_SEPARATOR*len(self.tx_inputs))
     # Create the psbt object using the serialized psbt
     self.psbt = psbt.parse(BytesIO(new_psbt_serialized))
示例#5
0
class Creator(PSBT_Role):
    '''The Creator creates a new psbt. It must create an unsigned transaction 
        and place it in the psbt. The Creator must create empty input fields.'''
    
    def __init__(self, inputs, outputs, tx_version=2, input_sequence=0xffffffff, locktime=0):
        '''Inputs should be a list of tuples in the form of (prev tx, prev index)
        Outputs should be a list of tuples in the form of (amount, scriptPubKey)'''
        # Specify current role as string for default file name in make_file()
        self.role = 'Creator'
        self.tx_inputs = []
        # outputs should be a list of tuples in the form of (amount, scriptPubKey), amount in satoshi
        self.tx_outputs = []
        # Initialize list of TxIn objects (Inputs)
        for i in inputs:
            self.tx_inputs.append(TxIn(prev_tx=i[0], prev_index=i[1], script_sig=b'', sequence=input_sequence))
        # Initialize list of TxOut objects (Outputs)
        for i in outputs:
            self.tx_outputs.append(TxOut(amount=i[0], script_pubkey=i[1]))
        self.tx_obj = Tx(version=tx_version, tx_ins=self.tx_inputs, tx_outs=self.tx_outputs, locktime=locktime)
        # Get a serialized version of the unsigned tx for the psbt
        serialized_tx = self.tx_obj.serialize()
        # Construct a serialized psbt manually
        new_psbt_serialized = MAGIC_BYTES + HEAD_SEPARATOR + psbt.serialize_map(key=PSBT_GLOBAL_UNSIGNED_TX, \
        value=serialized_tx)  + DATA_SEPARATOR + (DATA_SEPARATOR*len(self.tx_inputs)) + \
        (DATA_SEPARATOR*len(self.tx_inputs))
        # Create the psbt object using the serialized psbt
        self.psbt = psbt.parse(BytesIO(new_psbt_serialized))

    
    def get_utxo(self, input_index):
        raise RuntimeError('Function out of scope for this role')
    
    
    def _get_input_index(self, pubkey):
        raise RuntimeError('Function out of scope for this role')
    
    
    def _is_witness_input(self, an_input):
        raise RuntimeError('Function out of scope for this role')
    
    
    def get_output_redeem_script(self, output_index):
        raise RuntimeError('Function out of scope for this role')
        
        
    def get_output_witness_script(self, output_index):
        raise RuntimeError('Function out of scope for this role')
示例#6
0
 def add_partial_signature(self, new_sig, compressed_sec, input_index=None):
     '''Adds signature to input of PSBT. Signature and public key should be of type bytes
     
     If the public key has been added to an input in the PSBT, the input index will be found
     '''
     # TODO: Add more ways to find an input that matches the provided public key
     # If an input index is not specified in arguments, find it based on matching public key
     if input_index == None:
         input_index = self._get_input_index(compressed_sec)
     # Note that the below assumes that the sighash type is only the last byte of sig. 
     # This may be problematic
     # Note: Assumes inputs in psbt and indexed the same as in unsigned tx
     # TODO: Check on this
     this_sighash = little_endian_to_int(new_sig[-1:])
     if input_index is not None:
         # Check to make sure signature's sighash type correctly matches the type specified 
         # for this input
         if not self.check_sighash(input_index=input_index, sighash=this_sighash):
             raise ValueError('Sighash type {} on this signature does not match specified \
             sighash type {} for this input'.format(little_endian_to_int(this_sighash), 
                                                    self.get_sighash_type(input_index)))     
         curr_input = self.psbt.maps['inputs'][input_index]
         # Verify that if UTXO for witness or non-witness is present, it matches TXID of global unsigned tx
         if PSBT_IN_NON_WITNESS_UTXO in curr_input:
             global_txid = Tx.parse(BytesIO(self.psbt.maps['global'][PSBT_GLOBAL_UNSIGNED_TX])).tx_ins[input_index].prev_tx 
             utxo_txid = double_sha256(curr_input[PSBT_IN_NON_WITNESS_UTXO])[::-1]
             # Verify that txids match
             if utxo_txid != global_txid:
                 raise RuntimeError('UTXO of this input does not match with that in global unsigned tx') 
         # If witness UTXO, verify that hashes match there
         elif PSBT_IN_WITNESS_UTXO in curr_input:
             # TODO: Do more testing for native segwit utxos
             # Get the hash of witness program in scriptPubKey of the witness UTXO
             scriptPubKey = Script.parse(curr_input[PSBT_IN_WITNESS_UTXO][9:]) 
             # Determine script type of scriptPubKey
             if scriptPubKey.type() == 'p2wpkh':
                 # If scriptPubKey is p2wpkh, keyhash is last element
                 keyhash = scriptPubKey.elements[-1]
                 # Check to make sure hash160 of compressed pubkey of signature matches that in UTXO scriptPubKey
                 if hash160(compressed_sec) != keyhash:
                     raise RuntimeError('Hash of compressed pubkey in partial signature does not match \
                     that of hash in witness UTXOs scriptPubKey')     
             elif scriptPubKey.type() == 'p2wsh':
                 # If scriptPubKey is p2wsh, scripthash is last element
                 scripthash = scriptPubKey.elements[-1]
                 # Check to make sure single SHA256 of witnessScript (if provided) matches that in UTXO scriptPubKey
                 if PSBT_IN_WITNESS_SCRIPT in curr_input:
                     if sha256(curr_input[PSBT_IN_WITNESS_SCRIPT]).digest != scripthash:
                             raise RuntimeError('Hash of witnessScript does not match that of hash in witness UTXOs \
                             scriptPubKey')            
             # Otherwise check if P2SH (including P2SH wrapped segwit)   
             elif scriptPubKey.type() == 'p2sh':
                 # If scriptPubKey is p2sh, scripthash is 2nd to last element
                 scripthash = scriptPubKey.elements[-2]
                 # If redeemScript is present for this input, its hash160 should match scripthash in scriptPubKey
                 if PSBT_IN_REDEEM_SCRIPT in curr_input:
                     if hash160(curr_input[PSBT_IN_REDEEM_SCRIPT]) != scripthash:
                         raise RuntimeError('Hash of redeemScript does not match that of hash in witness UTXOs \
                         scriptPubKey')
                     # If witness script is also present, verify the hash of this input's witnessScript matches the hash
                     # inside the redeemScript
                     if PSBT_IN_WITNESS_SCRIPT in curr_input:
                         # Parse redeemScript to get hash of witnessScript inside it
                         redeemScript = Script.parse(curr_input[PSBT_IN_REDEEM_SCRIPT])
                         # The last item in a P2SH-segwit redeemScript is the hash of the witness program
                         redeem_wit_hash = redeemScript.elements[-1]
                         if redeemScript.type() == 'p2wsh':
                             if sha256(curr_input[PSBT_IN_WITNESS_SCRIPT]).digest() != redeem_wit_hash:
                                 raise RuntimeError('Hash of witnessScript does not match that of hash in redeemScript')                
         # If this point has been reached without any errors, add partial signature
         self.psbt.maps['inputs'][input_index][PSBT_IN_PARTIAL_SIG+compressed_sec] = new_sig      
     # If input_index is still None, partial signature cannot be added
     else:
         raise RuntimeError('If the public key for this signature has not been added to the PSBT \
         and an input_index has not been provided then partial signature cannot be added')
     return
示例#7
0
 def parse(cls, s):
     '''Takes byte stream of a serialized psbt and returns a psbt object.'''
     # Check that serialization begins with the magic bytes
     if s.read(4) != MAGIC_BYTES:
         raise RuntimeError('Missing magic bytes')
     # Check that that magic bytes are followed by the separator
     if s.read(1) != HEAD_SEPARATOR :
         raise RuntimeError('Missing head separator')
     # Begin parsing global types, which is required to start with an unsigned transaction
     new_map = {
         'global' : {},
         'inputs' : [],
         'outputs' : []
         }
     expect_globals = True
     num_inputs = 0
     num_outputs = 0
     while expect_globals or num_inputs > 0 or num_outputs > 0:
         try:
             new_key, new_value = psbt.parse_key(s)
         except IndexError:
             raise RuntimeError('Unexpected serialization encountered, possible missing input/output maps')
         if expect_globals:
             # If a separator has been reached, the new_key will be None 
             # So it's time to continue on
             if new_key == None:
                 expect_globals = False
                 continue
             # Add new key-value pair to global maps
             new_map['global'][new_key] = new_value
             # If adding the unsigned_tx, parse it as a Tx object from bitcoin_lib 
             # and count the number of inputs and outputs
             if new_key == PSBT_GLOBAL_UNSIGNED_TX:
                 unsigned_tx_obj = Tx.parse(BytesIO(new_value))
                 num_inputs = len(unsigned_tx_obj.tx_ins)
                 num_outputs = len(unsigned_tx_obj.tx_outs)
                 # Set the amount of input and output maps that are expected
                 [new_map['inputs'].append({}) for _ in range(num_inputs)]
                 [new_map['outputs'].append({}) for _ in range(num_outputs)]
         # Parse each input key-value map
         elif num_inputs > 0:
             # If a separator has been reached, the new_key will be None 
             # thus marking the end of that input
             if new_key == None:
                 num_inputs -= 1
                 continue
             # curr_index is the position of the current input being parsed in the 
             # list of inputs in new_map['inputs']
             # Determined by the absolute value of the total number of inputs left 
             # to parse - the total number of inputs
             curr_index = abs(num_inputs - len(new_map['inputs']))
             new_map['inputs'][curr_index][new_key] = new_value       
         # Parse each output key-value map
         elif num_outputs > 0:
             # If a separator has been reached, the new_key will be None marking 
             # the end of that output
             if new_key == None:
                 num_outputs -= 1
                 continue
             # curr_index is the position of the current output being parsed 
             # in the list of inputs in new_map['outputs']
             # Determined by the absolute value of the total number of inputs left 
             # to parse - the total number of inputs
             curr_index = abs(num_outputs - len(new_map['outputs']))
             new_map['outputs'][curr_index][new_key] = new_value     
     return cls(dict_of_maps=new_map)