def open_backup_file(file_path, private_key): backup_file_text = file_path.read_text() backup_file_json = json.loads(backup_file_text) meta_data = backup_file_json["meta_data"] meta_data_bytes = json.dumps(meta_data).encode() sig = backup_file_json["signature"] backup_pk = master_sk_to_backup_sk(private_key) my_pubkey = backup_pk.get_g1() key_base_64 = base64.b64encode(bytes(backup_pk)) f = Fernet(key_base_64) encrypted_data = backup_file_json["data"].encode() msg = std_hash(encrypted_data) + std_hash(meta_data_bytes) signature = SignatureMPL.from_bytes(hexstr_to_bytes(sig)) pubkey = PublicKeyMPL.from_bytes(hexstr_to_bytes(meta_data["pubkey"])) sig_match_my = AugSchemeMPL.verify(my_pubkey, msg, signature) sig_match_backup = AugSchemeMPL.verify(pubkey, msg, signature) assert sig_match_my is True assert sig_match_backup is True data_bytes = f.decrypt(encrypted_data) data_text = data_bytes.decode() data_json = json.loads(data_text) unencrypted = {} unencrypted["data"] = data_json unencrypted["meta_data"] = meta_data return unencrypted
async def sign_coin_solutions( coin_solutions: List[CoinSolution], secret_key_for_public_key_f: Callable[[bytes], Optional[PrivateKey]], ) -> SpendBundle: signatures = [] pk_list = [] msg_list = [] for coin_solution in coin_solutions: # Get AGG_SIG conditions err, conditions_dict, cost = conditions_dict_for_solution( coin_solution.puzzle_reveal, 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.name())): pk_list.append(_) msg_list.append(msg) 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) assert AugSchemeMPL.verify(_, msg, signature) signatures.append(signature) # Aggregate signatures aggsig = AugSchemeMPL.aggregate(signatures) assert AugSchemeMPL.aggregate_verify(pk_list, msg_list, aggsig) return SpendBundle(coin_solutions, aggsig)
async def get_farmer(self, request_obj) -> web.Response: # TODO(pool): add rate limiting launcher_id = hexstr_to_bytes(request_obj.rel_url.query["launcher_id"]) authentication_token = uint64(request_obj.rel_url.query["authentication_token"]) authentication_token_error: Optional[web.Response] = check_authentication_token( launcher_id, authentication_token, self.pool.authentication_token_timeout ) if authentication_token_error is not None: return authentication_token_error farmer_record: Optional[FarmerRecord] = await self.pool.store.get_farmer_record(launcher_id) if farmer_record is None: return error_response( PoolErrorCode.FARMER_NOT_KNOWN, f"Farmer with launcher_id {launcher_id.hex()} unknown." ) # Validate provided signature signature: G2Element = G2Element.from_bytes(hexstr_to_bytes(request_obj.rel_url.query["signature"])) message = std_hash(launcher_id + bytes(authentication_token)) if not AugSchemeMPL.verify(farmer_record.authentication_public_key, message, signature): return error_response( PoolErrorCode.INVALID_SIGNATURE, f"Failed to verify signature {signature} for launcher_id {launcher_id.hex()}.", ) response: GetFarmerResponse = GetFarmerResponse( farmer_record.authentication_public_key, farmer_record.payout_instructions, farmer_record.difficulty, farmer_record.points, ) self.pool.log.info(f"get_farmer response {response.to_json_dict()}, " f"launcher_id: {launcher_id.hex()}") return obj_to_response(response)
async def get_login(self, request_obj) -> web.Response: # TODO(pool): add rate limiting launcher_id = request_obj.rel_url.query["launcher_id"] authentication_token = request_obj.rel_url.query["authentication_token"] authentication_token_error = check_authentication_token( launcher_id, authentication_token, self.pool.authentication_token_timeout ) if authentication_token_error is not None: return authentication_token_error farmer_record: Optional[FarmerRecord] = await self.pool.store.get_farmer_record(launcher_id) if farmer_record is None: return error_response(PoolErrorCode.FARMER_NOT_KNOWN, f"Farmer with launcher_id {launcher_id} unknown.") # Validate provided signature signature = request_obj.rel_url.query["signature"] message = std_hash(launcher_id + bytes(authentication_token)) if not AugSchemeMPL.verify(farmer_record.authentication_public_key, message, signature): return error_response( PoolErrorCode.INVALID_SIGNATURE, f"Failed to verify signature {signature} for launcher_id {launcher_id}.", ) self.pool.log.info(f"Login successful for launcher_id: {launcher_id}") # TODO(pool) Do what ever you like with the successful login return obj_to_response({"login_data", "Put server side login information here?"})
async def respond_signature(self, response: harvester_protocol.RespondSignature): """ Receives a signature on a block header hash, which is required for submitting a block to the blockchain. """ header_hash = response.message proof_of_space: bytes32 = self.header_hash_to_pos[header_hash] validates: bool = False for sk in self._get_private_keys(): pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk) assert agg_pk == proof_of_space.plot_public_key farmer_share = AugSchemeMPL.sign(sk, header_hash, agg_pk) agg_sig = AugSchemeMPL.aggregate( [response.message_signature, farmer_share] ) validates = AugSchemeMPL.verify(agg_pk, header_hash, agg_sig) if validates: break assert validates pos_hash: bytes32 = proof_of_space.get_hash() request = farmer_protocol.HeaderSignature(pos_hash, header_hash, agg_sig) yield OutboundMessage( NodeType.FULL_NODE, Message("header_signature", request), Delivery.BROADCAST )
async def sign_coin_spends( coin_spends: List[CoinSpend], secret_key_for_public_key_f: Any, # Potentially awaitable function from G1Element => Optional[PrivateKey] additional_data: bytes, max_cost: int, ) -> SpendBundle: """ Sign_coin_spends runs the puzzle code with the given argument and searches the result for an AGG_SIG_ME condition, which it attempts to sign by requesting a matching PrivateKey corresponding with the given G1Element (public key) specified in the resulting condition output. It's important to note that as mentioned in the documentation about the standard spend that the public key presented to the secret_key_for_public_key_f function provided to sign_coin_spends must be prepared to do the key derivations required by the coin types it's allowed to spend (at least the derivation of the standard spend as done by calculate_synthetic_secret_key with DEFAULT_PUZZLE_HASH). If a coin performed a different key derivation, the pk presented to this function would be similarly alien, and would need to be tried against the first stage derived keys (those returned by master_sk_to_wallet_sk from the ['sk'] member of wallet rpc's get_private_key method). """ signatures: List[blspy.G2Element] = [] pk_list: List[blspy.G1Element] = [] msg_list: List[bytes] = [] for coin_spend in coin_spends: # Get AGG_SIG conditions err, conditions_dict, cost = conditions_dict_for_solution( coin_spend.puzzle_reveal, coin_spend.solution, max_cost) 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 pk_bytes, msg in pkm_pairs_for_conditions_dict( conditions_dict, coin_spend.coin.name(), additional_data): pk = blspy.G1Element.from_bytes(pk_bytes) pk_list.append(pk) msg_list.append(msg) if inspect.iscoroutinefunction(secret_key_for_public_key_f): secret_key = await secret_key_for_public_key_f(pk) else: secret_key = secret_key_for_public_key_f(pk) if secret_key is None: e_msg = f"no secret key for {pk}" raise ValueError(e_msg) assert bytes(secret_key.get_g1()) == bytes(pk) signature = AugSchemeMPL.sign(secret_key, msg) assert AugSchemeMPL.verify(pk, msg, signature) signatures.append(signature) # Aggregate signatures aggsig = AugSchemeMPL.aggregate(signatures) assert AugSchemeMPL.aggregate_verify(pk_list, msg_list, aggsig) return SpendBundle(coin_spends, aggsig)
def validate_alert(text: str, pubkey: str) -> bool: json_obj = json.loads(text) data = json_obj["data"] message = bytes(data, "UTF-8") signature = json_obj["signature"] signature = SignatureMPL.from_bytes(hexstr_to_bytes(signature)) pubkey_bls = PublicKeyMPL.from_bytes(hexstr_to_bytes(pubkey)) sig_match_my = AugSchemeMPL.verify(pubkey_bls, message, signature) return sig_match_my
async def get_login(self, request_obj) -> web.Response: # TODO(pool): add rate limiting launcher_id: bytes32 = hexstr_to_bytes( request_obj.rel_url.query["launcher_id"]) authentication_token: uint64 = uint64( request_obj.rel_url.query["authentication_token"]) authentication_token_error = check_authentication_token( launcher_id, authentication_token, self.pool.authentication_token_timeout) if authentication_token_error is not None: return authentication_token_error farmer_record: Optional[ FarmerRecord] = await self.pool.store.get_farmer_record(launcher_id ) if farmer_record is None: return error_response( PoolErrorCode.FARMER_NOT_KNOWN, f"Farmer with launcher_id {launcher_id.hex()} unknown.") # Validate provided signature signature: G2Element = G2Element.from_bytes( hexstr_to_bytes(request_obj.rel_url.query["signature"])) message: bytes32 = std_hash( AuthenticationPayload("get_login", launcher_id, self.pool.default_target_puzzle_hash, authentication_token)) if not AugSchemeMPL.verify(farmer_record.authentication_public_key, message, signature): return error_response( PoolErrorCode.INVALID_SIGNATURE, f"Failed to verify signature {signature} for launcher_id {launcher_id.hex()}.", ) self.pool.log.info( f"Login successful for launcher_id: {launcher_id.hex()}") record: Optional[ FarmerRecord] = await self.pool.store.get_farmer_record(launcher_id ) response = {} if record is not None: response["farmer_record"] = record recent_partials = await self.pool.store.get_recent_partials( launcher_id, 20) response["recent_partials"] = recent_partials # TODO(pool) Do what ever you like with the successful login return obj_to_response(response)
async def validate_block_body(self, block: FullBlock) -> Optional[Err]: """ Validates the transactions and body of the block. Returns None if everything validates correctly, or an Err if something does not validate. """ # 6. The compact block filter must be correct, according to the body (BIP158) if std_hash(block.transactions_filter) != block.header.data.filter_hash: return Err.INVALID_TRANSACTIONS_FILTER_HASH fee_base = calculate_base_fee(block.height) # target reward_fee = 1/8 coinbase reward + tx fees if block.transactions_generator is not None: # 14. Make sure transactions generator hash is valid (or all 0 if not present) if ( block.transactions_generator.get_tree_hash() != block.header.data.generator_hash ): return Err.INVALID_TRANSACTIONS_GENERATOR_HASH # 15. If not genesis, the transactions must be valid and fee must be valid # Verifies that fee_base + TX fees = fee_coin.amount err = await self._validate_transactions(block, fee_base) if err is not None: return err else: # Make sure transactions generator hash is valid (or all 0 if not present) if block.header.data.generator_hash != bytes32(bytes([0] * 32)): return Err.INVALID_TRANSACTIONS_GENERATOR_HASH # 16. If genesis, the fee must be the base fee, agg_sig must be None, and merkle roots must be valid if fee_base != block.header.data.total_transaction_fees: return Err.INVALID_BLOCK_FEE_AMOUNT root_error = self._validate_merkle_root(block) if root_error: return root_error # 17. Verify the pool signature even if there are no transactions pool_target_m = bytes(block.header.data.pool_target) validates = AugSchemeMPL.verify( block.proof_of_space.pool_public_key, pool_target_m, block.header.data.aggregated_signature, ) if not validates: return Err.BAD_AGGREGATE_SIGNATURE return None
def verify(args): if args.message is None: print("Please specify the message argument -d") quit() if args.public_key is None: print("Please specify the public_key argument -p") quit() if args.signature is None: print("Please specify the signature argument -s") quit() assert args.message is not None assert args.public_key is not None assert args.signature is not None message = bytes(args.message, "utf-8") public_key = G1Element.from_bytes(bytes.fromhex(args.public_key)) signature = G2Element.from_bytes(bytes.fromhex(args.signature)) print(AugSchemeMPL.verify(public_key, message, signature))
async def sign_coin_solutions( coin_solutions: List[CoinSolution], secret_key_for_public_key_f: Any, # Potentially awaitable function from G1Element => Optional[PrivateKey] additional_data: bytes, max_cost: int, ) -> SpendBundle: signatures: List[blspy.G2Element] = [] pk_list: List[blspy.G1Element] = [] msg_list: List[bytes] = [] for coin_solution in coin_solutions: # Get AGG_SIG conditions err, conditions_dict, cost = conditions_dict_for_solution( coin_solution.puzzle_reveal, coin_solution.solution, max_cost) 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 pk, msg in pkm_pairs_for_conditions_dict( conditions_dict, bytes(coin_solution.coin.name()), additional_data): pk_list.append(pk) msg_list.append(msg) if inspect.iscoroutinefunction(secret_key_for_public_key_f): secret_key = await secret_key_for_public_key_f(pk) else: secret_key = secret_key_for_public_key_f(pk) if secret_key is None: e_msg = f"no secret key for {pk}" raise ValueError(e_msg) assert bytes(secret_key.get_g1()) == bytes(pk) signature = AugSchemeMPL.sign(secret_key, msg) assert AugSchemeMPL.verify(pk, msg, signature) signatures.append(signature) # Aggregate signatures aggsig = AugSchemeMPL.aggregate(signatures) assert AugSchemeMPL.aggregate_verify(pk_list, msg_list, aggsig) return SpendBundle(coin_solutions, aggsig)
puzzle_end = time.time() puzzle_time = puzzle_end - puzzle_start print(f"Puzzle_time is: {puzzle_time}") print(f"Puzzle cost sum is: {clvm_cost}") private_key = master_sk_to_wallet_sk(secret_key, uint32(0)) public_key = private_key.get_g1() message = token_bytes() signature = AugSchemeMPL.sign(private_key, message) pk_message_pair = (public_key, message) # Run AggSig 1000 times agg_sig_start = time.time() agg_sig_cost = 0 for i in range(0, 1000): valid = AugSchemeMPL.verify(public_key, message, signature) assert valid agg_sig_cost += 20 agg_sig_end = time.time() agg_sig_time = agg_sig_end - agg_sig_start print(f"Aggsig Cost: {agg_sig_cost}") print(f"Aggsig time is: {agg_sig_time}") # clvm_should_cost = agg_sig_cost * puzzle_time / agg_sig_time clvm_should_cost = (agg_sig_cost * puzzle_time) / agg_sig_time print(f"Puzzle should cost: {clvm_should_cost}") constant = clvm_should_cost / clvm_cost format = float_to_str(constant) print(f"Constant factor: {format}") print(f"CLVM RATIO MULTIPLIER: {1/constant}")
def test_readme(): seed: bytes = bytes([ 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 19, 18, 12, 89, 6, 220, 18, 102, 58, 209, 82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22, ]) sk: PrivateKey = AugSchemeMPL.key_gen(seed) pk: G1Element = sk.get_g1() message: bytes = bytes([1, 2, 3, 4, 5]) signature: G2Element = AugSchemeMPL.sign(sk, message) ok: bool = AugSchemeMPL.verify(pk, message, signature) assert ok sk_bytes: bytes = bytes(sk) # 32 bytes pk_bytes: bytes = bytes(pk) # 48 bytes signature_bytes: bytes = bytes(signature) # 96 bytes print(sk_bytes.hex(), pk_bytes.hex(), signature_bytes.hex()) sk = PrivateKey.from_bytes(sk_bytes) pk = G1Element.from_bytes(pk_bytes) signature = G2Element.from_bytes(signature_bytes) seed = bytes([1]) + seed[1:] sk1: PrivateKey = AugSchemeMPL.key_gen(seed) seed = bytes([2]) + seed[1:] sk2: PrivateKey = AugSchemeMPL.key_gen(seed) message2: bytes = bytes([1, 2, 3, 4, 5, 6, 7]) pk1: G1Element = sk1.get_g1() sig1: G2Element = AugSchemeMPL.sign(sk1, message) pk2: G1Element = sk2.get_g1() sig2: G2Element = AugSchemeMPL.sign(sk2, message2) agg_sig: G2Element = AugSchemeMPL.aggregate([sig1, sig2]) ok = AugSchemeMPL.aggregate_verify([pk1, pk2], [message, message2], agg_sig) assert ok seed = bytes([3]) + seed[1:] sk3: PrivateKey = AugSchemeMPL.key_gen(seed) pk3: G1Element = sk3.get_g1() message3: bytes = bytes([100, 2, 254, 88, 90, 45, 23]) sig3: G2Element = AugSchemeMPL.sign(sk3, message3) agg_sig_final: G2Element = AugSchemeMPL.aggregate([agg_sig, sig3]) ok = AugSchemeMPL.aggregate_verify([pk1, pk2, pk3], [message, message2, message3], agg_sig_final) assert ok pop_sig1: G2Element = PopSchemeMPL.sign(sk1, message) pop_sig2: G2Element = PopSchemeMPL.sign(sk2, message) pop_sig3: G2Element = PopSchemeMPL.sign(sk3, message) pop1: G2Element = PopSchemeMPL.pop_prove(sk1) pop2: G2Element = PopSchemeMPL.pop_prove(sk2) pop3: G2Element = PopSchemeMPL.pop_prove(sk3) ok = PopSchemeMPL.pop_verify(pk1, pop1) assert ok ok = PopSchemeMPL.pop_verify(pk2, pop2) assert ok ok = PopSchemeMPL.pop_verify(pk3, pop3) assert ok pop_sig_agg: G2Element = PopSchemeMPL.aggregate( [pop_sig1, pop_sig2, pop_sig3]) ok = PopSchemeMPL.fast_aggregate_verify([pk1, pk2, pk3], message, pop_sig_agg) assert ok pop_agg_pk: G1Element = pk1 + pk2 + pk3 ok = PopSchemeMPL.verify(pop_agg_pk, message, pop_sig_agg) assert ok pop_agg_sk: PrivateKey = PrivateKey.aggregate([sk1, sk2, sk3]) ok = PopSchemeMPL.sign(pop_agg_sk, message) == pop_sig_agg assert ok master_sk: PrivateKey = AugSchemeMPL.key_gen(seed) child: PrivateKey = AugSchemeMPL.derive_child_sk(master_sk, 152) grandchild: PrivateKey = AugSchemeMPL.derive_child_sk(child, 952) master_pk: G1Element = master_sk.get_g1() child_u: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened( master_sk, 22) grandchild_u: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened( child_u, 0) child_u_pk: G1Element = AugSchemeMPL.derive_child_pk_unhardened( master_pk, 22) grandchild_u_pk: G1Element = AugSchemeMPL.derive_child_pk_unhardened( child_u_pk, 0) ok = grandchild_u_pk == grandchild_u.get_g1() assert ok
async def respond_signatures( self, response: harvester_protocol.RespondSignatures): """ There are two cases: receiving signatures for sps, or receiving signatures for the block. """ if response.sp_hash not in self.farmer.sps: self.farmer.log.warning( f"Do not have challenge hash {response.challenge_hash}") return is_sp_signatures: bool = False sps = self.farmer.sps[response.sp_hash] signage_point_index = sps[0].signage_point_index found_sp_hash_debug = False for sp_candidate in sps: if response.sp_hash == response.message_signatures[0][0]: found_sp_hash_debug = True if sp_candidate.reward_chain_sp == response.message_signatures[ 1][0]: is_sp_signatures = True if found_sp_hash_debug: assert is_sp_signatures pospace = None for plot_identifier, candidate_pospace in self.farmer.proofs_of_space[ response.sp_hash]: if plot_identifier == response.plot_identifier: pospace = candidate_pospace assert pospace is not None computed_quality_string = pospace.verify_and_get_quality_string( self.farmer.constants, response.challenge_hash, response.sp_hash) if computed_quality_string is None: self.farmer.log.warning(f"Have invalid PoSpace {pospace}") return if is_sp_signatures: ( challenge_chain_sp, challenge_chain_sp_harv_sig, ) = response.message_signatures[0] reward_chain_sp, reward_chain_sp_harv_sig = response.message_signatures[ 1] for sk in self.farmer.get_private_keys(): pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key( response.local_pk, pk) assert agg_pk == pospace.plot_public_key farmer_share_cc_sp = AugSchemeMPL.sign( sk, challenge_chain_sp, agg_pk) agg_sig_cc_sp = AugSchemeMPL.aggregate( [challenge_chain_sp_harv_sig, farmer_share_cc_sp]) assert AugSchemeMPL.verify(agg_pk, challenge_chain_sp, agg_sig_cc_sp) # This means it passes the sp filter farmer_share_rc_sp = AugSchemeMPL.sign( sk, reward_chain_sp, agg_pk) agg_sig_rc_sp = AugSchemeMPL.aggregate( [reward_chain_sp_harv_sig, farmer_share_rc_sp]) assert AugSchemeMPL.verify(agg_pk, reward_chain_sp, agg_sig_rc_sp) if pospace.pool_public_key is not None: assert pospace.pool_contract_puzzle_hash is None pool_pk = bytes(pospace.pool_public_key) if pool_pk not in self.farmer.pool_sks_map: self.farmer.log.error( f"Don't have the private key for the pool key used by harvester: {pool_pk.hex()}" ) return pool_target: Optional[PoolTarget] = PoolTarget( self.farmer.pool_target, uint32(0)) assert pool_target is not None pool_target_signature: Optional[ G2Element] = AugSchemeMPL.sign( self.farmer.pool_sks_map[pool_pk], bytes(pool_target)) else: assert pospace.pool_contract_puzzle_hash is not None pool_target = None pool_target_signature = None request = farmer_protocol.DeclareProofOfSpace( response.challenge_hash, challenge_chain_sp, signage_point_index, reward_chain_sp, pospace, agg_sig_cc_sp, agg_sig_rc_sp, self.farmer.farmer_target, pool_target, pool_target_signature, ) self.farmer.state_changed("proof", { "proof": request, "passed_filter": True }) msg = make_msg(ProtocolMessageTypes.declare_proof_of_space, request) await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE) return else: # This is a response with block signatures for sk in self.farmer.get_private_keys(): ( foliage_block_data_hash, foliage_sig_harvester, ) = response.message_signatures[0] ( foliage_transaction_block_hash, foliage_transaction_block_sig_harvester, ) = response.message_signatures[1] pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key( response.local_pk, pk) assert agg_pk == pospace.plot_public_key foliage_sig_farmer = AugSchemeMPL.sign( sk, foliage_block_data_hash, agg_pk) foliage_transaction_block_sig_farmer = AugSchemeMPL.sign( sk, foliage_transaction_block_hash, agg_pk) foliage_agg_sig = AugSchemeMPL.aggregate( [foliage_sig_harvester, foliage_sig_farmer]) foliage_block_agg_sig = AugSchemeMPL.aggregate([ foliage_transaction_block_sig_harvester, foliage_transaction_block_sig_farmer ]) assert AugSchemeMPL.verify(agg_pk, foliage_block_data_hash, foliage_agg_sig) assert AugSchemeMPL.verify(agg_pk, foliage_transaction_block_hash, foliage_block_agg_sig) request_to_nodes = farmer_protocol.SignedValues( computed_quality_string, foliage_agg_sig, foliage_block_agg_sig, ) msg = make_msg(ProtocolMessageTypes.signed_values, request_to_nodes) await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE)
async def update_farmer(self, request: PutFarmerRequest) -> Dict: farmer_record: Optional[ FarmerRecord] = await self.store.get_farmer_record( request.payload.launcher_id) if farmer_record is None: return error_dict( PoolErrorCode.FARMER_NOT_KNOWN, f"Farmer with launcher_id {request.payload.launcher_id} not known." ) singleton_state_tuple: Optional[ Tuple[CoinSolution, PoolState]] = await self.get_and_validate_singleton_state( request.payload.launcher_id) last_spend, last_state = singleton_state_tuple if singleton_state_tuple is None: return error_dict(PoolErrorCode.INVALID_SINGLETON, f"Invalid singleton, or not a pool member") if not AugSchemeMPL.verify(last_state.owner_pubkey, request.payload.get_hash(), request.signature): return error_dict(PoolErrorCode.INVALID_SIGNATURE, f"Invalid signature") farmer_dict = farmer_record.to_json_dict() response_dict = {} if request.payload.authentication_public_key is not None: is_new_value = farmer_record.authentication_public_key != request.payload.authentication_public_key response_dict["authentication_public_key"] = is_new_value if is_new_value: farmer_dict[ "authentication_public_key"] = request.payload.authentication_public_key if request.payload.payout_instructions is not None: is_new_value = ( farmer_record.payout_instructions != request.payload.payout_instructions and request.payload.payout_instructions is not None and len(hexstr_to_bytes( request.payload.payout_instructions)) == 32) response_dict["payout_instructions"] = is_new_value if is_new_value: farmer_dict[ "payout_instructions"] = request.payload.payout_instructions if request.payload.suggested_difficulty is not None: is_new_value = ( farmer_record.suggested_difficulty != request.payload.suggested_difficulty and request.payload.suggested_difficulty is not None and request.payload.suggested_difficulty >= self.min_difficulty) response_dict["suggested_difficulty"] = is_new_value if is_new_value: farmer_dict[ "suggested_difficulty"] = request.payload.suggested_difficulty self.log.info(f"Updated farmer: {response_dict}") await self.store.add_farmer_record( FarmerRecord.from_json_dict(farmer_dict)) return PutFarmerResponse.from_json_dict(response_dict).from_json_dict()
async def add_farmer(self, request: PostFarmerRequest) -> Dict: async with self.store.lock: farmer_record: Optional[ FarmerRecord] = await self.store.get_farmer_record( request.payload.launcher_id) if farmer_record is not None: return error_dict( PoolErrorCode.FARMER_ALREADY_KNOWN, f"Farmer with launcher_id {request.payload.launcher_id} already known.", ) singleton_state_tuple: Optional[Tuple[ CoinSolution, PoolState]] = await self.get_and_validate_singleton_state( request.payload.launcher_id) if singleton_state_tuple is None: return error_dict(PoolErrorCode.INVALID_SINGLETON, f"Invalid singleton, or not a pool member") last_spend, last_state = singleton_state_tuple if (request.payload.suggested_difficulty is None or request.payload.suggested_difficulty < self.min_difficulty): difficulty: uint64 = self.default_difficulty else: difficulty = request.payload.suggested_difficulty if len(hexstr_to_bytes(request.payload.payout_instructions)) != 32: return error_dict( PoolErrorCode.INVALID_PAYOUT_INSTRUCTIONS, f"Payout instructions must be an xch address for this pool.", ) if not AugSchemeMPL.verify(last_state.owner_pubkey, request.payload.get_hash(), request.signature): return error_dict(PoolErrorCode.INVALID_SIGNATURE, f"Invalid signature") launcher_coin: Optional[ CoinRecord] = await self.node_rpc_client.get_coin_record_by_name( request.payload.launcher_id) assert launcher_coin is not None and launcher_coin.spent launcher_solution: Optional[CoinSolution] = await get_coin_spend( self.node_rpc_client, launcher_coin) delay_time, delay_puzzle_hash = get_delayed_puz_info_from_launcher_spend( launcher_solution) if delay_time < 3600: return error_dict( PoolErrorCode.DELAY_TIME_TOO_SHORT, f"Delay time too short, must be at least 1 hour") p2_singleton_puzzle_hash = launcher_id_to_p2_puzzle_hash( request.payload.launcher_id, delay_time, delay_puzzle_hash) farmer_record = FarmerRecord( request.payload.launcher_id, p2_singleton_puzzle_hash, delay_time, delay_puzzle_hash, request.payload.authentication_public_key, last_spend, last_state, uint64(0), difficulty, request.payload.payout_instructions, True, ) self.scan_p2_singleton_puzzle_hashes.add(p2_singleton_puzzle_hash) await self.store.add_farmer_record(farmer_record) return PostFarmerResponse(self.welcome_message).to_json_dict()
def verify(message: str, public_key: str, signature: str): messageBytes = bytes(message, "utf-8") public_key = G1Element.from_bytes(bytes.fromhex(public_key)) signature = G2Element.from_bytes(bytes.fromhex(signature)) print(AugSchemeMPL.verify(public_key, messageBytes, signature))
async def new_proof_of_space( self, new_proof_of_space: harvester_protocol.NewProofOfSpace, peer: ws.WSChiaConnection ): """ This is a response from the harvester, for a NewChallenge. Here we check if the proof of space is sufficiently good, and if so, we ask for the whole proof. """ if new_proof_of_space.sp_hash not in self.farmer.number_of_responses: self.farmer.number_of_responses[new_proof_of_space.sp_hash] = 0 self.farmer.cache_add_time[new_proof_of_space.sp_hash] = uint64(int(time.time())) max_pos_per_sp = 5 if self.farmer.number_of_responses[new_proof_of_space.sp_hash] > max_pos_per_sp: # This will likely never happen for any farmer with less than 10% of global space # It's meant to make testnets more stable self.farmer.log.info( f"Surpassed {max_pos_per_sp} PoSpace for one SP, no longer submitting PoSpace for signage point " f"{new_proof_of_space.sp_hash}" ) return None if new_proof_of_space.sp_hash not in self.farmer.sps: self.farmer.log.warning( f"Received response for a signage point that we do not have {new_proof_of_space.sp_hash}" ) return None sps = self.farmer.sps[new_proof_of_space.sp_hash] for sp in sps: computed_quality_string = new_proof_of_space.proof.verify_and_get_quality_string( self.farmer.constants, new_proof_of_space.challenge_hash, new_proof_of_space.sp_hash, ) if computed_quality_string is None: self.farmer.log.error(f"Invalid proof of space {new_proof_of_space.proof}") return None self.farmer.number_of_responses[new_proof_of_space.sp_hash] += 1 required_iters: uint64 = calculate_iterations_quality( self.farmer.constants.DIFFICULTY_CONSTANT_FACTOR, computed_quality_string, new_proof_of_space.proof.size, sp.difficulty, new_proof_of_space.sp_hash, ) # If the iters are good enough to make a block, proceed with the block making flow if required_iters < calculate_sp_interval_iters(self.farmer.constants, sp.sub_slot_iters): # Proceed at getting the signatures for this PoSpace request = harvester_protocol.RequestSignatures( new_proof_of_space.plot_identifier, new_proof_of_space.challenge_hash, new_proof_of_space.sp_hash, [sp.challenge_chain_sp, sp.reward_chain_sp], ) if new_proof_of_space.sp_hash not in self.farmer.proofs_of_space: self.farmer.proofs_of_space[new_proof_of_space.sp_hash] = [] self.farmer.proofs_of_space[new_proof_of_space.sp_hash].append( ( new_proof_of_space.plot_identifier, new_proof_of_space.proof, ) ) self.farmer.cache_add_time[new_proof_of_space.sp_hash] = uint64(int(time.time())) self.farmer.quality_str_to_identifiers[computed_quality_string] = ( new_proof_of_space.plot_identifier, new_proof_of_space.challenge_hash, new_proof_of_space.sp_hash, peer.peer_node_id, ) self.farmer.cache_add_time[computed_quality_string] = uint64(int(time.time())) await peer.send_message(make_msg(ProtocolMessageTypes.request_signatures, request)) p2_singleton_puzzle_hash = new_proof_of_space.proof.pool_contract_puzzle_hash if p2_singleton_puzzle_hash is not None: # Otherwise, send the proof of space to the pool # When we win a block, we also send the partial to the pool if p2_singleton_puzzle_hash not in self.farmer.pool_state: self.farmer.log.info(f"Did not find pool info for {p2_singleton_puzzle_hash}") return pool_state_dict: Dict = self.farmer.pool_state[p2_singleton_puzzle_hash] pool_url = pool_state_dict["pool_config"].pool_url if pool_url == "": return if pool_state_dict["current_difficulty"] is None: self.farmer.log.warning( f"No pool specific difficulty has been set for {p2_singleton_puzzle_hash}, " f"check communication with the pool, skipping this partial to {pool_url}." ) return required_iters = calculate_iterations_quality( self.farmer.constants.DIFFICULTY_CONSTANT_FACTOR, computed_quality_string, new_proof_of_space.proof.size, pool_state_dict["current_difficulty"], new_proof_of_space.sp_hash, ) if required_iters >= calculate_sp_interval_iters( self.farmer.constants, self.farmer.constants.POOL_SUB_SLOT_ITERS ): self.farmer.log.info( f"Proof of space not good enough for pool {pool_url}: {pool_state_dict['current_difficulty']}" ) return authentication_token_timeout = pool_state_dict["authentication_token_timeout"] if authentication_token_timeout is None: self.farmer.log.warning( f"No pool specific authentication_token_timeout has been set for {p2_singleton_puzzle_hash}" f", check communication with the pool." ) return # Submit partial to pool is_eos = new_proof_of_space.signage_point_index == 0 payload = PostPartialPayload( pool_state_dict["pool_config"].launcher_id, get_current_authentication_token(authentication_token_timeout), new_proof_of_space.proof, new_proof_of_space.sp_hash, is_eos, peer.peer_node_id, ) # The plot key is 2/2 so we need the harvester's half of the signature m_to_sign = payload.get_hash() request = harvester_protocol.RequestSignatures( new_proof_of_space.plot_identifier, new_proof_of_space.challenge_hash, new_proof_of_space.sp_hash, [m_to_sign], ) response: Any = await peer.request_signatures(request) if not isinstance(response, harvester_protocol.RespondSignatures): self.farmer.log.error(f"Invalid response from harvester: {response}") return assert len(response.message_signatures) == 1 plot_signature: Optional[G2Element] = None for sk in self.farmer.get_private_keys(): pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk, True) assert agg_pk == new_proof_of_space.proof.plot_public_key sig_farmer = AugSchemeMPL.sign(sk, m_to_sign, agg_pk) taproot_sk: PrivateKey = ProofOfSpace.generate_taproot_sk(response.local_pk, pk) taproot_sig: G2Element = AugSchemeMPL.sign(taproot_sk, m_to_sign, agg_pk) plot_signature = AugSchemeMPL.aggregate( [sig_farmer, response.message_signatures[0][1], taproot_sig] ) assert AugSchemeMPL.verify(agg_pk, m_to_sign, plot_signature) authentication_pk = pool_state_dict["pool_config"].authentication_public_key if bytes(authentication_pk) is None: self.farmer.log.error(f"No authentication sk for {authentication_pk}") return authentication_sk: PrivateKey = self.farmer.authentication_keys[bytes(authentication_pk)] authentication_signature = AugSchemeMPL.sign(authentication_sk, m_to_sign) assert plot_signature is not None agg_sig: G2Element = AugSchemeMPL.aggregate([plot_signature, authentication_signature]) post_partial_request: PostPartialRequest = PostPartialRequest(payload, agg_sig) post_partial_body = json.dumps(post_partial_request.to_json_dict()) self.farmer.log.info( f"Submitting partial for {post_partial_request.payload.launcher_id.hex()} to {pool_url}" ) pool_state_dict["points_found_since_start"] += pool_state_dict["current_difficulty"] pool_state_dict["points_found_24h"].append((time.time(), pool_state_dict["current_difficulty"])) headers = { "content-type": "application/json;", } try: async with aiohttp.ClientSession() as session: async with session.post(f"{pool_url}/partial", data=post_partial_body, headers=headers) as resp: if resp.ok: pool_response: Dict = json.loads(await resp.text()) self.farmer.log.info(f"Pool response: {pool_response}") if "error_code" in pool_response: self.farmer.log.error( f"Error in pooling: " f"{pool_response['error_code'], pool_response['error_message']}" ) pool_state_dict["pool_errors_24h"].append(pool_response) if pool_response["error_code"] == PoolErrorCode.PROOF_NOT_GOOD_ENOUGH.value: self.farmer.log.error( "Partial not good enough, forcing pool farmer update to " "get our current difficulty." ) pool_state_dict["next_farmer_update"] = 0 await self.farmer.update_pool_state() else: new_difficulty = pool_response["new_difficulty"] pool_state_dict["points_acknowledged_since_start"] += new_difficulty pool_state_dict["points_acknowledged_24h"].append((time.time(), new_difficulty)) pool_state_dict["current_difficulty"] = new_difficulty else: self.farmer.log.error(f"Error sending partial to {pool_url}, {resp.status}") except Exception as e: self.farmer.log.error(f"Error connecting to pool: {e}") return return
def validate_unfinished_header_block( constants: ConsensusConstants, blocks: BlockchainInterface, header_block: UnfinishedHeaderBlock, check_filter: bool, expected_difficulty: uint64, expected_sub_slot_iters: uint64, skip_overflow_last_ss_validation: bool = False, skip_vdf_is_valid: bool = False, ) -> Tuple[Optional[uint64], Optional[ValidationError]]: """ Validates an unfinished header block. This is a block without the infusion VDFs (unfinished) and without transactions and transaction info (header). Returns (required_iters, error). This method is meant to validate only the unfinished part of the block. However, the finished_sub_slots refers to all sub-slots that were finishes from the previous block's infusion point, up to this blocks infusion point. Therefore, in the case where this is an overflow block, and the last sub-slot is not yet released, header_block.finished_sub_slots will be missing one sub-slot. In this case, skip_overflow_last_ss_validation must be set to True. This will skip validation of end of slots, sub-epochs, and lead to other small tweaks in validation. """ # 1. Check that the previous block exists in the blockchain, or that it is correct prev_b = blocks.try_block_record(header_block.prev_header_hash) genesis_block = prev_b is None if genesis_block and header_block.prev_header_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) overflow = is_overflow_block( constants, header_block.reward_chain_block.signage_point_index) if skip_overflow_last_ss_validation and overflow: if final_eos_is_already_included(header_block, blocks, expected_sub_slot_iters): skip_overflow_last_ss_validation = False finished_sub_slots_since_prev = len( header_block.finished_sub_slots) else: finished_sub_slots_since_prev = len( header_block.finished_sub_slots) + 1 else: finished_sub_slots_since_prev = len(header_block.finished_sub_slots) new_sub_slot: bool = finished_sub_slots_since_prev > 0 can_finish_se: bool = False can_finish_epoch: bool = False if genesis_block: height: uint32 = uint32(0) assert expected_difficulty == constants.DIFFICULTY_STARTING assert expected_sub_slot_iters == constants.SUB_SLOT_ITERS_STARTING else: assert prev_b is not None height = uint32(prev_b.height + 1) if prev_b.sub_epoch_summary_included is not None: can_finish_se, can_finish_epoch = False, False else: if new_sub_slot: can_finish_se, can_finish_epoch = can_finish_sub_and_full_epoch( constants, prev_b.height, prev_b.deficit, blocks, prev_b.prev_hash, False, ) else: can_finish_se = False can_finish_epoch = False # 2. Check finished slots that have been crossed since prev_b ses_hash: Optional[bytes32] = None if new_sub_slot and not skip_overflow_last_ss_validation: # Finished a slot(s) since previous block. The first sub-slot must have at least one block, and all # subsequent sub-slots must be empty for finished_sub_slot_n, sub_slot in enumerate( header_block.finished_sub_slots): # Start of slot challenge is fetched from SP challenge_hash: bytes32 = sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf.challenge if finished_sub_slot_n == 0: if genesis_block: # 2a. check sub-slot challenge hash for genesis block if challenge_hash != constants.GENESIS_CHALLENGE: return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) else: assert prev_b is not None curr: BlockRecord = prev_b while not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) assert curr.finished_challenge_slot_hashes is not None # 2b. check sub-slot challenge hash for non-genesis block if not curr.finished_challenge_slot_hashes[ -1] == challenge_hash: print(curr.finished_challenge_slot_hashes[-1], challenge_hash) return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) else: # 2c. check sub-slot challenge hash for empty slot if (not header_block.finished_sub_slots[ finished_sub_slot_n - 1].challenge_chain.get_hash() == challenge_hash): return None, ValidationError( Err.INVALID_PREV_CHALLENGE_SLOT_HASH) if genesis_block: # 2d. Validate that genesis block has no ICC if sub_slot.infused_challenge_chain is not None: return None, ValidationError(Err.SHOULD_NOT_HAVE_ICC) else: assert prev_b is not None icc_iters_committed: Optional[uint64] = None icc_iters_proof: Optional[uint64] = None icc_challenge_hash: Optional[bytes32] = None icc_vdf_input = None if prev_b.deficit < constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: # There should be no ICC chain if the last block's deficit is 16 # Prev sb's deficit is 0, 1, 2, 3, or 4 if finished_sub_slot_n == 0: # This is the first sub slot after the last sb, which must have deficit 1-4, and thus an ICC curr = prev_b while not curr.is_challenge_block( constants) and not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) if curr.is_challenge_block(constants): icc_challenge_hash = curr.challenge_block_info_hash icc_iters_committed = uint64( prev_b.sub_slot_iters - curr.ip_iters(constants)) else: assert curr.finished_infused_challenge_slot_hashes is not None icc_challenge_hash = curr.finished_infused_challenge_slot_hashes[ -1] icc_iters_committed = prev_b.sub_slot_iters icc_iters_proof = uint64(prev_b.sub_slot_iters - prev_b.ip_iters(constants)) if prev_b.is_challenge_block(constants): icc_vdf_input = ClassgroupElement.get_default_element( ) else: icc_vdf_input = prev_b.infused_challenge_vdf_output else: # This is not the first sub slot after the last block, so we might not have an ICC if (header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.deficit < constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK): finished_ss = header_block.finished_sub_slots[ finished_sub_slot_n - 1] assert finished_ss.infused_challenge_chain is not None # Only sets the icc iff the previous sub slots deficit is 4 or less icc_challenge_hash = finished_ss.infused_challenge_chain.get_hash( ) icc_iters_committed = prev_b.sub_slot_iters icc_iters_proof = icc_iters_committed icc_vdf_input = ClassgroupElement.get_default_element( ) # 2e. Validate that there is not icc iff icc_challenge hash is None assert (sub_slot.infused_challenge_chain is None) == (icc_challenge_hash is None) if sub_slot.infused_challenge_chain is not None: assert icc_vdf_input is not None assert icc_iters_proof is not None assert icc_challenge_hash is not None assert sub_slot.proofs.infused_challenge_chain_slot_proof is not None # 2f. Check infused challenge chain sub-slot VDF # Only validate from prev_b to optimize target_vdf_info = VDFInfo( icc_challenge_hash, icc_iters_proof, sub_slot.infused_challenge_chain. infused_challenge_chain_end_of_slot_vdf.output, ) if sub_slot.infused_challenge_chain.infused_challenge_chain_end_of_slot_vdf != dataclasses.replace( target_vdf_info, number_of_iterations=icc_iters_committed, ): return None, ValidationError(Err.INVALID_ICC_EOS_VDF) if not skip_vdf_is_valid and not sub_slot.proofs.infused_challenge_chain_slot_proof.is_valid( constants, icc_vdf_input, target_vdf_info, None): return None, ValidationError(Err.INVALID_ICC_EOS_VDF) if sub_slot.reward_chain.deficit == constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: # 2g. Check infused challenge sub-slot hash in challenge chain, deficit 16 if (sub_slot.infused_challenge_chain.get_hash() != sub_slot.challenge_chain. infused_challenge_chain_sub_slot_hash): return None, ValidationError( Err.INVALID_ICC_HASH_CC) else: # 2h. Check infused challenge sub-slot hash not included for other deficits if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError( Err.INVALID_ICC_HASH_CC) # 2i. Check infused challenge sub-slot hash in reward sub-slot if (sub_slot.infused_challenge_chain.get_hash() != sub_slot.reward_chain. infused_challenge_chain_sub_slot_hash): return None, ValidationError(Err.INVALID_ICC_HASH_RC) else: # 2j. If no icc, check that the cc doesn't include it if sub_slot.challenge_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError(Err.INVALID_ICC_HASH_CC) # 2k. If no icc, check that the cc doesn't include it if sub_slot.reward_chain.infused_challenge_chain_sub_slot_hash is not None: return None, ValidationError(Err.INVALID_ICC_HASH_RC) if sub_slot.challenge_chain.subepoch_summary_hash is not None: assert ses_hash is None # Only one of the slots can have it ses_hash = sub_slot.challenge_chain.subepoch_summary_hash # 2l. check sub-epoch summary hash is None for empty slots if finished_sub_slot_n != 0: if sub_slot.challenge_chain.subepoch_summary_hash is not None: return None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH) if can_finish_epoch and sub_slot.challenge_chain.subepoch_summary_hash is not None: # 2m. Check new difficulty and ssi if sub_slot.challenge_chain.new_sub_slot_iters != expected_sub_slot_iters: return None, ValidationError( Err.INVALID_NEW_SUB_SLOT_ITERS) if sub_slot.challenge_chain.new_difficulty != expected_difficulty: return None, ValidationError(Err.INVALID_NEW_DIFFICULTY) else: # 2n. Check new difficulty and ssi are None if we don't finish epoch if sub_slot.challenge_chain.new_sub_slot_iters is not None: return None, ValidationError( Err.INVALID_NEW_SUB_SLOT_ITERS) if sub_slot.challenge_chain.new_difficulty is not None: return None, ValidationError(Err.INVALID_NEW_DIFFICULTY) # 2o. Check challenge sub-slot hash in reward sub-slot if sub_slot.challenge_chain.get_hash( ) != sub_slot.reward_chain.challenge_chain_sub_slot_hash: return ( None, ValidationError( Err.INVALID_CHALLENGE_SLOT_HASH_RC, "sub-slot hash in reward sub-slot mismatch", ), ) eos_vdf_iters: uint64 = expected_sub_slot_iters cc_start_element: ClassgroupElement = ClassgroupElement.get_default_element( ) cc_eos_vdf_challenge: bytes32 = challenge_hash if genesis_block: if finished_sub_slot_n == 0: # First block, one empty slot. prior_point is the initial challenge rc_eos_vdf_challenge: bytes32 = constants.GENESIS_CHALLENGE cc_eos_vdf_challenge = constants.GENESIS_CHALLENGE else: # First block, but have at least two empty slots rc_eos_vdf_challenge = header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.get_hash() else: assert prev_b is not None if finished_sub_slot_n == 0: # No empty slots, so the starting point of VDF is the last reward block. Uses # the same IPS as the previous block, since it's the same slot rc_eos_vdf_challenge = prev_b.reward_infusion_new_challenge eos_vdf_iters = uint64(prev_b.sub_slot_iters - prev_b.ip_iters(constants)) cc_start_element = prev_b.challenge_vdf_output else: # At least one empty slot, so use previous slot hash. IPS might change because it's a new slot rc_eos_vdf_challenge = header_block.finished_sub_slots[ finished_sub_slot_n - 1].reward_chain.get_hash() # 2p. Check end of reward slot VDF target_vdf_info = VDFInfo( rc_eos_vdf_challenge, eos_vdf_iters, sub_slot.reward_chain.end_of_slot_vdf.output, ) if not skip_vdf_is_valid and not sub_slot.proofs.reward_chain_slot_proof.is_valid( constants, ClassgroupElement.get_default_element(), sub_slot.reward_chain.end_of_slot_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_EOS_VDF) # 2q. Check challenge chain sub-slot VDF partial_cc_vdf_info = VDFInfo( cc_eos_vdf_challenge, eos_vdf_iters, sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf. output, ) if genesis_block: cc_eos_vdf_info_iters = constants.SUB_SLOT_ITERS_STARTING else: assert prev_b is not None if finished_sub_slot_n == 0: cc_eos_vdf_info_iters = prev_b.sub_slot_iters else: cc_eos_vdf_info_iters = expected_sub_slot_iters # Check that the modified data is correct if sub_slot.challenge_chain.challenge_chain_end_of_slot_vdf != dataclasses.replace( partial_cc_vdf_info, number_of_iterations=cc_eos_vdf_info_iters, ): return None, ValidationError( Err.INVALID_CC_EOS_VDF, "wrong challenge chain end of slot vdf") # Pass in None for target info since we are only checking the proof from the temporary point, # but the challenge_chain_end_of_slot_vdf actually starts from the start of slot (for light clients) if not skip_vdf_is_valid and not sub_slot.proofs.challenge_chain_slot_proof.is_valid( constants, cc_start_element, partial_cc_vdf_info, None): return None, ValidationError(Err.INVALID_CC_EOS_VDF) if genesis_block: # 2r. Check deficit (MIN_SUB.. deficit edge case for genesis block) if sub_slot.reward_chain.deficit != constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: return ( None, ValidationError( Err.INVALID_DEFICIT, f"genesis, expected deficit {constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK}", ), ) else: assert prev_b is not None if prev_b.deficit == 0: # 2s. If prev sb had deficit 0, resets deficit to MIN_BLOCK_PER_CHALLENGE_BLOCK if sub_slot.reward_chain.deficit != constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK: log.error(constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK, ) return ( None, ValidationError( Err.INVALID_DEFICIT, f"expected deficit {constants.MIN_BLOCKS_PER_CHALLENGE_BLOCK}, saw " f"{sub_slot.reward_chain.deficit}", ), ) else: # 2t. Otherwise, deficit stays the same at the slot ends, cannot reset until 0 if sub_slot.reward_chain.deficit != prev_b.deficit: return None, ValidationError( Err.INVALID_DEFICIT, "deficit is wrong at slot end") # 3. Check sub-epoch summary # Note that the subepoch summary is the summary of the previous subepoch (not the one that just finished) if not skip_overflow_last_ss_validation: if ses_hash is not None: # 3a. Check that genesis block does not have sub-epoch summary if genesis_block: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH, "genesis with sub-epoch-summary hash", ), ) assert prev_b is not None # 3b. Check that we finished a slot and we finished a sub-epoch if not new_sub_slot or not can_finish_se: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY_HASH, f"new sub-slot: {new_sub_slot} finishes sub-epoch {can_finish_se}", ), ) # 3c. Check the actual sub-epoch is correct expected_sub_epoch_summary = make_sub_epoch_summary( constants, blocks, height, blocks.block_record(prev_b.prev_hash), expected_difficulty if can_finish_epoch else None, expected_sub_slot_iters if can_finish_epoch else None, ) expected_hash = expected_sub_epoch_summary.get_hash() if expected_hash != ses_hash: log.error(f"{expected_sub_epoch_summary}") return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY, f"expected ses hash: {expected_hash} got {ses_hash} ", ), ) elif new_sub_slot and not genesis_block: # 3d. Check that we don't have to include a sub-epoch summary if can_finish_se or can_finish_epoch: return ( None, ValidationError( Err.INVALID_SUB_EPOCH_SUMMARY, "block finishes sub-epoch but ses-hash is None", ), ) # 4. Check if the number of blocks is less than the max if not new_sub_slot and not genesis_block: assert prev_b is not None num_blocks = 2 # This includes the current block and the prev block curr = prev_b while not curr.first_in_sub_slot: num_blocks += 1 curr = blocks.block_record(curr.prev_hash) if num_blocks > constants.MAX_SUB_SLOT_BLOCKS: return None, ValidationError(Err.TOO_MANY_BLOCKS) # If block state is correct, we should always find a challenge here # This computes what the challenge should be for this block challenge = get_block_challenge( constants, header_block, blocks, genesis_block, overflow, skip_overflow_last_ss_validation, ) # 5a. Check proof of space if challenge != header_block.reward_chain_block.pos_ss_cc_challenge_hash: log.error(f"Finished slots: {header_block.finished_sub_slots}") log.error( f"Data: {genesis_block} {overflow} {skip_overflow_last_ss_validation} {header_block.total_iters} " f"{header_block.reward_chain_block.signage_point_index}" f"Prev: {prev_b}") log.error( f"Challenge {challenge} provided {header_block.reward_chain_block.pos_ss_cc_challenge_hash}" ) return None, ValidationError(Err.INVALID_CC_CHALLENGE) # 5b. Check proof of space if header_block.reward_chain_block.challenge_chain_sp_vdf is None: # Edge case of first sp (start of slot), where sp_iters == 0 cc_sp_hash: bytes32 = challenge else: cc_sp_hash = header_block.reward_chain_block.challenge_chain_sp_vdf.output.get_hash( ) q_str: Optional[ bytes32] = header_block.reward_chain_block.proof_of_space.verify_and_get_quality_string( constants, challenge, cc_sp_hash) if q_str is None: return None, ValidationError(Err.INVALID_POSPACE) # 6. check signage point index # no need to check negative values as this is uint 8 if header_block.reward_chain_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: return None, ValidationError(Err.INVALID_SP_INDEX) # Note that required iters might be from the previous slot (if we are in an overflow block) required_iters: uint64 = calculate_iterations_quality( constants.DIFFICULTY_CONSTANT_FACTOR, q_str, header_block.reward_chain_block.proof_of_space.size, expected_difficulty, cc_sp_hash, ) # 7. check signage point index # no need to check negative values as this is uint8. (Assumes types are checked) if header_block.reward_chain_block.signage_point_index >= constants.NUM_SPS_SUB_SLOT: return None, ValidationError(Err.INVALID_SP_INDEX) # 8a. check signage point index 0 has no cc sp if (header_block.reward_chain_block.signage_point_index == 0) != ( header_block.reward_chain_block.challenge_chain_sp_vdf is None): return None, ValidationError(Err.INVALID_SP_INDEX) # 8b. check signage point index 0 has no rc sp if (header_block.reward_chain_block.signage_point_index == 0) != ( header_block.reward_chain_block.reward_chain_sp_vdf is None): return None, ValidationError(Err.INVALID_SP_INDEX) sp_iters: uint64 = calculate_sp_iters( constants, expected_sub_slot_iters, header_block.reward_chain_block.signage_point_index, ) ip_iters: uint64 = calculate_ip_iters( constants, expected_sub_slot_iters, header_block.reward_chain_block.signage_point_index, required_iters, ) if header_block.reward_chain_block.challenge_chain_sp_vdf is None: # Blocks with very low required iters are not overflow blocks assert not overflow # 9. Check no overflows in the first sub-slot of a new epoch # (although they are OK in the second sub-slot), this is important if overflow and can_finish_epoch: if finished_sub_slots_since_prev < 2: return None, ValidationError( Err.NO_OVERFLOWS_IN_FIRST_SUB_SLOT_NEW_EPOCH) # 10. Check total iters if genesis_block: total_iters: uint128 = uint128(expected_sub_slot_iters * finished_sub_slots_since_prev) else: assert prev_b is not None if new_sub_slot: total_iters = prev_b.total_iters # Add the rest of the slot of prev_b total_iters = uint128(total_iters + prev_b.sub_slot_iters - prev_b.ip_iters(constants)) # Add other empty slots total_iters = uint128(total_iters + (expected_sub_slot_iters * (finished_sub_slots_since_prev - 1))) else: # Slot iters is guaranteed to be the same for header_block and prev_b # This takes the beginning of the slot, and adds ip_iters total_iters = uint128(prev_b.total_iters - prev_b.ip_iters(constants)) total_iters = uint128(total_iters + ip_iters) if total_iters != header_block.reward_chain_block.total_iters: return ( None, ValidationError( Err.INVALID_TOTAL_ITERS, f"expected {total_iters} got {header_block.reward_chain_block.total_iters}", ), ) sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - ( expected_sub_slot_iters if overflow else 0)) if overflow and skip_overflow_last_ss_validation: dummy_vdf_info = VDFInfo( bytes32([0] * 32), uint64(1), ClassgroupElement.get_default_element(), ) dummy_sub_slot = EndOfSubSlotBundle( ChallengeChainSubSlot(dummy_vdf_info, None, None, None, None), None, RewardChainSubSlot(dummy_vdf_info, bytes32([0] * 32), None, uint8(0)), SubSlotProofs(VDFProof(uint8(0), b""), None, VDFProof(uint8(0), b"")), ) sub_slots_to_pass_in = header_block.finished_sub_slots + [ dummy_sub_slot ] else: sub_slots_to_pass_in = header_block.finished_sub_slots ( cc_vdf_challenge, rc_vdf_challenge, cc_vdf_input, rc_vdf_input, cc_vdf_iters, rc_vdf_iters, ) = get_signage_point_vdf_info( constants, sub_slots_to_pass_in, overflow, prev_b, blocks, sp_total_iters, sp_iters, ) # 11. Check reward chain sp proof if sp_iters != 0: assert (header_block.reward_chain_block.reward_chain_sp_vdf is not None and header_block.reward_chain_sp_proof is not None) target_vdf_info = VDFInfo( rc_vdf_challenge, rc_vdf_iters, header_block.reward_chain_block.reward_chain_sp_vdf.output, ) if not skip_vdf_is_valid and not header_block.reward_chain_sp_proof.is_valid( constants, rc_vdf_input, header_block.reward_chain_block.reward_chain_sp_vdf, target_vdf_info, ): return None, ValidationError(Err.INVALID_RC_SP_VDF) rc_sp_hash = header_block.reward_chain_block.reward_chain_sp_vdf.output.get_hash( ) else: # Edge case of first sp (start of slot), where sp_iters == 0 assert overflow is not None if header_block.reward_chain_block.reward_chain_sp_vdf is not None: return None, ValidationError(Err.INVALID_RC_SP_VDF) if new_sub_slot: rc_sp_hash = header_block.finished_sub_slots[ -1].reward_chain.get_hash() else: if genesis_block: rc_sp_hash = constants.GENESIS_CHALLENGE else: assert prev_b is not None curr = prev_b while not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) assert curr.finished_reward_slot_hashes is not None rc_sp_hash = curr.finished_reward_slot_hashes[-1] # 12. Check reward chain sp signature if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, rc_sp_hash, header_block.reward_chain_block.reward_chain_sp_signature, ): return None, ValidationError(Err.INVALID_RC_SIGNATURE) # 13. Check cc sp vdf if sp_iters != 0: assert header_block.reward_chain_block.challenge_chain_sp_vdf is not None assert header_block.challenge_chain_sp_proof is not None target_vdf_info = VDFInfo( cc_vdf_challenge, cc_vdf_iters, header_block.reward_chain_block.challenge_chain_sp_vdf.output, ) if header_block.reward_chain_block.challenge_chain_sp_vdf != dataclasses.replace( target_vdf_info, number_of_iterations=sp_iters, ): return None, ValidationError(Err.INVALID_CC_SP_VDF) if not skip_vdf_is_valid and not header_block.challenge_chain_sp_proof.is_valid( constants, cc_vdf_input, target_vdf_info, None): return None, ValidationError(Err.INVALID_CC_SP_VDF) else: assert overflow is not None if header_block.reward_chain_block.challenge_chain_sp_vdf is not None: return None, ValidationError(Err.INVALID_CC_SP_VDF) # 14. Check cc sp sig if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, cc_sp_hash, header_block.reward_chain_block.challenge_chain_sp_signature, ): return None, ValidationError(Err.INVALID_CC_SIGNATURE, "invalid cc sp sig") # 15. Check is_transaction_block if genesis_block: if header_block.foliage.foliage_transaction_block_hash is None: return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK, "invalid genesis") else: assert prev_b is not None # Finds the previous block curr = prev_b while not curr.is_transaction_block: curr = blocks.block_record(curr.prev_hash) # The first block to have an sp > the last tx block's infusion iters, is a tx block if overflow: our_sp_total_iters: uint128 = uint128(total_iters - ip_iters + sp_iters - expected_sub_slot_iters) else: our_sp_total_iters = uint128(total_iters - ip_iters + sp_iters) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage.foliage_transaction_block_hash is not None): return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK) if (our_sp_total_iters > curr.total_iters) != ( header_block.foliage.foliage_transaction_block_signature is not None): return None, ValidationError(Err.INVALID_IS_TRANSACTION_BLOCK) # 16. Check foliage block signature by plot key if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, header_block.foliage.foliage_block_data.get_hash(), header_block.foliage.foliage_block_data_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 17. Check foliage block signature by plot key if header_block.foliage.foliage_transaction_block_hash is not None: if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space.plot_public_key, header_block.foliage.foliage_transaction_block_hash, header_block.foliage.foliage_transaction_block_signature, ): return None, ValidationError(Err.INVALID_PLOT_SIGNATURE) # 18. Check unfinished reward chain block hash if (header_block.reward_chain_block.get_hash() != header_block.foliage. foliage_block_data.unfinished_reward_block_hash): return None, ValidationError(Err.INVALID_URSB_HASH) # 19. Check pool target max height if (header_block.foliage.foliage_block_data.pool_target.max_height != 0 and header_block.foliage.foliage_block_data.pool_target.max_height < height): return None, ValidationError(Err.OLD_POOL_TARGET) # 20a. Check pre-farm puzzle hashes for genesis block. if genesis_block: if (header_block.foliage.foliage_block_data.pool_target.puzzle_hash != constants.GENESIS_PRE_FARM_POOL_PUZZLE_HASH): log.error( f"Pool target {header_block.foliage.foliage_block_data.pool_target} hb {header_block}" ) return None, ValidationError(Err.INVALID_PREFARM) if (header_block.foliage.foliage_block_data.farmer_reward_puzzle_hash != constants.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH): return None, ValidationError(Err.INVALID_PREFARM) else: # 20b. If pospace has a pool pk, heck pool target signature. Should not check this for genesis block. if header_block.reward_chain_block.proof_of_space.pool_public_key is not None: assert header_block.reward_chain_block.proof_of_space.pool_contract_puzzle_hash is None if not AugSchemeMPL.verify( header_block.reward_chain_block.proof_of_space. pool_public_key, bytes(header_block.foliage.foliage_block_data.pool_target), header_block.foliage.foliage_block_data.pool_signature, ): return None, ValidationError(Err.INVALID_POOL_SIGNATURE) else: # 20c. Otherwise, the plot is associated with a contract puzzle hash, not a public key assert header_block.reward_chain_block.proof_of_space.pool_contract_puzzle_hash is not None if (header_block.foliage.foliage_block_data.pool_target.puzzle_hash != header_block.reward_chain_block.proof_of_space. pool_contract_puzzle_hash): return None, ValidationError(Err.INVALID_POOL_TARGET) # 21. Check extension data if applicable. None for mainnet. # 22. Check if foliage block is present if (header_block.foliage.foliage_transaction_block_hash is not None) != (header_block.foliage_transaction_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if (header_block.foliage.foliage_transaction_block_signature is not None) != (header_block.foliage_transaction_block is not None): return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_PRESENCE) if header_block.foliage_transaction_block is not None: # 23. Check foliage block hash if header_block.foliage_transaction_block.get_hash( ) != header_block.foliage.foliage_transaction_block_hash: return None, ValidationError(Err.INVALID_FOLIAGE_BLOCK_HASH) if genesis_block: # 24a. Check prev block hash for genesis if header_block.foliage_transaction_block.prev_transaction_block_hash != constants.GENESIS_CHALLENGE: return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) else: assert prev_b is not None # 24b. Check prev block hash for non-genesis curr_b: BlockRecord = prev_b while not curr_b.is_transaction_block: curr_b = blocks.block_record(curr_b.prev_hash) if not header_block.foliage_transaction_block.prev_transaction_block_hash == curr_b.header_hash: log.error( f"Prev BH: {header_block.foliage_transaction_block.prev_transaction_block_hash} " f"{curr_b.header_hash} curr sb: {curr_b}") return None, ValidationError(Err.INVALID_PREV_BLOCK_HASH) # 25. The filter hash in the Foliage Block must be the hash of the filter if check_filter: if header_block.foliage_transaction_block.filter_hash != std_hash( header_block.transactions_filter): return None, ValidationError( Err.INVALID_TRANSACTIONS_FILTER_HASH) # 26. The timestamp in Foliage Block must comply with the timestamp rules if prev_b is not None: last_timestamps: List[uint64] = [] curr_b = blocks.block_record( header_block.foliage_transaction_block. prev_transaction_block_hash) assert curr_b.timestamp is not None while len(last_timestamps) < constants.NUMBER_OF_TIMESTAMPS: last_timestamps.append(curr_b.timestamp) fetched: Optional[BlockRecord] = blocks.try_block_record( curr_b.prev_transaction_block_hash) if not fetched: break curr_b = fetched if len(last_timestamps) != constants.NUMBER_OF_TIMESTAMPS: # For blocks 1 to 10, average timestamps of all previous blocks assert curr_b.height == 0 prev_time: uint64 = uint64( int(sum(last_timestamps) // len(last_timestamps))) if header_block.foliage_transaction_block.timestamp <= prev_time: return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_PAST) if header_block.foliage_transaction_block.timestamp > int( time.time() + constants.MAX_FUTURE_TIME): return None, ValidationError(Err.TIMESTAMP_TOO_FAR_IN_FUTURE) return required_iters, None # Valid unfinished header block
async def update_farmer(self, request: PutFarmerRequest, metadata: RequestMetadata) -> Dict: launcher_id = request.payload.launcher_id # First check if this launcher_id is currently blocked for farmer updates, if so there is no reason to validate # all the stuff below if launcher_id in self.farmer_update_blocked: return error_dict(PoolErrorCode.REQUEST_FAILED, f"Cannot update farmer yet.") farmer_record: Optional[ FarmerRecord] = await self.store.get_farmer_record(launcher_id) if farmer_record is None: return error_dict( PoolErrorCode.FARMER_NOT_KNOWN, f"Farmer with launcher_id {launcher_id} not known.") singleton_state_tuple: Optional[Tuple[ CoinSpend, PoolState, bool]] = await self.get_and_validate_singleton_state(launcher_id) if singleton_state_tuple is None: return error_dict( PoolErrorCode.INVALID_SINGLETON, f"Invalid singleton {request.payload.launcher_id}") last_spend, last_state, is_member = singleton_state_tuple if is_member is None: return error_dict(PoolErrorCode.INVALID_SINGLETON, f"Singleton is not assigned to this pool") if not AugSchemeMPL.verify(last_state.owner_pubkey, request.payload.get_hash(), request.signature): return error_dict(PoolErrorCode.INVALID_SIGNATURE, f"Invalid signature") farmer_dict = farmer_record.to_json_dict() response_dict = {} if request.payload.authentication_public_key is not None: is_new_value = farmer_record.authentication_public_key != request.payload.authentication_public_key response_dict["authentication_public_key"] = is_new_value if is_new_value: farmer_dict[ "authentication_public_key"] = request.payload.authentication_public_key if request.payload.payout_instructions is not None: new_ph: Optional[str] = await self.validate_payout_instructions( request.payload.payout_instructions) response_dict["payout_instructions"] = new_ph if new_ph: farmer_dict["payout_instructions"] = new_ph if request.payload.suggested_difficulty is not None: is_new_value = ( farmer_record.difficulty != request.payload.suggested_difficulty and request.payload.suggested_difficulty is not None and request.payload.suggested_difficulty >= self.min_difficulty) response_dict["suggested_difficulty"] = is_new_value if is_new_value: farmer_dict[ "difficulty"] = request.payload.suggested_difficulty async def update_farmer_later(): await asyncio.sleep(self.farmer_update_cooldown_seconds) await self.store.add_farmer_record( FarmerRecord.from_json_dict(farmer_dict), metadata) self.farmer_update_blocked.remove(launcher_id) self.log.info(f"Updated farmer: {response_dict}") self.farmer_update_blocked.add(launcher_id) asyncio.create_task(update_farmer_later()) # TODO Fix chia-blockchain's Streamable implementation to support Optional in `from_json_dict`, then use # PutFarmerResponse here and in the trace up. return response_dict