forked from LBRYCommunity/lbry-sdk
moved claim signing to TXO
This commit is contained in:
parent
9dfe7bbcf6
commit
cd2b535afb
5 changed files with 133 additions and 64 deletions
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
57
tests/unit/schema/test_signing.py
Normal file
57
tests/unit/schema/test_signing.py
Normal 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))
|
Loading…
Reference in a new issue