def ap_generate_signed_aggregation_transaction(self): list_of_coinsolutions = [] if self.aggregation_coins is False: # empty sets evaluate to false in python return consolidating_coin = self.aggregation_coins.pop() pubkey, secretkey = self.get_keys( self.temp_coin.puzzle_hash, self.a_pubkey) # Spend wallet coin puzzle = ap_make_puzzle(self.a_pubkey, bytes(pubkey)) solution = self.ap_make_solution_mode_2(self.temp_coin.puzzle_hash, consolidating_coin.parent_coin_info, consolidating_coin.puzzle_hash, consolidating_coin.amount, self.temp_coin.parent_coin_info, self.temp_coin.amount) signature = secretkey.sign(ProgramHash(solution)) list_of_coinsolutions.append(CoinSolution( self.temp_coin, clvm.to_sexp_f([puzzle, solution]))) # Spend consolidating coin puzzle = ap_make_aggregation_puzzle(self.temp_coin.puzzle_hash) solution = self.ac_make_aggregation_solution(consolidating_coin.name( ), self.temp_coin.parent_coin_info, self.temp_coin.amount) list_of_coinsolutions.append(CoinSolution( consolidating_coin, clvm.to_sexp_f([puzzle, solution]))) # Spend lock puzstring = f"(r (c (q 0x{consolidating_coin.name().hex()}) (q ())))" puzzle = Program(binutils.assemble(puzstring)) solution = Program(binutils.assemble("()")) list_of_coinsolutions.append(CoinSolution(Coin(self.temp_coin, ProgramHash( puzzle), 0), clvm.to_sexp_f([puzzle, solution]))) self.temp_coin = Coin(self.temp_coin, self.temp_coin.puzzle_hash, self.temp_coin.amount + consolidating_coin.amount) aggsig = BLSSignature.aggregate([signature]) solution_list = CoinSolutionList(list_of_coinsolutions) return SpendBundle(solution_list, aggsig)
def test_standard_spend(): remote = make_client_server() run = asyncio.get_event_loop().run_until_complete wallet_a = Wallet() wallet_b = Wallet() wallets = [wallet_a, wallet_b] commit_and_notify(remote, wallets, wallet_a) assert wallet_a.current_balance == 1000000000 assert len(wallet_a.my_utxos) == 2 assert wallet_b.current_balance == 0 assert len(wallet_b.my_utxos) == 0 # wallet a send to wallet b pubkey_puz_string = "(0x%s)" % hexlify( wallet_b.get_next_public_key().serialize()).decode('ascii') args = binutils.assemble(pubkey_puz_string) program = Program( clvm.eval_f( clvm.eval_f, binutils.assemble( wallet_a.generator_lookups[wallet_b.puzzle_generator_id]), args)) puzzlehash = ProgramHash(program) amount = 5000 spend_bundle = wallet_a.generate_signed_transaction(amount, puzzlehash) _ = run(remote.push_tx(tx=spend_bundle)) # give new wallet the reward to not complicate the one's we're tracking commit_and_notify(remote, wallets, Wallet()) assert wallet_a.current_balance == 999995000 assert wallet_b.current_balance == 5000 assert len(wallet_b.my_utxos) == 1 # wallet b sends back to wallet a pubkey_puz_string = "(0x%s)" % hexlify( wallet_a.get_next_public_key().serialize()).decode('ascii') args = binutils.assemble(pubkey_puz_string) program = Program( clvm.eval_f( clvm.eval_f, binutils.assemble( wallet_b.generator_lookups[wallet_a.puzzle_generator_id]), args)) puzzlehash = ProgramHash(program) amount = 5000 spend_bundle = wallet_b.generate_signed_transaction(amount, puzzlehash) _ = run(remote.push_tx(tx=spend_bundle)) # give new wallet the reward to not complicate the one's we're tracking commit_and_notify(remote, wallets, Wallet()) assert wallet_a.current_balance == 1000000000 assert wallet_b.current_balance == 0
def rl_generate_signed_aggregation_transaction(self): list_of_coinsolutions = [] if self.aggregation_coins is False: # empty sets evaluate to false in python return consolidating_coin = self.aggregation_coins.pop() pubkey, secretkey = self.get_keys(self.rl_coin.puzzle_hash) # Spend wallet coin puzzle = self.rl_puzzle_for_pk(pubkey.serialize(), self.limit, self.interval, self.rl_origin, self.rl_clawback_pk) if isinstance(self.rl_parent, Coin): solution = self.rl_make_solution_mode_2( self.rl_coin.puzzle_hash, consolidating_coin.parent_coin_info, consolidating_coin.puzzle_hash, consolidating_coin.amount, self.rl_coin.parent_coin_info, self.rl_coin.amount, self.rl_parent.amount, self.rl_parent.parent_coin_info) else: solution = self.rl_make_solution_mode_2( self.rl_coin.puzzle_hash, consolidating_coin.parent_coin_info, consolidating_coin.puzzle_hash, consolidating_coin.amount, self.rl_coin.parent_coin_info, self.rl_coin.amount, self.rl_parent["amount"], self.rl_parent["parent_coin_info"]) signature = BLSPrivateKey(secretkey).sign(ProgramHash(solution)) list_of_coinsolutions.append( CoinSolution(self.rl_coin, clvm.to_sexp_f([puzzle, solution]))) # Spend consolidating coin puzzle = self.rl_make_aggregation_puzzle(self.rl_coin.puzzle_hash) solution = self.rl_make_aggregation_solution( consolidating_coin.name(), self.rl_coin.parent_coin_info, self.rl_coin.amount) list_of_coinsolutions.append( CoinSolution(consolidating_coin, clvm.to_sexp_f([puzzle, solution]))) # Spend lock puzstring = "(r (c (q 0x" + hexlify( consolidating_coin.name()).decode('ascii') + ") (q ())))" puzzle = Program(binutils.assemble(puzstring)) solution = Program(binutils.assemble("()")) list_of_coinsolutions.append( CoinSolution(Coin(self.rl_coin, ProgramHash(puzzle), 0), clvm.to_sexp_f([puzzle, solution]))) aggsig = BLSSignature.aggregate([signature]) solution_list = CoinSolutionList(list_of_coinsolutions) return SpendBundle(solution_list, aggsig)
def ap_make_solution_mode_2(self, wallet_puzzle_hash, consolidating_primary_input, consolidating_coin_puzzle_hash, outgoing_amount, my_primary_input, incoming_amount): sol = f"(2 0x{wallet_puzzle_hash.hex()} 0x{consolidating_primary_input.hex()} 0x{consolidating_coin_puzzle_hash.hex()} {outgoing_amount} 0x{my_primary_input.hex()} {incoming_amount})" return Program(binutils.assemble(sol))
def solution_for_cp_permission(self, puzzlehash_amount_list=[]): opcode_create = hexlify(ConditionOpcode.CREATE_COIN).decode('ascii') sol = "(2 (" for puzhash, amount in puzzlehash_amount_list: sol += f"(0x{opcode_create} 0x{hexlify(puzhash).decode('ascii')} {amount})" sol += f"))" return Program(binutils.assemble(sol))
def ap_make_puzzle(a_pubkey_serialized, b_pubkey_serialized): a_pubkey = pubkey_format(a_pubkey_serialized) b_pubkey = pubkey_format(b_pubkey_serialized) # Mode one is for spending to one of the approved destinations # Solution contains (option 1 flag, new puzzle, new solution, my_primary_input, wallet_puzzle_hash) aggsig_entire_solution = f"(c (q 0x{ConditionOpcode.AGG_SIG.hex()}) (c (q {b_pubkey}) (c (sha256tree (a)) (q ()))))" create_outputs = f"((c (f (r (a))) (f (r (r (a))))))" aggsig_outputs = f"((c (q ((c (f (a)) (a)))) (c (q ((c (i (f (r (a))) (q ((c (i (= (f (f (f (r (a))))) (q 0x{ConditionOpcode.CREATE_COIN.hex()})) (q ((c (f (a)) (c (f (a)) (c (r (f (r (a)))) (c (c (c (q 0x{ConditionOpcode.AGG_SIG.hex()}) (c (q {a_pubkey}) (c (f (r (f (f (r (a)))))) (q ())))) (f (r (r (a))))) (q ()))))))) (q ((c (f (a)) (c (f (a)) (c (r (f (r (a)))) (c (f (r (r (a)))) (q ())))))))) (a)))) (q (f (r (r (a)))))) (a)))) (c {create_outputs} (c {create_outputs} (q ()))))))" sum_outputs = f"((c (q ((c (f (a)) (a)))) (c (q ((c (i (f (r (a))) (q ((c (i (= (f (f (f (r (a))))) (q 0x{ConditionOpcode.CREATE_COIN.hex()})) (q (+ (f (r (r (f (f (r (a))))))) ((c (f (a)) (c (f (a)) (c (r (f (r (a)))) (q ()))))))) (q (+ (q ()) ((c (f (a)) (c (f (a)) (c (r (f (r (a)))) (q ())))))))) (a)))) (q (q ()))) (a)))) (c {create_outputs} (q ())))))" mode_one_me_string = f"(c (q 0x{ConditionOpcode.ASSERT_MY_COIN_ID.hex()}) (c (sha256 (f (r (r (r (a))))) (f (r (r (r (r (a)))))) {sum_outputs}) (q ())))" mode_one = f"(c {aggsig_entire_solution} (c {mode_one_me_string} {aggsig_outputs}))" #mode_one = merge_two_lists(create_outputs, mode_one) # Mode two is for aggregating in another coin and expanding our single coin wallet # Solution contains (option 2 flag, wallet_puzzle_hash, consolidating_coin_primary_input, consolidating_coin_puzzle_hash, consolidating_coin_amount, my_primary_input, my_amount) create_consolidated = f"(c (q 0x{ConditionOpcode.CREATE_COIN.hex()}) (c (f (r (a))) (c (+ (f (r (r (r (r (a)))))) (f (r (r (r (r (r (r (a))))))))) (q ()))))" mode_two_me_string = f"(c (q 0x{ConditionOpcode.ASSERT_MY_COIN_ID.hex()}) (c (sha256 (f (r (r (r (r (r (a))))))) (f (r (a))) (f (r (r (r (r (r (r (a))))))))) (q ())))" create_lock = f"(c (q 0x{ConditionOpcode.CREATE_COIN.hex()}) (c (sha256tree (c (q 7) (c (c (q 5) (c (c (q 1) (c (sha256 (f (r (r (a)))) (f (r (r (r (a))))) (f (r (r (r (r (a))))))) (q ()))) (c (q (q ())) (q ())))) (q ())))) (c (q 0) (q ()))))" mode_two = f"(c {mode_two_me_string} (c {aggsig_entire_solution} \ (c {create_lock} (c {create_consolidated} (q ())))))" puz = f"((c (i (= (f (a)) (q 1)) (q {mode_one}) (q {mode_two})) (a)))" return Program(binutils.assemble(puz))
def get_new_puzzle_with_params_and_root(self, recovery_pubkey, pubkey, stake_factor, duration): op_create = ConditionOpcode.CREATE_COIN[0] op_consumed = ConditionOpcode.ASSERT_COIN_CONSUMED[0] solution = args(0) solution_args = args(1) secure_switch = args(2) parent = args(3) puzzle_hash = args(4) value = args(5) new_value = args(6) evaluate_solution = eval(solution, solution_args) standard_conditions = make_list(aggsig_condition(pubkey), terminator=evaluate_solution) escrow_program = self.get_escrow_puzzle_with_params( recovery_pubkey, pubkey, duration) escrow_puzzlehash = f'0x' + str(hexbytes(ProgramHash(escrow_program))) f = Fraction(stake_factor) stake_factor_numerator = quote(f.numerator) stake_factor_denominator = quote(f.denominator) create_condition = make_if( equal(multiply(new_value, stake_factor_denominator), multiply(value, stake_factor_numerator)), make_list(quote(op_create), quote(escrow_puzzlehash), new_value), fail()) coin_id = sha256(parent, puzzle_hash, uint64(value)) consumed_condition = make_list(quote(op_consumed), coin_id) escrow_conditions = make_list(create_condition, consumed_condition) puzzle = make_if(is_zero(secure_switch), standard_conditions, escrow_conditions) program = Program(binutils.assemble(puzzle)) return program
def as_make_puzzle(self, as_pubkey_sender, as_pubkey_receiver, as_amount, as_timelock_block, as_secret_hash): as_pubkey_sender_cl = "0x%s" % ( hexlify(as_pubkey_sender).decode('ascii')) as_pubkey_receiver_cl = "0x%s" % ( hexlify(as_pubkey_receiver).decode('ascii')) as_payout_puzzlehash_receiver = ProgramHash( puzzle_for_pk(as_pubkey_receiver)) as_payout_puzzlehash_sender = ProgramHash( puzzle_for_pk(as_pubkey_sender)) payout_receiver = "(c (q 0x%s) (c (q 0x%s) (c (q %d) (q ()))))" % ( hexlify(ConditionOpcode.CREATE_COIN).decode('ascii'), hexlify(as_payout_puzzlehash_receiver).decode('ascii'), as_amount) payout_sender = "(c (q 0x%s) (c (q 0x%s) (c (q %d) (q ()))))" % ( hexlify(ConditionOpcode.CREATE_COIN).decode('ascii'), hexlify(as_payout_puzzlehash_sender).decode('ascii'), as_amount) aggsig_receiver = "(c (q 0x%s) (c (q %s) (c (sha256 (wrap (a))) (q ()))))" % ( hexlify(ConditionOpcode.AGG_SIG).decode('ascii'), as_pubkey_receiver_cl) aggsig_sender = "(c (q 0x%s) (c (q %s) (c (sha256 (wrap (a))) (q ()))))" % ( hexlify( ConditionOpcode.AGG_SIG).decode('ascii'), as_pubkey_sender_cl) receiver_puz = ("((c (i (= (sha256 (f (r (a)))) (q %s)) (q (c " + aggsig_receiver + " (c " + payout_receiver + " (q ())))) (q (x (q 'invalid secret')))) (a))) ) " ) % (as_secret_hash) timelock = "(c (q 0x%s) (c (q %d) (q ()))) " % (hexlify( ConditionOpcode.ASSERT_BLOCK_INDEX_EXCEEDS).decode('ascii'), as_timelock_block) sender_puz = "(c " + aggsig_sender + " (c " + timelock + " (c " + payout_sender + " (q ()))))" as_puz_sender = "((c (i (= (f (a)) (q 77777)) (q " + sender_puz + ") (q (x (q 'not a valid option'))) ) (a)))" as_puz = "((c (i (= (f (a)) (q 33333)) (q " + receiver_puz + " (q " + as_puz_sender + ")) (a)))" return Program(binutils.assemble(as_puz))
def puzzle_for_synthetic_public_key(synthetic_public_key): puzzle_src = "((c (q %s) (c (q 0x%s) (a))))" % ( binutils.disassemble(puzzle_prog_template), synthetic_public_key.hex(), ) puzzle_prog = binutils.assemble(puzzle_src) return Program(puzzle_prog)
def rl_puzzle_for_pk(self, pubkey, rate_amount, interval_time, origin_id, clawback_pk): hex_pk = hexbytes(pubkey) #breakpoint() opcode_aggsig = hexlify(ConditionOpcode.AGG_SIG).decode('ascii') opcode_coin_block_age = hexlify( ConditionOpcode.ASSERT_BLOCK_AGE_EXCEEDS).decode('ascii') opcode_create = hexlify(ConditionOpcode.CREATE_COIN).decode('ascii') opcode_myid = hexlify( ConditionOpcode.ASSERT_MY_COIN_ID).decode('ascii') if (not origin_id): return None TEMPLATE_MY_PARENT_ID = "(sha256 (f (r (r (r (r (r (r (a)))))))) (f (r (a))) (uint64 (f (r (r (r (r (r (r (r (a)))))))))))" TEMPLATE_SINGLETON_RL = f"((c (i (i (= {TEMPLATE_MY_PARENT_ID} (f (a))) (q 1) (= (f (a)) (q 0x{origin_id}))) (q (c (q 1) (q ()))) (q (x (q \"Parent doesnt satisfy RL conditions\")))) (a)))" TEMPLATE_BLOCK_AGE = f"((c (i (i (= (* (f (r (r (r (r (r (a))))))) (q {rate_amount})) (* (f (r (r (r (r (a)))))) (q {interval_time}))) (q 1) (q (> (* (f (r (r (r (r (r (a))))))) (q {rate_amount})) (* (f (r (r (r (r (a))))))) (q {interval_time})))) (q (c (q 0x{opcode_coin_block_age}) (c (f (r (r (r (r (r (a))))))) (q ())))) (q (x (q \"wrong min block time\")))) (a) ))" TEMPLATE_MY_ID = f"(c (q 0x{opcode_myid}) (c (sha256 (f (a)) (f (r (a))) (uint64 (f (r (r (a)))))) (q ())))" CREATE_CHANGE = f"(c (q 0x{opcode_create}) (c (f (r (a))) (c (- (f (r (r (a)))) (f (r (r (r (r (a))))))) (q ()))))" CREATE_NEW_COIN = f"(c (q 0x{opcode_create}) (c (f (r (r (r (a))))) (c (f (r (r (r (r (a)))))) (q ()))))" RATE_LIMIT_PUZZLE = f"(c {TEMPLATE_SINGLETON_RL} (c {TEMPLATE_BLOCK_AGE} (c {CREATE_CHANGE} (c {TEMPLATE_MY_ID} (c {CREATE_NEW_COIN} (q ()))))))" TEMPLATE_MY_PARENT_ID_2 = "(sha256 (f (r (r (r (r (r (r (r (r (a)))))))))) (f (r (a))) (uint64 (f (r (r (r (r (r (r (r (a)))))))))))" TEMPLATE_SINGLETON_RL_2 = f"((c (i (i (= {TEMPLATE_MY_PARENT_ID_2} (f (r (r (r (r (r (a)))))))) (q 1) (= (f (r (r (r (r (r (a))))))) (q 0x{origin_id}))) (q (c (q 1) (q ()))) (q (x (q \"Parent doesnt satisfy RL conditions\")))) (a)))" CREATE_CONSOLIDATED = f"(c (q 0x{opcode_create}) (c (f (r (a))) (c (+ (f (r (r (r (r (a)))))) (f (r (r (r (r (r (r (a))))))))) (q ()))))" MODE_TWO_ME_STRING = f"(c (q 0x{opcode_myid}) (c (sha256 (f (r (r (r (r (r (a))))))) (f (r (a))) (uint64 (f (r (r (r (r (r (r (a)))))))))) (q ())))" CREATE_LOCK = f"(c (q 0x{opcode_create}) (c (sha256 (wrap (c (q 7) (c (c (q 5) (c (c (q 1) (c (sha256 (f (r (r (a)))) (f (r (r (r (a))))) (uint64 (f (r (r (r (r (a)))))))) (q ()))) (c (q (q ())) (q ())))) (q ()))))) (c (uint64 (q 0)) (q ()))))" MODE_TWO = f"(c {TEMPLATE_SINGLETON_RL_2} (c {MODE_TWO_ME_STRING} (c {CREATE_LOCK} (c {CREATE_CONSOLIDATED} (q ())))))" AGGSIG_ENTIRE_SOLUTION = f"(c (q 0x{opcode_aggsig}) (c (q 0x{hex_pk}) (c (sha256 (wrap (a))) (q ()))))" WHOLE_PUZZLE = f"(c {AGGSIG_ENTIRE_SOLUTION} ((c (i (= (f (a)) (q 1)) (q ((c (q {RATE_LIMIT_PUZZLE}) (r (a))))) (q {MODE_TWO})) (a))) (q ()))" CLAWBACK = f"(c (c (q 0x{opcode_aggsig}) (c (q 0x{clawback_pk}) (c (sha256 (wrap (a))) (q ())))) (r (a)))" WHOLE_PUZZLE_WITH_CLAWBACK = f"((c (i (= (f (a)) (q 3)) (q {CLAWBACK}) (q {WHOLE_PUZZLE})) (a)))" return Program(binutils.assemble(WHOLE_PUZZLE_WITH_CLAWBACK))
def generate_recovery_transaction(self, coins, root_public_key, secret_key, escrow_duration): recovery_pubkey = root_public_key.public_child( 0).get_public_key().serialize() signatures = [] coin_solutions = [] secret_key = BLSPrivateKey(secret_key) for coin in coins: pubkey = self.find_pubkey_for_escrow_puzzle( coin, root_public_key, escrow_duration) puzzle = self.get_escrow_puzzle_with_params( recovery_pubkey, pubkey.serialize(), escrow_duration) op_create_coin = ConditionOpcode.CREATE_COIN[0] puzzlehash = f'0x' + str(hexbytes(self.get_new_puzzlehash())) solution_src = sexp( quote(sexp(sexp(op_create_coin, puzzlehash, coin.amount))), sexp(), 1) solution = Program(binutils.assemble(solution_src)) puzzle_solution_list = clvm.to_sexp_f([puzzle, solution]) coin_solution = CoinSolution(coin, puzzle_solution_list) coin_solutions.append(coin_solution) conditions_dict = conditions_by_opcode( conditions_for_solution(puzzle_solution_list)) for _ in hash_key_pairs_for_conditions_dict(conditions_dict): signature = secret_key.sign(_.message_hash) signatures.append(signature) coin_solution_list = CoinSolutionList(coin_solutions) aggsig = BLSSignature.aggregate(signatures) spend_bundle = SpendBundle(coin_solution_list, aggsig) return spend_bundle
def as_create_spend_bundle(self, as_puzzlehash, as_amount, as_timelock_block, as_secret_hash, as_pubkey_sender=None, as_pubkey_receiver=None, who=None, as_sec_to_try=None): utxos = self.as_select_coins(as_amount, as_puzzlehash) spends = [] for coin in utxos: puzzle = self.as_make_puzzle(as_pubkey_sender, as_pubkey_receiver, as_amount, as_timelock_block, as_secret_hash) if who == "sender": solution = self.as_make_solution_sender() elif who == "receiver": solution = self.as_make_solution_receiver(as_sec_to_try) pair = solution.to([puzzle, solution]) signer = self.make_signer() spend_bundle = build_spend_bundle(coin, Program(pair), sign_f=signer) spends.append(spend_bundle) return SpendBundle.aggregate(spends)
def name_puzzle_conditions_list(body_program): """ Return a list of tuples of (coin_name, solved_puzzle_hash, conditions_dict) """ try: cost, sexp = run_program(body_program, []) except EvalError: breakpoint() raise ConsensusError(Err.INVALID_BLOCK_SOLUTION, body_program) npc_list = [] for name_solution in sexp.as_iter(): _ = name_solution.as_python() if len(_) != 2: raise ConsensusError(Err.INVALID_COIN_SOLUTION, name_solution) if not isinstance(_[0], bytes) or len(_[0]) != 32: raise ConsensusError(Err.INVALID_COIN_SOLUTION, name_solution) coin_name = CoinName(_[0]) if not isinstance(_[1], list) or len(_[1]) != 2: raise ConsensusError(Err.INVALID_COIN_SOLUTION, name_solution) puzzle_solution_program = name_solution.rest().first() puzzle_program = puzzle_solution_program.first() puzzle_hash = ProgramHash(Program(puzzle_program)) try: conditions_dict = conditions_dict_for_solution( puzzle_solution_program) except EvalError: raise ConsensusError(Err.INVALID_COIN_SOLUTION, coin_name) npc_list.append((coin_name, puzzle_hash, conditions_dict)) return npc_list
def rl_make_solution_mode_2(self, my_puzzle_hash, consolidating_primary_input, consolidating_coin_puzzle_hash, outgoing_amount, my_primary_input, incoming_amount, parent_amount, my_parent_parent_id): my_puzzle_hash = hexlify(my_puzzle_hash).decode('ascii') consolidating_primary_input = hexlify(consolidating_primary_input).decode('ascii') consolidating_coin_puzzle_hash = hexlify(consolidating_coin_puzzle_hash).decode('ascii') primary_input = hexlify(my_primary_input).decode('ascii') sol = f"(2 0x{my_puzzle_hash} 0x{consolidating_primary_input} 0x{consolidating_coin_puzzle_hash} {outgoing_amount} 0x{primary_input} {incoming_amount} {parent_amount} 0x{my_parent_parent_id})" return Program(binutils.assemble(sol))
def ap_make_solution_mode_1(self, outputs=[], my_primary_input=0x0000, my_puzzle_hash=0x0000): sol = "(1 (a) (" for puzhash, amount in outputs: sol += f"(0x{ConditionOpcode.CREATE_COIN.hex()} 0x{puzhash.hex()} {amount})" sol += f") 0x{my_primary_input.hex()} 0x{my_puzzle_hash.hex()})" return Program(binutils.assemble(sol))
def ap_make_aggregation_puzzle(wallet_puzzle): # If Wallet A wants to send further funds to Wallet B then they can lock them up using this code # Solution will be (my_id wallet_coin_primary_input wallet_coin_amount) me_is_my_id = f'(c (q 0x{ConditionOpcode.ASSERT_MY_COIN_ID.hex()}) (c (f (a)) (q ())))' # lock_puzzle is the hash of '(r (c (q "merge in ID") (q ())))' lock_puzzle = '(sha256tree (c (q 7) (c (c (q 5) (c (c (q 1) (c (f (a)) (q ()))) (c (q (q ())) (q ())))) (q ()))))' parent_coin_id = f"(sha256 (f (r (a))) (q 0x{wallet_puzzle.hex()}) (f (r (r (a)))))" input_of_lock = f'(c (q 0x{ConditionOpcode.ASSERT_COIN_CONSUMED.hex()}) (c (sha256 {parent_coin_id} {lock_puzzle} (q 0)) (q ())))' puz = f"(c {me_is_my_id} (c {input_of_lock} (q ())))" return Program(binutils.assemble(puz))
def ap_notify(self, additions): # this prevents unnecessary checks and stops us receiving multiple coins if self.AP_puzzlehash is not None and not self.my_utxos: for coin in additions: if coin.puzzle_hash == self.AP_puzzlehash: self.puzzle_generator = f"(q (c (c (q 0x{hexlify(ConditionOpcode.AGG_SIG).decode('ascii')}) (c (f (a)) (q ()))) (c (c (q 0x{hexlify(ConditionOpcode.ASSERT_COIN_CONSUMED).decode('ascii')}) (c (sha256 (sha256 (f (r (a))) (q 0x{hexlify(self.AP_puzzlehash).decode('ascii')}) (uint64 (f (r (r (a)))))) (sha256 (wrap (c (q 7) (c (c (q 5) (c (c (q 1) (c (f (a)) (q ()))) (c (q (q ())) (q ())))) (q ()))))) (uint64 (q 0))) (q ()))) (q ()))))" self.puzzle_generator_id = str(ProgramHash( Program(binutils.assemble(self.puzzle_generator)))) self.current_balance += coin.amount self.my_utxos.add(coin) print("this coin is locked using my ID, it's output must be for me")
def ap_make_aggregation_puzzle(wallet_puzzle): # If Wallet A wants to send further funds to Wallet B then they can lock them up using this code # Solution will be (my_id wallet_coin_primary_input wallet_coin_amount) me_is_my_id = '(c (q 0x%s) (c (f (a)) (q ())))' % ( hexlify(ConditionOpcode.ASSERT_MY_COIN_ID).decode('ascii')) # lock_puzzle is the hash of '(r (c (q "merge in ID") (q ())))' lock_puzzle = '(sha256 (wrap (c (q 7) (c (c (q 5) (c (c (q 1) (c (f (a)) (q ()))) (c (q (q ())) (q ())))) (q ())))))' parent_coin_id = "(sha256 (f (r (a))) (q 0x%s) (uint64 (f (r (r (a))))))" % hexlify( wallet_puzzle).decode('ascii') input_of_lock = '(c (q 0x%s) (c (sha256 %s %s (uint64 (q 0))) (q ())))' % (hexlify( ConditionOpcode.ASSERT_COIN_CONSUMED).decode('ascii'), parent_coin_id, lock_puzzle) puz = f"(c {me_is_my_id} (c {input_of_lock} (q ())))" return Program(binutils.assemble(puz))
def sign_clawback_transaction(self, spends: (Program, [CoinSolution]), clawback_pubkey): sigs = [] for puzzle, solution in spends: pubkey, secretkey = self.get_keys_pk(clawback_pubkey) signature = secretkey.sign( ProgramHash(Program(solution.solution))) sigs.append(signature) aggsig = BLSSignature.aggregate(sigs) solution_list = CoinSolutionList( [CoinSolution(coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])) for (puzzle, coin_solution) in spends]) spend_bundle = SpendBundle(solution_list, aggsig) return spend_bundle
def rl_make_aggregation_puzzle(self, wallet_puzzle): # If Wallet A wants to send further funds to Wallet B then they can lock them up using this code # Solution will be (my_id wallet_coin_primary_input wallet_coin_amount) opcode_myid = hexlify(ConditionOpcode.ASSERT_MY_COIN_ID).decode('ascii') opcode_consumed = hexlify(ConditionOpcode.ASSERT_COIN_CONSUMED).decode('ascii') me_is_my_id = f"(c (q 0x{opcode_myid}) (c (f (a)) (q ())))" # lock_puzzle is the hash of '(r (c (q "merge in ID") (q ())))' lock_puzzle = "(sha256tree (c (q 7) (c (c (q 5) (c (c (q 1) (c (f (a)) (q ()))) (c (q (q ())) (q ())))) (q ()))))" parent_coin_id = f"(sha256 (f (r (a))) (q 0x{wallet_puzzle}) (f (r (r (a)))))" input_of_lock = f"(c (q 0x{opcode_consumed}) (c (sha256 {parent_coin_id} {lock_puzzle} (q 0)) (q ())))" puz = f"(c {me_is_my_id} (c {input_of_lock} (q ())))" return Program(binutils.assemble(puz))
def ap_sign_transaction(self, spends: (Program, [CoinSolution]), signatures_from_a): sigs = [] for puzzle, solution in spends: pubkey, secretkey = self.get_keys( solution.coin.puzzle_hash, self.a_pubkey) signature = secretkey.sign( ProgramHash(Program(solution.solution))) sigs.append(signature) for s in signatures_from_a: sigs.append(s) aggsig = BLSSignature.aggregate(sigs) solution_list = CoinSolutionList( [CoinSolution(coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])) for (puzzle, coin_solution) in spends]) spend_bundle = SpendBundle(solution_list, aggsig) return spend_bundle
def get_escrow_puzzle_with_params(self, recovery_pubkey, pubkey, duration): op_block_age_exceeds = ConditionOpcode.ASSERT_BLOCK_AGE_EXCEEDS[0] solution = args(0) solution_args = args(1) secure_switch = args(2) evaluate_solution = eval(solution, solution_args) standard_conditions = make_list(aggsig_condition(pubkey), terminator=evaluate_solution) recovery_conditions = make_list(aggsig_condition(recovery_pubkey), make_list(quote(op_block_age_exceeds), quote(duration)), terminator=evaluate_solution) escrow_puzzle = make_if(is_zero(secure_switch), standard_conditions, recovery_conditions) program = Program(binutils.assemble(escrow_puzzle)) return program
def cp_puzzle(self, pubkey_my, pubkey_permission, unlock_time): opcode_aggsig = hexlify(ConditionOpcode.AGG_SIG).decode('ascii') opcode_time_exceeds = hexlify( ConditionOpcode.ASSERT_TIME_EXCEEDS).decode('ascii') TIME_EXCEEDS = f"(c (q 0x{opcode_time_exceeds}) (c (q {unlock_time}) (q ())))" AGGSIG_ME = f"(c (q 0x{opcode_aggsig}) (c (q 0x{pubkey_my}) (c (sha256tree (a)) (q ()))))" AGGSIG_PERMISSION = f"(c (q 0x{opcode_aggsig}) (c (q 0x{pubkey_permission}) (c (sha256tree (a)) (q ()))))" SOLO_PUZZLE_CONDITIONS = f"(c {TIME_EXCEEDS} (c {AGGSIG_ME} (q ())))" SOLUTION_OUTPUTS = f"(f (r (a)))" SOLO_PUZZLE = self.merge_two_lists(SOLO_PUZZLE_CONDITIONS, SOLUTION_OUTPUTS) PERMISSION_PUZZLE_CONDITIONS = f"(c {AGGSIG_PERMISSION} (c {AGGSIG_ME} (q ())))" PERMISSION_PUZZLE = self.merge_two_lists(PERMISSION_PUZZLE_CONDITIONS, SOLUTION_OUTPUTS) WHOLE_PUZZLE = f"(i (= (f (a)) (q 1)) {SOLO_PUZZLE} {PERMISSION_PUZZLE})" return Program(binutils.assemble(WHOLE_PUZZLE))
def test_spend_failure(): remote = make_client_server() run = asyncio.get_event_loop().run_until_complete wallet_a = Wallet() wallet_b = Wallet() wallets = [wallet_a, wallet_b] amount = 5000 # wallet a send to wallet b pubkey_puz_string = "(0x%s)" % hexlify( wallet_b.get_next_public_key().serialize()).decode('ascii') args = binutils.assemble(pubkey_puz_string) program = Program( clvm.eval_f( clvm.eval_f, binutils.assemble( wallet_a.generator_lookups[wallet_b.puzzle_generator_id]), args)) puzzlehash = ProgramHash(program) spend_bundle = wallet_a.generate_signed_transaction(amount, puzzlehash) assert spend_bundle is None commit_and_notify(remote, wallets, wallet_a) amount = 50000000000000 puzzlehash = wallet_b.get_new_puzzlehash() spend_bundle = wallet_a.generate_signed_transaction(amount, puzzlehash) assert spend_bundle is None amount = 999995000 puzzlehash = wallet_b.get_new_puzzlehash() spend_bundle = wallet_a.generate_signed_transaction(amount, puzzlehash) _ = run(remote.push_tx(tx=spend_bundle)) assert wallet_a.temp_balance == 5000 amount = 6000 puzzlehash = wallet_b.get_new_puzzlehash() spend_bundle = wallet_a.generate_signed_transaction(amount, puzzlehash) assert spend_bundle is None amount = 4000 puzzlehash = wallet_b.get_new_puzzlehash() spend_bundle = wallet_a.generate_signed_transaction(amount, puzzlehash) _ = run(remote.push_tx(tx=spend_bundle)) assert wallet_a.temp_balance == 1000 commit_and_notify(remote, wallets, Wallet()) assert wallet_a.current_balance == 1000 assert wallet_a.temp_balance == 1000
def make_solution(parent, puzzlehash, value, stake_factor, primaries=[], recovery=False): conditions = [] for primary in primaries: conditions.append( make_create_coin_condition(primary['puzzlehash'], primary['amount'])) conditions = [binutils.assemble("#q"), conditions] solution = [ conditions, [], 1 if recovery else 0, parent, puzzlehash, value, math.floor(value * stake_factor) ] program = Program(to_sexp_f(solution)) return program
def as_solution_list(self, body_program): try: cost, sexp = clvm.run_program(body_program, []) except clvm.EvalError.EvalError: raise ValueError(body_program) npc_list = [] for name_solution in sexp.as_iter(): _ = name_solution.as_python() if len(_) != 2: raise ValueError(name_solution) if not isinstance(_[0], bytes) or len(_[0]) != 32: raise ValueError(name_solution) if not isinstance(_[1], list) or len(_[1]) != 2: raise ValueError(name_solution) puzzle_solution_program = name_solution.rest().first() puzzle_program = puzzle_solution_program.first() puzzle_hash = ProgramHash(Program(puzzle_program)) npc_list.append((puzzle_hash, puzzle_solution_program)) return npc_list
def cp_sign_transaction(self, spends: (Program, [CoinSolution]), approval=None): sigs = [] for puzzle, solution in spends: pubkey, secretkey = self.get_keys(solution.coin.puzzle_hash) signature = secretkey.sign(ProgramHash(Program(solution.solution))) sigs.append(signature) if approval is not None: app = BLSSignature(approval) sigs.append(app) aggsig = BLSSignature.aggregate(sigs) solution_list = CoinSolutionList([ CoinSolution(coin_solution.coin, clvm.to_sexp_f([puzzle, coin_solution.solution])) for (puzzle, coin_solution) in spends ]) spend_bundle = SpendBundle(solution_list, aggsig) return spend_bundle
def as_solution_list(self, body_program): try: sexp = clvm.eval_f(clvm.eval_f, body_program, []) except clvm.EvalError.EvalError: breakpoint() raise ConsensusError(Err.INVALID_BLOCK_SOLUTION, body_program) npc_list = [] for name_solution in sexp.as_iter(): _ = name_solution.as_python() if len(_) != 2: raise ConsensusError(Err.INVALID_COIN_SOLUTION, name_solution) if not isinstance(_[0], bytes) or len(_[0]) != 32: raise ConsensusError(Err.INVALID_COIN_SOLUTION, name_solution) if not isinstance(_[1], list) or len(_[1]) != 2: raise ConsensusError(Err.INVALID_COIN_SOLUTION, name_solution) puzzle_solution_program = name_solution.rest().first() puzzle_program = puzzle_solution_program.first() puzzle_hash = ProgramHash(Program(puzzle_program)) npc_list.append((puzzle_hash, puzzle_solution_program)) return npc_list
def as_make_puzzle(self, as_pubkey_sender, as_pubkey_receiver, as_amount, as_timelock_block, as_secret_hash): as_pubkey_sender_cl = f"0x{as_pubkey_sender.hex()}" as_pubkey_receiver_cl = f"0x{as_pubkey_receiver.hex()}" as_payout_puzzlehash_receiver = ProgramHash( puzzle_for_pk(as_pubkey_receiver)) as_payout_puzzlehash_sender = ProgramHash( puzzle_for_pk(as_pubkey_sender)) payout_receiver = f"(c (q 0x{ConditionOpcode.CREATE_COIN.hex()}) (c (q 0x{as_payout_puzzlehash_receiver.hex()}) (c (q {as_amount}) (q ()))))" payout_sender = f"(c (q 0x{ConditionOpcode.CREATE_COIN.hex()}) (c (q 0x{as_payout_puzzlehash_sender.hex()}) (c (q {as_amount}) (q ()))))" aggsig_receiver = f"(c (q 0x{ConditionOpcode.AGG_SIG.hex()}) (c (q {as_pubkey_receiver_cl}) (c (sha256tree (a)) (q ()))))" aggsig_sender = f"(c (q 0x{ConditionOpcode.AGG_SIG.hex()}) (c (q {as_pubkey_sender_cl}) (c (sha256tree (a)) (q ()))))" receiver_puz = ( f"((c (i (= (sha256 (f (r (a)))) (q {as_secret_hash})) (q (c " + aggsig_receiver + " (c " + payout_receiver + " (q ())))) (q (x (q 'invalid secret')))) (a))) ) ") timelock = f"(c (q 0x{ConditionOpcode.ASSERT_BLOCK_INDEX_EXCEEDS.hex()}) (c (q {as_timelock_block}) (q ()))) " sender_puz = "(c " + aggsig_sender + " (c " + timelock + " (c " + payout_sender + " (q ()))))" as_puz_sender = "((c (i (= (f (a)) (q 77777)) (q " + sender_puz + ") (q (x (q 'not a valid option'))) ) (a)))" as_puz = "((c (i (= (f (a)) (q 33333)) (q " + receiver_puz + " (q " + as_puz_sender + ")) (a)))" return Program(binutils.assemble(as_puz))
class Wallet: seed = b'seed' next_address = 0 pubkey_num_lookup = {} puzzle_generator = f"(c (q 5) (c (c (q 5) (c (q (q 0x{hexlify(ConditionOpcode.AGG_SIG).decode('ascii')})) (c (c (q 5) (c (c (q 1) (c (f (a)) (q ()))) (q ((c (sha256 (wrap (f (a)))) (q ())))))) (q ())))) (q (((c (f (a)) (f (r (a)))))))))" puzzle_generator_id = str( ProgramHash(Program(binutils.assemble(puzzle_generator)))) def __init__(self): self.current_balance = 0 self.my_utxos = set() self.seed = urandom(1024) self.extended_secret_key = ExtendedPrivateKey.from_seed(self.seed) # self.contacts = {} # {'name': (puzzlegenerator, last, extradata)} self.generator_lookups = {} # {generator_hash: generator} self.name = "MyChiaWallet" self.generator_lookups[ self.puzzle_generator_id] = self.puzzle_generator self.temp_utxos = set() self.temp_balance = 0 self.all_additions = {} self.all_deletions = {} def get_next_public_key(self): pubkey = self.extended_secret_key.public_child( self.next_address).get_public_key() self.pubkey_num_lookup[pubkey.serialize()] = self.next_address self.next_address = self.next_address + 1 return pubkey # def add_contact(self, name, puzzlegenerator, last, extradata): # if name in self.contacts: # return None # else: # self.contacts[name] = [puzzlegenerator, last, extradata] def set_name(self, name): self.name = name def can_generate_puzzle_hash(self, hash): return any( map( lambda child: hash == ProgramHash( puzzle_for_pk( self.extended_secret_key.public_child(child). get_public_key().serialize())), reversed(range(self.next_address)))) def get_keys(self, hash): for child in range(self.next_address): pubkey = self.extended_secret_key.public_child( child).get_public_key() if hash == ProgramHash(self.puzzle_for_pk(pubkey.serialize())): return (pubkey, self.extended_secret_key.private_child( child).get_private_key()) def notify(self, additions, deletions): for coin in additions: if coin.name() in self.all_additions: continue self.all_additions[coin.name()] = coin if self.can_generate_puzzle_hash(coin.puzzle_hash): self.current_balance += coin.amount self.my_utxos.add(coin) for coin in deletions: if coin.name() in self.all_deletions: continue self.all_deletions[coin.name()] = coin if coin in self.my_utxos: self.my_utxos.remove(coin) self.current_balance -= coin.amount self.temp_utxos = self.my_utxos.copy() self.temp_balance = self.current_balance def select_coins(self, amount): if amount > self.temp_balance: return None used_utxos = set() while sum(map(lambda coin: coin.amount, used_utxos)) < amount: used_utxos.add(self.temp_utxos.pop()) return used_utxos def puzzle_for_pk(self, pubkey): args = f"({pubkey_format(pubkey)})" puzzle = Program( clvm.eval_f(clvm.eval_f, binutils.assemble(self.puzzle_generator), binutils.assemble(args))) return puzzle def get_new_puzzle(self): pubkey = self.get_next_public_key().serialize() puzzle = puzzle_for_pk(pubkey) return puzzle def get_new_puzzlehash(self): puzzle = self.get_new_puzzle() puzzlehash = ProgramHash(puzzle) return puzzlehash def sign(self, value, pubkey): privatekey = self.extended_secret_key.private_child( self.pubkey_num_lookup[pubkey]).get_private_key() blskey = BLSPrivateKey(privatekey) return blskey.sign(value) def generate_unsigned_transaction(self, amount, newpuzzlehash): if self.temp_balance < amount: return None # TODO: Should we throw a proper error here, or just return None? utxos = self.select_coins(amount) spends = [] output_id = None spend_value = sum([coin.amount for coin in utxos]) change = spend_value - amount for coin in utxos: puzzle_hash = coin.puzzle_hash pubkey, secretkey = self.get_keys(puzzle_hash) puzzle = self.puzzle_for_pk(pubkey.serialize()) if output_id is None: primaries = [{'puzzlehash': newpuzzlehash, 'amount': amount}] if change > 0: changepuzzlehash = self.get_new_puzzlehash() primaries.append({ 'puzzlehash': changepuzzlehash, 'amount': change }) # add change coin into temp_utxo set self.temp_utxos.add(Coin(coin, changepuzzlehash, change)) solution = make_solution(primaries=primaries) output_id = sha256(coin.name() + newpuzzlehash) else: solution = make_solution(consumed=[coin.name()]) spends.append((puzzle, CoinSolution(coin, solution))) self.temp_balance -= amount return spends def sign_transaction(self, spends: (Program, [CoinSolution])): sigs = [] for puzzle, solution in spends: pubkey, secretkey = self.get_keys(solution.coin.puzzle_hash) secretkey = BLSPrivateKey(secretkey) code_ = [puzzle, [solution.solution, []]] sexp = clvm.to_sexp_f(code_) conditions_dict = conditions_by_opcode( conditions_for_solution(sexp)) for _ in hash_key_pairs_for_conditions_dict(conditions_dict): signature = secretkey.sign(_.message_hash) sigs.append(signature) aggsig = BLSSignature.aggregate(sigs) solution_list = CoinSolutionList([ CoinSolution( coin_solution.coin, clvm.to_sexp_f([puzzle, [coin_solution.solution, []]])) for (puzzle, coin_solution) in spends ]) spend_bundle = SpendBundle(solution_list, aggsig) return spend_bundle def generate_signed_transaction(self, amount, newpuzzlehash): transaction = self.generate_unsigned_transaction(amount, newpuzzlehash) if transaction is None: return None # TODO: Should we throw a proper error here, or just return None? return self.sign_transaction(transaction)