class TransferTicket(TicketModelBase): methods = { "public_key": PubkeyField(), "recipient": PubkeyField(), "imagedata_hash": SHA3512Field(), "copies": IntegerField(minsize=0, maxsize=1000), } def validate(self, chainwrapper, artregistry): # make sure artwork is properly registered artregistry.get_ticket_for_artwork(self.imagedata_hash)
class Signature(TicketModelBase): methods = { "signature": SignatureField(), "pubkey": PubkeyField(), } def validate(self, ticket): if not pastel_id_verify_signature_with_public_key_func(ticket.serialize(), self.signature, self.pubkey): raise ValueError("Invalid signature")
class ActivationTicket(TicketModelBase): methods = { # mandatory fields for Final Ticket "author": PubkeyField(), "order_block_txid": TXIDField(), "registration_ticket_txid": TXIDField(), } def validate(self, chainwrapper, image): # TODO: # X validate that this ticket references a valid regticket # X regticket is on chain # X regticket is not yet activated # X regticket signatures are valid # X validate metadata: # X fingerprints matches image # X lubyhashes matches image # X thumbnailhash matches image # X validate image # X image actually hashes to imagedata_hash in the regticket # X image is sfw # X luby chunks generate the image # TODO: check that final_regticket ticket is not activated yet # validate image image.validate() # get registration ticket final_regticket = chainwrapper.retrieve_ticket(self.registration_ticket_txid) # validate final ticket final_regticket.validate(chainwrapper) # validate registration ticket regticket = final_regticket.ticket # validate that the authors match require_true(regticket.author == self.author) # validate that imagehash, fingerprints, lubyhashes and thumbnailhash indeed belong to the image require_true(regticket.fingerprints == image.generate_fingerprints()) # TODO: is this deterministic? require_true(regticket.lubyhashes == image.get_luby_hashes()) require_true(regticket.lubyseeds == image.get_luby_seeds()) require_true(regticket.thumbnailhash == image.get_thumbnail_hash()) # validate that MN order matches between registration ticket and activation ticket require_true(regticket.order_block_txid == self.order_block_txid) # image hash matches regticket hash require_true(regticket.imagedata_hash == image.get_artwork_hash()) # run nsfw check if NSFWDetector.is_nsfw(image.image): raise ValueError("Image is NSFW, score: %s" % NSFWDetector.get_score(image.image))
class IDTicket(TicketModelBase): methods = { # mandatory fields for Final Ticket "blockchain_address": BlockChainAddressField(), "public_key": PubkeyField(), "ticket_submission_time": UnixTimeField(), } def validate(self): # TODO: finish # Validate: # o x time hasn't passed # o x blocks hasn't passed # o blockchain address is legit pass
class TestPubkeyField(unittest.TestCase): def setUp(self): self.v = PubkeyField() def test_type(self): with self.assertRaises(TypeError): self.v.validate('test') def test_min(self): with self.assertRaises(ValueError): self.v.validate(b'W' * 65) def test_max(self): with self.assertRaises(ValueError): self.v.validate(b'Z' * 67) def test_valid(self): self.v.validate(b'Y' * 66)
class TradeTicket(TicketModelBase): methods = { "public_key": PubkeyField(), "imagedata_hash": SHA3512Field(), "type": StringChoiceField(choices=["ask", "bid"]), "copies": IntegerField(minsize=0, maxsize=1000), "price": IntegerField(minsize=0, maxsize=2 ** 32 - 1), "watched_address": BlockChainAddressField(), "collateral_txid": TXIDField(), "expiration": IntegerField(minsize=0, maxsize=1000), # x==0 means never expire, x > 0 mean X blocks } def validate(self, blockchain, chainwrapper, artregistry): # make sure artwork is properly registered artregistry.get_ticket_for_artwork(self.imagedata_hash) # if this is a bid, validate collateral if self.type == "bid": # does the collateral utxo exist? transaction = blockchain.getrawtransaction(self.collateral_txid, 1) # is the utxo a new one we are not currently watching? - this is to prevent a user reusing a collateral _, listen_utxos = artregistry.get_listen_addresses_and_utxos() require_true(self.collateral_txid not in listen_utxos) # validate collateral valid = False for vout in transaction["vout"]: if len(vout["scriptPubKey"]["addresses"]) > 1: continue # validate address and amount of collateral value = vout["value"] address = vout["scriptPubKey"]["addresses"][0] if address == self.watched_address and value == self.copies * self.price: valid = True break if not valid: raise ValueError("UTXO does not contain valid address as vout")
class RegistrationTicket(TicketModelBase): methods = { # mandatory fields for Final Ticket "author": PubkeyField(), # "author_wallet": BlockChainAddressField(), "order_block_txid": TXIDField(), "blocknum": IntegerField(minsize=0, maxsize=9999999999999), "imagedata_hash": SHA3512Field(), "artist_name": StringField(minsize=0, maxsize=120), "artist_website": StringField(minsize=0, maxsize=120), "artist_written_statement": StringField(minsize=0, maxsize=120), "artwork_title": StringField(minsize=0, maxsize=120), "artwork_series_name": StringField(minsize=0, maxsize=120), "artwork_creation_video_youtube_url": StringField(minsize=0, maxsize=120), "artwork_keyword_set": StringField(minsize=0, maxsize=120), "total_copies": IntegerField(minsize=0, maxsize=120), "fingerprints": FingerprintField(), "lubyhashes": LubyChunkHashField(), "lubyseeds": LubySeedField(), "thumbnailhash": SHA3512Field(), } def validate(self, chainwrapper): # we have no way to check these but will do so on activation: # o fingerprints # o lubyhashes # o thumbnailhash # # after these checks are done we know that fingerprints are not dupes and there is no race # validate that lubyhashes and lubychunks are the same length require_true(len(self.lubyhashes) == len(self.lubyseeds)) # validate that order txid is not too old block_distance = chainwrapper.get_block_distance(chainwrapper.get_last_block_hash(), self.order_block_txid) if block_distance > NetWorkSettings.MAX_REGISTRATION_BLOCK_DISTANCE: raise ValueError("Block distance between order_block_height and current block is too large!") # validate that art hash doesn't exist: # TODO: move this artwork index logic into chainwrapper fingerprint_db = {} if NetWorkSettings.LONG_REGTICKET_VALIDATION_ENABLED: for txid, ticket in chainwrapper.all_ticket_iterator(): if type(ticket) == FinalRegistrationTicket: ticket.validate(chainwrapper) else: continue regticket = ticket.ticket # collect fingerprints # TODO: only collect this for activated regtickets and tickets not older than X blocks fingerprint_db[regticket.imagedata_hash] = ("DUMMY_PATH", regticket.fingerprints) # TODO: do we need this? # validate that this art hash does not yet exist on the blockchain # TODO: only prohibit registration when this was registered in the past X blocks # TODO: if regticket is activated: prohibit registration forever require_true(regticket.imagedata_hash != self.imagedata_hash) # validate that fingerprints are not dupes if len(fingerprint_db) > 0: # TODO: check for fingerprint dupes if NetWorkSettings.DUPE_DETECTION_ENABLED: pandas_table = assemble_fingerprints_for_pandas([(k, v) for k, v in fingerprint_db.items()]) is_duplicate, params_df = measure_similarity(self.fingerprints, pandas_table) if is_duplicate: raise ValueError("Image failed fingerprint check!")
def setUp(self): self.v = PubkeyField()