def airdrop_tokens(self): """ Calculate airdrop eligibility via faucet registration and transfer tokens to selected recipients. """ with ThreadedSession(self.db_engine) as session: population = session.query(self.Recipient).count() message = f"{population} registered faucet recipients; " \ f"Distributed {str(NU(self.__distributed, 'NuNit'))} since {self.start_time.slang_time()}." self.log.debug(message) if population == 0: return # Abort - no recipients are registered. # For filtration since = datetime.now() - timedelta(hours=self.DISBURSEMENT_INTERVAL) datetime_filter = or_(self.Recipient.last_disbursement_time <= since, self.Recipient.last_disbursement_time == None) # This must be `==` not `is` with ThreadedSession(self.db_engine) as session: candidates = session.query( self.Recipient).filter(datetime_filter).all() if not candidates: self.log.info("No eligible recipients this round.") return # Discard invalid addresses, in-depth invalid_addresses = list() def siphon_invalid_entries(candidate): address_is_valid = eth_utils.is_checksum_address(candidate.address) if not address_is_valid: invalid_addresses.append(candidate.address) return address_is_valid candidates = list(filter(siphon_invalid_entries, candidates)) if invalid_addresses: self.log.info( f"{len(invalid_addresses)} invalid entries detected. Pruning database." ) # TODO: Is this needed? - Invalid entries are rejected at the endpoint view. # Prune database of invalid records # with ThreadedSession(self.db_engine) as session: # bad_eggs = session.query(self.Recipient).filter(self.Recipient.address in invalid_addresses).all() # for egg in bad_eggs: # session.delete(egg.id) # session.commit() if not candidates: self.log.info("No eligible recipients this round.") return d = threads.deferToThread(self.__do_airdrop, candidates=candidates) self._AIRDROP_QUEUE[self.__airdrop] = d return d
def reencrypt_via_rest(id_as_hex): # Get Policy Arrangement try: arrangement_id = binascii.unhexlify(id_as_hex) except (binascii.Error, TypeError): return Response(response=b'Invalid arrangement ID', status=405) try: with ThreadedSession(db_engine) as session: arrangement = datastore.get_policy_arrangement( arrangement_id=id_as_hex.encode(), session=session) except NotFound: return Response(response=arrangement_id, status=404) # Get KFrag # TODO: Yeah, well, what if this arrangement hasn't been enacted? 1702 kfrag = KFrag.from_bytes(arrangement.kfrag) # Get Work Order from nucypher.policy.collections import WorkOrder # Avoid circular import alice_verifying_key_bytes = arrangement.alice_verifying_key.key_data alice_verifying_key = UmbralPublicKey.from_bytes( alice_verifying_key_bytes) alice_address = canonical_address_from_umbral_key(alice_verifying_key) work_order_payload = request.data work_order = WorkOrder.from_rest_payload( arrangement_id=arrangement_id, rest_payload=work_order_payload, ursula=this_node, alice_address=alice_address) log.info( f"Work Order from {work_order.bob}, signed {work_order.receipt_signature}" ) # Re-encrypt response = this_node._reencrypt( kfrag=kfrag, work_order=work_order, alice_verifying_key=alice_verifying_key) # Now, Ursula saves this workorder to her database... with ThreadedSession(db_engine): this_node.datastore.save_workorder( bob_verifying_key=bytes(work_order.bob.stamp), bob_signature=bytes(work_order.receipt_signature), arrangement_id=work_order.arrangement_id) headers = {'Content-Type': 'application/octet-stream'} return Response(headers=headers, response=response)
def status(): with ThreadedSession(self.db_engine) as session: total_recipients = session.query(self.Recipient).count() last_recipient = session.query(self.Recipient).filter( self.Recipient.last_disbursement_time.isnot( None)).order_by('last_disbursement_time').first() last_address = last_recipient.address if last_recipient else None last_transaction_date = last_recipient.last_disbursement_time.isoformat( ) if last_recipient else None unfunded = session.query(self.Recipient).filter( self.Recipient.last_disbursement_time.is_(None)).count() return json.dumps({ "total_recipients": total_recipients, "latest_recipient": last_address, "latest_disburse_date": last_transaction_date, "unfunded_recipients": unfunded, "state": { "eth": str(self.eth_balance), "NU": str(self.token_balance), "address": self.checksum_address, "contract_address": self.token_agent.contract_address, } })
def set_policy(id_as_hex): """ REST endpoint for setting a kFrag. TODO: Instead of taking a Request, use the apistar typing system to type a payload and validate / split it. TODO: Validate that the kfrag being saved is pursuant to an approved Policy (see #121). """ policy_message_kit = UmbralMessageKit.from_bytes(request.data) alices_verifying_key = policy_message_kit.sender_verifying_key alice = _alice_class.from_public_keys(verifying_key=alices_verifying_key) try: cleartext = this_node.verify_from(alice, policy_message_kit, decrypt=True) except InvalidSignature: # TODO: Perhaps we log this? return Response(status_code=400) kfrag = KFrag.from_bytes(cleartext) if not kfrag.verify(signing_pubkey=alices_verifying_key): raise InvalidSignature("{} is invalid".format(kfrag)) with ThreadedSession(db_engine) as session: datastore.attach_kfrag_to_saved_arrangement( alice, id_as_hex, kfrag, session=session) # TODO: Sign the arrangement here. #495 return "" # TODO: Return A 200, with whatever policy metadata.
def reencrypt_via_rest(self, id_as_hex, request: http.Request): from nucypher.policy.models import WorkOrder # Avoid circular import id = binascii.unhexlify(id_as_hex) work_order = WorkOrder.from_rest_payload(id, request.body) self.log.info("Work Order from {}, signed {}".format( work_order.bob, work_order.receipt_signature)) with ThreadedSession(self.db_engine) as session: kfrag_bytes = self.datastore.get_policy_arrangement( id.hex().encode(), session=session).k_frag # Careful! :-) # TODO: Push this to a lower level. kfrag = KFrag.from_bytes(kfrag_bytes) cfrag_byte_stream = b"" for capsule in work_order.capsules: # TODO: Sign the result of this. See #141. cfrag = pre.reencrypt(kfrag, capsule) self.log.info( "Re-encrypting for Capsule {}, made CFrag {}.".format( capsule, cfrag)) cfrag_byte_stream += VariableLengthBytestring(cfrag) # TODO: Put this in Ursula's datastore self._work_orders.append(work_order) headers = {'Content-Type': 'application/octet-stream'} return Response(content=cfrag_byte_stream, headers=headers)
def set_policy(self, id_as_hex, request: http.Request): """ REST endpoint for setting a kFrag. TODO: Instead of taking a Request, use the apistar typing system to type a payload and validate / split it. TODO: Validate that the kfrag being saved is pursuant to an approved Policy (see #121). """ policy_message_kit = UmbralMessageKit.from_bytes(request.body) alice = self._alice_class.from_public_keys( {SigningPower: policy_message_kit.sender_pubkey_sig}) try: cleartext = self.verify_from(alice, policy_message_kit, decrypt=True) except self.InvalidSignature: # TODO: What do we do if the Policy isn't signed properly? pass kfrag = KFrag.from_bytes(cleartext) with ThreadedSession(self.db_engine) as session: self.datastore.attach_kfrag_to_saved_arrangement(alice, id_as_hex, kfrag, session=session) return # TODO: Return A 200, with whatever policy metadata.
def reencrypt_via_rest(id_as_hex): from nucypher.policy.models import WorkOrder # Avoid circular import arrangement_id = binascii.unhexlify(id_as_hex) work_order = WorkOrder.from_rest_payload(arrangement_id, request.data) log.info("Work Order from {}, signed {}".format(work_order.bob, work_order.receipt_signature)) with ThreadedSession(db_engine) as session: policy_arrangement = datastore.get_policy_arrangement(arrangement_id=id_as_hex.encode(), session=session) kfrag_bytes = policy_arrangement.kfrag # Careful! :-) verifying_key_bytes = policy_arrangement.alice_pubkey_sig.key_data # TODO: Push this to a lower level. kfrag = KFrag.from_bytes(kfrag_bytes) alices_verifying_key = UmbralPublicKey.from_bytes(verifying_key_bytes) cfrag_byte_stream = b"" for capsule, capsule_signature in zip(work_order.capsules, work_order.capsule_signatures): # This is the capsule signed by Bob capsule_signature = bytes(capsule_signature) # Ursula signs on top of it. Now both are committed to the same capsule. capsule_signed_by_both = bytes(stamp(capsule_signature)) capsule.set_correctness_keys(verifying=alices_verifying_key) cfrag = pre.reencrypt(kfrag, capsule, metadata=capsule_signed_by_both) log.info("Re-encrypting for {}, made {}.".format(capsule, cfrag)) signature = stamp(bytes(cfrag) + bytes(capsule)) cfrag_byte_stream += VariableLengthBytestring(cfrag) + signature # TODO: Put this in Ursula's datastore work_order_tracker.append(work_order) headers = {'Content-Type': 'application/octet-stream'} return Response(response=cfrag_byte_stream, headers=headers)
def register(): """Handle new recipient registration via POST request.""" try: new_address = request.form['address'] except KeyError: return Response(status=400) # TODO if not eth_utils.is_checksum_address(new_address): return Response(status=400) # TODO if new_address in self.reserved_addresses: return Response(status=400) # TODO try: with ThreadedSession(self.db_engine) as session: existing = Recipient.query.filter_by(address=new_address).all() if existing: # Address already exists; Abort self.log.debug(f"{new_address} is already enrolled.") return Response(status=400) # Create the record recipient = Recipient(address=new_address, joined=datetime.now()) session.add(recipient) session.commit() except Exception as e: # Pass along exceptions to the logger self.log.critical(str(e)) raise else: return Response(status=200) # TODO
def revoke_arrangement(id_as_hex): """ REST endpoint for revoking/deleting a KFrag from a node. """ from nucypher.policy.collections import Revocation revocation = Revocation.from_bytes(request.data) log.info("Received revocation: {} -- for arrangement {}".format(bytes(revocation).hex(), id_as_hex)) try: with ThreadedSession(db_engine) as session: # Verify the Notice was signed by Alice policy_arrangement = datastore.get_policy_arrangement( id_as_hex.encode(), session=session) alice_pubkey = UmbralPublicKey.from_bytes( policy_arrangement.alice_verifying_key.key_data) # Check that the request is the same for the provided revocation if id_as_hex != revocation.arrangement_id.hex(): log.debug("Couldn't identify an arrangement with id {}".format(id_as_hex)) return Response(status_code=400) elif revocation.verify_signature(alice_pubkey): datastore.del_policy_arrangement( id_as_hex.encode(), session=session) except (NotFound, InvalidSignature) as e: log.debug("Exception attempting to revoke: {}".format(e)) return Response(response='KFrag not found or revocation signature is invalid.', status=404) else: log.info("KFrag successfully removed.") return Response(response='KFrag deleted!', status=200)
def set_policy(id_as_hex): """ REST endpoint for setting a kFrag. """ policy_message_kit = UmbralMessageKit.from_bytes(request.data) alices_verifying_key = policy_message_kit.sender_verifying_key alice = _alice_class.from_public_keys( verifying_key=alices_verifying_key) try: cleartext = this_node.verify_from(alice, policy_message_kit, decrypt=True) except InvalidSignature: # TODO: Perhaps we log this? Essentially 355. return Response(status_code=400) if not this_node.federated_only: # This splitter probably belongs somewhere canonical. transaction_splitter = BytestringSplitter(32) tx, kfrag_bytes = transaction_splitter(cleartext, return_remainder=True) try: # Get all of the arrangements and verify that we'll be paid. # TODO: We'd love for this part to be impossible to reduce the risk of collusion. #1274 arranged_addresses = this_node.policy_agent.fetch_arrangement_addresses_from_policy_txid( tx, timeout=this_node.synchronous_query_timeout) except TimeExhausted: # Alice didn't pay. Return response with that weird status code. this_node.suspicious_activities_witnessed['freeriders'].append( (alice, f"No transaction matching {tx}.")) return Response(status=402) this_node_has_been_arranged = this_node.checksum_address in arranged_addresses if not this_node_has_been_arranged: this_node.suspicious_activities_witnessed['freeriders'].append( (alice, f"The transaction {tx} does not list me as a Worker - it lists {arranged_addresses}." )) return Response(status=402) else: _tx = NO_BLOCKCHAIN_CONNECTION kfrag_bytes = cleartext kfrag = KFrag.from_bytes(kfrag_bytes) if not kfrag.verify(signing_pubkey=alices_verifying_key): raise InvalidSignature("{} is invalid".format(kfrag)) with ThreadedSession(db_engine) as session: datastore.attach_kfrag_to_saved_arrangement(alice, id_as_hex, kfrag, session=session) # TODO: Sign the arrangement here. #495 return "" # TODO: Return A 200, with whatever policy metadata.
def reencrypt_via_rest(id_as_hex): from nucypher.policy.models import WorkOrder # Avoid circular import arrangement_id = binascii.unhexlify(id_as_hex) work_order = WorkOrder.from_rest_payload(arrangement_id, request.data) log.info("Work Order from {}, signed {}".format( work_order.bob, work_order.receipt_signature)) with ThreadedSession(db_engine) as session: policy_arrangement = datastore.get_policy_arrangement( arrangement_id=id_as_hex.encode(), session=session) kfrag_bytes = policy_arrangement.kfrag # Careful! :-) verifying_key_bytes = policy_arrangement.alice_pubkey_sig.key_data # TODO: Push this to a lower level. Perhaps to Ursula character? #619 kfrag = KFrag.from_bytes(kfrag_bytes) alices_verifying_key = UmbralPublicKey.from_bytes(verifying_key_bytes) cfrag_byte_stream = b"" alices_address = canonical_address_from_umbral_key( alices_verifying_key) if not alices_address == work_order.alice_address: message = f"This Bob ({work_order.bob}) sent an Alice's ETH address " \ f"({work_order.alice_address}) that doesn't match " \ f"the one I have ({alices_address})." raise SuspiciousActivity(message) bob_pubkey = work_order.bob.stamp.as_umbral_pubkey() if not work_order.alice_address_signature.verify( message=alices_address, verifying_key=bob_pubkey): message = f"This Bob ({work_order.bob}) sent an invalid signature of Alice's ETH address" raise InvalidSignature(message) # This is Bob's signature of Alice's verifying key as ETH address. alice_address_signature = bytes(work_order.alice_address_signature) for capsule, capsule_signature in zip(work_order.capsules, work_order.capsule_signatures): # This is the capsule signed by Bob capsule_signature = bytes(capsule_signature) # Ursula signs on top of it. Now both are committed to the same capsule. # She signs Alice's address too. ursula_signature = stamp(capsule_signature + alice_address_signature) capsule.set_correctness_keys(verifying=alices_verifying_key) cfrag = pre.reencrypt(kfrag, capsule, metadata=bytes(ursula_signature)) log.info(f"Re-encrypting for {capsule}, made {cfrag}.") signature = stamp(bytes(cfrag) + bytes(capsule)) cfrag_byte_stream += VariableLengthBytestring(cfrag) + signature # TODO: Put this in Ursula's datastore work_order_tracker.append(work_order) headers = {'Content-Type': 'application/octet-stream'} return Response(response=cfrag_byte_stream, headers=headers)
def reencrypt_via_rest(id_as_hex): # TODO: How to pass Ursula's identity evidence to Bob? #962 # 'Identity evidence' is a signature of her stamp with the checksum address from nucypher.policy.models import WorkOrder # Avoid circular import arrangement_id = binascii.unhexlify(id_as_hex) with ThreadedSession(db_engine) as session: policy_arrangement = datastore.get_policy_arrangement( arrangement_id=id_as_hex.encode(), session=session) kfrag_bytes = policy_arrangement.kfrag # Careful! :-) verifying_key_bytes = policy_arrangement.alice_pubkey_sig.key_data # TODO: Push this to a lower level. Perhaps to Ursula character? #619 kfrag = KFrag.from_bytes(kfrag_bytes) alices_verifying_key = UmbralPublicKey.from_bytes(verifying_key_bytes) alices_address = canonical_address_from_umbral_key( alices_verifying_key) work_order = WorkOrder.from_rest_payload(arrangement_id=arrangement_id, rest_payload=request.data, ursula_pubkey_bytes=bytes( this_node.stamp), alice_address=alices_address) log.info( f"Work Order from {work_order.bob}, signed {work_order.receipt_signature}" ) cfrag_byte_stream = b"" for task in work_order.tasks: # Ursula signs on top of Bob's signature of each task. # Now both are committed to the same task. See #259. reencryption_metadata = bytes( this_node.stamp(bytes(task.signature))) capsule = task.capsule capsule.set_correctness_keys(verifying=alices_verifying_key) cfrag = pre.reencrypt(kfrag, capsule, metadata=reencryption_metadata) log.info(f"Re-encrypting for {capsule}, made {cfrag}.") # Finally, Ursula commits to her result reencryption_signature = this_node.stamp(bytes(cfrag)) cfrag_byte_stream += VariableLengthBytestring( cfrag) + reencryption_signature # TODO: Put this in Ursula's datastore this_node._work_orders.append(work_order) headers = {'Content-Type': 'application/octet-stream'} return Response(response=cfrag_byte_stream, headers=headers)
def register(): """Handle new recipient registration via POST request.""" new_address = (request.form.get('address') or request.get_json().get('address')) if not new_address: return Response(response="no address was supplied", status=411) if not eth_utils.is_address(new_address): return Response( response= "an invalid ethereum address was supplied. please ensure the address is a proper checksum.", status=400) else: new_address = eth_utils.to_checksum_address(new_address) if new_address in self.reserved_addresses: return Response( response= "sorry, that address is reserved and cannot receive funds.", status=403) try: with ThreadedSession(self.db_engine) as session: existing = Recipient.query.filter_by( address=new_address).all() if len(existing) > self.MAX_INDIVIDUAL_REGISTRATIONS: # Address already exists; Abort self.log.debug( f"{new_address} is already enrolled {self.MAX_INDIVIDUAL_REGISTRATIONS} times." ) return Response( response= f"{new_address} requested too many times - Please use another address.", status=409) # Create the record recipient = Recipient(address=new_address, joined=datetime.now()) session.add(recipient) session.commit() except Exception as e: # Pass along exceptions to the logger self.log.critical(str(e)) raise else: return Response(status=200) # TODO
def consider_arrangement(): from nucypher.policy.policies import Arrangement arrangement = Arrangement.from_bytes(request.data) with ThreadedSession(db_engine) as session: new_policy_arrangement = datastore.add_policy_arrangement( arrangement.expiration.datetime(), id=arrangement.id.hex().encode(), alice_verifying_key=arrangement.alice.stamp, session=session, ) # TODO: Make the rest of this logic actually work - do something here # to decide if this Arrangement is worth accepting. headers = {'Content-Type': 'application/octet-stream'} # TODO: Make this a legit response #234. return Response(b"This will eventually be an actual acceptance of the arrangement.", headers=headers)
def consider_arrangement(self, request: http.Request): from nucypher.policy.models import Arrangement arrangement = Arrangement.from_bytes(request.body) with ThreadedSession(self.db_engine) as session: new_policyarrangement = self.datastore.add_policy_arrangement( arrangement.expiration.datetime(), bytes(arrangement.deposit), hrac=arrangement.hrac.hex().encode(), alice_pubkey_sig=arrangement.alice.stamp, session=session, ) # TODO: Make the rest of this logic actually work - do something here # to decide if this Arrangement is worth accepting. headers = {'Content-Type': 'application/octet-stream'} # TODO: Make this a legit response #234. return Response( b"This will eventually be an actual acceptance of the arrangement.", headers=headers)
def consider_arrangement(): from nucypher.policy.policies import Arrangement arrangement = Arrangement.from_bytes(request.data) # TODO: Look at the expiration and figure out if we're even staking that long. 1701 with ThreadedSession(db_engine) as session: new_policy_arrangement = datastore.add_policy_arrangement( arrangement.expiration.datetime(), id=arrangement.id.hex().encode(), alice_verifying_key=arrangement.alice.stamp, session=session, ) # TODO: Fine, we'll add the arrangement here, but if we never hear from Alice again to enact it, # we need to prune it at some point. #1700 headers = {'Content-Type': 'application/octet-stream'} # TODO: Make this a legit response #234. return Response( b"This will eventually be an actual acceptance of the arrangement.", headers=headers)
def set_policy(self, hrac_as_hex, request: http.Request): """ REST endpoint for setting a kFrag. TODO: Instead of taking a Request, use the apistar typing system to type a payload and validate / split it. TODO: Validate that the kfrag being saved is pursuant to an approved Policy (see #121). """ hrac = binascii.unhexlify(hrac_as_hex) policy_message_kit = UmbralMessageKit.from_bytes(request.body) # group_payload_splitter = BytestringSplitter(PublicKey) # policy_payload_splitter = BytestringSplitter((KFrag, KFRAG_LENGTH)) alice = self._alice_class.from_public_keys( {SigningPower: policy_message_kit.sender_pubkey_sig}) verified, cleartext = self.verify_from(alice, policy_message_kit, decrypt=True) if not verified: # TODO: What do we do if the Policy isn't signed properly? pass # # alices_signature, policy_payload =\ # BytestringSplitter(Signature)(cleartext, return_remainder=True) # TODO: If we're not adding anything else in the payload, stop using the # splitter here. # kfrag = policy_payload_splitter(policy_payload)[0] kfrag = KFrag.from_bytes(cleartext) with ThreadedSession(self.db_engine) as session: self.datastore.attach_kfrag_to_saved_arrangement(alice, hrac_as_hex, kfrag, session=session) return # TODO: Return A 200, with whatever policy metadata.
def reencrypt_via_rest(self, hrac_as_hex, request: http.Request): from nucypher.policy.models import WorkOrder # Avoid circular import hrac = binascii.unhexlify(hrac_as_hex) work_order = WorkOrder.from_rest_payload(hrac, request.body) with ThreadedSession(self.db_engine) as session: kfrag_bytes = self.datastore.get_policy_arrangement( hrac.hex().encode(), session=session).k_frag # Careful! :-) # TODO: Push this to a lower level. kfrag = KFrag.from_bytes(kfrag_bytes) cfrag_byte_stream = b"" for capsule in work_order.capsules: # TODO: Sign the result of this. See #141. cfrag_byte_stream += VariableLengthBytestring( pre.reencrypt(kfrag, capsule)) # TODO: Put this in Ursula's datastore self._work_orders.append(work_order) headers = {'Content-Type': 'application/octet-stream'} return Response(content=cfrag_byte_stream, headers=headers)