add tests and fix verification of old signatures

This commit is contained in:
Victor Shyba 2019-03-23 01:07:22 -03:00 committed by Lex Berezhny
parent 0830917afb
commit a9e0eeba7e
4 changed files with 87 additions and 25 deletions

View file

@ -50,7 +50,7 @@ class MainNetLedger(BaseLedger):
@property @property
def resolver(self): def resolver(self):
return Resolver(self.headers.claim_trie_root, self.headers.height, self.transaction_class, 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): async def resolve(self, page, page_size, *uris):
for uri in uris: for uri in uris:

View file

@ -1,6 +1,6 @@
import logging import logging
from ecdsa import BadSignatureError from cryptography.exceptions import InvalidSignature
from binascii import unhexlify, hexlify from binascii import unhexlify, hexlify
from lbrynet.wallet.dewies import dewies_to_lbc from lbrynet.wallet.dewies import dewies_to_lbc
from lbrynet.error import UnknownNameError, UnknownClaimID, UnknownURI, UnknownOutpoint from lbrynet.error import UnknownNameError, UnknownClaimID, UnknownURI, UnknownOutpoint
@ -15,12 +15,13 @@ log = logging.getLogger(__name__)
class Resolver: 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.claim_trie_root = claim_trie_root
self.height = height self.height = height
self.transaction_class = transaction_class self.transaction_class = transaction_class
self.hash160_to_address = hash160_to_address self.hash160_to_address = hash160_to_address
self.network = network self.network = network
self.ledger = ledger
async def _handle_resolutions(self, resolutions, requested_uris, page, page_size): async def _handle_resolutions(self, resolutions, requested_uris, page, page_size):
results = {} results = {}
@ -188,8 +189,7 @@ class Resolver:
claim_result['has_signature'] = True claim_result['has_signature'] = True
claim_result['signature_is_valid'] = False claim_result['signature_is_valid'] = False
validated, channel_name = validate_claim_signature_and_get_channel_name( validated, channel_name = validate_claim_signature_and_get_channel_name(
claim_result, certificate, claim_result['address'], claim_result['name'], claim_result, certificate, self.ledger, claim_tx=claim_tx, cert_tx=cert_tx
claim_tx=claim_tx, cert_tx=cert_tx
) )
claim_result['channel_name'] = channel_name claim_result['channel_name'] = channel_name
if validated: if validated:
@ -389,33 +389,21 @@ def _verify_proof(name, claim_trie_root, result, height, depth, transaction_clas
return {'error': "proof not in result"} return {'error': "proof not in result"}
def validate_claim_signature_and_get_channel_name(claim_result, certificate_claim, def validate_claim_signature_and_get_channel_name(claim_result, certificate_claim, ledger,
claim_address, name, decoded_certificate=None,
claim_tx=None, cert_tx=None): claim_tx=None, cert_tx=None):
if cert_tx and certificate_claim and claim_tx and claim_result: if cert_tx and certificate_claim and claim_tx and claim_result:
tx = Transaction(unhexlify(claim_tx)) tx = Transaction(unhexlify(claim_tx))
cert_tx = Transaction(unhexlify(cert_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 is_signed, certificate_claim['name']
return False, None 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. # 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): def _decode_claim_result(claim):
if 'has_signature' in claim and claim['has_signature']: if 'has_signature' in claim and claim['has_signature']:

View file

@ -96,6 +96,7 @@ class Output(BaseOutput):
def is_signed_by(self, channel: 'Output', ledger=None): def is_signed_by(self, channel: 'Output', ledger=None):
if self.claim.unsigned_payload: if self.claim.unsigned_payload:
pieces = [ pieces = [
self.claim_name.lower().encode(),
Base58.decode(self.get_address(ledger)), Base58.decode(self.get_address(ledger)),
self.claim.unsigned_payload, self.claim.unsigned_payload,
self.claim.signing_channel_hash self.claim.signing_channel_hash

View file

@ -1,12 +1,16 @@
import hashlib
import tempfile import tempfile
from binascii import unhexlify 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.error import InsufficientFundsError
from lbrynet.schema.claim import Claim from lbrynet.schema.claim import Claim
from lbrynet.schema.compat import OldClaimMessage from lbrynet.schema.compat import OldClaimMessage
from integration.testcase import CommandTestCase from integration.testcase import CommandTestCase
from torba.client.hash import sha256, Base58
class ClaimCommands(CommandTestCase): class ClaimCommands(CommandTestCase):
@ -402,3 +406,72 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(c2['claim_id'], r4c) self.assertEqual(c2['claim_id'], r4c)
self.assertEqual(r3c, r4c) self.assertEqual(r3c, r4c)
self.assertEqual(r3n, r4n) 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