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)
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
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)
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
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
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
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)
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
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
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)
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}")
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}")