def validate_channel_signatures(self, height, new_claims, updated_claims, spent_claims): if not new_claims and not updated_claims and not spent_claims: return channels, new_channel_keys, signables = {}, {}, {} for txo in chain(new_claims, updated_claims): try: claim = txo.claim except: continue if claim.is_channel: channels[txo.claim_hash] = txo new_channel_keys[txo.claim_hash] = claim.channel.public_key_bytes else: signables[txo.claim_hash] = txo missing_channel_keys = set() for txo in signables.values(): claim = txo.claim if claim.is_signed and claim.signing_channel_hash not in new_channel_keys: missing_channel_keys.add(claim.signing_channel_hash) all_channel_keys = {} if new_channel_keys or missing_channel_keys: all_channel_keys = dict(self.execute(*query( "SELECT claim_hash, public_key_bytes FROM claim", claim_hash__in=[ sqlite3.Binary(channel_hash) for channel_hash in set(new_channel_keys) | missing_channel_keys ] ))) changed_channel_keys = {} for claim_hash, new_key in new_channel_keys.items(): if claim_hash not in all_channel_keys or all_channel_keys[claim_hash] != new_key: all_channel_keys[claim_hash] = new_key changed_channel_keys[claim_hash] = new_key claim_updates = [] for claim_hash, txo in signables.items(): claim = txo.claim update = { 'claim_hash': sqlite3.Binary(claim_hash), 'channel_hash': None, 'signature': None, 'signature_digest': None, 'is_channel_signature_valid': False } if claim.is_signed: update.update({ 'channel_hash': sqlite3.Binary(claim.signing_channel_hash), 'signature': sqlite3.Binary(txo.get_encoded_signature()), 'signature_digest': sqlite3.Binary(txo.get_signature_digest(self.ledger)) }) claim_updates.append(update) if changed_channel_keys: sql = f""" SELECT * FROM claim WHERE channel_hash IN ({','.join('?' for _ in changed_channel_keys)}) AND signature IS NOT NULL """ for affected_claim in self.execute(sql, [sqlite3.Binary(h) for h in changed_channel_keys]): if affected_claim['claim_hash'] not in signables: claim_updates.append({ 'claim_hash': sqlite3.Binary(affected_claim['claim_hash']), 'channel_hash': sqlite3.Binary(affected_claim['channel_hash']), 'signature': sqlite3.Binary(affected_claim['signature']), 'signature_digest': sqlite3.Binary(affected_claim['signature_digest']), 'is_channel_signature_valid': False }) for update in claim_updates: channel_pub_key = all_channel_keys.get(update['channel_hash']) if channel_pub_key and update['signature']: update['is_channel_signature_valid'] = Output.is_signature_valid( bytes(update['signature']), bytes(update['signature_digest']), channel_pub_key ) if claim_updates: self.db.executemany(f""" UPDATE claim SET channel_hash=:channel_hash, signature=:signature, signature_digest=:signature_digest, is_channel_signature_valid=:is_channel_signature_valid, channel_join=CASE WHEN is_channel_signature_valid AND :is_channel_signature_valid THEN channel_join WHEN :is_channel_signature_valid THEN {height} END, canonical_url=CASE WHEN is_channel_signature_valid AND :is_channel_signature_valid THEN canonical_url WHEN :is_channel_signature_valid THEN (SELECT short_url FROM claim WHERE claim_hash=:channel_hash)||'/'|| claim_name||COALESCE( (SELECT shortest_id(other_claim.claim_id, claim.claim_id) FROM claim AS other_claim WHERE other_claim.normalized = claim.normalized AND other_claim.channel_hash = :channel_hash AND other_claim.is_channel_signature_valid = 1), '#'||substr(claim_id, 1, 1) ) END WHERE claim_hash=:claim_hash; """, claim_updates) if spent_claims: self.execute( f""" UPDATE claim SET is_channel_signature_valid=0, channel_join=NULL, canonical_url=NULL WHERE channel_hash IN ({','.join('?' for _ in spent_claims)}) """, [sqlite3.Binary(cid) for cid in spent_claims] ) if channels: self.db.executemany( "UPDATE claim SET public_key_bytes=:public_key_bytes WHERE claim_hash=:claim_hash", [{ 'claim_hash': sqlite3.Binary(claim_hash), 'public_key_bytes': sqlite3.Binary(txo.claim.channel.public_key_bytes) } for claim_hash, txo in channels.items()] ) if all_channel_keys: self.db.executemany(f""" UPDATE claim SET claims_in_channel=( SELECT COUNT(*) FROM claim AS claim_in_channel WHERE claim_in_channel.channel_hash=claim.claim_hash AND claim_in_channel.is_channel_signature_valid ) WHERE claim_hash = ? """, [(sqlite3.Binary(channel_hash),) for channel_hash in all_channel_keys.keys()])
def validate_channel_signatures(self, height, new_claims, updated_claims, spent_claims, timer): if not new_claims and not updated_claims and not spent_claims: return sub_timer = timer.add_timer('segregate channels and signables') sub_timer.start() channels, new_channel_keys, signables = {}, {}, {} for txo in chain(new_claims, updated_claims): try: claim = txo.claim except: continue if claim.is_channel: channels[txo.claim_hash] = txo new_channel_keys[ txo.claim_hash] = claim.channel.public_key_bytes else: signables[txo.claim_hash] = txo sub_timer.stop() sub_timer = timer.add_timer('make list of channels we need to lookup') sub_timer.start() missing_channel_keys = set() for txo in signables.values(): claim = txo.claim if claim.is_signed and claim.signing_channel_hash not in new_channel_keys: missing_channel_keys.add(claim.signing_channel_hash) sub_timer.stop() sub_timer = timer.add_timer('lookup missing channels') sub_timer.start() all_channel_keys = {} if new_channel_keys or missing_channel_keys: all_channel_keys = dict( self.execute( *query("SELECT claim_hash, public_key_bytes FROM claim", claim_hash__in=[ sqlite3.Binary(channel_hash) for channel_hash in set(new_channel_keys) | missing_channel_keys ]))) sub_timer.stop() sub_timer = timer.add_timer('prepare for updating claims') sub_timer.start() changed_channel_keys = {} for claim_hash, new_key in new_channel_keys.items(): if claim_hash not in all_channel_keys or all_channel_keys[ claim_hash] != new_key: all_channel_keys[claim_hash] = new_key changed_channel_keys[claim_hash] = new_key claim_updates = [] for claim_hash, txo in signables.items(): claim = txo.claim update = { 'claim_hash': sqlite3.Binary(claim_hash), 'channel_hash': None, 'signature': None, 'signature_digest': None, 'signature_valid': None } if claim.is_signed: update.update({ 'channel_hash': sqlite3.Binary(claim.signing_channel_hash), 'signature': sqlite3.Binary(txo.get_encoded_signature()), 'signature_digest': sqlite3.Binary(txo.get_signature_digest(self.ledger)), 'signature_valid': 0 }) claim_updates.append(update) sub_timer.stop() sub_timer = timer.add_timer( 'find claims affected by a change in channel key') sub_timer.start() if changed_channel_keys: sql = f""" SELECT * FROM claim WHERE channel_hash IN ({','.join('?' for _ in changed_channel_keys)}) AND signature IS NOT NULL """ for affected_claim in self.execute( sql, [sqlite3.Binary(h) for h in changed_channel_keys]): if affected_claim['claim_hash'] not in signables: claim_updates.append({ 'claim_hash': sqlite3.Binary(affected_claim['claim_hash']), 'channel_hash': sqlite3.Binary(affected_claim['channel_hash']), 'signature': sqlite3.Binary(affected_claim['signature']), 'signature_digest': sqlite3.Binary(affected_claim['signature_digest']), 'signature_valid': 0 }) sub_timer.stop() sub_timer = timer.add_timer('verify signatures') sub_timer.start() for update in claim_updates: channel_pub_key = all_channel_keys.get(update['channel_hash']) if channel_pub_key and update['signature']: update['signature_valid'] = Output.is_signature_valid( bytes(update['signature']), bytes(update['signature_digest']), channel_pub_key) sub_timer.stop() sub_timer = timer.add_timer('update claims') sub_timer.start() if claim_updates: self.db.executemany( f""" UPDATE claim SET channel_hash=:channel_hash, signature=:signature, signature_digest=:signature_digest, signature_valid=:signature_valid, channel_join=CASE WHEN signature_valid=1 AND :signature_valid=1 THEN channel_join WHEN :signature_valid=1 THEN {height} END, canonical_url=CASE WHEN signature_valid=1 AND :signature_valid=1 THEN canonical_url WHEN :signature_valid=1 THEN (SELECT short_url FROM claim WHERE claim_hash=:channel_hash)||'/'|| claim_name||COALESCE( (SELECT shortest_id(other_claim.claim_id, claim.claim_id) FROM claim AS other_claim WHERE other_claim.signature_valid = 1 AND other_claim.channel_hash = :channel_hash AND other_claim.normalized = claim.normalized), '#'||substr(claim_id, 1, 1) ) END WHERE claim_hash=:claim_hash; """, claim_updates) sub_timer.stop() sub_timer = timer.add_timer('update claims affected by spent channels') sub_timer.start() if spent_claims: self.execute( f""" UPDATE claim SET signature_valid=CASE WHEN signature IS NOT NULL THEN 0 END, channel_join=NULL, canonical_url=NULL WHERE channel_hash IN ({','.join('?' for _ in spent_claims)}) """, [sqlite3.Binary(cid) for cid in spent_claims]) sub_timer.stop() sub_timer = timer.add_timer('update channels') sub_timer.start() if channels: self.db.executemany( """ UPDATE claim SET public_key_bytes=:public_key_bytes, public_key_hash=:public_key_hash WHERE claim_hash=:claim_hash""", [{ 'claim_hash': sqlite3.Binary(claim_hash), 'public_key_bytes': sqlite3.Binary(txo.claim.channel.public_key_bytes), 'public_key_hash': sqlite3.Binary( self.ledger.address_to_hash160( self.ledger.public_key_to_address( txo.claim.channel.public_key_bytes))) } for claim_hash, txo in channels.items()]) sub_timer.stop() sub_timer = timer.add_timer('update claims_in_channel counts') sub_timer.start() if all_channel_keys: self.db.executemany( f""" UPDATE claim SET claims_in_channel=( SELECT COUNT(*) FROM claim AS claim_in_channel WHERE claim_in_channel.signature_valid=1 AND claim_in_channel.channel_hash=claim.claim_hash ) WHERE claim_hash = ? """, [(sqlite3.Binary(channel_hash), ) for channel_hash in all_channel_keys.keys()]) sub_timer.stop()