forked from LBRYCommunity/lbry-sdk
wip
This commit is contained in:
parent
323694fb12
commit
1ec8f0b0b4
5 changed files with 166 additions and 115 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue