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 typing import Optional
from lbrynet.schema.constants import SECP256k1
from lbrynet.schema.claim import Claim
from torba.client.basemanager import BaseWalletManager
from torba.rpc.jsonrpc import CodeMessageError
@ -394,15 +394,11 @@ class LbryWalletManager(BaseWalletManager):
def get_utxos(account: BaseAccount):
return account.get_utxos()
async def claim_name(self, account, name, amount, claim_dict, certificate=None, claim_address=None):
claim = ClaimDict.load_dict(claim_dict)
async def claim_name(self, account, name, amount, claim: Claim, certificate=None, claim_address=None):
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, name=name,
force_detached=False # TODO: delete it and make True default everywhere when its out
)
claim = claim.sign(certificate.claim_id, certificate.private_key)
existing_claims = await account.get_claims(
claim_name_type__any={'is_claim': 1, 'is_update': 1}, # exclude is_supports
claim_name=name
@ -417,9 +413,12 @@ class LbryWalletManager(BaseWalletManager):
)
else:
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 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
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.hash import hash160
from lbrynet.schema.decode import smart_decode
from lbrynet.schema.claim import ClaimDict
from lbrynet.schema.claim import Claim
from lbrynet.extras.wallet.account import Account
from lbrynet.extras.wallet.script import InputScript, OutputScript
@ -19,12 +18,12 @@ class Output(BaseOutput):
script: OutputScript
script_class = OutputScript
__slots__ = '_claim_dict', 'channel', 'private_key'
__slots__ = '_claim', 'channel', 'private_key'
def __init__(self, *args, channel: Optional['Output'] = None,
private_key: Optional[str] = None, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._claim_dict = None
self._claim = None
self.channel = channel
self.private_key = private_key
@ -60,17 +59,13 @@ class Output(BaseOutput):
raise ValueError('No claim_name associated.')
@property
def claim(self) -> ClaimDict:
def claim(self) -> 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.')
@property
def claim_dict(self) -> dict:
if self._claim_dict is None:
self._claim_dict = self.claim.claim_dict
return self._claim_dict
@property
def permanent_url(self) -> str:
if self.script.is_claim_involved:
@ -124,11 +119,11 @@ class Transaction(BaseTransaction):
return cls.create([], [output], funding_accounts, change_account)
@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):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
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)
@ -142,12 +137,12 @@ class Transaction(BaseTransaction):
return cls.create([], [claim_output], funding_accounts, change_account)
@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):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
updated_claim = Output.pay_update_claim_pubkey_hash(
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)

View file

@ -3,6 +3,15 @@ from collections import OrderedDict
from typing import List, Tuple
from decimal import Decimal
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.message import DecodeError as DecodeError_pb # pylint: disable=no-name-in-module,import-error
@ -204,10 +213,14 @@ class ClaimDict(OrderedDict):
class Claim:
__slots__ = '_claim',
__slots__ = '_claim', 'signature', 'certificate_id', 'signature_type', 'unsigned_payload'
def __init__(self, claim_message=None):
self._claim = claim_message or ClaimMessage()
self.signature = None
self.signature_type = 'SECP256k1'
self.certificate_id = None
self.unsigned_payload = None
@property
def is_undetermined(self):
@ -221,6 +234,40 @@ class Claim:
def is_channel(self):
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
def stream_message(self):
if self.is_undetermined:
@ -400,6 +447,95 @@ class Fee:
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:
__slots__ = '_claim', '_stream'
@ -523,92 +659,3 @@ class Stream:
@release_time.setter
def release_time(self, release_time: int):
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
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.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
@ -60,6 +60,13 @@ def from_types_v1(claim, payload: bytes):
stream.fee.usd = Decimal(fee.amount)
else:
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:
channel = claim.channel
channel.public_key_bytes = old.certificate.publicKey

View file

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