diff --git a/examples/ram/ram.py b/examples/ram/ram.py index f532b31..15765f9 100644 --- a/examples/ram/ram.py +++ b/examples/ram/ram.py @@ -53,7 +53,6 @@ def get_completions(self, document, complete_event): yield Completion(argument, start_position=-len(word_before_cursor)) - load_dotenv() rpc_user = os.getenv("RPC_USER", "rpcuser") @@ -64,10 +63,10 @@ def get_completions(self, document, complete_event): def parse_outputs(output_strings: List[str]) -> List[Tuple[str, int]]: """Parses a list of strings in the form "address:amount" into a list of (address, amount) tuples. - + Args: - output_strings (list of str): List of strings in the form "address:amount". - + Returns: - list of (str, int): List of (address, amount) tuples. """ @@ -148,12 +147,12 @@ def execute_command(input_line: str): if leaf_index not in range(len(R_inst.data_expanded)): raise ValueError("Invalid leaf index") - outputs=[] + outputs = [] for address, amount in outputs_amounts: outputs.append(CTxOut( - nValue=amount, - scriptPubKey=addr_to_script(address) - ) + nValue=amount, + scriptPubKey=addr_to_script(address) + ) ) R_inst("withdraw", @@ -176,10 +175,10 @@ def execute_command(input_line: str): raise ValueError("Invalid leaf index") result = R_inst("write", - merkle_root=mt.root, - new_value=new_value, - merkle_proof=mt.prove_leaf(leaf_index) - ) + merkle_root=mt.root, + new_value=new_value, + merkle_proof=mt.prove_leaf(leaf_index) + ) assert len(result) == 1 @@ -251,7 +250,6 @@ def script_main(script_filename: str): # map from known ctv hashes to the corresponding template (used for withdrawals) ctv_templates: Dict[bytes, CTransaction] = {} - if args.script: script_main(args.script) else: diff --git a/examples/ram/ram_contracts.py b/examples/ram/ram_contracts.py index c100bea..9120a8b 100644 --- a/examples/ram/ram_contracts.py +++ b/examples/ram/ram_contracts.py @@ -40,8 +40,8 @@ def __init__(self, size: List[bytes]): # TODO: should we check that it's either exactly 0 or exactly 1? OP_NOTIF, - # left child; swap, as we want x || h_i - OP_SWAP, + # left child; swap, as we want x || h_i + OP_SWAP, OP_ENDIF, OP_CAT, @@ -140,10 +140,10 @@ def __init__(self, size: List[bytes]): OP_SWAP, OP_FROMALTSTACK, OP_EQUALVERIFY, - + # stack: - - # Check that new_root is committed in the next output, + + # Check that new_root is committed in the next output, 0, # index 0, # NUMS -1, # keep current taptree @@ -154,7 +154,8 @@ def __init__(self, size: List[bytes]): ]), arg_specs=[ ("merkle_proof", MerkleProofType(n)), - ('new_value', BytesType()), # the new value of the element (its index is specified by the directions in the merkle proof) + # the new value of the element (its index is specified by the directions in the merkle proof) + ('new_value', BytesType()), ('merkle_root', BytesType()), ], next_output_fn=lambda args: [ diff --git a/examples/rps/rps.py b/examples/rps/rps.py index baab9c0..9ca40a4 100644 --- a/examples/rps/rps.py +++ b/examples/rps/rps.py @@ -69,7 +69,8 @@ def __init__(self, env: Environment, args: dict): self.env = env self.args = args - self.priv_key = key.ExtendedKey.deserialize("tprv8ZgxMBicQKsPdpwA4vW8DcSdXzPn7GkS2RdziGXUX8k86bgDQLKhyXtB3HMbJhPFd2vKRpChWxgPe787WWVqEtjy8hGbZHqZKeRrEwMm3SN") + self.priv_key = key.ExtendedKey.deserialize( + "tprv8ZgxMBicQKsPdpwA4vW8DcSdXzPn7GkS2RdziGXUX8k86bgDQLKhyXtB3HMbJhPFd2vKRpChWxgPe787WWVqEtjy8hGbZHqZKeRrEwMm3SN") def start_session(self, m_a: int): assert 0 <= m_a <= 2 @@ -127,8 +128,7 @@ def start_session(self, m_a: int): print(f"Game result: {outcome}") self.env.prompt("Broadcasting adjudication transaction") - C2(outcome, m_a=m_a, m_b=m_b,r_a=r_a) - + C2(outcome, m_a=m_a, m_b=m_b, r_a=r_a) s.close() @@ -138,7 +138,8 @@ def __init__(self, env: Environment, args: dict): self.env = env self.args = args - self.priv_key = key.ExtendedKey.deserialize("tprv8ZgxMBicQKsPeDvaW4xxmiMXxqakLgvukT8A5GR6mRwBwjsDJV1jcZab8mxSerNcj22YPrusm2Pz5oR8LTw9GqpWT51VexTNBzxxm49jCZZ") + self.priv_key = key.ExtendedKey.deserialize( + "tprv8ZgxMBicQKsPeDvaW4xxmiMXxqakLgvukT8A5GR6mRwBwjsDJV1jcZab8mxSerNcj22YPrusm2Pz5oR8LTw9GqpWT51VexTNBzxxm49jCZZ") def join_session(self, m_b: int): assert 0 <= m_b <= 2 diff --git a/examples/rps/rps_contracts.py b/examples/rps/rps_contracts.py index 1fbd853..6ae5d46 100644 --- a/examples/rps/rps_contracts.py +++ b/examples/rps/rps_contracts.py @@ -140,8 +140,8 @@ def make_script(diff: int, ctv_hash: bytes): OP_DUP, # if the result is negative, add 3 0, OP_LESSTHAN, OP_IF, - 3, - OP_ADD, + 3, + OP_ADD, OP_ENDIF, diff, # draw / Bob wins / Alice wins, respectively @@ -166,8 +166,11 @@ def make_script(diff: int, ctv_hash: bytes): ('m_a', IntType()), ('r_a', BytesType()), ] - alice_wins = StandardClause("tie", make_script(0, tmpl_alice_wins.get_standard_template_hash(0)), arg_specs, lambda _: tmpl_alice_wins) - bob_wins = StandardClause("bob_wins", make_script(1, tmpl_bob_wins.get_standard_template_hash(0)), arg_specs, lambda _: tmpl_bob_wins) - tie = StandardClause("alice_wins", make_script(2, tmpl_tie.get_standard_template_hash(0)), arg_specs, lambda _: tmpl_tie) + alice_wins = StandardClause("tie", make_script( + 0, tmpl_alice_wins.get_standard_template_hash(0)), arg_specs, lambda _: tmpl_alice_wins) + bob_wins = StandardClause("bob_wins", make_script( + 1, tmpl_bob_wins.get_standard_template_hash(0)), arg_specs, lambda _: tmpl_bob_wins) + tie = StandardClause("alice_wins", make_script( + 2, tmpl_tie.get_standard_template_hash(0)), arg_specs, lambda _: tmpl_tie) super().__init__(NUMS_KEY, [alice_wins, [bob_wins, tie]]) diff --git a/examples/vault/vault.py b/examples/vault/vault.py index e10a120..3414206 100644 --- a/examples/vault/vault.py +++ b/examples/vault/vault.py @@ -39,7 +39,7 @@ class ActionArgumentCompleter(Completer): "printall": [], "recover": ["item="], "trigger": ["items=\"[", "outputs=\"["], - "withdraw": ["item="], # TODO: allow multiple items? + "withdraw": ["item="], # TODO: allow multiple items? } def get_completions(self, document, complete_event): @@ -81,10 +81,10 @@ def segwit_addr_to_scriptpubkey(addr: str) -> bytes: def parse_outputs(output_strings: List[str]) -> List[Tuple[str, int]]: """Parses a list of strings in the form "address:amount" into a list of (address, amount) tuples. - + Args: - output_strings (list of str): List of strings in the form "address:amount". - + Returns: - list of (str, int): List of (address, amount) tuples. """ @@ -177,7 +177,7 @@ def execute_command(input_line: str): ctv_tmpl = CTransaction() ctv_tmpl.nVersion = 2 ctv_tmpl.vin = [CTxIn(nSequence=10)] - + outputs = parse_outputs(json.loads(args_dict["outputs"])) outputs_total_amount = sum(out[1] for out in outputs) @@ -191,9 +191,9 @@ def execute_command(input_line: str): ctv_tmpl.vout = [] for address, amount in outputs: ctv_tmpl.vout.append(CTxOut( - nValue=amount, - scriptPubKey=segwit_addr_to_scriptpubkey(address) - ) + nValue=amount, + scriptPubKey=segwit_addr_to_scriptpubkey(address) + ) ) # we assume the output is spent as first input later in the withdrawal transaction @@ -209,7 +209,8 @@ def execute_command(input_line: str): # at this time, we don't support partially revaulting from multiple UTXOs # (which might perhaps be useful for UTXO management in a real vault) if revault_amount > v.get_value(): - raise ValueError(f"Input's amount {v.get_value()} is not enough for the revault amount {revault_amount}") + raise ValueError( + f"Input's amount {v.get_value()} is not enough for the revault amount {revault_amount}") spends.append( (v, "trigger_and_revault", {"out_i": 0, "revault_out_i": 1, "ctv_hash": ctv_hash}) ) @@ -231,7 +232,6 @@ def execute_command(input_line: str): {**args, "sig": sigs[i]} )) - print("Waiting for trigger transaction to be confirmed...") result = manager.spend_and_wait(spending_vaults, spend_tx) @@ -343,7 +343,6 @@ def script_main(script_filename: str): # map from known ctv hashes to the corresponding template (used for withdrawals) ctv_templates: Dict[bytes, CTransaction] = {} - if args.script: script_main(args.script) else: diff --git a/examples/vault/vault_contracts.py b/examples/vault/vault_contracts.py index 88478a0..8a6afec 100644 --- a/examples/vault/vault_contracts.py +++ b/examples/vault/vault_contracts.py @@ -34,7 +34,8 @@ def __init__(self, alternate_pk: Optional[bytes], spend_delay: int, recover_pk: ('ctv_hash', BytesType()), ('out_i', IntType()), ], - next_output_fn=lambda args: [ClauseOutput(n=args['out_i'], next_contract=unvaulting, next_data=args['ctv_hash'])] + next_output_fn=lambda args: [ClauseOutput( + n=args['out_i'], next_contract=unvaulting, next_data=args['ctv_hash'])] ) # witness: @@ -43,7 +44,7 @@ def __init__(self, alternate_pk: Optional[bytes], spend_delay: int, recover_pk: script=CScript([ 0, OP_SWAP # no data tweak # from the witness - -1, # current input's taptweak + - 1, # current input's taptweak -1, # taptree CCV_FLAG_DEDUCT_OUTPUT_AMOUNT, # revault output OP_CHECKCONTRACTVERIFY, @@ -64,7 +65,8 @@ def __init__(self, alternate_pk: Optional[bytes], spend_delay: int, recover_pk: ('revault_out_i', IntType()), ], next_output_fn=lambda args: [ - ClauseOutput(n=args['revault_out_i'], next_contract=self, next_amount=ClauseOutputAmountBehaviour.DEDUCT_OUTPUT), + ClauseOutput(n=args['revault_out_i'], next_contract=self, + next_amount=ClauseOutputAmountBehaviour.DEDUCT_OUTPUT), ClauseOutput(n=args['out_i'], next_contract=unvaulting, next_data=args['ctv_hash']), ] ) diff --git a/matt/argtypes.py b/matt/argtypes.py index 7e41022..f146766 100644 --- a/matt/argtypes.py +++ b/matt/argtypes.py @@ -71,6 +71,7 @@ class SignerType(BytesType): It is encoded as bytes, but labeling it allows the ContractManager to get the correct signatures by calling SchnorrSigner object instances. """ + def __init__(self, pubkey: bytes): if len(pubkey) != 32: raise ValueError("pubkey must be an x-only pubkey") diff --git a/matt/contracts.py b/matt/contracts.py index 16a0736..c16b8ec 100644 --- a/matt/contracts.py +++ b/matt/contracts.py @@ -61,8 +61,6 @@ def __init__(self, name: str, script: CScript, arg_specs: List[Tuple[str, ArgTyp self.next_outputs_fn = next_output_fn - - def next_outputs(self, args: dict) -> Union[List[ClauseOutput], CTransaction]: if self.next_outputs_fn is not None: return self.next_outputs_fn(args) @@ -122,6 +120,7 @@ def get_address(self) -> str: def __repr__(self) -> str: return f"{self.__class__.__name__}(pubkey={self.pubkey.hex()})" + Tapleaf = Tuple[str, Union[CScript, bytes]] TaptreeDescription = List['TaptreeDescription'] diff --git a/matt/manager.py b/matt/manager.py index 7211f3e..56e90b2 100644 --- a/matt/manager.py +++ b/matt/manager.py @@ -39,7 +39,7 @@ def sign(self, msg: bytes, pubkey: bytes) -> Optional[bytes]: for k in self.keys: if k.pubkey[1:] == pubkey: return sign_schnorr(k.privkey, msg) - + return None @@ -54,7 +54,7 @@ def __init__(self, contract: Union[StandardP2TR, StandardAugmentedP2TR]): self.contract = contract self.data = None if not self.is_augm() else b'\0'*32 - self.data_expanded = None # TODO: figure out a good API for this + self.data_expanded = None # TODO: figure out a good API for this self.manager: ContractManager = None @@ -151,7 +151,8 @@ def wait_for_outpoint(self, instance: ContractInstance, txid: Optional[str] = No if self.mine_automatically: self._mine_blocks(1) - instance.outpoint, instance.last_height = wait_for_output(self.rpc, scriptPubKey, txid=txid, poll_interval=self.poll_interval) + instance.outpoint, instance.last_height = wait_for_output( + self.rpc, scriptPubKey, txid=txid, poll_interval=self.poll_interval) funding_tx_raw = self.rpc.getrawtransaction(instance.outpoint.hash.to_bytes(32, byteorder="big").hex()) funding_tx = CTransaction() @@ -161,10 +162,10 @@ def wait_for_outpoint(self, instance: ContractInstance, txid: Optional[str] = No instance.status = ContractInstanceStatus.FUNDED def get_spend_tx( - self, - spends: Union[Tuple[ContractInstance, str, dict], List[Tuple[ContractInstance, str, dict]]], - output_amounts: Dict[int, int] = {} - ) -> Tuple[CTransaction, List[bytes]]: + self, + spends: Union[Tuple[ContractInstance, str, dict], List[Tuple[ContractInstance, str, dict]]], + output_amounts: Dict[int, int] = {} + ) -> Tuple[CTransaction, List[bytes]]: if not isinstance(spends, list): spends = [spends] @@ -217,9 +218,11 @@ def get_spend_tx( preserve_output_used = True elif clause_output.next_amount == ClauseOutputAmountBehaviour.DEDUCT_OUTPUT: if preserve_output_used: - raise ValueError("DEDUCT_OUTPUT clause outputs must be declared before PRESERVE_OUTPUT clause outputs") + raise ValueError( + "DEDUCT_OUTPUT clause outputs must be declared before PRESERVE_OUTPUT clause outputs") if clause_output.n not in output_amounts: - raise ValueError("The output amount must be specified for clause outputs using DEDUCT_AMOUNT") + raise ValueError( + "The output amount must be specified for clause outputs using DEDUCT_AMOUNT") outputs_map[clause_output.n].nValue = output_amounts[clause_output.n] ccv_amount -= output_amounts[clause_output.n] @@ -301,7 +304,7 @@ def wait_for_spend(self, instances: Union[ContractInstance, List[ContractInstanc self.rpc, instance.outpoint, starting_height=instance.last_height, - poll_interval = self.poll_interval + poll_interval=self.poll_interval ) tx.rehash() diff --git a/matt/merkle.py b/matt/merkle.py index edcdf71..56cac80 100644 --- a/matt/merkle.py +++ b/matt/merkle.py @@ -157,7 +157,7 @@ def to_wit_stack(self): def from_wit_stack(wit_stack: List[bytes]): """ Constructs a MerkleProof instance from a given witness stack. - + Args: wit_stack (list): A list of hashes and directions followed by an element x, structured as [, , , , ..., , , ] diff --git a/tests/conftest.py b/tests/conftest.py index 5dc7dc0..d2fd80c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,8 @@ def rpc_test_wallet(): def manager(rpc): return ContractManager([], rpc, mine_automatically=True, poll_interval=0.01) + @pytest.fixture(scope="session") def report_file(): with open("report.md", "w") as file: - yield file \ No newline at end of file + yield file diff --git a/tests/test_ram.py b/tests/test_ram.py index 424b0e4..95320d2 100644 --- a/tests/test_ram.py +++ b/tests/test_ram.py @@ -27,10 +27,10 @@ def test_withdraw(rpc, manager: ContractManager): ] out_instances = R_inst("withdraw", - outputs = outputs, - merkle_root = mt.root, - merkle_proof = mt.prove_leaf(leaf_index) - ) + outputs=outputs, + merkle_root=mt.root, + merkle_proof=mt.prove_leaf(leaf_index) + ) assert len(out_instances) == 0 @@ -47,10 +47,10 @@ def test_write(manager: ContractManager): R_inst = manager.fund_instance(RAM(len(data)), AMOUNT, data=mt.root) out_instances = R_inst("write", - merkle_root = mt.root, - new_value = new_value, - merkle_proof = mt.prove_leaf(leaf_index) - ) + merkle_root=mt.root, + new_value=new_value, + merkle_proof=mt.prove_leaf(leaf_index) + ) assert len(out_instances) == 1 @@ -75,10 +75,10 @@ def test_write_loop(manager: ContractManager): new_value = sha256((100 + i).to_bytes(1, byteorder='little')) out_instances = R_inst("write", - merkle_root = MerkleTree(data).root, - new_value = new_value, - merkle_proof = MerkleTree(data).prove_leaf(leaf_index) - ) + merkle_root=MerkleTree(data).root, + new_value=new_value, + merkle_proof=MerkleTree(data).prove_leaf(leaf_index) + ) assert len(out_instances) == 1 diff --git a/tests/test_rps.py b/tests/test_rps.py index ec57e7e..72020fc 100644 --- a/tests/test_rps.py +++ b/tests/test_rps.py @@ -12,8 +12,10 @@ random.seed(0) -alice_key = key.ExtendedKey.deserialize("tprv8ZgxMBicQKsPdpwA4vW8DcSdXzPn7GkS2RdziGXUX8k86bgDQLKhyXtB3HMbJhPFd2vKRpChWxgPe787WWVqEtjy8hGbZHqZKeRrEwMm3SN") -bob_key = key.ExtendedKey.deserialize("tprv8ZgxMBicQKsPeDvaW4xxmiMXxqakLgvukT8A5GR6mRwBwjsDJV1jcZab8mxSerNcj22YPrusm2Pz5oR8LTw9GqpWT51VexTNBzxxm49jCZZ") +alice_key = key.ExtendedKey.deserialize( + "tprv8ZgxMBicQKsPdpwA4vW8DcSdXzPn7GkS2RdziGXUX8k86bgDQLKhyXtB3HMbJhPFd2vKRpChWxgPe787WWVqEtjy8hGbZHqZKeRrEwMm3SN") +bob_key = key.ExtendedKey.deserialize( + "tprv8ZgxMBicQKsPeDvaW4xxmiMXxqakLgvukT8A5GR6mRwBwjsDJV1jcZab8mxSerNcj22YPrusm2Pz5oR8LTw9GqpWT51VexTNBzxxm49jCZZ") moves = { "rock": 0, @@ -41,12 +43,11 @@ def test_rps(manager: ContractManager): # cheating attempt with pytest.raises(JSONRPCException, match='Script failed an OP_EQUALVERIFY operation'): - S1_inst("alice_wins", m_a = m_a, m_b = m_b, r_a = r_a) + S1_inst("alice_wins", m_a=m_a, m_b=m_b, r_a=r_a) # cheat a bit less with pytest.raises(JSONRPCException, match='Script failed an OP_EQUALVERIFY operation'): - S1_inst("tie", m_a = m_a, m_b = m_b, r_a = r_a) + S1_inst("tie", m_a=m_a, m_b=m_b, r_a=r_a) # correct adjudication - S1_inst("bob_wins", m_a = m_a, m_b = m_b, r_a = r_a) - + S1_inst("bob_wins", m_a=m_a, m_b=m_b, r_a=r_a) diff --git a/tests/test_vault.py b/tests/test_vault.py index e7e6f06..e5154cd 100644 --- a/tests/test_vault.py +++ b/tests/test_vault.py @@ -26,7 +26,7 @@ def test_vault_recover(manager: ContractManager, report_file: TextIOWrapper): V_inst = manager.fund_instance(V, amount) - out_instances = V_inst("recover", out_i = 0) + out_instances = V_inst("recover", out_i=0) out: CTxOut = V_inst.spending_tx.vout[0] @@ -37,6 +37,7 @@ def test_vault_recover(manager: ContractManager, report_file: TextIOWrapper): assert len(out_instances) == 0 + def test_vault_trigger_and_recover(manager: ContractManager, report_file: TextIOWrapper): locktime = 10 V = Vault(None, locktime, recover_priv_key.pubkey[1:], unvault_priv_key.pubkey[1:]) @@ -53,13 +54,12 @@ def test_vault_trigger_and_recover(manager: ContractManager, report_file: TextIO ("bcrt1q6vqduw24yjjll6nfkxlfy2twwt52w58tnvnd46", 4999990000), ], nSequence=locktime) - [U_inst] = V_inst("trigger", signer = signer, - out_i = 0, ctv_hash = ctv_tmpl.get_standard_template_hash(0)) + [U_inst] = V_inst("trigger", signer=signer, + out_i=0, ctv_hash=ctv_tmpl.get_standard_template_hash(0)) report_file.write(format_tx_markdown(V_inst.spending_tx, "Trigger [3 vault inputs]")) - - out_instances = U_inst("recover", out_i = 0) + out_instances = U_inst("recover", out_i=0) assert len(out_instances) == 0 @@ -82,8 +82,8 @@ def test_vault_trigger_and_withdraw(rpc: AuthServiceProxy, manager: ContractMana ("bcrt1q6vqduw24yjjll6nfkxlfy2twwt52w58tnvnd46", 1666663334), ], nSequence=locktime) - [U_inst] = V_inst("trigger", signer = signer, - out_i = 0, ctv_hash = ctv_tmpl.get_standard_template_hash(0)) + [U_inst] = V_inst("trigger", signer=signer, + out_i=0, ctv_hash=ctv_tmpl.get_standard_template_hash(0)) report_file.write(format_tx_markdown(V_inst.spending_tx, "Trigger [3 vault inputs]")) @@ -115,7 +115,6 @@ def test_vault_trigger_and_withdraw(rpc: AuthServiceProxy, manager: ContractMana report_file.write(format_tx_markdown(U_inst.spending_tx, "Withdraw [3 outputs]")) - def test_vault_trigger_with_revault_and_withdraw(rpc: AuthServiceProxy, manager: ContractManager, report_file: TextIOWrapper): # get coins on 3 different Vaults, then trigger with partial withdrawal # one of the vault uses "trigger_with_revault", the others us normal "trigger" @@ -157,7 +156,6 @@ def test_vault_trigger_with_revault_and_withdraw(rpc: AuthServiceProxy, manager: {**args, "sig": sigs[i]} )) - [U_inst] = manager.spend_and_wait([V_inst_1, V_inst_2, V_inst_3], spend_tx) report_file.write(format_tx_markdown(spend_tx, "Trigger (with revault) [3 vault inputs]")) @@ -184,4 +182,4 @@ def test_vault_trigger_with_revault_and_withdraw(rpc: AuthServiceProxy, manager: manager.spend_and_wait(U_inst, spend_tx) - report_file.write(format_tx_markdown(U_inst.spending_tx, "Withdraw (3 outputs)")) \ No newline at end of file + report_file.write(format_tx_markdown(U_inst.spending_tx, "Withdraw (3 outputs)"))