def _script_at(self, master_key: BIP32, index: int, account: int) -> Script: """ Render the script at a specific index and account pair. """ pubkey = master_key.get_pubkey_from_path(self.path.to_list(index, account)) script = self.script_type.build_output_script(pubkey) return Script(script, index, account, self)
def __init__(self, master_key: BIP32, utxos: List[scanner.Utxo], address: str, amount_in_sat: int): """ Craft and sign a transaction that spends all the UTXOs and sends the requested funds to a specific address. """ output_script = scripts.build_output_script_from_address(address) if output_script is None: raise ValueError( 'The address is invalid or the format isn\'t recognized.') if amount_in_sat < NON_SEGWIT_DUST: raise ValueError('Not enough funds to create a sweep transaction.') self.outputs = [(amount_in_sat, output_script)] self.inputs = [] for index in range(len(utxos)): utxo = utxos[index] # Build the inputs for signing: they should all have empty scripts, save for the input that we are signing, # which should have the output script of a P2PKH output. pubkey = master_key.get_pubkey_from_path(utxo.path.to_list()) script = scripts.ScriptType.LEGACY.build_output_script(pubkey) inputs = [(u, script if u == utxo else b'', []) for u in utxos] if utxo.script_type == scripts.ScriptType.LEGACY: # If this is a legacy input, then the transaction digest is just the wire format serialization. tx = _serialize_tx(inputs, self.outputs, include_witness=False) else: # If this is a segwit input (native or not), then the transaction digest is the one defined in BIP143. tx = _serialize_tx_for_segwit_signing(index, inputs, self.outputs) # To produce the final message digest we need to append the sig-hash type, and double sha256 the message. tx.extend(SIGHASH_ALL.to_bytes(4, 'little')) hash = scripts.sha256(scripts.sha256(bytes(tx))) privkey = master_key.get_privkey_from_path(utxo.path.to_list()) signature = coincurve.PrivateKey(privkey).sign(hash, hasher=None) extended_signature = bytearray(signature) extended_signature.append(SIGHASH_ALL) extended_signature = bytes(extended_signature) self.inputs.append( (utxo, utxo.script_type.build_input_script(pubkey, extended_signature), utxo.script_type.build_witness(pubkey, extended_signature)))
def next_script( self, master_key: BIP32) -> Optional[Tuple[bytes, Path, ScriptType]]: """ Fetch the next script for the current descriptor. """ if self.index > self.max_index or self.account > self.max_account: return None # derive the next script path = self.path.with_account(self.account) pubkey = master_key.get_pubkey_from_path(path.to_list(self.index)) script = self.script_type.build_output_script(pubkey) # Since traversing the entire [0; MAX_INDEX] x [0; MAX_ACCOUNT] space of combinations might take a while, we # walk the (index, account) grid in diagonal order. This order prioritizes the most probable combinations # (ie. low index, low account), while letting us explore a large space in the long run. # # 0 1 2 # ↙ ↙ ↙ # (0,0) (1,0) (2,0) 3 # ↙ ↙ ↙ ↙ # (0,1) (1,1) (2,1) 4 # ↙ ↙ ↙ ↙ # (0,2) (1,2) (2,2) 5 # ↙ ↙ ↙ ↙ # (0,3) (1,3) (2,3) # ↙ ↙ ↙ if self.index == 0 or self.account == self.max_account: # if we reached the border, start in the next diagonal diagonal_total = self.index + self.account + 1 self.index = min(diagonal_total, self.max_index) self.account = diagonal_total - self.index else: # go down the diagonal self.index -= 1 self.account += 1 return script, path, self.script_type