This commit is contained in:
Lex Berezhny 2019-03-18 00:59:13 -04:00
parent 323694fb12
commit 1ec8f0b0b4
5 changed files with 166 additions and 115 deletions

View file

@ -6,7 +6,7 @@ from binascii import unhexlify
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from lbrynet.schema.constants import SECP256k1 from lbrynet.schema.claim import Claim
from torba.client.basemanager import BaseWalletManager from torba.client.basemanager import BaseWalletManager
from torba.rpc.jsonrpc import CodeMessageError from torba.rpc.jsonrpc import CodeMessageError
@ -394,15 +394,11 @@ class LbryWalletManager(BaseWalletManager):
def get_utxos(account: BaseAccount): def get_utxos(account: BaseAccount):
return account.get_utxos() return account.get_utxos()
async def claim_name(self, account, name, amount, claim_dict, certificate=None, claim_address=None): async def claim_name(self, account, name, amount, claim: Claim, certificate=None, claim_address=None):
claim = ClaimDict.load_dict(claim_dict)
if not claim_address: if not claim_address:
claim_address = await account.receiving.get_or_create_usable_address() claim_address = await account.receiving.get_or_create_usable_address()
if certificate: if certificate:
claim = claim.sign( claim = claim.sign(certificate.claim_id, certificate.private_key)
certificate.private_key, claim_address, certificate.claim_id, curve=SECP256k1, name=name,
force_detached=False # TODO: delete it and make True default everywhere when its out
)
existing_claims = await account.get_claims( existing_claims = await account.get_claims(
claim_name_type__any={'is_claim': 1, 'is_update': 1}, # exclude is_supports claim_name_type__any={'is_claim': 1, 'is_update': 1}, # exclude is_supports
claim_name=name claim_name=name
@ -417,9 +413,12 @@ class LbryWalletManager(BaseWalletManager):
) )
else: else:
raise NameError(f"More than one other claim exists with the name '{name}'.") raise NameError(f"More than one other claim exists with the name '{name}'.")
if certificate:
claim.sign(certificate.claim_id, certificate.private_key, tx.inputs[0].txo_ref.id.encode())
tx._reset()
await account.ledger.broadcast(tx) await account.ledger.broadcast(tx)
await self.old_db.save_claims([self._old_get_temp_claim_info( await self.old_db.save_claims([self._old_get_temp_claim_info(
tx, tx.outputs[0], claim_address, claim_dict, name, dewies_to_lbc(amount) tx, tx.outputs[0], claim_address, claim, name, dewies_to_lbc(amount)
)]) )])
# TODO: release reserved tx outputs in case anything fails by this point # TODO: release reserved tx outputs in case anything fails by this point
return tx return tx

View file

@ -4,8 +4,7 @@ from typing import List, Iterable, Optional
from torba.client.basetransaction import BaseTransaction, BaseInput, BaseOutput from torba.client.basetransaction import BaseTransaction, BaseInput, BaseOutput
from torba.client.hash import hash160 from torba.client.hash import hash160
from lbrynet.schema.decode import smart_decode from lbrynet.schema.claim import Claim
from lbrynet.schema.claim import ClaimDict
from lbrynet.extras.wallet.account import Account from lbrynet.extras.wallet.account import Account
from lbrynet.extras.wallet.script import InputScript, OutputScript from lbrynet.extras.wallet.script import InputScript, OutputScript
@ -19,12 +18,12 @@ class Output(BaseOutput):
script: OutputScript script: OutputScript
script_class = OutputScript script_class = OutputScript
__slots__ = '_claim_dict', 'channel', 'private_key' __slots__ = '_claim', 'channel', 'private_key'
def __init__(self, *args, channel: Optional['Output'] = None, def __init__(self, *args, channel: Optional['Output'] = None,
private_key: Optional[str] = None, **kwargs) -> None: private_key: Optional[str] = None, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._claim_dict = None self._claim = None
self.channel = channel self.channel = channel
self.private_key = private_key self.private_key = private_key
@ -60,17 +59,13 @@ class Output(BaseOutput):
raise ValueError('No claim_name associated.') raise ValueError('No claim_name associated.')
@property @property
def claim(self) -> ClaimDict: def claim(self) -> Claim:
if self.is_claim: if self.is_claim:
return smart_decode(self.script.values['claim']) if self._claim is None:
self._claim = Claim.from_bytes(self.script.values['claim'])
return self._claim
raise ValueError('Only claim name and claim update have the claim payload.') raise ValueError('Only claim name and claim update have the claim payload.')
@property
def claim_dict(self) -> dict:
if self._claim_dict is None:
self._claim_dict = self.claim.claim_dict
return self._claim_dict
@property @property
def permanent_url(self) -> str: def permanent_url(self) -> str:
if self.script.is_claim_involved: if self.script.is_claim_involved:
@ -124,11 +119,11 @@ class Transaction(BaseTransaction):
return cls.create([], [output], funding_accounts, change_account) return cls.create([], [output], funding_accounts, change_account)
@classmethod @classmethod
def claim(cls, name: str, meta: ClaimDict, amount: int, holding_address: bytes, def claim(cls, name: str, meta: Claim, amount: int, holding_address: bytes,
funding_accounts: List[Account], change_account: Account): funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account) ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
claim_output = Output.pay_claim_name_pubkey_hash( claim_output = Output.pay_claim_name_pubkey_hash(
amount, name, meta.serialized, ledger.address_to_hash160(holding_address) amount, name, meta.to_bytes(), ledger.address_to_hash160(holding_address)
) )
return cls.create([], [claim_output], funding_accounts, change_account) return cls.create([], [claim_output], funding_accounts, change_account)
@ -142,12 +137,12 @@ class Transaction(BaseTransaction):
return cls.create([], [claim_output], funding_accounts, change_account) return cls.create([], [claim_output], funding_accounts, change_account)
@classmethod @classmethod
def update(cls, previous_claim: Output, meta: ClaimDict, amount: int, holding_address: bytes, def update(cls, previous_claim: Output, meta: Claim, amount: int, holding_address: bytes,
funding_accounts: List[Account], change_account: Account): funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account) ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
updated_claim = Output.pay_update_claim_pubkey_hash( updated_claim = Output.pay_update_claim_pubkey_hash(
amount, previous_claim.claim_name, previous_claim.claim_id, amount, previous_claim.claim_name, previous_claim.claim_id,
meta.serialized, ledger.address_to_hash160(holding_address) meta.to_bytes(), ledger.address_to_hash160(holding_address)
) )
return cls.create([Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account) return cls.create([Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account)

View file

@ -3,6 +3,15 @@ from collections import OrderedDict
from typing import List, Tuple from typing import List, Tuple
from decimal import Decimal from decimal import Decimal
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from hashlib import sha256
import ecdsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_der_public_key
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from ecdsa.util import sigencode_der
from google.protobuf import json_format # pylint: disable=no-name-in-module from google.protobuf import json_format # pylint: disable=no-name-in-module
from google.protobuf.message import DecodeError as DecodeError_pb # pylint: disable=no-name-in-module,import-error from google.protobuf.message import DecodeError as DecodeError_pb # pylint: disable=no-name-in-module,import-error
@ -204,10 +213,14 @@ class ClaimDict(OrderedDict):
class Claim: class Claim:
__slots__ = '_claim', __slots__ = '_claim', 'signature', 'certificate_id', 'signature_type', 'unsigned_payload'
def __init__(self, claim_message=None): def __init__(self, claim_message=None):
self._claim = claim_message or ClaimMessage() self._claim = claim_message or ClaimMessage()
self.signature = None
self.signature_type = 'SECP256k1'
self.certificate_id = None
self.unsigned_payload = None
@property @property
def is_undetermined(self): def is_undetermined(self):
@ -221,6 +234,40 @@ class Claim:
def is_channel(self): def is_channel(self):
return self._claim.WhichOneof('type') == 'channel' return self._claim.WhichOneof('type') == 'channel'
@property
def is_signed(self):
return self.signature is not None
def is_signed_by(self, channel: 'Channel', claim_address):
if self.unsigned_payload:
digest = sha256(b''.join([
claim_address, self.unsigned_payload, self.certificate_id
])).digest()
public_key = load_der_public_key(channel.public_key_bytes, default_backend())
hash = hashes.SHA256()
signature = hexlify(self.signature)
r = int(signature[:int(len(signature)/2)], 16)
s = int(signature[int(len(signature)/2):], 16)
encoded_sig = sigencode_der(r, s, len(signature)*4)
public_key.verify(encoded_sig, digest, ec.ECDSA(Prehashed(hash)))
return True
else:
digest = sha256(b''.join([
self.certificate_id.encode(),
first_input_txid_nout.encode(),
self.to_bytes()
])).digest()
def sign(self, certificate_id: str, private_key_text: str, first_input_txid_nout):
digest = sha256(b''.join([
certificate_id.encode(),
first_input_txid_nout.encode(),
self.to_bytes()
])).digest()
private_key = ecdsa.SigningKey.from_pem(private_key_text, hashfunc="sha256")
self.signature = private_key.sign_digest_deterministic(digest, hashfunc="sha256")
self.certificate_id = certificate_id
@property @property
def stream_message(self): def stream_message(self):
if self.is_undetermined: if self.is_undetermined:
@ -400,6 +447,95 @@ class Fee:
self._fee.currency = FeeMessage.USD self._fee.currency = FeeMessage.USD
class Channel:
__slots__ = '_claim', '_channel'
def __init__(self, claim: Claim = None):
self._claim = claim or Claim()
self._channel = self._claim.channel_message
@property
def claim(self) -> Claim:
return self._claim
@property
def tags(self) -> List:
return self._channel.tags
@property
def public_key(self) -> str:
return hexlify(self._channel.public_key).decode()
@public_key.setter
def public_key(self, sd_public_key: str):
self._channel.public_key = unhexlify(sd_public_key.encode())
@property
def public_key_bytes(self) -> bytes:
return self._channel.public_key
@public_key_bytes.setter
def public_key_bytes(self, public_key: bytes):
self._channel.public_key = public_key
@property
def language(self) -> str:
return self._channel.language
@language.setter
def language(self, language: str):
self._channel.language = language
@property
def title(self) -> str:
return self._channel.title
@title.setter
def title(self, title: str):
self._channel.title = title
@property
def description(self) -> str:
return self._channel.description
@description.setter
def description(self, description: str):
self._channel.description = description
@property
def contact_email(self) -> str:
return self._channel.contact_email
@contact_email.setter
def contact_email(self, contact_email: str):
self._channel.contact_email = contact_email
@property
def homepage_url(self) -> str:
return self._channel.homepage_url
@homepage_url.setter
def homepage_url(self, homepage_url: str):
self._channel.homepage_url = homepage_url
@property
def thumbnail_url(self) -> str:
return self._channel.thumbnail_url
@thumbnail_url.setter
def thumbnail_url(self, thumbnail_url: str):
self._channel.thumbnail_url = thumbnail_url
@property
def cover_url(self) -> str:
return self._channel.cover_url
@cover_url.setter
def cover_url(self, cover_url: str):
self._channel.cover_url = cover_url
class Stream: class Stream:
__slots__ = '_claim', '_stream' __slots__ = '_claim', '_stream'
@ -523,92 +659,3 @@ class Stream:
@release_time.setter @release_time.setter
def release_time(self, release_time: int): def release_time(self, release_time: int):
self._stream.release_time = release_time self._stream.release_time = release_time
class Channel:
__slots__ = '_claim', '_channel'
def __init__(self, claim: Claim = None):
self._claim = claim or Claim()
self._channel = self._claim.channel_message
@property
def claim(self) -> Claim:
return self._claim
@property
def tags(self) -> List:
return self._channel.tags
@property
def public_key(self) -> str:
return hexlify(self._channel.public_key).decode()
@public_key.setter
def public_key(self, sd_public_key: str):
self._channel.public_key = unhexlify(sd_public_key.encode())
@property
def public_key_bytes(self) -> bytes:
return self._channel.public_key
@public_key_bytes.setter
def public_key_bytes(self, public_key: bytes):
self._channel.public_key = public_key
@property
def language(self) -> str:
return self._channel.language
@language.setter
def language(self, language: str):
self._channel.language = language
@property
def title(self) -> str:
return self._channel.title
@title.setter
def title(self, title: str):
self._channel.title = title
@property
def description(self) -> str:
return self._channel.description
@description.setter
def description(self, description: str):
self._channel.description = description
@property
def contact_email(self) -> str:
return self._channel.contact_email
@contact_email.setter
def contact_email(self, contact_email: str):
self._channel.contact_email = contact_email
@property
def homepage_url(self) -> str:
return self._channel.homepage_url
@homepage_url.setter
def homepage_url(self, homepage_url: str):
self._channel.homepage_url = homepage_url
@property
def thumbnail_url(self) -> str:
return self._channel.thumbnail_url
@thumbnail_url.setter
def thumbnail_url(self, thumbnail_url: str):
self._channel.thumbnail_url = thumbnail_url
@property
def cover_url(self) -> str:
return self._channel.cover_url
@cover_url.setter
def cover_url(self, cover_url: str):
self._channel.cover_url = cover_url

View file

@ -1,9 +1,9 @@
import json import json
from decimal import Decimal from decimal import Decimal
from lbrynet.schema.address import decode_address, encode_address
from lbrynet.schema.types.v1.legacy_claim_pb2 import Claim as OldClaimMessage from lbrynet.schema.types.v1.legacy_claim_pb2 import Claim as OldClaimMessage
from lbrynet.schema.types.v1.metadata_pb2 import Metadata as MetadataMessage from lbrynet.schema.types.v1.metadata_pb2 import Metadata as MetadataMessage
from lbrynet.schema.types.v1.certificate_pb2 import KeyType
from lbrynet.schema.types.v1.fee_pb2 import Fee as FeeMessage from lbrynet.schema.types.v1.fee_pb2 import Fee as FeeMessage
@ -60,6 +60,13 @@ def from_types_v1(claim, payload: bytes):
stream.fee.usd = Decimal(fee.amount) stream.fee.usd = Decimal(fee.amount)
else: else:
raise ValueError(f'Unsupported currency: {currency}') raise ValueError(f'Unsupported currency: {currency}')
if old.HasField('publisherSignature'):
sig = old.publisherSignature
claim.signature = sig.signature
claim.signature_type = KeyType.Name(sig.signatureType)
claim.certificate_id = sig.certificateId
old.ClearField("publisherSignature")
claim.unsigned_payload = old.SerializeToString()
elif old.claimType == 2: elif old.claimType == 2:
channel = claim.channel channel = claim.channel
channel.public_key_bytes = old.certificate.publicKey channel.public_key_bytes = old.certificate.publicKey

View file

@ -2,6 +2,7 @@ from unittest import TestCase
from binascii import unhexlify from binascii import unhexlify
from lbrynet.schema import Claim from lbrynet.schema import Claim
from lbrynet.schema.base import b58decode
class TestOldJSONSchemaCompatibility(TestCase): class TestOldJSONSchemaCompatibility(TestCase):
@ -143,6 +144,8 @@ class TestTypesV1Compatibility(TestCase):
'3f17' '3f17'
) )
self.assertTrue(stream.is_signed_by(channel, b58decode('bb4UAfujhmvTgyx7ufoEa4aevum6hKSW36')))
def test_unsigned_with_fee(self): def test_unsigned_with_fee(self):
claim = Claim.from_bytes(unhexlify( claim = Claim.from_bytes(unhexlify(
b'080110011ad6010801127c080410011a08727067206d69646922046d6964692a08727067206d696469322' b'080110011ad6010801127c080410011a08727067206d69646922046d6964692a08727067206d696469322'