From a9e0eeba7e78fe837e0a62088eefc58797a4c32d Mon Sep 17 00:00:00 2001 From: Victor Shyba Date: Sat, 23 Mar 2019 01:07:22 -0300 Subject: [PATCH] add tests and fix verification of old signatures --- lbrynet/wallet/ledger.py | 2 +- lbrynet/wallet/resolve.py | 34 ++++------- lbrynet/wallet/transaction.py | 1 + tests/integration/test_claim_commands.py | 75 +++++++++++++++++++++++- 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/lbrynet/wallet/ledger.py b/lbrynet/wallet/ledger.py index c7bf1f5af..a39e15233 100644 --- a/lbrynet/wallet/ledger.py +++ b/lbrynet/wallet/ledger.py @@ -50,7 +50,7 @@ class MainNetLedger(BaseLedger): @property def resolver(self): return Resolver(self.headers.claim_trie_root, self.headers.height, self.transaction_class, - hash160_to_address=self.hash160_to_address, network=self.network) + hash160_to_address=self.hash160_to_address, network=self.network, ledger=self) async def resolve(self, page, page_size, *uris): for uri in uris: diff --git a/lbrynet/wallet/resolve.py b/lbrynet/wallet/resolve.py index f295f5d67..664a3cad4 100644 --- a/lbrynet/wallet/resolve.py +++ b/lbrynet/wallet/resolve.py @@ -1,6 +1,6 @@ import logging -from ecdsa import BadSignatureError +from cryptography.exceptions import InvalidSignature from binascii import unhexlify, hexlify from lbrynet.wallet.dewies import dewies_to_lbc from lbrynet.error import UnknownNameError, UnknownClaimID, UnknownURI, UnknownOutpoint @@ -15,12 +15,13 @@ log = logging.getLogger(__name__) class Resolver: - def __init__(self, claim_trie_root, height, transaction_class, hash160_to_address, network): + def __init__(self, claim_trie_root, height, transaction_class, hash160_to_address, network, ledger): self.claim_trie_root = claim_trie_root self.height = height self.transaction_class = transaction_class self.hash160_to_address = hash160_to_address self.network = network + self.ledger = ledger async def _handle_resolutions(self, resolutions, requested_uris, page, page_size): results = {} @@ -188,8 +189,7 @@ class Resolver: claim_result['has_signature'] = True claim_result['signature_is_valid'] = False validated, channel_name = validate_claim_signature_and_get_channel_name( - claim_result, certificate, claim_result['address'], claim_result['name'], - claim_tx=claim_tx, cert_tx=cert_tx + claim_result, certificate, self.ledger, claim_tx=claim_tx, cert_tx=cert_tx ) claim_result['channel_name'] = channel_name if validated: @@ -389,33 +389,21 @@ def _verify_proof(name, claim_trie_root, result, height, depth, transaction_clas return {'error': "proof not in result"} -def validate_claim_signature_and_get_channel_name(claim_result, certificate_claim, - claim_address, name, decoded_certificate=None, +def validate_claim_signature_and_get_channel_name(claim_result, certificate_claim, ledger, claim_tx=None, cert_tx=None): if cert_tx and certificate_claim and claim_tx and claim_result: tx = Transaction(unhexlify(claim_tx)) cert_tx = Transaction(unhexlify(cert_tx)) - is_signed = tx.outputs[claim_result['nout']].is_signed_by(cert_tx.outputs[certificate_claim['nout']]) + try: + is_signed = tx.outputs[claim_result['nout']].is_signed_by( + cert_tx.outputs[certificate_claim['nout']], ledger + ) + except InvalidSignature: + return False, None return is_signed, certificate_claim['name'] return False, None -def _validate_signed_claim(claim, claim_address, name, certificate): - if not claim.has_signature: - raise Exception("Claim is not signed") - try: - if claim.validate_signature(claim_address, certificate.protobuf, name): - return True - except BadSignatureError: - # print_msg("Signature for %s is invalid" % claim_id) - return False - except Exception as err: - log.error("Signature for %s is invalid, reason: %s - %s", claim_address, - str(type(err)), err) - return False - return False - - # TODO: The following came from code handling lbryum results. Now that it's all in one place a refactor should unify it. def _decode_claim_result(claim): if 'has_signature' in claim and claim['has_signature']: diff --git a/lbrynet/wallet/transaction.py b/lbrynet/wallet/transaction.py index 202fdb91a..c1a4e8aca 100644 --- a/lbrynet/wallet/transaction.py +++ b/lbrynet/wallet/transaction.py @@ -96,6 +96,7 @@ class Output(BaseOutput): def is_signed_by(self, channel: 'Output', ledger=None): if self.claim.unsigned_payload: pieces = [ + self.claim_name.lower().encode(), Base58.decode(self.get_address(ledger)), self.claim.unsigned_payload, self.claim.signing_channel_hash diff --git a/tests/integration/test_claim_commands.py b/tests/integration/test_claim_commands.py index 5e01dd8f4..d1a30514d 100644 --- a/tests/integration/test_claim_commands.py +++ b/tests/integration/test_claim_commands.py @@ -1,12 +1,16 @@ +import hashlib import tempfile from binascii import unhexlify -from lbrynet.wallet.transaction import Transaction +import ecdsa + +from lbrynet.wallet.transaction import Transaction, Output from lbrynet.error import InsufficientFundsError from lbrynet.schema.claim import Claim from lbrynet.schema.compat import OldClaimMessage from integration.testcase import CommandTestCase +from torba.client.hash import sha256, Base58 class ClaimCommands(CommandTestCase): @@ -402,3 +406,72 @@ class ClaimCommands(CommandTestCase): self.assertEqual(c2['claim_id'], r4c) self.assertEqual(r3c, r4c) self.assertEqual(r3n, r4n) + + async def test_resolve_old_claim(self): + channel = await self.daemon.jsonrpc_channel_new('@olds', "1.0") + self.assertTrue(channel['success']) + await self.confirm_tx(channel['tx'].id) + address = channel['output'].get_address(self.account.ledger) + claim = generate_signed_legacy('example', address, channel['output']) + tx = await Transaction.claim('example', claim.SerializeToString(), 1, address, [self.account], self.account) + await tx.sign([self.account]) + await self.broadcast(tx) + await self.ledger.wait(tx) + await self.generate(1) + await self.ledger.wait(tx) + + response = await self.daemon.jsonrpc_resolve(urls='@olds/example') + self.assertTrue(response['@olds/example']['claim']['signature_is_valid']) + + claim.publisherSignature.signature = bytes(reversed(claim.publisherSignature.signature)) + tx = await Transaction.claim( + 'bad_example', claim.SerializeToString(), 1, address, [self.account], self.account + ) + await tx.sign([self.account]) + await self.broadcast(tx) + await self.ledger.wait(tx) + await self.generate(1) + await self.ledger.wait(tx) + + response = await self.daemon.jsonrpc_resolve(urls='bad_example') + self.assertFalse(response['bad_example']['claim']['signature_is_valid'], response) + response = await self.daemon.jsonrpc_resolve(urls='@olds/bad_example') + self.assertEqual('URI lbry://@olds/bad_example cannot be resolved', response['@olds/bad_example']['error']) + + +def generate_signed_legacy(name: str, address: bytes, output: Output): + decoded_address = Base58.decode(address) + claim = OldClaimMessage() + claim.ParseFromString(unhexlify( + '080110011aee04080112a604080410011a2b4865726520617265203520526561736f6e73204920e29da4e' + 'fb88f204e657874636c6f7564207c20544c4722920346696e64206f7574206d6f72652061626f7574204e' + '657874636c6f75643a2068747470733a2f2f6e657874636c6f75642e636f6d2f0a0a596f752063616e206' + '6696e64206d65206f6e20746865736520736f6369616c733a0a202a20466f72756d733a2068747470733a' + '2f2f666f72756d2e6865617679656c656d656e742e696f2f0a202a20506f64636173743a2068747470733' + 'a2f2f6f6666746f706963616c2e6e65740a202a2050617472656f6e3a2068747470733a2f2f7061747265' + '6f6e2e636f6d2f7468656c696e757867616d65720a202a204d657263683a2068747470733a2f2f7465657' + '37072696e672e636f6d2f73746f7265732f6f6666696369616c2d6c696e75782d67616d65720a202a2054' + '77697463683a2068747470733a2f2f7477697463682e74762f786f6e64616b0a202a20547769747465723' + 'a2068747470733a2f2f747769747465722e636f6d2f7468656c696e757867616d65720a0a2e2e2e0a6874' + '7470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d4672546442434f535f66632a0' + 'f546865204c696e75782047616d6572321c436f7079726967687465642028636f6e746163742061757468' + '6f722938004a2968747470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f46725464424' + '34f535f666352005a001a41080110011a30040e8ac6e89c061f982528c23ad33829fd7146435bf7a4cc22' + 'f0bff70c4fe0b91fd36da9a375e3e1c171db825bf5d1f32209766964656f2f6d70342a5c080110031a406' + '2b2dd4c45e364030fbfad1a6fefff695ebf20ea33a5381b947753e2a0ca359989a5cc7d15e5392a0d354c' + '0b68498382b2701b22c03beb8dcb91089031b871e72214feb61536c007cdf4faeeaab4876cb397feaf6b51' + )) + claim.ClearField("publisherSignature") + digest = sha256(b''.join([ + name.lower().encode(), + decoded_address, + claim.SerializeToString(), + output.claim_hash + ])) + private_key = ecdsa.SigningKey.from_pem(output.private_key, hashfunc=hashlib.sha256) + signature = private_key.sign_digest_deterministic(digest, hashfunc=hashlib.sha256) + claim.publisherSignature.version = 1 + claim.publisherSignature.signatureType = 1 + claim.publisherSignature.signature = signature + claim.publisherSignature.certificateId = output.claim_hash + return claim \ No newline at end of file