def test_cert_info_is_updated_on_signed_claim_update_advances(block_processor): cert_claim_name = b'@certificate1' cert, privkey = create_cert() cert_claim_id, cert_claim_info = make_claim(block_processor, cert_claim_name, cert.serialized) signed_claim_name = b'signed-claim' value = ClaimDict.load_dict(claim_data.test_claim_dict).serialized signed_claim_id, _ = make_claim(block_processor, signed_claim_name, value, privkey, cert_claim_id) second_cert_claim_name = b'@certificate2' cert2, privkey2 = create_cert() cert2_claim_id, cert2_claim_info = make_claim(block_processor, second_cert_claim_name, cert2.serialized) value = ClaimDict.load_dict(claim_data.test_claim_dict).serialized signed_claim_id, _ = update_claim(block_processor, signed_claim_name, value, privkey2, cert2_claim_id, claim_id=signed_claim_id) block_processor.get_signed_claim_ids_by_cert_id(cert_claim_id) == [] block_processor.get_signed_claim_ids_by_cert_id(cert2_claim_id) == [ signed_claim_id ]
def test_fail_to_validate_ecdsa_sig_for_altered_claim(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) altered = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1) sd_hash = altered['stream']['source']['source'] altered['stream']['source']['source'] = sd_hash[::-1] altered_copy = ClaimDict.load_dict(altered.claim_dict) self.assertRaises(ecdsa.keys.BadSignatureError, altered_copy.validate_signature, claim_address_1, cert)
def _claim(self): return ClaimDict.load_dict({ "version": "_0_0_1", "claimType": "streamType", "stream": { "source": { "source": generate_id(self.random.getrandbits(512)).encode('hex'), "version": "_0_0_1", "contentType": "video/mp4", "sourceType": "lbry_sd_hash" }, "version": "_0_0_1", "metadata": { "license": "LBRY Inc", "description": "What is LBRY? An introduction with Alex Tabarrok", "language": "en", "title": "What is LBRY?", "author": "Samuel Bryan", "version": "_0_1_0", "nsfw": False, "licenseUrl": "", "preview": "", "thumbnail": "https://s3.amazonaws.com/files.lbry.io/logo.png" } } })
def test_remove_signature_equals_unsigned(self): unsigned = ClaimDict.load_dict(example_010) signed = unsigned.sign(nist384p_private_key, claim_address_1, claim_id_1, curve=NIST384p) self.assertEqual(unsigned.serialized, signed.serialized_no_signature)
def claim_name(self, name, amount, claim_dict, certificate=None, claim_address=None): account = self.default_account claim = ClaimDict.load_dict(claim_dict) if not claim_address: claim_address = yield account.receiving.get_or_create_usable_address( ) if certificate: claim = claim.sign(certificate.signature, claim_address, certificate.claim_id) existing_claims = yield account.get_utxos(include_claims=True, claim_name=name) if len(existing_claims) == 0: tx = yield Transaction.claim(name, claim, amount, claim_address, [account], account) elif len(existing_claims) == 1: tx = yield Transaction.update(existing_claims[0], claim, amount, claim_address, [account], account) else: raise NameError( "More than one other claim exists with the name '{}'.".format( name)) yield account.ledger.broadcast(tx) yield self.old_db.save_claims([ self._old_get_temp_claim_info(tx, tx.outputs[0], claim_address, claim_dict, name, amount) ]) # TODO: release reserved tx outputs in case anything fails by this point return tx
def claim_name(self, name, bid, metadata, certificate_id=None, claim_address=None, change_address=None): """ Claim a name, or update if name already claimed by user @param name: str, name to claim @param bid: float, bid amount @param metadata: ClaimDict compliant dict @param certificate_id: str (optional), claim id of channel certificate @param claim_address: str (optional), address to send claim to @param change_address: str (optional), address to send change @return: Deferred which returns a dict containing below items txid - txid of the resulting transaction nout - nout of the resulting claim fee - transaction fee paid to make claim claim_id - claim id of the claim """ decoded = ClaimDict.load_dict(metadata) serialized = decoded.serialized if self.get_balance() < Decimal(bid): raise InsufficientFundsError() claim = yield self._send_name_claim(name, serialized.encode('hex'), bid, certificate_id, claim_address, change_address) if not claim['success']: log.error(claim) msg = 'Claim to name {} failed: {}'.format(name, claim['reason']) raise Exception(msg) claim = self._process_claim_out(claim) yield self.storage.save_claim(self._get_temp_claim_info(claim, name, bid), smart_decode(claim['value'])) defer.returnValue(claim)
async def claim_name(self, account, name, amount, claim_dict, certificate=None, claim_address=None): claim = ClaimDict.load_dict(claim_dict) if not claim_address: claim_address = await account.receiving.get_or_create_usable_address( ) if certificate: claim = claim.sign(certificate.private_key, claim_address, certificate.claim_id, curve=SECP256k1) existing_claims = await account.get_claims(claim_name=name) if len(existing_claims) == 0: tx = await Transaction.claim(name, claim, amount, claim_address, [account], account) elif len(existing_claims) == 1: tx = await Transaction.update(existing_claims[0], claim, amount, claim_address, [account], account) else: raise NameError( "More than one other claim exists with the name '{}'.".format( name)) await account.ledger.broadcast(tx) await self.old_db.save_claims([ self._old_get_temp_claim_info(tx, tx.outputs[0], claim_address, claim_dict, name, amount) ]).asFuture(asyncio.get_event_loop()) # TODO: release reserved tx outputs in case anything fails by this point return tx
def test_remove_signature_equals_unsigned(self): unsigned = ClaimDict.load_dict(example_010) signed = unsigned.sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1) self.assertEquals(unsigned.serialized, signed.serialized_no_signature)
def test_alpha2(self): prefixes = [ 'en', 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', 'el', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu' ] for prefix in prefixes: metadata = deepcopy(example_010) metadata['stream']['metadata']['language'] = prefix claim = ClaimDict.load_dict(metadata) serialized = claim.serialized self.assertDictEqual( metadata, dict(ClaimDict.deserialize(serialized).claim_dict))
def test_validate_ecdsa_signature(self): cert = ClaimDict.generate_certificate(nist384p_private_key, curve=NIST384p) signed = ClaimDict.load_dict(example_010).sign(nist384p_private_key, claim_address_2, claim_id_1, curve=NIST384p) self.assertDictEqual(signed.claim_dict, claim_010_signed_nist384p) signed_copy = ClaimDict.load_protobuf(signed.protobuf) self.assertEquals(signed_copy.validate_signature(claim_address_2, cert), True)
def test_successful_send_name_claim(self): expected_claim_out = { "claim_id": "f43dc06256a69988bdbea09a58c80493ba15dcfa", "fee": "0.00012", "nout": 0, "success": True, "txid": "6f8180002ef4d21f5b09ca7d9648a54d213c666daf8639dc283e2fd47450269e", "value": ClaimDict.load_dict(test_claim_dict).serialized.encode('hex'), "claim_address": "", "channel_claim_id": "", "channel_name": "" } def success_send_name_claim(self, name, val, amount, certificate_id=None, claim_address=None, change_address=None): return defer.succeed(expected_claim_out) self.wallet._send_name_claim = success_send_name_claim claim_out = yield self.wallet.claim_name('test', 1, test_claim_dict) self.assertTrue('success' not in claim_out) self.assertEqual(expected_claim_out['claim_id'], claim_out['claim_id']) self.assertEqual(expected_claim_out['fee'], claim_out['fee']) self.assertEqual(expected_claim_out['nout'], claim_out['nout']) self.assertEqual(expected_claim_out['txid'], claim_out['txid']) self.assertEqual(expected_claim_out['value'], claim_out['value'])
async def test_creating_updating_and_abandoning_claim_with_channel(self): await d2f(self.account.ensure_address_gap()) address1, address2 = await d2f( self.account.receiving.get_addresses(2, only_usable=True)) sendtxid1 = await self.blockchain.send_to_address(address1, 5) sendtxid2 = await self.blockchain.send_to_address(address2, 5) await self.blockchain.generate(1) await asyncio.wait([ self.on_transaction_id(sendtxid1), self.on_transaction_id(sendtxid2), ]) self.assertEqual( round(await d2f(self.account.get_balance(0)) / COIN, 1), 10.0) cert, key = generate_certificate() cert_tx = await d2f( Transaction.claim('@bar', cert, 1 * COIN, address1, [self.account], self.account)) claim = ClaimDict.load_dict(example_claim_dict) claim = claim.sign(key, address1, cert_tx.outputs[0].claim_id) claim_tx = await d2f( Transaction.claim('foo', claim, 1 * COIN, address1, [self.account], self.account)) await self.broadcast(cert_tx) await self.broadcast(claim_tx) await asyncio.wait([ # mempool self.on_transaction_id(claim_tx.id), self.on_transaction_id(cert_tx.id), ]) await self.blockchain.generate(1) await asyncio.wait([ # confirmed self.on_transaction_id(claim_tx.id), self.on_transaction_id(cert_tx.id), ]) self.assertEqual( round(await d2f(self.account.get_balance(0)) / COIN, 1), 8.0) self.assertEqual( round(await d2f(self.account.get_balance(0, True)) / COIN, 1), 10.0) response = await d2f(self.ledger.resolve(0, 10, 'lbry://@bar/foo')) self.assertIn('lbry://@bar/foo', response) self.assertIn('claim', response['lbry://@bar/foo']) abandon_tx = await d2f( Transaction.abandon([claim_tx.outputs[0]], [self.account], self.account)) await self.broadcast(abandon_tx) await self.on_transaction(abandon_tx) await self.blockchain.generate(1) await self.on_transaction(abandon_tx) response = await d2f(self.ledger.resolve(0, 10, 'lbry://@bar/foo')) self.assertNotIn('claim', response['lbry://@bar/foo'])
def test_fail_to_validate_with_no_claim_address(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_2, claim_id_1, curve=SECP256k1) self.assertDictEqual(signed.claim_dict, claim_010_signed_secp256k1) signed_copy = ClaimDict.load_protobuf(signed.protobuf) self.assertRaises(Exception, signed_copy.validate_signature, None, cert)
def test_encode_decode(self): test_claim = ClaimDict.load_dict(example_010) self.assertEquals(test_claim.is_certificate, False) self.assertDictEqual(test_claim.claim_dict, example_010) test_pb = test_claim.protobuf self.assertDictEqual(ClaimDict.load_protobuf(test_pb).claim_dict, example_010) self.assertEquals(test_pb.ByteSize(), ClaimDict.load_protobuf(test_pb).protobuf_len) self.assertEquals(test_claim.json_len, ClaimDict.load_protobuf(test_pb).json_len)
def test_fail_to_validate_fake_ecdsa_signature(self): signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1) signed_copy = ClaimDict.load_protobuf(signed.protobuf) fake_key = get_signer(SECP256k1).generate().private_key.to_pem() fake_cert = ClaimDict.generate_certificate(fake_key, curve=SECP256k1) self.assertRaises(ecdsa.keys.BadSignatureError, signed_copy.validate_signature, claim_address_2, fake_cert)
def get_my_claim(self, name): my_claims = yield self.get_name_claims() my_claim = False for claim in my_claims: if claim['name'] == name: claim['value'] = ClaimDict.load_dict(claim['value']) my_claim = claim break defer.returnValue(my_claim)
def test_fail_to_sign_with_no_claim_address(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) self.assertRaises(Exception, ClaimDict.load_dict(example_010).sign, secp256k1_private_key, None, claim_id_1, curve=SECP256k1)
def test_validate_ecdsa_signature(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_2, claim_id_1, curve=SECP256k1) self.assertDictEqual(signed.claim_dict, claim_010_signed_secp256k1) signed_copy = ClaimDict.load_protobuf(signed.protobuf) self.assertEqual(signed_copy.validate_signature(claim_address_2, cert), True)
async def test_creating_updating_and_abandoning_claim_with_channel(self): await d2f(self.account.ensure_address_gap()) address1, address2 = await d2f(self.account.receiving.get_addresses(2, only_usable=True)) sendtxid1 = await self.blockchain.send_to_address(address1, 5) sendtxid2 = await self.blockchain.send_to_address(address2, 5) await self.blockchain.generate(1) await asyncio.wait([ self.on_transaction_id(sendtxid1), self.on_transaction_id(sendtxid2), ]) self.assertEqual(d2l(await d2f(self.account.get_balance(0))), '10.0') cert, key = generate_certificate() cert_tx = await d2f(Transaction.claim('@bar', cert, l2d('1.0'), address1, [self.account], self.account)) claim = ClaimDict.load_dict(example_claim_dict) claim = claim.sign(key, address1, cert_tx.outputs[0].claim_id) claim_tx = await d2f(Transaction.claim('foo', claim, l2d('1.0'), address1, [self.account], self.account)) await self.broadcast(cert_tx) await self.broadcast(claim_tx) await asyncio.wait([ # mempool self.on_transaction_id(claim_tx.id), self.on_transaction_id(cert_tx.id), ]) await self.blockchain.generate(1) await asyncio.wait([ # confirmed self.on_transaction_id(claim_tx.id), self.on_transaction_id(cert_tx.id), ]) self.assertEqual(d2l(await d2f(self.account.get_balance(0))), '7.985786') self.assertEqual(d2l(await d2f(self.account.get_balance(0, include_claims=True))), '9.985786') response = await d2f(self.ledger.resolve(0, 10, 'lbry://@bar/foo')) self.assertIn('lbry://@bar/foo', response) self.assertIn('claim', response['lbry://@bar/foo']) abandon_tx = await d2f(Transaction.abandon([claim_tx.outputs[0]], [self.account], self.account)) await self.broadcast(abandon_tx) await self.on_transaction(abandon_tx) await self.blockchain.generate(1) await self.on_transaction(abandon_tx) response = await d2f(self.ledger.resolve(0, 10, 'lbry://@bar/foo')) self.assertNotIn('claim', response['lbry://@bar/foo']) # checks for expected format in inexistent URIs response = await d2f(self.ledger.resolve(0, 10, 'lbry://404', 'lbry://@404')) self.assertEqual('URI lbry://404 cannot be resolved', response['lbry://404']['error']) self.assertEqual('URI lbry://@404 cannot be resolved', response['lbry://@404']['error'])
def smart_decode(claim_value): """ Decode a claim value Try decoding claim protobuf, if this fails try decoding json and migrating it. If unable to decode or migrate, raise DecodeError """ # if already decoded, return if isinstance(claim_value, ClaimDict): return claim_value elif isinstance(claim_value, dict): return ClaimDict.load_dict(claim_value) # see if we were given a hex string, try decoding it skip_hex = sum(1 if char not in hex_chars else 0 for char in claim_value) if not skip_hex: try: decoded = claim_value.decode('hex') claim_value = decoded except (TypeError, ValueError): pass if claim_value.startswith("{"): # try deserializing protobuf, if that fails try parsing from json try: decoded_json = json.loads(claim_value) except (ValueError, TypeError): try: decoded_claim = ClaimDict.deserialize(claim_value) return decoded_claim except (DecodeError, InvalidAddress, KeyError): raise DecodeError() migrated_claim = migrate_json_claim_value(decoded_json) return migrated_claim else: try: decoded_claim = ClaimDict.deserialize(claim_value) return decoded_claim except (DecodeError, InvalidAddress, KeyError): try: decoded_json = json.loads(claim_value) except (ValueError, TypeError): raise DecodeError() migrated_claim = migrate_json_claim_value(decoded_json) return migrated_claim
def migrate_003_to_010(value): migrated_to_003 = LegacyMetadata(value) metadata = {"version": "_0_1_0"} for k in [ "author", "description", "language", "license", "nsfw", "thumbnail", "title", "preview" ]: if k in migrated_to_003: metadata.update({k: migrated_to_003[k]}) if 'license_url' in migrated_to_003: metadata['licenseUrl'] = migrated_to_003['license_url'] if "fee" in migrated_to_003: fee = migrated_to_003["fee"] currency = fee.keys()[0] amount = fee[currency]['amount'] address = fee[currency]['address'] metadata.update( dict( fee={ "currency": currency, "version": "_0_0_1", "amount": amount, "address": address })) source = { "source": migrated_to_003['sources']['lbry_sd_hash'], "contentType": migrated_to_003['content_type'], "sourceType": "lbry_sd_hash", "version": "_0_0_1" } migrated = { "version": "_0_0_1", "claimType": "streamType", "stream": { "version": "_0_0_1", "metadata": metadata, "source": source } } return ClaimDict.load_dict(migrated)
def test_signed_claim_info_advance(block_processor): cert_claim_name = b'@certified-claims' cert, privkey = create_cert() cert_claim_id, expected_claim_info = make_claim(block_processor, cert_claim_name, cert.serialized) signed_claim_name = b'signed-claim' value = ClaimDict.load_dict(claim_data.test_claim_dict).serialized signed_claim_id, expected_signed_claim_info = make_claim( block_processor, signed_claim_name, value, privkey, cert_claim_id) cert_claim_info = block_processor.get_claim_info(cert_claim_id) assert_claim_info_equal(cert_claim_info, expected_claim_info) signed_cert_claim_info = block_processor.get_claim_info(signed_claim_id) assert_claim_info_equal(signed_cert_claim_info, expected_signed_claim_info) block_processor.get_signed_claim_ids_by_cert_id(cert_claim_id) == [ signed_claim_id ]
def claim_name(self, name, bid, metadata, certificate_id=None): """ Claim a name, or update if name already claimed by user @param name: str, name to claim @param bid: float, bid amount @param metadata: ClaimDict compliant dict @param certificate_id: str (optional), claim id of channel certificate @return: Deferred which returns a dict containing below items txid - txid of the resulting transaction nout - nout of the resulting claim fee - transaction fee paid to make claim claim_id - claim id of the claim """ decoded = ClaimDict.load_dict(metadata) serialized = decoded.serialized if self.get_balance() < Decimal(bid): raise InsufficientFundsError() claim = yield self._send_name_claim(name, serialized.encode('hex'), bid, certificate_id) if not claim['success']: msg = 'Claim to name {} failed: {}'.format(name, claim['reason']) raise Exception(msg) claim = self._process_claim_out(claim) claim_outpoint = ClaimOutpoint(claim['txid'], claim['nout']) log.info("Saving metadata for claim %s %d", claim['txid'], claim['nout']) yield self._update_claimid(claim['claim_id'], name, claim_outpoint) yield self._save_name_metadata(name, claim_outpoint, decoded.source_hash) defer.returnValue(claim)
def test_fail_to_load_wrong_private_key_for_cert(self): cert_claim = ClaimDict.load_dict(secp256k1_cert) self.assertEqual(cert_claim.validate_private_key(nist256p_private_key, claim_id_1), False)
def test_valid_private_key_for_cert(self): cert_claim = ClaimDict.load_dict(secp256k1_cert) self.assertEqual(cert_claim.validate_private_key(secp256k1_private_key, claim_id_1), True)
def test_invalid_cert_curve(self): with self.assertRaises(Exception): ClaimDict.load_dict(malformed_secp256k1_cert)
def test_deserialize(self): deserialized_claim = ClaimDict.deserialize( binascii.unhexlify(example_010_serialized)) self.assertDictEqual( ClaimDict.load_dict(example_010).claim_dict, deserialized_claim.claim_dict)
def _handle_claim_result(self, results): if not results: raise UnknownNameError("No results to return") if 'error' in results: if results['error'] == 'name is not claimed': raise UnknownNameError(results['error']) else: raise Exception(results['error']) if 'claim' in results: claim = results['claim'] if 'has_signature' in claim and claim['has_signature']: if not claim['signature_is_valid']: log.warning("lbry://%s#%s has an invalid signature", claim['name'], claim['claim_id']) decoded = ClaimDict.load_dict(claim['value']) claim_dict = decoded.claim_dict claim['value'] = claim_dict defer.returnValue(claim) try: decoded = smart_decode(claim['value']) claim_dict = decoded.claim_dict outpoint = ClaimOutpoint(claim['txid'], claim['nout']) name = claim['name'] claim['value'] = claim_dict claim['hex'] = decoded.serialized.encode('hex') yield self._save_name_metadata(name, outpoint, decoded.source_hash) yield self._update_claimid(claim['claim_id'], name, outpoint) except DecodeError: claim['hex'] = claim['value'] claim['value'] = None claim['error'] = "Failed to decode value" results = claim elif 'value' in results: if 'has_signature' in results and results['has_signature']: if not results['signature_is_valid']: log.warning("lbry://%s#%s has an invalid signature", results['name'], results['claim_id']) decoded = ClaimDict.load_dict(results['value']) claim_dict = decoded.claim_dict results['value'] = claim_dict defer.returnValue(results) try: decoded = ClaimDict.load_dict(results['value']) claim_dict = decoded.claim_dict claim_hex = decoded.serialized.encode('hex') claim_err = None outpoint = ClaimOutpoint(results['txid'], results['nout']) name = results['name'] yield self._save_name_metadata(name, outpoint, decoded.source_hash) yield self._update_claimid(results['claim_id'], name, outpoint) except DecodeError: claim_dict = None claim_hex = results['value'] claim_err = "Failed to decode value" if claim_err: results['error'] = claim_err results['hex'] = claim_hex results['value'] = claim_dict log.info("get claim info lbry://%s#%s", results['name'], results['claim_id']) defer.returnValue(results)
def test_deserialize(self): deserialized_claim = ClaimDict.deserialize(example_010_serialized.decode('hex')) self.assertDictEqual(ClaimDict.load_dict(example_010).claim_dict, deserialized_claim.claim_dict)