moved claim signing to TXO

This commit is contained in:
Lex Berezhny 2019-03-18 19:34:01 -04:00
parent 9dfe7bbcf6
commit cd2b535afb
5 changed files with 133 additions and 64 deletions

View file

@ -3,15 +3,6 @@ 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
@ -19,10 +10,7 @@ from google.protobuf.message import DecodeError as DecodeError_pb # pylint: dis
from torba.client.constants import COIN from torba.client.constants import COIN
from lbrynet.schema.signature import Signature from lbrynet.schema.signature import Signature
from lbrynet.schema.validator import get_validator
from lbrynet.schema.signer import get_signer
from lbrynet.schema.constants import CURVE_NAMES, SECP256k1 from lbrynet.schema.constants import CURVE_NAMES, SECP256k1
from lbrynet.schema.encoding import decode_fields, decode_b64_fields, encode_fields
from lbrynet.schema.error import DecodeError from lbrynet.schema.error import DecodeError
from lbrynet.schema.types.v2.claim_pb2 import Claim as ClaimMessage, Fee as FeeMessage from lbrynet.schema.types.v2.claim_pb2 import Claim as ClaimMessage, Fee as FeeMessage
from lbrynet.schema.base import b58decode, b58encode from lbrynet.schema.base import b58decode, b58encode
@ -238,35 +226,6 @@ class Claim:
def is_signed(self): def is_signed(self):
return self.signature is not None 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):

View file

@ -2,18 +2,27 @@ import json
import logging import logging
import binascii import binascii
from hashlib import sha256 from hashlib import sha256
from string import hexdigits
from lbrynet.schema.validator import validate_claim_id
from torba.client.baseaccount import BaseAccount from torba.client.baseaccount import BaseAccount
from torba.client.basetransaction import TXORef from torba.client.basetransaction import TXORef
from lbrynet.schema.claim import ClaimDict from lbrynet.schema.claim import ClaimDict
from lbrynet.schema.signer import SECP256k1, get_signer #from lbrynet.schema.signer import SECP256k1, get_signer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def validate_claim_id(claim_id):
if not len(claim_id) == 40:
raise Exception("Incorrect claimid length: %i" % len(claim_id))
if isinstance(claim_id, bytes):
claim_id = claim_id.decode('utf-8')
if set(claim_id).difference(hexdigits):
raise Exception("Claim id is not hex encoded")
def generate_certificate(): def generate_certificate():
secp256k1_private_key = get_signer(SECP256k1).generate().private_key.to_pem() secp256k1_private_key = get_signer(SECP256k1).generate().private_key.to_pem()
return ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1), secp256k1_private_key return ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1), secp256k1_private_key

View file

@ -2,13 +2,12 @@ import asyncio
import logging import logging
from binascii import unhexlify from binascii import unhexlify
from lbrynet.schema.validator import validate_claim_id
from torba.client.baseledger import BaseLedger from torba.client.baseledger import BaseLedger
from lbrynet.schema.error import URIParseError from lbrynet.schema.error import URIParseError
from lbrynet.schema.uri import parse_lbry_uri from lbrynet.schema.uri import parse_lbry_uri
from lbrynet.wallet.dewies import dewies_to_lbc from lbrynet.wallet.dewies import dewies_to_lbc
from lbrynet.wallet.resolve import Resolver from lbrynet.wallet.resolve import Resolver
from lbrynet.wallet.account import Account from lbrynet.wallet.account import Account, validate_claim_id
from lbrynet.wallet.network import Network from lbrynet.wallet.network import Network
from lbrynet.wallet.database import WalletDatabase from lbrynet.wallet.database import WalletDatabase
from lbrynet.wallet.transaction import Transaction from lbrynet.wallet.transaction import Transaction

View file

@ -2,8 +2,16 @@ import struct
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from typing import List, Iterable, Optional from typing import List, Iterable, Optional
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 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, sha256, Base58
from lbrynet.schema.claim import Claim from lbrynet.schema.claim import Claim
from lbrynet.wallet.account import Account from lbrynet.wallet.account import Account
from lbrynet.wallet.script import InputScript, OutputScript from lbrynet.wallet.script import InputScript, OutputScript
@ -82,11 +90,60 @@ class Output(BaseOutput):
def has_private_key(self): def has_private_key(self):
return self.private_key is not None return self.private_key is not None
def is_signed_by(self, channel: 'Output', ledger):
if self.claim.unsigned_payload:
digest = sha256(b''.join([
Base58.decode(self.get_address(ledger)),
self.claim.unsigned_payload,
self.claim.certificate_id
]))
public_key = load_der_public_key(channel.claim.channel.public_key_bytes, default_backend())
hash = hashes.SHA256()
signature = hexlify(self.claim.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, channel: 'Output'):
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
self.script.values['claim'] = self._claim.to_bytes()
@classmethod @classmethod
def pay_claim_name_pubkey_hash( def pay_claim_name_pubkey_hash(
cls, amount: int, claim_name: str, claim: bytes, pubkey_hash: bytes) -> 'Output': cls, amount: int, claim_name: str, claim: Claim, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_claim_name_pubkey_hash( script = cls.script_class.pay_claim_name_pubkey_hash(
claim_name.encode(), claim, pubkey_hash) claim_name.encode(), claim.to_bytes(), pubkey_hash)
txo = cls(amount, script)
txo._claim = claim
return txo
@classmethod
def pay_update_claim_pubkey_hash(
cls, amount: int, claim_name: str, claim_id: str, claim: Claim, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_update_claim_pubkey_hash(
claim_name.encode(), unhexlify(claim_id)[::-1], claim, pubkey_hash)
txo = cls(amount, script)
txo._claim = claim
return txo
@classmethod
def pay_support_pubkey_hash(cls, amount: int, claim_name: str, claim_id: str, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_support_pubkey_hash(claim_name.encode(), unhexlify(claim_id)[::-1], pubkey_hash)
return cls(amount, script) return cls(amount, script)
@classmethod @classmethod
@ -94,18 +151,6 @@ class Output(BaseOutput):
script = cls.script_class.purchase_claim_pubkey_hash(unhexlify(claim_id)[::-1], pubkey_hash) script = cls.script_class.purchase_claim_pubkey_hash(unhexlify(claim_id)[::-1], pubkey_hash)
return cls(amount, script) return cls(amount, script)
@classmethod
def pay_update_claim_pubkey_hash(
cls, amount: int, claim_name: str, claim_id: str, claim: bytes, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_update_claim_pubkey_hash(
claim_name.encode(), unhexlify(claim_id)[::-1], claim, pubkey_hash)
return cls(amount, script)
@classmethod
def pay_support_pubkey_hash(cls, amount: int, claim_name: str, claim_id: str, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_support_pubkey_hash(claim_name.encode(), unhexlify(claim_id)[::-1], pubkey_hash)
return cls(amount, script)
class Transaction(BaseTransaction): class Transaction(BaseTransaction):
@ -119,11 +164,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: Claim, amount: int, holding_address: bytes, def claim(cls, name: str, claim: 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.to_bytes(), ledger.address_to_hash160(holding_address) amount, name, claim, ledger.address_to_hash160(holding_address)
) )
return cls.create([], [claim_output], funding_accounts, change_account) return cls.create([], [claim_output], funding_accounts, change_account)
@ -137,12 +182,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: Claim, amount: int, holding_address: bytes, def update(cls, previous_claim: Output, claim: 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.to_bytes(), ledger.address_to_hash160(holding_address) claim, 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

@ -0,0 +1,57 @@
from unittest import TestCase
from binascii import unhexlify
from lbrynet.wallet.ledger import MainNetLedger
from lbrynet.wallet.transaction import Transaction
class TestValidatingOldSignatures(TestCase):
def test_signed_claim_made_by_ytsync(self):
stream_tx = Transaction(unhexlify(
b'0100000001eb2a756e15bde95db3d2ae4a6e9b2796a699087890644607b5b04a5f15b67062010000006a4'
b'7304402206444b920bd318a07d9b982e30eb66245fdaaa6c9866e1f6e5900161d9b0ffd70022036464714'
b'4f1830898a2042aa0d6cef95a243799cc6e36630a58d411e2f9111f00121029b15f9a00a7c3f21b10bd4b'
b'98ab23a9e895bd9160e21f71317862bf55fbbc89effffffff0240420f0000000000fd1503b52268657265'
b'2d6172652d352d726561736f6e732d692d6e657874636c6f75642d746c674dd302080110011aee0408011'
b'2a604080410011a2b4865726520617265203520526561736f6e73204920e29da4efb88f204e657874636c'
b'6f7564207c20544c4722920346696e64206f7574206d6f72652061626f7574204e657874636c6f75643a2'
b'068747470733a2f2f6e657874636c6f75642e636f6d2f0a0a596f752063616e2066696e64206d65206f6e'
b'20746865736520736f6369616c733a0a202a20466f72756d733a2068747470733a2f2f666f72756d2e686'
b'5617679656c656d656e742e696f2f0a202a20506f64636173743a2068747470733a2f2f6f6666746f7069'
b'63616c2e6e65740a202a2050617472656f6e3a2068747470733a2f2f70617472656f6e2e636f6d2f74686'
b'56c696e757867616d65720a202a204d657263683a2068747470733a2f2f746565737072696e672e636f6d'
b'2f73746f7265732f6f6666696369616c2d6c696e75782d67616d65720a202a205477697463683a2068747'
b'470733a2f2f7477697463682e74762f786f6e64616b0a202a20547769747465723a2068747470733a2f2f'
b'747769747465722e636f6d2f7468656c696e757867616d65720a0a2e2e2e0a68747470733a2f2f7777772'
b'e796f75747562652e636f6d2f77617463683f763d4672546442434f535f66632a0f546865204c696e7578'
b'2047616d6572321c436f7079726967687465642028636f6e7461637420617574686f722938004a2968747'
b'470733a2f2f6265726b2e6e696e6a612f7468756d626e61696c732f4672546442434f535f666352005a00'
b'1a41080110011a30040e8ac6e89c061f982528c23ad33829fd7146435bf7a4cc22f0bff70c4fe0b91fd36'
b'da9a375e3e1c171db825bf5d1f32209766964656f2f6d70342a5c080110031a4062b2dd4c45e364030fbf'
b'ad1a6fefff695ebf20ea33a5381b947753e2a0ca359989a5cc7d15e5392a0d354c0b68498382b2701b22c'
b'03beb8dcb91089031b871e72214feb61536c007cdf4faeeaab4876cb397feaf6b516d7576a914f4f43f6f'
b'7a472bbf27fa3630329f771135fc445788ac86ff0600000000001976a914cef0fe3eeaf04416f0c3ff3e7'
b'8a598a081e70ee788ac00000000'
))
stream = stream_tx.outputs[0]
channel_tx = Transaction(unhexlify(
b'010000000192a1e1e3f66b8ca05a021cfa5fb6645ebc066b46639ccc9b3781fa588a88da65010000006a4'
b'7304402206be09a355f6abea8a10b5512180cd258460b42d516b5149431ffa3230a02533a0220325e83c6'
b'176b295d633b18aad67adb4ad766d13152536ac04583f86d14645c9901210269c63bc8bac8143ef02f972'
b'4a4ab35b12bdfa65ee1ad8c0db3d6511407a4cc2effffffff0240420f000000000091b50e405468654c69'
b'6e757847616d65724c6408011002225e0801100322583056301006072a8648ce3d020106052b8104000a0'
b'34200043878b1edd4a1373149909ef03f4339f6da9c2bd2214c040fd2e530463ffe66098eca14fc70b50f'
b'f3aefd106049a815f595ed5a13eda7419ad78d9ed7ae473f176d7576a914994dad5f21c384ff526749b87'
b'6d9d017d257b69888ac00dd6d00000000001976a914979202508a44f0e8290cea80787c76f98728845388'
b'ac00000000'
))
channel = channel_tx.outputs[0]
ledger = MainNetLedger({
'db': MainNetLedger.database_class(':memory:'),
'headers': MainNetLedger.headers_class(':memory:')
})
self.assertTrue(stream.is_signed_by(channel, ledger))