move lbryschema/lbryschema into lbrynet/schema

This commit is contained in:
Victor Shyba 2018-09-17 17:13:30 -03:00 committed by Lex Berezhny
parent d12b6cddb3
commit 2dcebe48b7
42 changed files with 3892 additions and 0 deletions

View file

@ -0,0 +1,13 @@
import os
__version__ = "0.0.16"
BLOCKCHAIN_NAME_ENVVAR = "LBRYSCHEMA_BLOCKCHAIN_NAME"
if BLOCKCHAIN_NAME_ENVVAR in os.environ:
if os.environ[BLOCKCHAIN_NAME_ENVVAR] in ['lbrycrd_main', 'lbrycrd_regtest',
'lbrycrd_testnet']:
BLOCKCHAIN_NAME = os.environ[BLOCKCHAIN_NAME_ENVVAR]
else:
raise OSError("invalid blockchain name: %s" % os.environ[BLOCKCHAIN_NAME_ENVVAR])
else:
BLOCKCHAIN_NAME = "lbrycrd_main"

68
lbrynet/schema/address.py Normal file
View file

@ -0,0 +1,68 @@
import six
import lbryschema
from lbryschema.base import b58encode, b58decode, validate_b58_checksum
from lbryschema.hashing import double_sha256, hash160
from lbryschema.error import InvalidAddress
from lbryschema.schema import ADDRESS_LENGTH, ADDRESS_PREFIXES, PUBKEY_ADDRESS, SCRIPT_ADDRESS
def validate_address_length(addr_bytes):
if len(addr_bytes) != ADDRESS_LENGTH:
raise InvalidAddress("Invalid address length: %i" % len(addr_bytes))
def validate_address_prefix(addr_bytes):
if six.PY3:
prefix = addr_bytes[0]
else:
prefix = ord(addr_bytes[0])
if prefix not in ADDRESS_PREFIXES[lbryschema.BLOCKCHAIN_NAME].values():
raise InvalidAddress("Invalid address prefix: %.2X" % prefix)
def validate_lbrycrd_address_bytes(addr_bytes):
validate_address_length(addr_bytes)
validate_address_prefix(addr_bytes)
validate_b58_checksum(addr_bytes)
return addr_bytes
def decode_address(v):
"""decode and validate a b58 address"""
return validate_lbrycrd_address_bytes(b58decode(v))
def encode_address(addr_bytes):
"""validate and encode an address as b58"""
v = validate_lbrycrd_address_bytes(addr_bytes)
return b58encode(v)
def hash_160_bytes_to_address(h160, addrtype=PUBKEY_ADDRESS):
if addrtype == PUBKEY_ADDRESS:
prefix = chr(ADDRESS_PREFIXES[lbryschema.BLOCKCHAIN_NAME][PUBKEY_ADDRESS])
elif addrtype == SCRIPT_ADDRESS:
prefix = chr(ADDRESS_PREFIXES[lbryschema.BLOCKCHAIN_NAME][SCRIPT_ADDRESS])
else:
raise Exception("Invalid address prefix")
return b58encode(prefix + h160 + double_sha256(prefix + h160)[0:4])
def public_key_to_address(public_key):
return hash_160_bytes_to_address(hash160(public_key))
def address_to_hash_160(addr):
bytes = decode_address(addr)
prefix, pubkey_bytes, addr_checksum = bytes[0], bytes[1:21], bytes[21:]
if prefix == chr(ADDRESS_PREFIXES[lbryschema.BLOCKCHAIN_NAME][PUBKEY_ADDRESS]):
return PUBKEY_ADDRESS, pubkey_bytes
return SCRIPT_ADDRESS, pubkey_bytes
def is_address(addr):
try:
addr_bytes = decode_address(addr)
return True
except InvalidAddress:
return False

105
lbrynet/schema/base.py Normal file
View file

@ -0,0 +1,105 @@
import six
from lbryschema.schema import ADDRESS_CHECKSUM_LENGTH
from lbryschema.hashing import double_sha256
from lbryschema.error import InvalidAddress
alphabet = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
if six.PY2:
iseq, bseq, buffer = (
lambda s: map(ord, s),
lambda s: ''.join(map(chr, s)),
lambda s: s,
)
elif six.PY3:
iseq, bseq, buffer = (
lambda s: s,
bytes,
lambda s: s.buffer,
)
def scrub_input(v):
if isinstance(v, str) and not isinstance(v, bytes):
v = v.encode('ascii')
return v
def b58encode_int(i, default_one=True):
'''Encode an integer using Base58'''
if not i and default_one:
return alphabet[0:1]
string = b""
while i:
i, idx = divmod(i, 58)
string = alphabet[idx:idx+1] + string
return string
def b58encode(v):
'''Encode a string using Base58'''
v = scrub_input(v)
nPad = len(v)
v = v.lstrip(b'\0')
nPad -= len(v)
p, acc = 1, 0
for c in iseq(reversed(v)):
acc += p * c
p = p << 8
result = b58encode_int(acc, default_one=False)
return alphabet[0:1] * nPad + result
def b58decode_int(v):
'''Decode a Base58 encoded string as an integer'''
v = scrub_input(v)
decimal = 0
for char in v:
decimal = decimal * 58 + alphabet.index(char)
return decimal
def b58decode(v):
'''Decode a Base58 encoded string'''
v = scrub_input(v)
origlen = len(v)
v = v.lstrip(alphabet[0:1])
newlen = len(v)
acc = b58decode_int(v)
result = []
while acc > 0:
acc, mod = divmod(acc, 256)
result.append(mod)
return (b'\0' * (origlen - newlen) + bseq(reversed(result)))
def validate_b58_checksum(addr_bytes):
addr_without_checksum = addr_bytes[:-ADDRESS_CHECKSUM_LENGTH]
addr_checksum = addr_bytes[-ADDRESS_CHECKSUM_LENGTH:]
if double_sha256(addr_without_checksum)[:ADDRESS_CHECKSUM_LENGTH] != addr_checksum:
raise InvalidAddress("Invalid address checksum")
def b58decode_strip_checksum(v):
addr_bytes = b58decode(v)
validate_b58_checksum(addr_bytes)
return addr_bytes[:-ADDRESS_CHECKSUM_LENGTH]
def b58encode_with_checksum(addr_bytes):
addr_checksum = double_sha256(addr_bytes)[:ADDRESS_CHECKSUM_LENGTH]
return b58encode(addr_bytes + addr_checksum)

193
lbrynet/schema/claim.py Normal file
View file

@ -0,0 +1,193 @@
import json
import binascii
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 collections import OrderedDict
from lbryschema.schema.claim import Claim
from lbryschema.proto import claim_pb2
from lbryschema.validator import get_validator
from lbryschema.signer import get_signer
from lbryschema.schema import NIST256p, CURVE_NAMES, CLAIM_TYPE_NAMES
from lbryschema.encoding import decode_fields, decode_b64_fields, encode_fields
from lbryschema.error import DecodeError
from lbryschema.fee import Fee
class ClaimDict(OrderedDict):
def __init__(self, claim_dict=None):
if isinstance(claim_dict, claim_pb2.Claim):
raise Exception("To initialize %s with a Claim protobuf use %s.load_protobuf" %
(self.__class__.__name__, self.__class__.__name__))
OrderedDict.__init__(self, claim_dict or [])
@property
def protobuf_dict(self):
"""Claim dictionary using base64 to represent bytes"""
return json.loads(json_format.MessageToJson(self.protobuf, True))
@property
def protobuf(self):
"""Claim message object"""
return Claim.load(self)
@property
def serialized(self):
"""Serialized Claim protobuf"""
return self.protobuf.SerializeToString()
@property
def serialized_no_signature(self):
"""Serialized Claim protobuf without publisherSignature field"""
claim = self.protobuf
claim.ClearField("publisherSignature")
return ClaimDict.load_protobuf(claim).serialized
@property
def has_signature(self):
claim = self.protobuf
if claim.HasField("publisherSignature"):
return True
return False
@property
def is_certificate(self):
claim = self.protobuf
return CLAIM_TYPE_NAMES[claim.claimType] == "certificate"
@property
def is_stream(self):
claim = self.protobuf
return CLAIM_TYPE_NAMES[claim.claimType] == "stream"
@property
def source_hash(self):
claim = self.protobuf
if not CLAIM_TYPE_NAMES[claim.claimType] == "stream":
return None
return binascii.hexlify(claim.stream.source.source)
@property
def has_fee(self):
claim = self.protobuf
if not CLAIM_TYPE_NAMES[claim.claimType] == "stream":
return None
if claim.stream.metadata.HasField("fee"):
return True
return False
@property
def source_fee(self):
claim = self.protobuf
if not CLAIM_TYPE_NAMES[claim.claimType] == "stream":
return None
if claim.stream.metadata.HasField("fee"):
return Fee.load_protobuf(claim.stream.metadata.fee)
return None
@property
def certificate_id(self):
if not self.has_signature:
return None
return binascii.hexlify(self.protobuf.publisherSignature.certificateId)
@property
def signature(self):
if not self.has_signature:
return None
return binascii.hexlify(self.protobuf.publisherSignature.signature)
@property
def protobuf_len(self):
"""Length of serialized string"""
return self.protobuf.ByteSize()
@property
def json_len(self):
"""Length of json encoded string"""
return len(json.dumps(self.claim_dict))
@property
def claim_dict(self):
"""Claim dictionary with bytes represented as hex and base58"""
return dict(encode_fields(self))
@classmethod
def load_protobuf_dict(cls, protobuf_dict):
"""
Load a ClaimDict from a dictionary with base64 encoded bytes
(as returned by the protobuf json formatter)
"""
return cls(decode_b64_fields(protobuf_dict))
@classmethod
def load_protobuf(cls, protobuf_claim):
"""Load ClaimDict from a protobuf Claim message"""
return cls.load_protobuf_dict(json.loads(json_format.MessageToJson(protobuf_claim, True)))
@classmethod
def load_dict(cls, claim_dict):
"""Load ClaimDict from a dictionary with hex and base58 encoded bytes"""
try:
return cls.load_protobuf(cls(decode_fields(claim_dict)).protobuf)
except json_format.ParseError as err:
raise DecodeError(str(err))
@classmethod
def deserialize(cls, serialized):
"""Load a ClaimDict from a serialized protobuf string"""
temp_claim = claim_pb2.Claim()
try:
temp_claim.ParseFromString(serialized)
except DecodeError_pb:
raise DecodeError(DecodeError_pb)
return cls.load_protobuf(temp_claim)
@classmethod
def generate_certificate(cls, private_key, curve=NIST256p):
signer = get_signer(curve).load_pem(private_key)
return cls.load_protobuf(signer.certificate)
def sign(self, private_key, claim_address, cert_claim_id, curve=NIST256p):
signer = get_signer(curve).load_pem(private_key)
signed = signer.sign_stream_claim(self, claim_address, cert_claim_id)
return ClaimDict.load_protobuf(signed)
def validate_signature(self, claim_address, certificate):
if isinstance(certificate, ClaimDict):
certificate = certificate.protobuf
curve = CURVE_NAMES[certificate.certificate.keyType]
validator = get_validator(curve).load_from_certificate(certificate, self.certificate_id)
return validator.validate_claim_signature(self, claim_address)
def validate_private_key(self, private_key, certificate_id):
certificate = self.protobuf
if CLAIM_TYPE_NAMES[certificate.claimType] != "certificate":
return
curve = CURVE_NAMES[certificate.certificate.keyType]
validator = get_validator(curve).load_from_certificate(certificate, certificate_id)
signing_key = validator.signing_key_from_pem(private_key)
return validator.validate_private_key(signing_key)
def get_validator(self, certificate_id):
"""
Get a lbryschema.validator.Validator object for a certificate claim
:param certificate_id: claim id of this certificate claim
:return: None or lbryschema.validator.Validator object
"""
claim = self.protobuf
if CLAIM_TYPE_NAMES[claim.claimType] != "certificate":
return
curve = CURVE_NAMES[claim.certificate.keyType]
return get_validator(curve).load_from_certificate(claim, certificate_id)

63
lbrynet/schema/decode.py Normal file
View file

@ -0,0 +1,63 @@
import json
import binascii
import six
from lbryschema.error import DecodeError, InvalidAddress
from lbryschema.legacy.migrate import migrate as schema_migrator
from lbryschema.claim import ClaimDict
from google.protobuf import json_format # pylint: disable=no-name-in-module
def migrate_json_claim_value(decoded_json):
try:
if 'fee' in decoded_json:
old_fee = decoded_json['fee']
if not old_fee[list(old_fee.keys())[0]]['amount']:
del decoded_json['fee']
return migrate_json_claim_value(decoded_json)
except (TypeError, AttributeError, InvalidAddress):
raise DecodeError("Failed to decode claim")
try:
pb_migrated = schema_migrator(decoded_json)
return pb_migrated
except json_format.ParseError as parse_error:
raise DecodeError("Failed to parse protobuf: %s" % parse_error)
except Exception as err:
raise DecodeError("Failed to migrate claim: %s" % err)
def smart_decode(claim_value):
"""
Decode a claim value
Try decoding claim protobuf, if this fails try decoding json and migrating it.
If unable to decode or migrate, raise DecodeError
"""
# if already decoded, return
if isinstance(claim_value, ClaimDict):
return claim_value
elif isinstance(claim_value, dict):
return ClaimDict.load_dict(claim_value)
try:
claim_value = binascii.unhexlify(claim_value)
except (TypeError, ValueError):
pass
if claim_value[0] in ['{', ord('{')]:
try:
if six.PY3 and isinstance(claim_value, six.binary_type):
claim_value = claim_value.decode()
decoded_json = json.loads(claim_value)
return migrate_json_claim_value(decoded_json)
except (ValueError, TypeError):
pass
try:
if six.PY3 and isinstance(claim_value, six.text_type):
claim_value = claim_value.encode()
return ClaimDict.deserialize(claim_value)
except (DecodeError, InvalidAddress, KeyError, TypeError):
raise DecodeError(claim_value)

View file

@ -0,0 +1,78 @@
import base64, binascii
from copy import deepcopy
from lbryschema.address import decode_address, encode_address
from lbryschema.schema import CLAIM_TYPES, CLAIM_TYPE, STREAM_TYPE, CERTIFICATE_TYPE
from lbryschema.schema import SIGNATURE
from lbryschema.error import DecodeError, InvalidAddress
def encode_fields(claim_dictionary):
"""Encode bytes to hex and b58 for return by ClaimDict"""
claim_dictionary = deepcopy(claim_dictionary)
claim_type = CLAIM_TYPES[claim_dictionary[CLAIM_TYPE]]
claim_value = claim_dictionary[claim_type]
if claim_type == CLAIM_TYPES[STREAM_TYPE]:
claim_value['source']['source'] = binascii.hexlify(claim_value['source']['source']).decode()
if 'fee' in claim_value['metadata']:
try:
address = encode_address(claim_value['metadata']['fee']['address'])
except InvalidAddress as err:
raise DecodeError("Invalid fee address: %s" % err)
claim_value['metadata']['fee']['address'] = address
elif claim_type == CLAIM_TYPES[CERTIFICATE_TYPE]:
public_key = claim_value["publicKey"]
claim_value["publicKey"] = binascii.hexlify(public_key).decode()
if SIGNATURE in claim_dictionary:
encoded_sig = binascii.hexlify(claim_dictionary[SIGNATURE]['signature']).decode()
encoded_cert_id = binascii.hexlify(claim_dictionary[SIGNATURE]['certificateId']).decode()
claim_dictionary[SIGNATURE]['signature'] = encoded_sig
claim_dictionary[SIGNATURE]['certificateId'] = encoded_cert_id
claim_dictionary[claim_type] = claim_value
return claim_dictionary
def decode_fields(claim_dictionary):
"""Decode hex and b58 encoded bytes in dictionaries given to ClaimDict"""
claim_dictionary = deepcopy(claim_dictionary)
claim_type = CLAIM_TYPES[claim_dictionary[CLAIM_TYPE]]
claim_value = claim_dictionary[claim_type]
if claim_type == CLAIM_TYPES[STREAM_TYPE]:
claim_value['source']['source'] = binascii.unhexlify(claim_value['source']['source'])
if 'fee' in claim_value['metadata']:
try:
address = decode_address(claim_value['metadata']['fee']['address'])
except InvalidAddress as err:
raise DecodeError("Invalid fee address: %s" % err)
claim_value['metadata']['fee']['address'] = address
elif claim_type == CLAIM_TYPES[CERTIFICATE_TYPE]:
public_key = binascii.unhexlify(claim_value["publicKey"])
claim_value["publicKey"] = public_key
if SIGNATURE in claim_dictionary:
decoded_sig = binascii.unhexlify(claim_dictionary[SIGNATURE]['signature'])
decoded_cert_id = binascii.unhexlify(claim_dictionary[SIGNATURE]['certificateId'])
claim_dictionary[SIGNATURE]['signature'] = decoded_sig
claim_dictionary[SIGNATURE]['certificateId'] = decoded_cert_id
claim_dictionary[claim_type] = claim_value
return claim_dictionary
def decode_b64_fields(claim_dictionary):
"""Decode b64 encoded bytes in protobuf generated dictionary to be given to ClaimDict"""
claim_dictionary = deepcopy(claim_dictionary)
claim_type = CLAIM_TYPES[claim_dictionary[CLAIM_TYPE]]
claim_value = claim_dictionary[claim_type]
if claim_type == CLAIM_TYPES[STREAM_TYPE]:
claim_value['source']['source'] = base64.b64decode(claim_value['source']['source'])
if 'fee' in claim_value['metadata']:
address = base64.b64decode(claim_value['metadata']['fee']['address'])
claim_value['metadata']['fee']['address'] = address
elif claim_type == CLAIM_TYPES[CERTIFICATE_TYPE]:
public_key = base64.b64decode(claim_value["publicKey"])
claim_value["publicKey"] = public_key
if SIGNATURE in claim_dictionary:
encoded_sig = base64.b64decode(claim_dictionary[SIGNATURE]['signature'])
encoded_cert_id = base64.b64decode(claim_dictionary[SIGNATURE]['certificateId'])
claim_dictionary[SIGNATURE]['signature'] = encoded_sig
claim_dictionary[SIGNATURE]['certificateId'] = encoded_cert_id
claim_dictionary[claim_type] = claim_value
return claim_dictionary

22
lbrynet/schema/error.py Normal file
View file

@ -0,0 +1,22 @@
class UnknownSourceType(Exception):
pass
class InvalidSourceHashLength(Exception):
pass
class DecodeError(Exception):
pass
class URIParseError(Exception):
pass
class CertificateError(Exception):
pass
class InvalidAddress(Exception):
pass

79
lbrynet/schema/fee.py Normal file
View file

@ -0,0 +1,79 @@
from collections import OrderedDict
from lbryschema.address import encode_address, decode_address
from lbryschema.schema import CURRENCY_NAMES, CURRENCY_MAP
from lbryschema.schema.fee import Fee as FeeHelper
from lbryschema.proto import fee_pb2
def migrate(fee):
if len(list(fee.keys())) == 3 and 'currency' in fee and 'amount' in fee and 'address' in fee:
return FeeHelper.load({
"version": "_0_0_1",
"currency": fee['currency'],
"amount": fee['amount'],
"address": decode_address(fee['address'])
})
if len(list(fee.keys())) > 1:
raise Exception("Invalid fee")
currency = list(fee.keys())[0]
amount = fee[currency]['amount']
address = fee[currency]['address']
return FeeHelper.load({
"version": "_0_0_1",
"currency": currency,
"amount": amount,
"address": decode_address(address)
})
class Fee(OrderedDict):
def __init__(self, fee):
if (len(fee) == 4 and "version" in fee and "currency" in fee
and "amount" in fee and "address" in fee):
OrderedDict.__init__(self, fee)
else:
OrderedDict.__init__(self, Fee.load_protobuf(migrate(fee)))
@property
def currency(self):
return self['currency']
@property
def address(self):
return self['address']
@property
def amount(self):
return self['amount']
@property
def version(self):
return self['version']
@property
def protobuf(self):
pb = {
"version": self.version,
"currency": CURRENCY_MAP[self.currency],
"address": decode_address(self.address),
"amount": self.amount
}
return FeeHelper.load(pb)
@classmethod
def load_protobuf(cls, pb):
return cls({
"version": pb.version,
"currency": CURRENCY_NAMES[pb.currency],
"address": encode_address(pb.address),
"amount": pb.amount
})
@classmethod
def deserialize(cls, serialized):
pb = fee_pb2.Fee()
pb.ParseFromString(serialized)
return cls.load_protobuf(pb)

24
lbrynet/schema/hashing.py Normal file
View file

@ -0,0 +1,24 @@
import six
import hashlib
def sha256(x):
if isinstance(x, six.text_type):
x = x.encode('utf-8')
return hashlib.sha256(x).digest()
def double_sha256(x):
return sha256(sha256(x))
def ripemd160(x):
if isinstance(x, six.text_type):
x = x.encode('utf-8')
md = hashlib.new('ripemd160')
md.update(x)
return md.digest()
def hash160(x):
return ripemd160(sha256(x))

View file

@ -0,0 +1,59 @@
import jsonschema
class StructuredDict(dict):
"""
A dictionary that enforces a structure specified by a schema, and supports
migration between different versions of the schema.
"""
# To be specified in sub-classes, an array in the format
# [(version, schema, migration), ...]
_versions = []
# Used internally to allow schema lookups by version number
_schemas = {}
version = None
def __init__(self, value, starting_version, migrate=True, target_version=None):
dict.__init__(self, value)
self.version = starting_version
self._schemas = dict([(version, schema) for (version, schema, _) in self._versions])
self.validate(starting_version)
if migrate:
self.migrate(target_version)
def _upgrade_version_range(self, start_version, end_version):
after_starting_version = False
for version, schema, migration in self._versions:
if not after_starting_version:
if version == self.version:
after_starting_version = True
continue
yield version, schema, migration
if end_version and version == end_version:
break
def validate(self, version):
jsonschema.validate(self, self._schemas[version])
def migrate(self, target_version=None):
if target_version:
assert self._versions.index(target_version) > self._versions.index(self.version), \
"Current version is above target version"
for version, schema, migration in self._upgrade_version_range(self.version, target_version):
migration(self)
try:
self.validate(version)
except jsonschema.ValidationError as e:
raise jsonschema.ValidationError(
"Could not migrate to version %s due to validation error: %s" %
(version, e.message))
self.version = version

View file

View file

@ -0,0 +1,276 @@
VER_001 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY metadata schema 0.0.1',
'definitions': {
'fee_info': {
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}
},
'type': 'object',
'properties': {
'ver': {
'type': 'string',
'default': '0.0.1'
},
'title': {
'type': 'string'
},
'description': {
'type': 'string'
},
'author': {
'type': 'string'
},
'language': {
'type': 'string'
},
'license': {
'type': 'string'
},
'content-type': {
'type': 'string'
},
'sources': {
'type': 'object',
'properties': {
'lbry_sd_hash': {
'type': 'string'
},
'btih': {
'type': 'string'
},
'url': {
'type': 'string'
}
},
'additionalProperties': False
},
'thumbnail': {
'type': 'string'
},
'preview': {
'type': 'string'
},
'fee': {
'properties': {
'LBC': {'$ref': '#/definitions/fee_info'},
'BTC': {'$ref': '#/definitions/fee_info'},
'USD': {'$ref': '#/definitions/fee_info'}
}
},
'contact': {
'type': 'number'
},
'pubkey': {
'type': 'string'
},
},
'required': [
'title', 'description', 'author', 'language', 'license', 'content-type', 'sources'],
'additionalProperties': False
}
VER_002 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY metadata schema 0.0.2',
'definitions': {
'fee_info': {
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}
},
'type': 'object',
'properties': {
'ver': {
'type': 'string',
'enum': ['0.0.2'],
},
'title': {
'type': 'string'
},
'description': {
'type': 'string'
},
'author': {
'type': 'string'
},
'language': {
'type': 'string'
},
'license': {
'type': 'string'
},
'content-type': {
'type': 'string'
},
'sources': {
'type': 'object',
'properties': {
'lbry_sd_hash': {
'type': 'string'
},
'btih': {
'type': 'string'
},
'url': {
'type': 'string'
}
},
'additionalProperties': False
},
'thumbnail': {
'type': 'string'
},
'preview': {
'type': 'string'
},
'fee': {
'properties': {
'LBC': {'$ref': '#/definitions/fee_info'},
'BTC': {'$ref': '#/definitions/fee_info'},
'USD': {'$ref': '#/definitions/fee_info'}
}
},
'contact': {
'type': 'number'
},
'pubkey': {
'type': 'string'
},
'license_url': {
'type': 'string'
},
'nsfw': {
'type': 'boolean',
'default': False
},
},
'required': [
'ver', 'title', 'description', 'author', 'language', 'license',
'content-type', 'sources', 'nsfw'
],
'additionalProperties': False
}
VER_003 = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'title': 'LBRY metadata schema 0.0.3',
'definitions': {
'fee_info': {
'type': 'object',
'properties': {
'amount': {
'type': 'number',
'minimum': 0,
'exclusiveMinimum': True,
},
'address': {
'type': 'string'
}
},
}
},
'type': 'object',
'properties': {
'ver': {
'type': 'string',
'enum': ['0.0.3'],
},
'title': {
'type': 'string'
},
'description': {
'type': 'string'
},
'author': {
'type': 'string'
},
'language': {
'type': 'string'
},
'license': {
'type': 'string'
},
'content_type': {
'type': 'string'
},
'sources': {
'type': 'object',
'properties': {
'lbry_sd_hash': {
'type': 'string'
},
'btih': {
'type': 'string'
},
'url': {
'type': 'string'
}
},
'additionalProperties': False
},
'thumbnail': {
'type': 'string'
},
'preview': {
'type': 'string'
},
'fee': {
'properties': {
'LBC': {'$ref': '#/definitions/fee_info'},
'BTC': {'$ref': '#/definitions/fee_info'},
'USD': {'$ref': '#/definitions/fee_info'}
}
},
'contact': {
'type': 'number'
},
'pubkey': {
'type': 'string'
},
'license_url': {
'type': 'string'
},
'nsfw': {
'type': 'boolean',
'default': False
},
'sig': {
'type': 'string'
}
},
'required': [
'ver', 'title', 'description', 'author', 'language', 'license',
'content_type', 'sources', 'nsfw'
],
'additionalProperties': False,
'dependencies': {
'pubkey': ['sig'],
'sig': ['pubkey']
}
}

View file

@ -0,0 +1,79 @@
"""
migrate claim json schema (0.0.1-3) to protobuf (0.1.0)
"""
from lbryschema.legacy import metadata_schemas
from lbryschema.claim import ClaimDict
from .StructuredDict import StructuredDict
def migrate_001_to_002(metadata):
metadata['ver'] = '0.0.2'
metadata['nsfw'] = False
def migrate_002_to_003(metadata):
metadata['ver'] = '0.0.3'
if 'content-type' in metadata:
metadata['content_type'] = metadata['content-type']
del metadata['content-type']
class LegacyMetadata(StructuredDict):
current_version = '0.0.3'
_versions = [
('0.0.1', metadata_schemas.VER_001, None),
('0.0.2', metadata_schemas.VER_002, migrate_001_to_002),
('0.0.3', metadata_schemas.VER_003, migrate_002_to_003)
]
def __init__(self, metadata, migrate=True, target_version=None):
if not isinstance(metadata, dict):
raise TypeError("{} is not a dictionary".format(metadata))
starting_version = metadata.get('ver', '0.0.1')
StructuredDict.__init__(self, metadata, starting_version, migrate, target_version)
def migrate_003_to_010(value):
migrated_to_003 = LegacyMetadata(value)
metadata = {
"version": "_0_1_0"
}
for k in ["author", "description", "language", "license", "nsfw", "thumbnail", "title",
"preview"]:
if k in migrated_to_003:
metadata.update({k: migrated_to_003[k]})
if 'license_url' in migrated_to_003:
metadata['licenseUrl'] = migrated_to_003['license_url']
if "fee" in migrated_to_003:
fee = migrated_to_003["fee"]
currency = list(fee.keys())[0]
amount = fee[currency]['amount']
address = fee[currency]['address']
metadata.update(dict(fee={"currency": currency, "version": "_0_0_1",
"amount": amount, "address": address}))
source = {
"source": migrated_to_003['sources']['lbry_sd_hash'],
"contentType": migrated_to_003['content_type'],
"sourceType": "lbry_sd_hash",
"version": "_0_0_1"
}
migrated = {
"version": "_0_0_1",
"claimType": "streamType",
"stream": {
"version": "_0_0_1",
"metadata": metadata,
"source": source
}
}
return ClaimDict.load_dict(migrated)
def migrate(value):
return migrate_003_to_010(value)

View file

View file

@ -0,0 +1,18 @@
syntax = "proto2";
enum KeyType {
UNKNOWN_PUBLIC_KEY_TYPE = 0;
NIST256p = 1;
NIST384p = 2;
SECP256k1 = 3;
}
message Certificate {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
}
required Version version = 1;
required KeyType keyType = 2;
required bytes publicKey = 4;
}

View file

@ -0,0 +1,144 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: lbryschema/proto/certificate.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='lbryschema/proto/certificate.proto',
package='',
serialized_pb=_b('\n\"lbryschema/proto/certificate.proto\"\x8e\x01\n\x0b\x43\x65rtificate\x12%\n\x07version\x18\x01 \x02(\x0e\x32\x14.Certificate.Version\x12\x19\n\x07keyType\x18\x02 \x02(\x0e\x32\x08.KeyType\x12\x11\n\tpublicKey\x18\x04 \x02(\x0c\"*\n\x07Version\x12\x13\n\x0fUNKNOWN_VERSION\x10\x00\x12\n\n\x06_0_0_1\x10\x01*Q\n\x07KeyType\x12\x1b\n\x17UNKNOWN_PUBLIC_KEY_TYPE\x10\x00\x12\x0c\n\x08NIST256p\x10\x01\x12\x0c\n\x08NIST384p\x10\x02\x12\r\n\tSECP256k1\x10\x03')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_KEYTYPE = _descriptor.EnumDescriptor(
name='KeyType',
full_name='KeyType',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_PUBLIC_KEY_TYPE', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='NIST256p', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='NIST384p', index=2, number=2,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='SECP256k1', index=3, number=3,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=183,
serialized_end=264,
)
_sym_db.RegisterEnumDescriptor(_KEYTYPE)
KeyType = enum_type_wrapper.EnumTypeWrapper(_KEYTYPE)
UNKNOWN_PUBLIC_KEY_TYPE = 0
NIST256p = 1
NIST384p = 2
SECP256k1 = 3
_CERTIFICATE_VERSION = _descriptor.EnumDescriptor(
name='Version',
full_name='Certificate.Version',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_VERSION', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='_0_0_1', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=139,
serialized_end=181,
)
_sym_db.RegisterEnumDescriptor(_CERTIFICATE_VERSION)
_CERTIFICATE = _descriptor.Descriptor(
name='Certificate',
full_name='Certificate',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='version', full_name='Certificate.version', index=0,
number=1, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='keyType', full_name='Certificate.keyType', index=1,
number=2, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='publicKey', full_name='Certificate.publicKey', index=2,
number=4, type=12, cpp_type=9, label=2,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_CERTIFICATE_VERSION,
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=39,
serialized_end=181,
)
_CERTIFICATE.fields_by_name['version'].enum_type = _CERTIFICATE_VERSION
_CERTIFICATE.fields_by_name['keyType'].enum_type = _KEYTYPE
_CERTIFICATE_VERSION.containing_type = _CERTIFICATE
DESCRIPTOR.message_types_by_name['Certificate'] = _CERTIFICATE
DESCRIPTOR.enum_types_by_name['KeyType'] = _KEYTYPE
Certificate = _reflection.GeneratedProtocolMessageType('Certificate', (_message.Message,), dict(
DESCRIPTOR = _CERTIFICATE,
__module__ = 'lbryschema.proto.certificate_pb2'
# @@protoc_insertion_point(class_scope:Certificate)
))
_sym_db.RegisterMessage(Certificate)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,22 @@
syntax = "proto2";
import "lbryschema/proto/stream.proto";
import "lbryschema/proto/certificate.proto";
import "lbryschema/proto/signature.proto";
message Claim {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
}
required Version version = 1;
enum ClaimType {
UNKNOWN_CLAIM_TYPE = 0;
streamType = 1;
certificateType = 2;
}
required ClaimType claimType = 2;
optional Stream stream = 3;
optional Certificate certificate = 4;
optional Signature publisherSignature = 5;
}

View file

@ -0,0 +1,156 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: lbryschema/proto/claim.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import lbryschema.proto.stream_pb2
import lbryschema.proto.certificate_pb2
import lbryschema.proto.signature_pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='lbryschema/proto/claim.proto',
package='',
serialized_pb=_b('\n\x1clbryschema/proto/claim.proto\x1a\x1dlbryschema/proto/stream.proto\x1a\"lbryschema/proto/certificate.proto\x1a lbryschema/proto/signature.proto\"\xa7\x02\n\x05\x43laim\x12\x1f\n\x07version\x18\x01 \x02(\x0e\x32\x0e.Claim.Version\x12#\n\tclaimType\x18\x02 \x02(\x0e\x32\x10.Claim.ClaimType\x12\x17\n\x06stream\x18\x03 \x01(\x0b\x32\x07.Stream\x12!\n\x0b\x63\x65rtificate\x18\x04 \x01(\x0b\x32\x0c.Certificate\x12&\n\x12publisherSignature\x18\x05 \x01(\x0b\x32\n.Signature\"*\n\x07Version\x12\x13\n\x0fUNKNOWN_VERSION\x10\x00\x12\n\n\x06_0_0_1\x10\x01\"H\n\tClaimType\x12\x16\n\x12UNKNOWN_CLAIM_TYPE\x10\x00\x12\x0e\n\nstreamType\x10\x01\x12\x13\n\x0f\x63\x65rtificateType\x10\x02')
,
dependencies=[lbryschema.proto.stream_pb2.DESCRIPTOR,lbryschema.proto.certificate_pb2.DESCRIPTOR,lbryschema.proto.signature_pb2.DESCRIPTOR,])
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_CLAIM_VERSION = _descriptor.EnumDescriptor(
name='Version',
full_name='Claim.Version',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_VERSION', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='_0_0_1', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=313,
serialized_end=355,
)
_sym_db.RegisterEnumDescriptor(_CLAIM_VERSION)
_CLAIM_CLAIMTYPE = _descriptor.EnumDescriptor(
name='ClaimType',
full_name='Claim.ClaimType',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_CLAIM_TYPE', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='streamType', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='certificateType', index=2, number=2,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=357,
serialized_end=429,
)
_sym_db.RegisterEnumDescriptor(_CLAIM_CLAIMTYPE)
_CLAIM = _descriptor.Descriptor(
name='Claim',
full_name='Claim',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='version', full_name='Claim.version', index=0,
number=1, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='claimType', full_name='Claim.claimType', index=1,
number=2, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='stream', full_name='Claim.stream', index=2,
number=3, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='certificate', full_name='Claim.certificate', index=3,
number=4, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='publisherSignature', full_name='Claim.publisherSignature', index=4,
number=5, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_CLAIM_VERSION,
_CLAIM_CLAIMTYPE,
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=134,
serialized_end=429,
)
_CLAIM.fields_by_name['version'].enum_type = _CLAIM_VERSION
_CLAIM.fields_by_name['claimType'].enum_type = _CLAIM_CLAIMTYPE
_CLAIM.fields_by_name['stream'].message_type = lbryschema.proto.stream_pb2._STREAM
_CLAIM.fields_by_name['certificate'].message_type = lbryschema.proto.certificate_pb2._CERTIFICATE
_CLAIM.fields_by_name['publisherSignature'].message_type = lbryschema.proto.signature_pb2._SIGNATURE
_CLAIM_VERSION.containing_type = _CLAIM
_CLAIM_CLAIMTYPE.containing_type = _CLAIM
DESCRIPTOR.message_types_by_name['Claim'] = _CLAIM
Claim = _reflection.GeneratedProtocolMessageType('Claim', (_message.Message,), dict(
DESCRIPTOR = _CLAIM,
__module__ = 'lbryschema.proto.claim_pb2'
# @@protoc_insertion_point(class_scope:Claim)
))
_sym_db.RegisterMessage(Claim)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,18 @@
syntax = "proto2";
message Fee {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
}
enum Currency {
UNKNOWN_CURRENCY = 0;
LBC = 1;
BTC = 2;
USD = 3;
}
required Version version = 1;
required Currency currency = 2;
required bytes address = 3;
required float amount = 4;
}

View file

@ -0,0 +1,146 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: lbryschema/proto/fee.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='lbryschema/proto/fee.proto',
package='',
serialized_pb=_b('\n\x1albryschema/proto/fee.proto\"\xcf\x01\n\x03\x46\x65\x65\x12\x1d\n\x07version\x18\x01 \x02(\x0e\x32\x0c.Fee.Version\x12\x1f\n\x08\x63urrency\x18\x02 \x02(\x0e\x32\r.Fee.Currency\x12\x0f\n\x07\x61\x64\x64ress\x18\x03 \x02(\x0c\x12\x0e\n\x06\x61mount\x18\x04 \x02(\x02\"*\n\x07Version\x12\x13\n\x0fUNKNOWN_VERSION\x10\x00\x12\n\n\x06_0_0_1\x10\x01\";\n\x08\x43urrency\x12\x14\n\x10UNKNOWN_CURRENCY\x10\x00\x12\x07\n\x03LBC\x10\x01\x12\x07\n\x03\x42TC\x10\x02\x12\x07\n\x03USD\x10\x03')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_FEE_VERSION = _descriptor.EnumDescriptor(
name='Version',
full_name='Fee.Version',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_VERSION', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='_0_0_1', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=135,
serialized_end=177,
)
_sym_db.RegisterEnumDescriptor(_FEE_VERSION)
_FEE_CURRENCY = _descriptor.EnumDescriptor(
name='Currency',
full_name='Fee.Currency',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_CURRENCY', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='LBC', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='BTC', index=2, number=2,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='USD', index=3, number=3,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=179,
serialized_end=238,
)
_sym_db.RegisterEnumDescriptor(_FEE_CURRENCY)
_FEE = _descriptor.Descriptor(
name='Fee',
full_name='Fee',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='version', full_name='Fee.version', index=0,
number=1, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='currency', full_name='Fee.currency', index=1,
number=2, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='address', full_name='Fee.address', index=2,
number=3, type=12, cpp_type=9, label=2,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='amount', full_name='Fee.amount', index=3,
number=4, type=2, cpp_type=6, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_FEE_VERSION,
_FEE_CURRENCY,
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=31,
serialized_end=238,
)
_FEE.fields_by_name['version'].enum_type = _FEE_VERSION
_FEE.fields_by_name['currency'].enum_type = _FEE_CURRENCY
_FEE_VERSION.containing_type = _FEE
_FEE_CURRENCY.containing_type = _FEE
DESCRIPTOR.message_types_by_name['Fee'] = _FEE
Fee = _reflection.GeneratedProtocolMessageType('Fee', (_message.Message,), dict(
DESCRIPTOR = _FEE,
__module__ = 'lbryschema.proto.fee_pb2'
# @@protoc_insertion_point(class_scope:Fee)
))
_sym_db.RegisterMessage(Fee)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,212 @@
syntax = "proto2";
import "lbryschema/proto/fee.proto";
message Metadata {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
_0_0_2 = 2;
_0_0_3 = 3;
_0_1_0 = 4;
}
enum Language {
UNKNOWN_LANGUAGE = 0;
en = 1;
aa = 2;
ab = 3;
ae = 4;
af = 5;
ak = 6;
am = 7;
an = 8;
ar = 9;
as = 10;
av = 11;
ay = 12;
az = 13;
ba = 14;
be = 15;
bg = 16;
bh = 17;
bi = 18;
bm = 19;
bn = 20;
bo = 21;
br = 22;
bs = 23;
ca = 24;
ce = 25;
ch = 26;
co = 27;
cr = 28;
cs = 29;
cu = 30;
cv = 31;
cy = 32;
da = 33;
de = 34;
dv = 35;
dz = 36;
ee = 37;
el = 38;
eo = 39;
es = 40;
et = 41;
eu = 42;
fa = 43;
ff = 44;
fi = 45;
fj = 46;
fo = 47;
fr = 48;
fy = 49;
ga = 50;
gd = 51;
gl = 52;
gn = 53;
gu = 54;
gv = 55;
ha = 56;
he = 57;
hi = 58;
ho = 59;
hr = 60;
ht = 61;
hu = 62;
hy = 63;
hz = 64;
ia = 65;
id = 66;
ie = 67;
ig = 68;
ii = 69;
ik = 70;
io = 71;
is = 72;
it = 73;
iu = 74;
ja = 75;
jv = 76;
ka = 77;
kg = 78;
ki = 79;
kj = 80;
kk = 81;
kl = 82;
km = 83;
kn = 84;
ko = 85;
kr = 86;
ks = 87;
ku = 88;
kv = 89;
kw = 90;
ky = 91;
la = 92;
lb = 93;
lg = 94;
li = 95;
ln = 96;
lo = 97;
lt = 98;
lu = 99;
lv = 100;
mg = 101;
mh = 102;
mi = 103;
mk = 104;
ml = 105;
mn = 106;
mr = 107;
ms = 108;
mt = 109;
my = 110;
na = 111;
nb = 112;
nd = 113;
ne = 114;
ng = 115;
nl = 116;
nn = 117;
no = 118;
nr = 119;
nv = 120;
ny = 121;
oc = 122;
oj = 123;
om = 124;
or = 125;
os = 126;
pa = 127;
pi = 128;
pl = 129;
ps = 130;
pt = 131;
qu = 132;
rm = 133;
rn = 134;
ro = 135;
ru = 136;
rw = 137;
sa = 138;
sc = 139;
sd = 140;
se = 141;
sg = 142;
si = 143;
sk = 144;
sl = 145;
sm = 146;
sn = 147;
so = 148;
sq = 149;
sr = 150;
ss = 151;
st = 152;
su = 153;
sv = 154;
sw = 155;
ta = 156;
te = 157;
tg = 158;
th = 159;
ti = 160;
tk = 161;
tl = 162;
tn = 163;
to = 164;
tr = 165;
ts = 166;
tt = 167;
tw = 168;
ty = 169;
ug = 170;
uk = 171;
ur = 172;
uz = 173;
ve = 174;
vi = 175;
vo = 176;
wa = 177;
wo = 178;
xh = 179;
yi = 180;
yo = 181;
za = 182;
zh = 183;
zu = 184;
}
required Version version = 1;
required Language language = 2;
required string title = 3;
required string description = 4;
required string author = 5;
required string license = 6;
required bool nsfw = 7;
optional Fee fee = 8;
optional string thumbnail = 9;
optional string preview = 10;
optional string licenseUrl = 11;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,14 @@
syntax = "proto2";
import "lbryschema/proto/certificate.proto";
message Signature {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
}
required Version version = 1;
required KeyType signatureType = 2;
required bytes signature = 3;
required bytes certificateId = 4;
}

View file

@ -0,0 +1,116 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: lbryschema/proto/signature.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import lbryschema.proto.certificate_pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='lbryschema/proto/signature.proto',
package='',
serialized_pb=_b('\n lbryschema/proto/signature.proto\x1a\"lbryschema/proto/certificate.proto\"\xa7\x01\n\tSignature\x12#\n\x07version\x18\x01 \x02(\x0e\x32\x12.Signature.Version\x12\x1f\n\rsignatureType\x18\x02 \x02(\x0e\x32\x08.KeyType\x12\x11\n\tsignature\x18\x03 \x02(\x0c\x12\x15\n\rcertificateId\x18\x04 \x02(\x0c\"*\n\x07Version\x12\x13\n\x0fUNKNOWN_VERSION\x10\x00\x12\n\n\x06_0_0_1\x10\x01')
,
dependencies=[lbryschema.proto.certificate_pb2.DESCRIPTOR,])
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_SIGNATURE_VERSION = _descriptor.EnumDescriptor(
name='Version',
full_name='Signature.Version',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_VERSION', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='_0_0_1', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=198,
serialized_end=240,
)
_sym_db.RegisterEnumDescriptor(_SIGNATURE_VERSION)
_SIGNATURE = _descriptor.Descriptor(
name='Signature',
full_name='Signature',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='version', full_name='Signature.version', index=0,
number=1, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='signatureType', full_name='Signature.signatureType', index=1,
number=2, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='signature', full_name='Signature.signature', index=2,
number=3, type=12, cpp_type=9, label=2,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='certificateId', full_name='Signature.certificateId', index=3,
number=4, type=12, cpp_type=9, label=2,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_SIGNATURE_VERSION,
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=73,
serialized_end=240,
)
_SIGNATURE.fields_by_name['version'].enum_type = _SIGNATURE_VERSION
_SIGNATURE.fields_by_name['signatureType'].enum_type = lbryschema.proto.certificate_pb2._KEYTYPE
_SIGNATURE_VERSION.containing_type = _SIGNATURE
DESCRIPTOR.message_types_by_name['Signature'] = _SIGNATURE
Signature = _reflection.GeneratedProtocolMessageType('Signature', (_message.Message,), dict(
DESCRIPTOR = _SIGNATURE,
__module__ = 'lbryschema.proto.signature_pb2'
# @@protoc_insertion_point(class_scope:Signature)
))
_sym_db.RegisterMessage(Signature)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,16 @@
syntax = "proto2";
message Source {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
}
required Version version = 1;
enum SourceTypes {
UNKNOWN_SOURCE_TYPE = 0;
lbry_sd_hash = 1;
}
required SourceTypes sourceType = 2;
required bytes source = 3;
required string contentType = 4;
}

View file

@ -0,0 +1,138 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: lbryschema/proto/source.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='lbryschema/proto/source.proto',
package='',
serialized_pb=_b('\n\x1dlbryschema/proto/source.proto\"\xde\x01\n\x06Source\x12 \n\x07version\x18\x01 \x02(\x0e\x32\x0f.Source.Version\x12\'\n\nsourceType\x18\x02 \x02(\x0e\x32\x13.Source.SourceTypes\x12\x0e\n\x06source\x18\x03 \x02(\x0c\x12\x13\n\x0b\x63ontentType\x18\x04 \x02(\t\"*\n\x07Version\x12\x13\n\x0fUNKNOWN_VERSION\x10\x00\x12\n\n\x06_0_0_1\x10\x01\"8\n\x0bSourceTypes\x12\x17\n\x13UNKNOWN_SOURCE_TYPE\x10\x00\x12\x10\n\x0clbry_sd_hash\x10\x01')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_SOURCE_VERSION = _descriptor.EnumDescriptor(
name='Version',
full_name='Source.Version',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_VERSION', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='_0_0_1', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=156,
serialized_end=198,
)
_sym_db.RegisterEnumDescriptor(_SOURCE_VERSION)
_SOURCE_SOURCETYPES = _descriptor.EnumDescriptor(
name='SourceTypes',
full_name='Source.SourceTypes',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_SOURCE_TYPE', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='lbry_sd_hash', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=200,
serialized_end=256,
)
_sym_db.RegisterEnumDescriptor(_SOURCE_SOURCETYPES)
_SOURCE = _descriptor.Descriptor(
name='Source',
full_name='Source',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='version', full_name='Source.version', index=0,
number=1, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='sourceType', full_name='Source.sourceType', index=1,
number=2, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='source', full_name='Source.source', index=2,
number=3, type=12, cpp_type=9, label=2,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='contentType', full_name='Source.contentType', index=3,
number=4, type=9, cpp_type=9, label=2,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_SOURCE_VERSION,
_SOURCE_SOURCETYPES,
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=34,
serialized_end=256,
)
_SOURCE.fields_by_name['version'].enum_type = _SOURCE_VERSION
_SOURCE.fields_by_name['sourceType'].enum_type = _SOURCE_SOURCETYPES
_SOURCE_VERSION.containing_type = _SOURCE
_SOURCE_SOURCETYPES.containing_type = _SOURCE
DESCRIPTOR.message_types_by_name['Source'] = _SOURCE
Source = _reflection.GeneratedProtocolMessageType('Source', (_message.Message,), dict(
DESCRIPTOR = _SOURCE,
__module__ = 'lbryschema.proto.source_pb2'
# @@protoc_insertion_point(class_scope:Source)
))
_sym_db.RegisterMessage(Source)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,15 @@
syntax = "proto2";
import "lbryschema/proto/metadata.proto";
import "lbryschema/proto/source.proto";
message Stream {
enum Version {
UNKNOWN_VERSION = 0;
_0_0_1 = 1;
}
required Version version = 1;
required Metadata metadata = 2;
required Source source = 3;
}

View file

@ -0,0 +1,111 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: lbryschema/proto/stream.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import lbryschema.proto.metadata_pb2
import lbryschema.proto.source_pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='lbryschema/proto/stream.proto',
package='',
serialized_pb=_b('\n\x1dlbryschema/proto/stream.proto\x1a\x1flbryschema/proto/metadata.proto\x1a\x1dlbryschema/proto/source.proto\"\x8c\x01\n\x06Stream\x12 \n\x07version\x18\x01 \x02(\x0e\x32\x0f.Stream.Version\x12\x1b\n\x08metadata\x18\x02 \x02(\x0b\x32\t.Metadata\x12\x17\n\x06source\x18\x03 \x02(\x0b\x32\x07.Source\"*\n\x07Version\x12\x13\n\x0fUNKNOWN_VERSION\x10\x00\x12\n\n\x06_0_0_1\x10\x01')
,
dependencies=[lbryschema.proto.metadata_pb2.DESCRIPTOR,lbryschema.proto.source_pb2.DESCRIPTOR,])
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_STREAM_VERSION = _descriptor.EnumDescriptor(
name='Version',
full_name='Stream.Version',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='UNKNOWN_VERSION', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='_0_0_1', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=196,
serialized_end=238,
)
_sym_db.RegisterEnumDescriptor(_STREAM_VERSION)
_STREAM = _descriptor.Descriptor(
name='Stream',
full_name='Stream',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='version', full_name='Stream.version', index=0,
number=1, type=14, cpp_type=8, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='metadata', full_name='Stream.metadata', index=1,
number=2, type=11, cpp_type=10, label=2,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='source', full_name='Stream.source', index=2,
number=3, type=11, cpp_type=10, label=2,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
_STREAM_VERSION,
],
options=None,
is_extendable=False,
extension_ranges=[],
oneofs=[
],
serialized_start=98,
serialized_end=238,
)
_STREAM.fields_by_name['version'].enum_type = _STREAM_VERSION
_STREAM.fields_by_name['metadata'].message_type = lbryschema.proto.metadata_pb2._METADATA
_STREAM.fields_by_name['source'].message_type = lbryschema.proto.source_pb2._SOURCE
_STREAM_VERSION.containing_type = _STREAM
DESCRIPTOR.message_types_by_name['Stream'] = _STREAM
Stream = _reflection.GeneratedProtocolMessageType('Stream', (_message.Message,), dict(
DESCRIPTOR = _STREAM,
__module__ = 'lbryschema.proto.stream_pb2'
# @@protoc_insertion_point(class_scope:Stream)
))
_sym_db.RegisterMessage(Stream)
# @@protoc_insertion_point(module_scope)

View file

@ -0,0 +1,102 @@
V_0_0_1 = "_0_0_1"
V_0_0_2 = "_0_0_2"
V_0_0_3 = "_0_0_3"
V_0_1_0 = "_0_1_0"
VERSION_MAP = {
V_0_0_1: 1,
V_0_0_2: 2,
V_0_0_3: 3,
V_0_1_0: 4,
}
VERSION_NAMES = {
1: V_0_0_1,
2: V_0_0_2,
3: V_0_0_3,
4: V_0_1_0
}
LBC = "LBC"
BTC = "BTC"
USD = "USD"
CURRENCY_MAP = {
LBC: 1,
BTC: 2,
USD: 3
}
CURRENCY_NAMES = {
1: LBC,
2: BTC,
3: USD
}
ADDRESS_LENGTH = 25
ADDRESS_CHECKSUM_LENGTH = 4
VERSION = "version"
STREAM_TYPE = "streamType"
CERTIFICATE_TYPE = "certificateType"
CLAIM_TYPE = "claimType"
SIGNATURE = "publisherSignature"
CLAIM_TYPES = {
STREAM_TYPE: "stream",
CERTIFICATE_TYPE: "certificate"
}
CLAIM_TYPE_NAMES = {
1: "stream",
2: "certificate"
}
LBRY_SD_HASH = "lbry_sd_hash"
LBRY_SD_HASH_LENGTH = 48
SOURCE_TYPES = {
LBRY_SD_HASH: 1
}
NIST256p = "NIST256p"
NIST384p = "NIST384p"
SECP256k1 = "SECP256k1"
ECDSA_CURVES = {
NIST256p: 1,
NIST384p: 2,
SECP256k1: 3
}
CURVE_NAMES = {
1: NIST256p,
2: NIST384p,
3: SECP256k1
}
SHA256 = "sha256"
SHA384 = "sha384"
B58_CHARS = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
assert len(B58_CHARS) == 58
PUBKEY_ADDRESS = 0
SCRIPT_ADDRESS = 5
ADDRESS_PREFIXES = {
"lbrycrd_main": {
PUBKEY_ADDRESS: 85,
SCRIPT_ADDRESS: 122
},
"lbrycrd_regtest": {
PUBKEY_ADDRESS: 111,
SCRIPT_ADDRESS: 196
},
"lbrycrd_testnet": {
PUBKEY_ADDRESS: 111,
SCRIPT_ADDRESS: 196
},
}

View file

@ -0,0 +1,54 @@
from copy import deepcopy
from lbryschema.proto import certificate_pb2 as cert_pb
from lbryschema.schema.schema import Schema
from lbryschema.schema import VERSION_MAP, V_0_0_1, ECDSA_CURVES, CURVE_NAMES
from lbryschema.validator import get_key_type_from_dem
class _ECDSAKeyHelper(object):
def __init__(self, key):
self._key = key
@property
def der(self):
return self._key.to_der()
@property
def curve_name(self):
return self._key.curve.name
class Certificate(Schema):
@classmethod
def load(cls, message):
_key = deepcopy(message)
_message_pb = cert_pb.Certificate()
if isinstance(_key, dict):
_message_pb.publicKey = _key.pop("publicKey")
_message_pb.version = VERSION_MAP[_key.pop("version")]
_message_pb.keyType = ECDSA_CURVES[_key.pop("keyType")]
else:
_message_pb.version = _key.version
_message_pb.keyType = _key.keyType
_message_pb.publicKey = _key.publicKey
if _message_pb.keyType not in CURVE_NAMES:
raise Exception("Unknown curve")
if get_key_type_from_dem(_message_pb.publicKey) != _message_pb.keyType:
raise Exception("Curve mismatch")
return cls._load(_key, _message_pb)
@classmethod
def load_from_key_obj(cls, key, key_type):
if key_type in ECDSA_CURVES:
_key = _ECDSAKeyHelper(key)
else:
raise Exception("Unknown key type: %s" % str(type(key)))
if key_type != _key.curve_name:
raise Exception("Curve mismatch")
msg = {
"version": V_0_0_1,
"keyType": key_type,
"publicKey": _key.der,
}
return cls.load(msg)

View file

@ -0,0 +1,51 @@
from copy import deepcopy
from lbryschema.proto import claim_pb2 as claim_pb
from lbryschema.schema import VERSION_MAP
from lbryschema.schema.signature import Signature
from lbryschema.schema.certificate import Certificate
from lbryschema.schema.schema import Schema
from lbryschema.schema.stream import Stream
class Claim(Schema):
CLAIM_TYPE_STREAM = 1
CLAIM_TYPE_CERT = 2
@classmethod
def load(cls, message):
_claim = deepcopy(message)
_message_pb = claim_pb.Claim()
_message_pb.version = VERSION_MAP[_claim.pop("version")]
if "certificate" in _claim:
_cert = _claim.pop("certificate")
if isinstance(_cert, dict):
cert = Certificate.load(_cert)
else:
cert = _cert
claim_type = Claim.CLAIM_TYPE_CERT
_message_pb.certificate.MergeFrom(cert)
elif "stream" in _claim:
_stream = _claim.pop("stream")
if isinstance(_stream, dict):
stream = Stream.load(_stream)
else:
stream = _stream
claim_type = Claim.CLAIM_TYPE_STREAM
_message_pb.stream.MergeFrom(stream)
else:
raise AttributeError
_message_pb.claimType = claim_type
if "publisherSignature" in _claim:
_publisherSignature = _claim.pop("publisherSignature")
if isinstance(_publisherSignature, dict):
publisherSignature = Signature.load(_publisherSignature)
else:
publisherSignature = _publisherSignature
_message_pb.publisherSignature.MergeFrom(publisherSignature)
return cls._load(_claim, _message_pb)

View file

@ -0,0 +1,17 @@
from copy import deepcopy
from lbryschema.schema.schema import Schema
from lbryschema.proto import fee_pb2 as fee_pb
from lbryschema.schema import VERSION_MAP, CURRENCY_MAP
class Fee(Schema):
@classmethod
def load(cls, message):
_fee = deepcopy(message)
currency = CURRENCY_MAP[_fee.pop('currency')]
_message_pb = fee_pb.Fee()
_message_pb.version = VERSION_MAP[_fee.pop("version")]
_message_pb.currency = currency
_message_pb.address = _fee.pop('address')
return cls._load(_fee, _message_pb)

View file

@ -0,0 +1,17 @@
from copy import deepcopy
from lbryschema.proto import metadata_pb2 as metadata_pb
from lbryschema.schema.fee import Fee
from lbryschema.schema.schema import Schema
from lbryschema.schema import VERSION_MAP
class Metadata(Schema):
@classmethod
def load(cls, message):
_metadata = deepcopy(message)
_message_pb = metadata_pb.Metadata()
_message_pb.version = VERSION_MAP[_metadata.pop("version")]
if 'fee' in _metadata:
fee_pb = Fee.load(_metadata.pop('fee'))
_message_pb.fee.CopyFrom(fee_pb)
return cls._load(_metadata, _message_pb)

View file

@ -0,0 +1,15 @@
import json
import google.protobuf.json_format as json_pb # pylint: disable=no-name-in-module
from google.protobuf.message import Message # pylint: disable=no-name-in-module,import-error
class Schema(Message):
@classmethod
def load(cls, message):
raise NotImplementedError
@classmethod
def _load(cls, data, message):
if isinstance(data, dict):
data = json.dumps(data)
return json_pb.Parse(data, message)

View file

@ -0,0 +1,17 @@
from copy import deepcopy
from lbryschema.proto import signature_pb2 as signature_pb
from lbryschema.schema import VERSION_MAP, ECDSA_CURVES
from lbryschema.schema.schema import Schema
class Signature(Schema):
@classmethod
def load(cls, message):
_signature = deepcopy(message)
_message_pb = signature_pb.Signature()
_message_pb.version = VERSION_MAP[_signature.pop("version")]
_message_pb.signatureType = ECDSA_CURVES[_signature.pop("signatureType")]
_message_pb.certificateId = _signature.pop("certificateId")
_message_pb.signature = _signature.pop("signature")
return cls._load(_signature, _message_pb)

View file

@ -0,0 +1,19 @@
from copy import deepcopy
from lbryschema.proto import source_pb2 as source_pb
from lbryschema.schema import SOURCE_TYPES, LBRY_SD_HASH_LENGTH, VERSION_MAP
from lbryschema.schema.schema import Schema
from lbryschema.error import InvalidSourceHashLength
class Source(Schema):
@classmethod
def load(cls, message):
_source = deepcopy(message)
sd_hash = _source.pop('source')
assert len(sd_hash) == LBRY_SD_HASH_LENGTH, InvalidSourceHashLength(len(sd_hash))
_message_pb = source_pb.Source()
_message_pb.version = VERSION_MAP[_source.pop("version")]
_message_pb.sourceType = SOURCE_TYPES[_source.pop('sourceType')]
_message_pb.source = sd_hash
_message_pb.contentType = _source.pop('contentType')
return cls._load(_source, _message_pb)

View file

@ -0,0 +1,20 @@
from copy import deepcopy
from lbryschema.schema.source import Source
from lbryschema.proto import stream_pb2 as stream_pb
from lbryschema.schema import VERSION_MAP
from lbryschema.schema.metadata import Metadata
from lbryschema.schema.schema import Schema
class Stream(Schema):
@classmethod
def load(cls, message):
_claim = deepcopy(message)
source = Source.load(_claim.pop('source'))
metadata = Metadata.load(_claim.pop('metadata'))
_message_pb = stream_pb.Stream()
_message_pb.version = VERSION_MAP[_claim.pop("version")]
_message_pb.source.CopyFrom(source)
_message_pb.metadata.CopyFrom(metadata)
return cls._load(_claim, _message_pb)

View file

@ -0,0 +1,41 @@
class SignatureSerializationFlag:
UNSIGNED = 0
'''
Format:
<FLAG><CLAIM BINARY>
or (legacy)
<CLAIM BINARY>
'''
ECDSA_LEGACY = 1
'''
Old claim format, which carried the signature inside the protobuf. Requires serializing back the claim with
signature stripped out for validation. This process requires knowledge on how a claim is serialized, thus requires
old fixed protobuf schema to work.
Format:
<CLAIM PROTOBUF SERIALIZED>
Curves: NIST256p, NIST384p, SECP256k1
Signature content: `r` and `s` in each half of the 64 or 96 bytes (depends on curve)
Signed payload:
1. Claim transaction output address (raw, decoded using base58)
2. Stripped out claim protobuf serialization (without the signature)
3. Certificate claim id (binary, not in network byte order)
'''
ECDSA_SECP256K1 = 2
'''
Format:
<FLAG><CERTIFICATE ID><SIGNATURE><BINARY PAYLOAD>
Curve: SECP256K1
Signature content: 64 bytes total, each half represents `r` and `s`
Signed payload:
1. raw claim name as bytes
2. Claim transaction output address (raw, decoded using base58)
3. Binary payload, independent of serialization (everything after the signature last byte)
4. Certificate claim id, not in network byte order.
A certificate can be signed as well, but this serialization model is unaware of content type or protobuf format.
'''
@classmethod
def is_flag_valid(cls, flag):
# todo: use python 3 enum when fully ported, but not worth now as its an extra dependency for py2
return 0 <= flag <= 2

View file

@ -0,0 +1,11 @@
import struct
from collections import namedtuple
from .flags import SignatureSerializationFlag
class Signature(namedtuple("Signature", "flags signature certificate_id")):
def deserialize(cls, payload):
flag = struct.unpack("<b", payload[0])[0]
if not SignatureSerializationFlag.is_flag_valid(flag):
return Signature(SignatureSerializationFlag.ECDSA_LEGACY, )
certificate

102
lbrynet/schema/signer.py Normal file
View file

@ -0,0 +1,102 @@
import ecdsa
import hashlib
import binascii
from lbryschema.address import decode_address
from lbryschema.encoding import decode_b64_fields
from lbryschema.schema.certificate import Certificate
from lbryschema.schema.claim import Claim
from lbryschema.validator import validate_claim_id
from lbryschema.schema import V_0_0_1, CLAIM_TYPE, CLAIM_TYPES, CERTIFICATE_TYPE, VERSION
from lbryschema.schema import NIST256p, NIST384p, SECP256k1, SHA256, SHA384
class NIST_ECDSASigner(object):
CURVE = None
CURVE_NAME = None
HASHFUNC = hashlib.sha256
HASHFUNC_NAME = SHA256
def __init__(self, private_key):
self._private_key = private_key
@property
def private_key(self):
return self._private_key
@property
def public_key(self):
return self.private_key.get_verifying_key()
@property
def certificate(self):
certificate_claim = {
VERSION: V_0_0_1,
CLAIM_TYPE: CERTIFICATE_TYPE,
CLAIM_TYPES[CERTIFICATE_TYPE]: Certificate.load_from_key_obj(self.public_key,
self.CURVE_NAME)
}
return Claim.load(certificate_claim)
@classmethod
def load_pem(cls, pem_string):
return cls(ecdsa.SigningKey.from_pem(pem_string, hashfunc=cls.HASHFUNC_NAME))
@classmethod
def generate(cls):
return cls(ecdsa.SigningKey.generate(curve=cls.CURVE, hashfunc=cls.HASHFUNC_NAME))
def sign_stream_claim(self, claim, claim_address, cert_claim_id):
validate_claim_id(cert_claim_id)
decoded_addr = decode_address(claim_address)
to_sign = bytearray()
to_sign.extend(decoded_addr)
to_sign.extend(claim.serialized_no_signature)
to_sign.extend(binascii.unhexlify(cert_claim_id))
digest = self.HASHFUNC(to_sign).digest()
if not isinstance(self.private_key, ecdsa.SigningKey):
raise Exception("Not given a signing key")
sig_dict = {
"version": V_0_0_1,
"signatureType": self.CURVE_NAME,
"signature": self.private_key.sign_digest_deterministic(digest, hashfunc=self.HASHFUNC),
"certificateId": binascii.unhexlify(cert_claim_id)
}
msg = {
"version": V_0_0_1,
"stream": decode_b64_fields(claim.protobuf_dict)['stream'],
"publisherSignature": sig_dict
}
return Claim.load(msg)
class NIST256pSigner(NIST_ECDSASigner):
CURVE = ecdsa.NIST256p
CURVE_NAME = NIST256p
class NIST384pSigner(NIST_ECDSASigner):
CURVE = ecdsa.NIST384p
CURVE_NAME = NIST384p
HASHFUNC = hashlib.sha384
HASHFUNC_NAME = SHA384
class SECP256k1Signer(NIST_ECDSASigner):
CURVE = ecdsa.SECP256k1
CURVE_NAME = SECP256k1
def get_signer(curve):
if curve == NIST256p:
return NIST256pSigner
elif curve == NIST384p:
return NIST384pSigner
elif curve == SECP256k1:
return SECP256k1Signer
else:
raise Exception("Unknown curve: %s" % str(curve))

171
lbrynet/schema/uri.py Normal file
View file

@ -0,0 +1,171 @@
import re
from lbryschema.error import URIParseError
PROTOCOL = 'lbry://'
CHANNEL_CHAR = '@'
CLAIM_ID_CHAR = '#'
CLAIM_SEQUENCE_CHAR = ':'
BID_POSITION_CHAR = '$'
PATH_CHAR = '/'
QUERY_CHAR = '?'
CLAIM_ID_MAX_LENGTH = 40
CHANNEL_NAME_MIN_LENGTH = 1
class URI(object):
__slots__ = ['name', 'claim_sequence', 'bid_position', 'claim_id', 'path']
def __init__(self, name, claim_sequence=None, bid_position=None, claim_id=None, path=None):
if len([v for v in [claim_sequence, bid_position, claim_id] if v is not None]) > 1:
raise ValueError(
"Only one of these may be present at a time: claim_sequence, bid_position, claim_id"
)
self.name = name
self.claim_sequence = claim_sequence
self.bid_position = bid_position
self.claim_id = claim_id
self.path = path
if self.path is not None and not self.is_channel:
raise ValueError("Content claims cannot have paths")
def __str__(self):
return self.to_uri_string()
def __eq__(self, other):
for prop in self.__slots__:
if not hasattr(other, prop) or getattr(self, prop) != getattr(other, prop):
return False
return self.__class__ == other.__class__
@property
def is_channel(self):
return self.name.startswith(CHANNEL_CHAR)
def to_uri_string(self):
uri_string = PROTOCOL + "%s" % self.name
if self.claim_sequence is not None:
uri_string += CLAIM_SEQUENCE_CHAR + "%i" % self.claim_sequence
elif self.bid_position is not None:
uri_string += BID_POSITION_CHAR + "%i" % self.bid_position
elif self.claim_id is not None:
uri_string += CLAIM_ID_CHAR + "%s" % self.claim_id
if self.path is not None:
uri_string += PATH_CHAR + "%s" % self.path
return uri_string
def to_dict(self):
return {
"name": self.name,
'claim_sequence': self.claim_sequence,
'bid_position': self.bid_position,
'claim_id': self.claim_id,
'path': self.path,
}
@classmethod
def from_uri_string(cls, uri_string):
"""
Parses LBRY uri into its components
:param uri_string: format - lbry://name:n$rank#id/path
optional modifiers:
claim_sequence (int): the nth claim to the name
bid_position (int): the bid queue position of the claim for the name
claim_id (str): the claim id for the claim
path (str): claim within a channel
:return: URI
"""
match = re.match(get_schema_regex(), uri_string)
if match is None:
raise URIParseError('Invalid URI')
if match.group('content_name') and match.group('path'):
raise URIParseError('Only channels may have paths')
return cls(
name=match.group("content_or_channel_name"),
claim_sequence=int(match.group("claim_sequence")) if match.group(
"claim_sequence") is not None else None,
bid_position=int(match.group("bid_position")) if match.group(
"bid_position") is not None else None,
claim_id=match.group("claim_id"),
path=match.group("path")
)
@classmethod
def from_dict(cls, uri_dict):
"""
Creates URI from dict
:return: URI
"""
return cls(**uri_dict)
def get_schema_regex():
def _named(name, regex):
return "(?P<" + name + ">" + regex + ")"
def _group(regex):
return "(?:" + regex + ")"
# TODO: regex should include the fact that content names cannot have paths
# right now this is only enforced in code, not in the regex
# Escape constants
claim_id_char = re.escape(CLAIM_ID_CHAR)
claim_sequence_char = re.escape(CLAIM_SEQUENCE_CHAR)
bid_position_char = re.escape(BID_POSITION_CHAR)
channel_char = re.escape(CHANNEL_CHAR)
path_char = re.escape(PATH_CHAR)
protocol = _named("protocol", re.escape(PROTOCOL))
# Define basic building blocks
valid_name_char = "[a-zA-Z0-9\-]" # these characters are the only valid name characters
name_content = valid_name_char + '+'
name_min_channel_length = valid_name_char + '{' + str(CHANNEL_NAME_MIN_LENGTH) + ',}'
positive_number = "[1-9][0-9]*"
number = '\-?' + positive_number
# Define URI components
content_name = _named("content_name", name_content)
channel_name = _named("channel_name", channel_char + name_min_channel_length)
content_or_channel_name = _named("content_or_channel_name", content_name + "|" + channel_name)
claim_id_piece = _named("claim_id", "[0-9a-f]{1," + str(CLAIM_ID_MAX_LENGTH) + "}")
claim_id = _group(claim_id_char + claim_id_piece)
bid_position_piece = _named("bid_position", number)
bid_position = _group(bid_position_char + bid_position_piece)
claim_sequence_piece = _named("claim_sequence", number)
claim_sequence = _group(claim_sequence_char + claim_sequence_piece)
modifier = _named("modifier", claim_id + "|" + bid_position + "|" + claim_sequence)
path_piece = _named("path", name_content)
path = _group(path_char + path_piece)
# Combine components
uri = _named("uri", (
'^' +
protocol + '?' +
content_or_channel_name +
modifier + '?' +
path + '?' +
'$'
))
return uri
def parse_lbry_uri(lbry_uri):
return URI.from_uri_string(lbry_uri)

136
lbrynet/schema/validator.py Normal file
View file

@ -0,0 +1,136 @@
from string import hexdigits
import six
import ecdsa
import hashlib
import binascii
from cryptography.hazmat.primitives import serialization
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 cryptography.exceptions import InvalidSignature
from ecdsa.util import sigencode_der
from lbryschema.address import decode_address
from lbryschema.schema import NIST256p, NIST384p, SECP256k1, ECDSA_CURVES, CURVE_NAMES
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, six.binary_type):
claim_id = claim_id.decode('utf-8')
if set(claim_id).difference(hexdigits):
raise Exception("Claim id is not hex encoded")
class Validator(object):
CURVE_NAME = None
HASHFUNC = hashlib.sha256
def __init__(self, public_key, certificate_claim_id):
validate_claim_id(certificate_claim_id)
if CURVE_NAMES.get(get_key_type_from_dem(public_key)) != self.CURVE_NAME:
raise Exception("Curve mismatch")
self._public_key = public_key
self._certificate_claim_id = certificate_claim_id
@property
def public_key(self):
return self._public_key
@property
def certificate_claim_id(self):
return self._certificate_claim_id
@classmethod
def signing_key_from_pem(cls, pem):
return ecdsa.SigningKey.from_pem(pem, hashfunc=cls.HASHFUNC)
@classmethod
def signing_key_from_der(cls, der):
return ecdsa.SigningKey.from_der(der, hashfunc=cls.HASHFUNC)
@classmethod
def load_from_certificate(cls, certificate_claim, certificate_claim_id):
certificate = certificate_claim.certificate
return cls(certificate.publicKey, certificate_claim_id)
def validate_signature(self, digest, signature):
public_key = load_der_public_key(self.public_key, default_backend())
if len(signature) == 64:
hash = hashes.SHA256()
elif len(signature) == 96:
hash = hashes.SHA384()
signature = binascii.hexlify(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)
try:
public_key.verify(encoded_sig, digest, ec.ECDSA(Prehashed(hash)))
return True
except InvalidSignature:
# TODO Fixme. This is what is expected today on the outer calls. This should be implementation independent
# but requires changing everything calling that
from ecdsa import BadSignatureError
raise BadSignatureError
def validate_claim_signature(self, claim, claim_address):
decoded_address = decode_address(claim_address)
# extract and serialize the stream from the claim, then check the signature
signature = binascii.unhexlify(claim.signature)
if signature is None:
raise Exception("No signature to validate")
to_sign = bytearray()
to_sign.extend(decoded_address)
to_sign.extend(claim.serialized_no_signature)
to_sign.extend(binascii.unhexlify(self.certificate_claim_id))
return self.validate_signature(self.HASHFUNC(to_sign).digest(), signature)
def validate_private_key(self, private_key):
if not isinstance(private_key, ecdsa.SigningKey):
raise TypeError("Not given a signing key, given a %s" % str(type(private_key)))
return private_key.get_verifying_key().to_der() == self.public_key
class NIST256pValidator(Validator):
CURVE_NAME = NIST256p
HASHFUNC = hashlib.sha256
class NIST384pValidator(Validator):
CURVE_NAME = NIST384p
HASHFUNC = hashlib.sha384
class SECP256k1Validator(Validator):
CURVE_NAME = SECP256k1
HASHFUNC = hashlib.sha256
def get_validator(curve):
if curve == NIST256p:
return NIST256pValidator
elif curve == NIST384p:
return NIST384pValidator
elif curve == SECP256k1:
return SECP256k1Validator
else:
raise Exception("Unknown curve: %s" % str(curve))
def get_key_type_from_dem(pubkey_dem):
name = serialization.load_der_public_key(pubkey_dem, default_backend()).curve.name
if name == 'secp256k1':
return ECDSA_CURVES[SECP256k1]
elif name == 'secp256r1':
return ECDSA_CURVES[NIST256p]
elif name == 'secp384r1':
return ECDSA_CURVES[NIST384p]
raise Exception("unexpected curve: %s" % name)