Example #1
0
    def validate_spend_bundle(
        self,
        spend_bundle: SpendBundle,
        now: CoinTimestamp,
    ) -> int:
        # this should use blockchain consensus code

        announcements: List[Announcement] = []
        conditions_dicts = []
        for coin_solution in spend_bundle.coin_solutions:
            err, conditions_dict, cost = conditions_dict_for_solution(
                coin_solution.puzzle_reveal, coin_solution.solution)
            if conditions_dict is None:
                raise BadSpendBundleError(f"clvm validation failure {err}")
            conditions_dicts.append(conditions_dict)
            announcements.extend(
                created_announcements_for_conditions_dict(
                    conditions_dict, coin_solution.coin.name()))

        for coin_solution, conditions_dict in zip(spend_bundle.coin_solutions,
                                                  conditions_dicts):
            prev_transaction_block_height = now.height
            timestamp = now.seconds
            coin_record = self._db[coin_solution.coin.name()]
            err = blockchain_check_conditions_dict(
                coin_record, announcements, conditions_dict,
                uint32(prev_transaction_block_height), uint64(timestamp))
        if err is not None:
            raise BadSpendBundleError(f"condition validation failure {err}")

        return 0
async def sign_coin_solutions(
    coin_solutions: List[CoinSolution],
    secret_key_for_public_key_f: Callable[[bytes], Optional[PrivateKey]],
) -> SpendBundle:
    signatures = []

    for coin_solution in coin_solutions:
        # Get AGGSIG conditions
        err, conditions_dict, cost = conditions_dict_for_solution(
            coin_solution.solution)
        if err or conditions_dict is None:
            error_msg = f"Sign transaction failed, con:{conditions_dict}, error: {err}"
            raise ValueError(error_msg)

        # Create signature
        for _, msg in pkm_pairs_for_conditions_dict(conditions_dict,
                                                    bytes(coin_solution.coin)):
            secret_key = secret_key_for_public_key_f(_)
            if secret_key is None:
                e_msg = f"no secret key for {_}"
                raise ValueError(e_msg)
            assert bytes(secret_key.get_g1()) == bytes(_)
            signature = AugSchemeMPL.sign(secret_key, msg)
            signatures.append(signature)

    # Aggregate signatures
    aggsig = AugSchemeMPL.aggregate(signatures)
    return SpendBundle(coin_solutions, aggsig)
Example #3
0
def get_output_amount_for_puzzle_and_solution(puzzle, solution):
    error, conditions, cost = conditions_dict_for_solution(Program.to([puzzle, solution]))
    total = 0
    if conditions:
        for _ in conditions.get(ConditionOpcode.CREATE_COIN, []):
            total += Program.to(_.vars[1]).as_int()
    return total
Example #4
0
    async def sign_transaction(
            self, coin_solutions: List[CoinSolution]) -> SpendBundle:
        signatures = []

        for coin_solution in coin_solutions:
            await self.hack_populate_secret_key_for_puzzle_hash(
                coin_solution.coin.puzzle_hash)

            # Get AGGSIG conditions
            err, conditions_dict, cost = conditions_dict_for_solution(
                coin_solution.solution)
            if err or conditions_dict is None:
                error_msg = (
                    f"Sign transaction failed, con:{conditions_dict}, error: {err}"
                )
                self.log.error(error_msg)
                raise ValueError(error_msg)

            # Create signature
            for _, msg in pkm_pairs_for_conditions_dict(
                    conditions_dict, bytes(coin_solution.coin)):
                secret_key = self.secret_key_for_public_key(_)
                if secret_key is None:
                    e_msg = f"no secret key for {_}"
                    self.log.error(e_msg)
                    raise ValueError(e_msg)
                signature = AugSchemeMPL.sign(secret_key, msg)
                signatures.append(signature)

        # Aggregate signatures
        aggsig = AugSchemeMPL.aggregate(signatures)
        return SpendBundle(coin_solutions, aggsig)
def announcements_for_solution(coin_name: bytes, puzzle_reveal: Program, solution: Program) -> List[Announcement]:
    """
    Checks the conditions created by CoinSolution and returns the list of announcements
    """
    err, dic, cost = conditions_dict_for_solution(puzzle_reveal, solution)
    if err or dic is None:
        return []
    return created_announcements_for_conditions_dict(dic, coin_name)
def announcements_for_solution(coin_name, solution) -> List[Announcement]:
    """
    Checks the conditions created by CoinSolution and returns the list of announcements
    """
    err, dic, cost = conditions_dict_for_solution(solution)
    if err or dic is None:
        return []
    return created_announcements_for_conditions_dict(dic, coin_name)
def additions_for_solution(coin_name: bytes32, puzzle_reveal: Program, solution: Program) -> List[Coin]:
    """
    Checks the conditions created by CoinSolution and returns the list of all coins created
    """
    err, dic, cost = conditions_dict_for_solution(puzzle_reveal, solution)
    if err or dic is None:
        return []
    return created_outputs_for_conditions_dict(dic, coin_name)
def additions_for_solution(coin_name, solution) -> List[Coin]:
    """
    Checks the conditions created by CoinSolution and returns the list of all coins created
    """
    err, dic, cost = conditions_dict_for_solution(solution)
    if err or dic is None:
        return []
    return created_outputs_for_conditions_dict(dic, coin_name)
Example #9
0
 async def get_sigs(self, innerpuz: Program, innersol: Program, coin_name) -> List[G2Element]:
     puzzle_hash = innerpuz.get_tree_hash()
     pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash)
     synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH)
     sigs: List[G2Element] = []
     error, conditions, cost = conditions_dict_for_solution(innerpuz, innersol)
     if conditions is not None:
         for _, msg in pkm_pairs_for_conditions_dict(conditions, coin_name):
             signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
             sigs.append(signature)
     return sigs
Example #10
0
 async def get_sigs(self, innerpuz: Program,
                    innersol: Program) -> List[G2Element]:
     puzzle_hash = innerpuz.get_tree_hash()
     pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash)
     sigs: List[G2Element] = []
     code_ = [innerpuz, innersol]
     sexp = Program.to(code_)
     error, conditions, cost = conditions_dict_for_solution(sexp)
     if conditions is not None:
         for _, msg in pkm_pairs_for_conditions_dict(conditions):
             signature = AugSchemeMPL.sign(private, msg)
             sigs.append(signature)
     return sigs
Example #11
0
 async def get_sigs_for_innerpuz_with_innersol(
         self, innerpuz: Program, innersol: Program) -> List[BLSSignature]:
     puzzle_hash = innerpuz.get_tree_hash()
     pubkey, private = await self.wallet_state_manager.get_keys(puzzle_hash)
     private = BLSPrivateKey(private)
     sigs: List[BLSSignature] = []
     code_ = [innerpuz, innersol]
     sexp = Program.to(code_)
     error, conditions, cost = conditions_dict_for_solution(sexp)
     if conditions is not None:
         for _ in hash_key_pairs_for_conditions_dict(conditions):
             signature = private.sign(_.message_hash)
             sigs.append(signature)
     return sigs
Example #12
0
def get_name_puzzle_conditions(
    block_program: Program, ) -> Tuple[Optional[Err], List[NPC], uint64]:
    """
    Returns an error if it's unable to evaluate, otherwise
    returns a list of NPC (coin_name, solved_puzzle_hash, conditions_dict)
    """
    cost_sum = 0
    try:
        cost_run, sexp = block_program.run_with_cost([])
        cost_sum += cost_run
    except Program.EvalError:
        return Err.INVALID_COIN_SOLUTION, [], uint64(0)

    npc_list = []
    for name_solution in sexp.as_iter():
        _ = name_solution.as_python()
        if len(_) != 2:
            return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
        if not isinstance(_[0], bytes) or len(_[0]) != 32:
            return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
        coin_name = bytes32(_[0])
        if not isinstance(_[1], list) or len(_[1]) != 2:
            return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
        puzzle_solution_program = name_solution.rest().first()
        puzzle_program = puzzle_solution_program.first()
        puzzle_hash = Program(puzzle_program).get_tree_hash()
        try:
            error, conditions_dict, cost_run = conditions_dict_for_solution(
                puzzle_solution_program)
            cost_sum += cost_run
            if error:
                return error, [], uint64(cost_sum)
        except Program.EvalError:
            return Err.INVALID_COIN_SOLUTION, [], uint64(cost_sum)
        if conditions_dict is None:
            conditions_dict = {}
        npc: NPC = NPC(coin_name, puzzle_hash, conditions_dict)
        npc_list.append(npc)

    return None, npc_list, uint64(cost_sum)
Example #13
0
    async def search_for_parent_info(self, block_program: Program,
                                     removals: List[Coin]) -> bool:
        """
        Returns an error if it's unable to evaluate, otherwise
        returns a list of NPC (coin_name, solved_puzzle_hash, conditions_dict)
        """
        cost_sum = 0
        try:
            cost_run, sexp = run_program(block_program, [])
            cost_sum += cost_run
        except EvalError:
            return False

        for name_solution in sexp.as_iter():
            _ = name_solution.as_python()
            if len(_) != 2:
                return False
            if not isinstance(_[0], bytes) or len(_[0]) != 32:
                return False
            coin_name = bytes32(_[0])
            if not isinstance(_[1], list) or len(_[1]) != 2:
                return False
            puzzle_solution_program = name_solution.rest().first()
            puzzle_program = puzzle_solution_program.first()
            try:
                error, conditions_dict, cost_run = conditions_dict_for_solution(
                    puzzle_solution_program)
                cost_sum += cost_run
                if error:
                    return False
            except EvalError:

                return False
            if conditions_dict is None:
                conditions_dict = {}

            if ConditionOpcode.CREATE_COIN in conditions_dict:
                created_output_conditions = conditions_dict[
                    ConditionOpcode.CREATE_COIN]
            else:
                continue
            for cvp in created_output_conditions:
                result = await self.wallet_state_manager.puzzle_store.wallet_info_for_puzzle_hash(
                    cvp.var1)
                if result is None:
                    continue

                wallet_id, wallet_type = result
                if wallet_id != self.wallet_info.id:
                    continue

                coin = None
                for removed in removals:
                    if removed.name() == coin_name:
                        coin = removed
                        break

                if coin is not None:
                    if cc_wallet_puzzles.check_is_cc_puzzle(puzzle_program):
                        puzzle_string = binutils.disassemble(puzzle_program)
                        inner_puzzle_hash = hexstr_to_bytes(
                            get_innerpuzzle_from_puzzle(puzzle_string))
                        self.log.info(
                            f"parent: {coin_name} inner_puzzle for parent is {inner_puzzle_hash.hex()}"
                        )

                        await self.add_parent(
                            coin_name,
                            CCParent(coin.parent_coin_info, inner_puzzle_hash,
                                     coin.amount),
                        )

                return True

        return False
Example #14
0
    async def search_for_parent_info(self, block_program: Program,
                                     removals: List[Coin]) -> bool:
        """
        Returns an error if it's unable to evaluate, otherwise
        returns a list of NPC (coin_name, solved_puzzle_hash, conditions_dict)
        """
        cost_sum = 0
        try:
            cost_run, sexp = block_program.run_with_cost([])
            cost_sum += cost_run
        except Program.EvalError:
            return False

        parents = []

        for name_solution in sexp.as_iter():
            _ = name_solution.as_python()
            if len(_) != 2:
                return False
            if not isinstance(_[0], bytes) or len(_[0]) != 32:
                return False
            coin_name = bytes32(_[0])
            if not isinstance(_[1], list) or len(_[1]) != 2:
                return False
            puzzle_solution_program = name_solution.rest().first()
            puzzle_program = puzzle_solution_program.first()
            try:
                error, conditions_dict, cost_run = conditions_dict_for_solution(
                    puzzle_solution_program)
                cost_sum += cost_run
                if error:
                    return False
            except Program.EvalError:

                return False
            if conditions_dict is None:
                conditions_dict = {}

            if ConditionOpcode.CREATE_COIN in conditions_dict:
                created_output_conditions = conditions_dict[
                    ConditionOpcode.CREATE_COIN]
            else:
                continue
            for cvp in created_output_conditions:
                result = await self.wallet_state_manager.puzzle_store.wallet_info_for_puzzle_hash(
                    cvp.var1)
                if result is None:
                    continue

                wallet_id, wallet_type = result
                if wallet_id != self.id():
                    continue

                coin = None
                for removed in removals:
                    if removed.name() == coin_name:
                        coin = removed
                        break

                if coin is not None:
                    r = uncurry_cc(puzzle_program)
                    if r is not None:
                        mod_hash, genesis_coin_checker, inner_puzzle = r
                        self.log.info(
                            f"parent: {coin_name} inner_puzzle for parent is {inner_puzzle}"
                        )
                        lineage_proof = get_lineage_proof_from_coin_and_puz(
                            coin, puzzle_program)
                        parents.append(lineage_proof)
                        await self.add_lineage(coin_name, lineage_proof)

        return len(parents) > 0
Example #15
0
def debug_spend_bundle(spend_bundle: SpendBundle) -> None:
    """
    Print a lot of useful information about a `SpendBundle` that might help with debugging
    its clvm.
    """

    assert_consumed_set = set()

    print("=" * 80)
    for coin_solution in spend_bundle.coin_solutions:
        coin, solution_pair = coin_solution.coin, coin_solution.solution
        puzzle_reveal = solution_pair.first()
        solution = solution_pair.rest().first()

        print(f"consuming coin {dump_coin(coin)}")
        print(f"  with id {coin.name()}")
        print()
        print(
            f"\nbrun -y main.sym '{bu_disassemble(puzzle_reveal)}' '{bu_disassemble(solution)}'"
        )
        error, conditions, cost = conditions_dict_for_solution(
            Program.to([puzzle_reveal, solution]))
        if error:
            print(f"*** error {error}")
        else:
            print()
            cost, r = run_program(puzzle_reveal, solution)
            print(disassemble(r))
            print()
            if conditions and len(conditions) > 0:
                print("grouped conditions:")
                for condition_programs in conditions.values():
                    print()
                    for c in condition_programs:
                        as_prog = Program.to([c.opcode, c.var1, c.var2])
                        print(f"  {disassemble(as_prog)}")
                print()
                for _ in conditions.get(ConditionOpcode.ASSERT_COIN_CONSUMED,
                                        []):
                    assert_consumed_set.add(bytes32(_.var1))
            else:
                print("(no output conditions generated)")
        print()
        print("-------")

    created = set(spend_bundle.additions())
    spent = set(spend_bundle.removals())

    zero_coin_set = set(coin.name() for coin in created if coin.amount == 0)

    ephemeral = created.intersection(spent)
    created.difference_update(ephemeral)
    spent.difference_update(ephemeral)
    print()
    print("spent coins")
    for coin in sorted(spent, key=lambda _: _.name()):
        print(f"  {dump_coin(coin)}")
        print(f"      => spent coin id {coin.name()}")
    print()
    print("created coins")
    for coin in sorted(created, key=lambda _: _.name()):
        print(f"  {dump_coin(coin)}")
        print(f"      => created coin id {coin.name()}")

    if ephemeral:
        print()
        print("ephemeral coins")
        for coin in sorted(ephemeral, key=lambda _: _.name()):
            print(f"  {dump_coin(coin)}")
            print(f"      => created coin id {coin.name()}")

    print()
    print(f"assert_consumed_set = {sorted(assert_consumed_set)}")
    print()
    print(f"zero_coin_set = {sorted(zero_coin_set)}")
    print()
    set_difference = zero_coin_set ^ assert_consumed_set
    print(f"zero_coin_set ^ assert_consumed_set = {sorted(set_difference)}")
    if len(set_difference):
        print(
            "not all zero coins asserted consumed or vice versa, entering debugger"
        )
        breakpoint()

    print()
    print("=" * 80)
Example #16
0
def spend_bundle_for_spendable_ccs(
    mod_code: Program,
    genesis_coin_checker: Program,
    spendable_cc_list: List[SpendableCC],
    inner_solutions: List[Program],
    sigs: Optional[List[G2Element]] = [],
) -> SpendBundle:
    """
    Given a list of `SpendableCC` objects and inner solutions for those objects, create a `SpendBundle`
    that spends all those coins. Note that it the signature is not calculated it, so the caller is responsible
    for fixing it.
    """

    N = len(spendable_cc_list)

    if len(inner_solutions) != N:
        raise ValueError("spendable_cc_list and inner_solutions are different lengths")

    input_coins = [_.coin for _ in spendable_cc_list]

    # figure out what the output amounts are by running the inner puzzles & solutions
    output_amounts = []
    for cc_spend_info, inner_solution in zip(spendable_cc_list, inner_solutions):
        error, conditions, cost = conditions_dict_for_solution(Program.to([cc_spend_info.inner_puzzle, inner_solution]))
        total = 0
        if conditions:
            for _ in conditions.get(ConditionOpcode.CREATE_COIN, []):
                total += Program.to(_.vars[1]).as_int()
        output_amounts.append(total)

    coin_solutions = []

    deltas = [input_coins[_].amount - output_amounts[_] for _ in range(N)]
    subtotals = subtotals_for_deltas(deltas)

    if sum(deltas) != 0:
        raise ValueError("input and output amounts don't match")

    bundles = [bundle_for_spendable_cc_list(_) for _ in spendable_cc_list]

    for index in range(N):
        cc_spend_info = spendable_cc_list[index]

        puzzle_reveal = cc_puzzle_for_inner_puzzle(mod_code, genesis_coin_checker, cc_spend_info.inner_puzzle)

        prev_index = (index - 1) % N
        next_index = (index + 1) % N
        prev_bundle = bundles[prev_index]
        my_bundle = bundles[index]
        next_bundle = bundles[next_index]

        solution = [
            inner_solutions[index],
            prev_bundle,
            my_bundle,
            next_bundle,
            subtotals[index],
        ]
        full_solution = Program.to([puzzle_reveal, solution])

        coin_solution = CoinSolution(input_coins[index], full_solution)
        coin_solutions.append(coin_solution)

    # now add solutions to consume the lock coins

    for _ in range(N):
        prev_index = (_ - 1) % N
        prev_coin = spendable_cc_list[prev_index].coin
        this_coin = spendable_cc_list[_].coin
        subtotal = subtotals[_]
        coin_solution = coin_solution_for_lock_coin(prev_coin, subtotal, this_coin)
        coin_solutions.append(coin_solution)
    if sigs is None or sigs == []:
        return SpendBundle(coin_solutions, NULL_SIGNATURE)
    else:
        return SpendBundle(coin_solutions, AugSchemeMPL.aggregate(sigs))
def debug_spend_bundle(spend_bundle: SpendBundle) -> None:
    """
    Print a lot of useful information about a `SpendBundle` that might help with debugging
    its clvm.
    """

    assert_consumed_set = set()

    pks = []
    msgs = []

    print("=" * 80)
    for coin_solution in spend_bundle.coin_solutions:
        coin, solution_pair = coin_solution.coin, Program.to(
            coin_solution.solution)
        puzzle_reveal = solution_pair.first()
        solution = solution_pair.rest().first()

        print(f"consuming coin {dump_coin(coin)}")
        print(f"  with id {coin.name()}")
        print()
        print(
            f"\nbrun -y main.sym '{bu_disassemble(puzzle_reveal)}' '{bu_disassemble(solution)}'"
        )
        error, conditions, cost = conditions_dict_for_solution(
            Program.to([puzzle_reveal, solution]))
        if error:
            print(f"*** error {error}")
        elif conditions is not None:
            for pk, m in pkm_pairs_for_conditions_dict(conditions,
                                                       coin.name()):
                pks.append(pk)
                msgs.append(m)
            print()
            r = puzzle_reveal.run(solution)
            print(disassemble(r))
            print()
            if conditions and len(conditions) > 0:
                print("grouped conditions:")
                for condition_programs in conditions.values():
                    print()
                    for c in condition_programs:
                        as_prog = Program.to([c.opcode, c.vars[0], c.vars[1]])
                        print(f"  {disassemble(as_prog)}")
                print()
                for _ in conditions.get(ConditionOpcode.ASSERT_COIN_CONSUMED,
                                        []):
                    assert_consumed_set.add(bytes32(c.vars[0]))
            else:
                print("(no output conditions generated)")
        print()
        print("-------")

    created = set(spend_bundle.additions())
    spent = set(spend_bundle.removals())

    zero_coin_set = set(coin.name() for coin in created if coin.amount == 0)

    ephemeral = created.intersection(spent)
    created.difference_update(ephemeral)
    spent.difference_update(ephemeral)
    print()
    print("spent coins")
    for coin in sorted(spent, key=lambda _: _.name()):
        print(f"  {dump_coin(coin)}")
        print(f"      => spent coin id {coin.name()}")
    print()
    print("created coins")
    for coin in sorted(created, key=lambda _: _.name()):
        print(f"  {dump_coin(coin)}")
        print(f"      => created coin id {coin.name()}")

    if ephemeral:
        print()
        print("ephemeral coins")
        for coin in sorted(ephemeral, key=lambda _: _.name()):
            print(f"  {dump_coin(coin)}")
            print(f"      => created coin id {coin.name()}")

    print()
    print(f"assert_consumed_set = {sorted(assert_consumed_set)}")
    print()
    print(f"zero_coin_set = {sorted(zero_coin_set)}")
    print()
    set_difference = zero_coin_set ^ assert_consumed_set
    print(f"zero_coin_set ^ assert_consumed_set = {sorted(set_difference)}")
    if len(set_difference):
        print("not all zero coins asserted consumed or vice versa")

    print()
    print("=" * 80)
    print()
    if len(msgs) > 0:
        validates = AugSchemeMPL.aggregate_verify(
            pks, msgs, spend_bundle.aggregated_signature)
        print(f"aggregated signature check pass: {validates}")
Example #18
0
def debug_spend_bundle(spend_bundle: SpendBundle) -> None:
    """
    Print a lot of useful information about a `SpendBundle` that might help with debugging
    its clvm.
    """

    pks = []
    msgs = []

    created_announcements: List[List[bytes]] = []
    asserted_annoucements = []

    print("=" * 80)
    for coin_solution in spend_bundle.coin_solutions:
        coin = coin_solution.coin
        puzzle_reveal = coin_solution.puzzle_reveal
        solution = coin_solution.solution
        coin_name = coin.name()

        print(f"consuming coin {dump_coin(coin)}")
        print(f"  with id {coin_name}")
        print()
        print(f"\nbrun -y main.sym '{bu_disassemble(puzzle_reveal)}' '{bu_disassemble(solution)}'")
        error, conditions, cost = conditions_dict_for_solution(puzzle_reveal, solution)
        if error:
            print(f"*** error {error}")
        elif conditions is not None:
            for pk, m in pkm_pairs_for_conditions_dict(conditions, coin_name):
                pks.append(pk)
                msgs.append(m)
            print()
            r = puzzle_reveal.run(solution)
            print(disassemble(r))
            print()
            if conditions and len(conditions) > 0:
                print("grouped conditions:")
                for condition_programs in conditions.values():
                    print()
                    for c in condition_programs:
                        if len(c.vars) == 1:
                            as_prog = Program.to([c.opcode, c.vars[0]])
                        if len(c.vars) == 2:
                            as_prog = Program.to([c.opcode, c.vars[0], c.vars[1]])
                        print(f"  {disassemble(as_prog)}")
                created_announcements.extend(
                    [coin_name] + _.vars for _ in conditions.get(ConditionOpcode.CREATE_ANNOUNCEMENT, [])
                )
                asserted_annoucements.extend(
                    [_.vars[0].hex() for _ in conditions.get(ConditionOpcode.ASSERT_ANNOUNCEMENT, [])]
                )
                print()
            else:
                print("(no output conditions generated)")
        print()
        print("-------")

    created = set(spend_bundle.additions())
    spent = set(spend_bundle.removals())

    zero_coin_set = set(coin.name() for coin in created if coin.amount == 0)

    ephemeral = created.intersection(spent)
    created.difference_update(ephemeral)
    spent.difference_update(ephemeral)
    print()
    print("spent coins")
    for coin in sorted(spent, key=lambda _: _.name()):
        print(f"  {dump_coin(coin)}")
        print(f"      => spent coin id {coin.name()}")
    print()
    print("created coins")
    for coin in sorted(created, key=lambda _: _.name()):
        print(f"  {dump_coin(coin)}")
        print(f"      => created coin id {coin.name()}")

    if ephemeral:
        print()
        print("ephemeral coins")
        for coin in sorted(ephemeral, key=lambda _: _.name()):
            print(f"  {dump_coin(coin)}")
            print(f"      => created coin id {coin.name()}")

    created_announcement_pairs = [(_, std_hash(b"".join(_)).hex()) for _ in created_announcements]
    if created_announcements:
        print("created announcements")
        for announcement, hashed in sorted(created_announcement_pairs, key=lambda _: _[-1]):
            as_hex = [f"0x{_.hex()}" for _ in announcement]
            print(f"  {as_hex} =>\n      {hashed}")

    eor_announcements = sorted(set(_[-1] for _ in created_announcement_pairs) ^ set(asserted_annoucements))

    print()
    print()
    print(f"zero_coin_set = {sorted(zero_coin_set)}")
    print()
    print(f"created announcements = {sorted([_[-1] for _ in created_announcement_pairs])}")
    print()
    print(f"asserted announcements = {sorted(asserted_annoucements)}")
    print()
    print(f"symdiff of announcements = {sorted(eor_announcements)}")
    print()
    print()
    print("=" * 80)
    print()
    validates = AugSchemeMPL.aggregate_verify(pks, msgs, spend_bundle.aggregated_signature)
    print(f"aggregated signature check pass: {validates}")