mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
threshold fulfillment
tests
This commit is contained in:
parent
46cd3323e8
commit
821ca9f8e6
@ -51,6 +51,8 @@ class BitmaskRegistry:
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
from bigchaindb.crypto.fulfillments.threshold_sha256 import ThresholdSha256Fulfillment
|
||||||
from bigchaindb.crypto.fulfillments.ed25519_sha256 import Ed25519Sha256Fulfillment
|
from bigchaindb.crypto.fulfillments.ed25519_sha256 import Ed25519Sha256Fulfillment
|
||||||
|
|
||||||
|
BitmaskRegistry.register_type(ThresholdSha256Fulfillment)
|
||||||
BitmaskRegistry.register_type(Ed25519Sha256Fulfillment)
|
BitmaskRegistry.register_type(Ed25519Sha256Fulfillment)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from abc import ABCMeta
|
|||||||
|
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
from bigchaindb.crypto.iostream import base64_add_padding, base64_remove_padding
|
from bigchaindb.crypto.iostream import base64_add_padding, base64_remove_padding, Writer, Reader
|
||||||
|
|
||||||
CONDITION_REGEX = r'^cc:1:[1-9a-f][0-9a-f]{0,2}:[a-zA-Z0-9_-]{43}:[1-9][0-9]{0,50}$'
|
CONDITION_REGEX = r'^cc:1:[1-9a-f][0-9a-f]{0,2}:[a-zA-Z0-9_-]{43}:[1-9][0-9]{0,50}$'
|
||||||
|
|
||||||
@ -47,6 +47,27 @@ class Condition(metaclass=ABCMeta):
|
|||||||
|
|
||||||
return condition
|
return condition
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_binary(reader):
|
||||||
|
"""
|
||||||
|
* Create a Condition object from a binary blob.
|
||||||
|
*
|
||||||
|
* This method will parse a stream of binary data and construct a
|
||||||
|
* corresponding Condition object.
|
||||||
|
*
|
||||||
|
Args:
|
||||||
|
reader (Reader): Binary stream implementing the Reader interface
|
||||||
|
Returns:
|
||||||
|
Condition: Resulting object
|
||||||
|
"""
|
||||||
|
reader = Reader.from_source(reader)
|
||||||
|
|
||||||
|
# Instantiate condition
|
||||||
|
condition = Condition()
|
||||||
|
condition.parse_binary(reader)
|
||||||
|
|
||||||
|
return condition
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bitmask(self):
|
def bitmask(self):
|
||||||
"""
|
"""
|
||||||
@ -152,12 +173,53 @@ class Condition(metaclass=ABCMeta):
|
|||||||
Turns the condition into a URI containing only URL-safe characters. This
|
Turns the condition into a URI containing only URL-safe characters. This
|
||||||
format is convenient for passing around conditions in URLs, JSON and other text-based formats.
|
format is convenient for passing around conditions in URLs, JSON and other text-based formats.
|
||||||
|
|
||||||
|
"cc:" BASE10(VERSION) ":" BASE16(TYPE_BITMASK) ":" BASE64URL(HASH) ":" BASE10(MAX_FULFILLMENT_LENGTH)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: Condition as a URI
|
string: Condition as a URI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return 'cc:1:{}:{}:{}'.format(self.bitmask,
|
return 'cc:1:{:x}:{}:{}'.format(self.bitmask,
|
||||||
base64_remove_padding(
|
base64_remove_padding(
|
||||||
base64.urlsafe_b64encode(self.hash)
|
base64.urlsafe_b64encode(self.hash)
|
||||||
).decode('utf-8'),
|
).decode('utf-8'),
|
||||||
self.max_fulfillment_length)
|
self.max_fulfillment_length)
|
||||||
|
|
||||||
|
def serialize_binary(self):
|
||||||
|
"""
|
||||||
|
Serialize condition to a buffer.
|
||||||
|
|
||||||
|
Encodes the condition as a string of bytes. This is used internally for
|
||||||
|
encoding subconditions, but can also be used to passing around conditions
|
||||||
|
in a binary protocol for instance.
|
||||||
|
|
||||||
|
CONDITION =
|
||||||
|
VARUINT TYPE_BITMASK
|
||||||
|
VARBYTES HASH
|
||||||
|
VARUINT MAX_FULFILLMENT_LENGTH
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Serialized condition
|
||||||
|
"""
|
||||||
|
writer = Writer()
|
||||||
|
writer.write_var_uint(self.bitmask)
|
||||||
|
writer.write_var_bytes(self.hash)
|
||||||
|
writer.write_var_uint(self.max_fulfillment_length)
|
||||||
|
return b''.join(writer.components)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_binary(self, reader):
|
||||||
|
"""
|
||||||
|
* Parse any condition in binary format.
|
||||||
|
*
|
||||||
|
* Will populate the condition object with data from the provided binary
|
||||||
|
* stream.
|
||||||
|
*
|
||||||
|
Args:
|
||||||
|
reader (Reader): Binary stream containing the condition.
|
||||||
|
"""
|
||||||
|
self.bitmask = reader.read_var_uint()
|
||||||
|
|
||||||
|
# TODO: Ensure bitmask is supported?
|
||||||
|
self.hash = reader.read_var_bytes()
|
||||||
|
self.max_fulfillment_length = reader.read_var_uint()
|
||||||
|
|||||||
@ -54,6 +54,30 @@ class Fulfillment(metaclass=ABCMeta):
|
|||||||
|
|
||||||
return fulfillment
|
return fulfillment
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_binary(reader):
|
||||||
|
"""
|
||||||
|
Create a Fulfillment object from a binary blob.
|
||||||
|
|
||||||
|
This method will parse a stream of binary data and construct a
|
||||||
|
corresponding Fulfillment object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader (Reader): Binary stream implementing the Reader interface
|
||||||
|
Returns:
|
||||||
|
Fulfillment: Resulting object
|
||||||
|
"""
|
||||||
|
reader = Reader.from_source(reader)
|
||||||
|
|
||||||
|
from bigchaindb.crypto.bitmark_registry import BitmaskRegistry
|
||||||
|
|
||||||
|
cls = BitmaskRegistry.get_class_from_typebit(reader.read_var_uint())
|
||||||
|
|
||||||
|
fulfillment = cls()
|
||||||
|
fulfillment.parse_payload(reader)
|
||||||
|
|
||||||
|
return fulfillment
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bitmask(self):
|
def bitmask(self):
|
||||||
"""
|
"""
|
||||||
@ -99,16 +123,38 @@ class Fulfillment(metaclass=ABCMeta):
|
|||||||
format is convenient for passing around fulfillments in URLs, JSON and
|
format is convenient for passing around fulfillments in URLs, JSON and
|
||||||
other text-based formats.
|
other text-based formats.
|
||||||
|
|
||||||
|
"cf:" BASE10(VERSION) ":" BASE16(TYPE_BIT) ":" BASE64URL(FULFILLMENT_PAYLOAD)
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
string: Fulfillment as a URI
|
string: Fulfillment as a URI
|
||||||
"""
|
"""
|
||||||
return 'cf:1:{}:{}'.format(self.bitmask,
|
return 'cf:1:{:x}:{}'.format(self._bitmask,
|
||||||
base64_remove_padding(
|
base64_remove_padding(
|
||||||
base64.urlsafe_b64encode(
|
base64.urlsafe_b64encode(
|
||||||
b''.join(self.serialize_payload().components)
|
b''.join(self.serialize_payload().components)
|
||||||
)
|
)
|
||||||
).decode('utf-8'))
|
).decode('utf-8'))
|
||||||
|
|
||||||
|
def serialize_binary(self):
|
||||||
|
"""
|
||||||
|
Serialize fulfillment to a buffer.
|
||||||
|
|
||||||
|
Encodes the fulfillment as a string of bytes. This is used internally for
|
||||||
|
encoding subfulfillments, but can also be used to passing around
|
||||||
|
fulfillments in a binary protocol for instance.
|
||||||
|
|
||||||
|
FULFILLMENT =
|
||||||
|
VARUINT TYPE_BIT
|
||||||
|
FULFILLMENT_PAYLOAD
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Serialized fulfillment
|
||||||
|
"""
|
||||||
|
writer = Writer()
|
||||||
|
writer.write_var_uint(self.bitmask)
|
||||||
|
self.write_payload(writer)
|
||||||
|
return b''.join(writer.components)
|
||||||
|
|
||||||
def serialize_payload(self):
|
def serialize_payload(self):
|
||||||
"""
|
"""
|
||||||
Return the fulfillment payload as a buffer.
|
Return the fulfillment payload as a buffer.
|
||||||
|
|||||||
@ -8,11 +8,13 @@ from bigchaindb.crypto.iostream import Predictor
|
|||||||
class Ed25519Sha256Fulfillment(BaseSha256Fulfillment):
|
class Ed25519Sha256Fulfillment(BaseSha256Fulfillment):
|
||||||
|
|
||||||
_bitmask = 0x08
|
_bitmask = 0x08
|
||||||
_message_prefix = None
|
|
||||||
_max_dynamic_message_length = None
|
def __init__(self):
|
||||||
_public_key = None
|
self._message_prefix = None
|
||||||
_message = None
|
self._max_dynamic_message_length = None
|
||||||
_signature = None
|
self._public_key = None
|
||||||
|
self._message = None
|
||||||
|
self._signature = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message_prefix(self):
|
def message_prefix(self):
|
||||||
@ -155,7 +157,6 @@ class Ed25519Sha256Fulfillment(BaseSha256Fulfillment):
|
|||||||
self.max_dynamic_message_length = reader.read_var_uint()
|
self.max_dynamic_message_length = reader.read_var_uint()
|
||||||
self.message = reader.read_var_bytes()
|
self.message = reader.read_var_bytes()
|
||||||
self.signature = reader.read_var_bytes()
|
self.signature = reader.read_var_bytes()
|
||||||
print(self.signature)
|
|
||||||
|
|
||||||
def write_payload(self, writer):
|
def write_payload(self, writer):
|
||||||
"""
|
"""
|
||||||
|
|||||||
231
bigchaindb/crypto/fulfillments/threshold_sha256.py
Normal file
231
bigchaindb/crypto/fulfillments/threshold_sha256.py
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
from bigchaindb.crypto.condition import Condition
|
||||||
|
from bigchaindb.crypto.fulfillment import Fulfillment
|
||||||
|
from bigchaindb.crypto.fulfillments.base_sha256 import BaseSha256Fulfillment
|
||||||
|
from bigchaindb.crypto.iostream import Predictor, Reader, Writer
|
||||||
|
|
||||||
|
|
||||||
|
class ThresholdSha256Fulfillment(BaseSha256Fulfillment):
|
||||||
|
_bitmask = 0x04
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._threshold = None
|
||||||
|
|
||||||
|
self.subconditions = []
|
||||||
|
self.subfulfillments = []
|
||||||
|
|
||||||
|
def add_subcondition(self, subcondition):
|
||||||
|
"""
|
||||||
|
Add a subcondition (unfulfilled).
|
||||||
|
|
||||||
|
This can be used to generate a new threshold condition from a set of
|
||||||
|
subconditions or to provide a non-fulfilled subcondition when creating a threshold fulfillment.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subcondition (Condition): Condition to add
|
||||||
|
"""
|
||||||
|
if not isinstance(subcondition, Condition):
|
||||||
|
raise TypeError('Subconditions must be objects of type Condition')
|
||||||
|
self.subconditions.append(subcondition)
|
||||||
|
|
||||||
|
def add_subfulfillment(self, subfulfillment):
|
||||||
|
"""
|
||||||
|
Add a fulfilled subcondition.
|
||||||
|
|
||||||
|
When constructing a threshold fulfillment, this method allows you to
|
||||||
|
provide a fulfillment for one of the subconditions.
|
||||||
|
|
||||||
|
Note that you do **not** have to add the subcondition if you're adding the
|
||||||
|
fulfillment. The condition can be calculated from the fulfillment and will
|
||||||
|
be added automatically.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subfulfillment (Fulfillment): Fulfillment to add
|
||||||
|
"""
|
||||||
|
if not isinstance(subfulfillment, Fulfillment):
|
||||||
|
raise TypeError('Subfulfillments must be objects of type Fulfillment')
|
||||||
|
|
||||||
|
self.subfulfillments.append(subfulfillment)
|
||||||
|
|
||||||
|
def get_all_subconditions(self):
|
||||||
|
"""
|
||||||
|
Returns all subconditions including fulfilled ones.
|
||||||
|
|
||||||
|
This method returns the subconditions plus all subfulfillments, converted to conditions.
|
||||||
|
|
||||||
|
@return {Condition[]} Set of subconditions
|
||||||
|
"""
|
||||||
|
return self.subconditions + [f.condition for f in self.subfulfillments]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def threshold(self):
|
||||||
|
return self._threshold
|
||||||
|
|
||||||
|
@threshold.setter
|
||||||
|
def threshold(self, value):
|
||||||
|
"""
|
||||||
|
Set the threshold.
|
||||||
|
|
||||||
|
Determines the weighted threshold that is used to consider this condition
|
||||||
|
fulfilled. If the added weight of all valid subfulfillments is greater or
|
||||||
|
equal to this number, the threshold condition is considered to be fulfilled.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (int): Integer threshold
|
||||||
|
"""
|
||||||
|
self._threshold = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bitmask(self):
|
||||||
|
"""
|
||||||
|
Get full bitmask.
|
||||||
|
|
||||||
|
This is a type of condition that can contain subconditions. A complete
|
||||||
|
bitmask must contain the set of types that must be supported in order to
|
||||||
|
validate this fulfillment. Therefore, we need to calculate the bitwise OR
|
||||||
|
of this condition's TYPE_BIT and all subcondition's and subfulfillment's bitmasks.
|
||||||
|
|
||||||
|
@return {Number} Complete bitmask for this fulfillment.
|
||||||
|
"""
|
||||||
|
bitmask = self._bitmask
|
||||||
|
|
||||||
|
for cond in self.subconditions:
|
||||||
|
bitmask |= cond.bitmask
|
||||||
|
|
||||||
|
for f in self.subfulfillments:
|
||||||
|
bitmask |= f.bitmask
|
||||||
|
|
||||||
|
return bitmask
|
||||||
|
|
||||||
|
def write_hash_payload(self, hasher):
|
||||||
|
"""
|
||||||
|
Produce the contents of the condition hash.
|
||||||
|
|
||||||
|
This function is called internally by the `getCondition` method.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hasher (Hasher): Hash generator
|
||||||
|
"""
|
||||||
|
if not (len(self.subconditions) or len(self.subfulfillments)):
|
||||||
|
raise ValueError('Requires subconditions')
|
||||||
|
|
||||||
|
subconditions = [c.serialize_binary() for c in self.get_all_subconditions()]
|
||||||
|
subconditions.sort(key=len)
|
||||||
|
|
||||||
|
hasher.write_var_uint(ThresholdSha256Fulfillment()._bitmask)
|
||||||
|
hasher.write_var_uint(self.threshold)
|
||||||
|
hasher.write_var_uint(len(subconditions))
|
||||||
|
for cond in subconditions:
|
||||||
|
hasher.write(cond)
|
||||||
|
return hasher
|
||||||
|
|
||||||
|
def calculate_max_fulfillment_length(self):
|
||||||
|
"""
|
||||||
|
Calculates the longest possible fulfillment length.
|
||||||
|
|
||||||
|
In a threshold condition, the maximum length of the fulfillment depends on
|
||||||
|
the maximum lengths of the fulfillments of the subconditions. However,
|
||||||
|
usually not all subconditions must be fulfilled to meet the threshold. This
|
||||||
|
means we only need to consider the worst case where the largest number of
|
||||||
|
largest fulfillments are provided and the smaller fulfillments are not.
|
||||||
|
|
||||||
|
The algorithm to calculate the worst case fulfillment size is not trivial,
|
||||||
|
however, it does not need to provide the exact worst-case fulfillment
|
||||||
|
length, only an upper bound for it.
|
||||||
|
|
||||||
|
@return {Number} Maximum length of the fulfillment payload
|
||||||
|
|
||||||
|
"""
|
||||||
|
# TODO: Currently wrong
|
||||||
|
|
||||||
|
predictor = Predictor()
|
||||||
|
|
||||||
|
# Calculate length of longest fulfillments
|
||||||
|
max_fulfillments_length = [c.max_fulfillment_length for c in self.get_all_subconditions()]
|
||||||
|
max_fulfillments_length.sort()
|
||||||
|
worst_case_fulfillments_length = sum(max_fulfillments_length[-self.threshold:])
|
||||||
|
|
||||||
|
predictor.write_var_uint(2)
|
||||||
|
predictor.skip(worst_case_fulfillments_length)
|
||||||
|
|
||||||
|
return predictor.size
|
||||||
|
|
||||||
|
def parse_payload(self, reader):
|
||||||
|
"""
|
||||||
|
Parse a fulfillment payload.
|
||||||
|
|
||||||
|
Read a fulfillment payload from a Reader and populate this object with that fulfillment.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader (Reader): Source to read the fulfillment payload from.
|
||||||
|
"""
|
||||||
|
if not isinstance(reader, Reader):
|
||||||
|
raise TypeError('reader must be a Reader instance')
|
||||||
|
self.threshold = reader.read_var_uint()
|
||||||
|
|
||||||
|
fulfillment_count = reader.read_var_uint()
|
||||||
|
for i in range(fulfillment_count):
|
||||||
|
# TODO: Read weights
|
||||||
|
# const weight = 1
|
||||||
|
reader.skip_var_uint()
|
||||||
|
self.add_subfulfillment(Fulfillment.from_binary(reader))
|
||||||
|
|
||||||
|
condition_count = reader.read_var_uint()
|
||||||
|
for i in range(condition_count):
|
||||||
|
# TODO: Read weights
|
||||||
|
# const weight = 1
|
||||||
|
reader.skip_var_uint()
|
||||||
|
self.add_subcondition(Condition.from_binary(reader))
|
||||||
|
|
||||||
|
def write_payload(self, writer):
|
||||||
|
"""
|
||||||
|
Generate the fulfillment payload.
|
||||||
|
|
||||||
|
This writes the fulfillment payload to a Writer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
writer (Writer): Subject for writing the fulfillment payload.
|
||||||
|
"""
|
||||||
|
if not isinstance(writer, Writer):
|
||||||
|
raise TypeError('writer must be a Writer instance')
|
||||||
|
conditions = [c.serialize_binary() for c in self.subconditions]
|
||||||
|
|
||||||
|
# Get as many fulfillments as possible
|
||||||
|
fulfillments = [{'fulfillment': f, 'binary': f.serialize_binary()} for f in self.subfulfillments]
|
||||||
|
|
||||||
|
# Prefer shorter fulfillments
|
||||||
|
fulfillments.sort(key=lambda f: len(f['binary']))
|
||||||
|
|
||||||
|
if len(fulfillments) < self.threshold:
|
||||||
|
raise ValueError('Not enough subfulfillments')
|
||||||
|
|
||||||
|
while len(fulfillments) > self.threshold:
|
||||||
|
# TODO: only for valid fulfillments?
|
||||||
|
conditions.append(fulfillments.pop()['fulfillment'].condition.serialize_binary())
|
||||||
|
|
||||||
|
writer.write_var_uint(self.threshold)
|
||||||
|
|
||||||
|
writer.write_var_uint(len(fulfillments))
|
||||||
|
for fulfillment in fulfillments:
|
||||||
|
# TODO: Support custom weights
|
||||||
|
writer.write_var_uint(1)
|
||||||
|
writer.write(fulfillment['binary'])
|
||||||
|
|
||||||
|
writer.write_var_uint(len(conditions))
|
||||||
|
for condition in conditions:
|
||||||
|
# TODO: Support custom weights
|
||||||
|
writer.write_var_uint(1)
|
||||||
|
writer.write(condition)
|
||||||
|
|
||||||
|
return writer
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
"""
|
||||||
|
Check whether this fulfillment meets all validation criteria.
|
||||||
|
|
||||||
|
This will validate the subfulfillments and verify that there are enough
|
||||||
|
subfulfillments to meet the threshold.
|
||||||
|
|
||||||
|
@return {Boolean} Whether this fulfillment is valid.
|
||||||
|
"""
|
||||||
|
validations = [f.validate() for f in self.subfulfillments]
|
||||||
|
return len([v for v in validations]) >= self.threshold
|
||||||
@ -96,7 +96,9 @@ class Writer:
|
|||||||
|
|
||||||
class Hasher(Writer):
|
class Hasher(Writer):
|
||||||
|
|
||||||
hash = None
|
def __init__(self):
|
||||||
|
self.hash = None
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
def __init__(self, algorithm):
|
def __init__(self, algorithm):
|
||||||
if algorithm == 'sha256':
|
if algorithm == 'sha256':
|
||||||
@ -149,7 +151,9 @@ class Hasher(Writer):
|
|||||||
|
|
||||||
|
|
||||||
class Predictor:
|
class Predictor:
|
||||||
size = 0
|
|
||||||
|
def __init__(self):
|
||||||
|
self.size = 0
|
||||||
|
|
||||||
def write_var_uint(self, val):
|
def write_var_uint(self, val):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
|
from math import floor, ceil
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from bigchaindb.crypto.condition import Condition
|
from bigchaindb.crypto.condition import Condition
|
||||||
from bigchaindb.crypto.ed25519 import ED25519PrivateKey, ED25519PublicKey
|
from bigchaindb.crypto.ed25519 import ED25519PrivateKey, ED25519PublicKey
|
||||||
from bigchaindb.crypto.fulfillment import Fulfillment
|
from bigchaindb.crypto.fulfillment import Fulfillment
|
||||||
from bigchaindb.crypto.fulfillments.ed25519_sha256 import Ed25519Sha256Fulfillment
|
from bigchaindb.crypto.fulfillments.ed25519_sha256 import Ed25519Sha256Fulfillment
|
||||||
|
from bigchaindb.crypto.fulfillments.threshold_sha256 import ThresholdSha256Fulfillment
|
||||||
|
|
||||||
|
|
||||||
class TestBigchainILPFulfillmentEd25519Sha256:
|
class TestBigchainILPSha256Condition:
|
||||||
|
CONDITION_SHA256_ILP = 'cc:1:1:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:1'
|
||||||
|
|
||||||
|
def test_deserialize_condition(self):
|
||||||
|
example_condition = self.CONDITION_SHA256_ILP
|
||||||
|
condition = Condition.from_uri(example_condition)
|
||||||
|
assert condition.serialize_uri() == self.CONDITION_SHA256_ILP
|
||||||
|
|
||||||
|
|
||||||
|
class TestBigchainILPEd25519Sha256Fulfillment:
|
||||||
PUBLIC_HEX_ILP = b'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf'
|
PUBLIC_HEX_ILP = b'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf'
|
||||||
PUBLIC_B64_ILP = b'7Bcrk61eVjv0kyxw4SRQNMNUZ+8u/U1k6/gZaDRn4r8'
|
PUBLIC_B64_ILP = b'7Bcrk61eVjv0kyxw4SRQNMNUZ+8u/U1k6/gZaDRn4r8'
|
||||||
PUBLIC_B58_ILP = 'Gtbi6WQDB6wUePiZm8aYs5XZ5pUqx9jMMLvRVHPESTjU'
|
PUBLIC_B58_ILP = 'Gtbi6WQDB6wUePiZm8aYs5XZ5pUqx9jMMLvRVHPESTjU'
|
||||||
@ -54,7 +68,6 @@ class TestBigchainILPFulfillmentEd25519Sha256:
|
|||||||
assert fulfillment.validate()
|
assert fulfillment.validate()
|
||||||
|
|
||||||
def test_deserialize_condition(self):
|
def test_deserialize_condition(self):
|
||||||
|
|
||||||
deserialized_condition = Condition.from_uri(self.CONDITION_ED25519_ILP)
|
deserialized_condition = Condition.from_uri(self.CONDITION_ED25519_ILP)
|
||||||
|
|
||||||
assert deserialized_condition.serialize_uri() == self.CONDITION_ED25519_ILP
|
assert deserialized_condition.serialize_uri() == self.CONDITION_ED25519_ILP
|
||||||
@ -85,7 +98,7 @@ class TestBigchainILPFulfillmentEd25519Sha256:
|
|||||||
assert fulfillment.public_key.public_key.to_ascii(encoding='hex') == self.PUBLIC_HEX_ILP
|
assert fulfillment.public_key.public_key.to_ascii(encoding='hex') == self.PUBLIC_HEX_ILP
|
||||||
assert fulfillment.validate()
|
assert fulfillment.validate()
|
||||||
|
|
||||||
def test_serializer_deserialize_fulfillment(self):
|
def test_serialize_deserialize_fulfillment(self):
|
||||||
sk = ED25519PrivateKey(self.PRIVATE_B58_ILP)
|
sk = ED25519PrivateKey(self.PRIVATE_B58_ILP)
|
||||||
vk = ED25519PublicKey(self.PUBLIC_B58_ILP)
|
vk = ED25519PublicKey(self.PUBLIC_B58_ILP)
|
||||||
|
|
||||||
@ -105,11 +118,121 @@ class TestBigchainILPFulfillmentEd25519Sha256:
|
|||||||
assert deserialized_fulfillment.validate()
|
assert deserialized_fulfillment.validate()
|
||||||
|
|
||||||
|
|
||||||
class TestBigchainILPConditionSha256:
|
class TestBigchainILPThresholdSha256Fulfillment:
|
||||||
|
PUBLIC_B58_ILP = 'Gtbi6WQDB6wUePiZm8aYs5XZ5pUqx9jMMLvRVHPESTjU'
|
||||||
|
PRIVATE_B58_ILP = '9qLvREC54mhKYivr88VpckyVWdAFmifJpGjbvV5AiTRs'
|
||||||
|
|
||||||
CONDITION_SHA256_ILP = 'cc:1:1:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:1'
|
CONDITION_ED25519_ILP = 'cc:1:8:qQINW2um59C4DB9JSVXH1igqAmaYGGqryllHUgCpfPU:113'
|
||||||
|
FULFILLMENT_ED25519_ILP = \
|
||||||
|
'cf:1:8:IOwXK5OtXlY79JMscOEkUDTDVGfvLv1NZOv4GWg0Z-K_DEhlbGxvIHdvcmxkISAVIENvbmRpdGlvbnMgYXJlIGhlcmUhQENbql531' \
|
||||||
|
'PbCJlRUvKjP56k0XKJMOrIGo2F66ueuTtRnYrJB2t2ZttdfXM4gzD_87eH1nZTpu4rTkAx81hSdpwI'
|
||||||
|
|
||||||
def test_deserialize_condition(self):
|
CONDITION_ED25519_ILP_2 = 'cc:1:8:_WzTrHvFnv4I-H0cAKWZ6Q3g3Y0Du3aW01nIsaAsio8:116'
|
||||||
example_condition = self.CONDITION_SHA256_ILP
|
FULFILLMENT_ED25519_ILP_2 = \
|
||||||
condition = Condition.from_uri(example_condition)
|
'cf:1:8:IOwXK5OtXlY79JMscOEkUDTDVGfvLv1NZOv4GWg0Z-K_D0hlbGxvIHVuaXZlcnNlISAbIENvbmRpdGlvbnMgYXJlIGV2ZXJ5d2hlc' \
|
||||||
assert condition.serialize_uri() == self.CONDITION_SHA256_ILP
|
'mUhQNmD2Cvk7e3EFOo-arA2TKYTP-474Z4okhbYmKij6XxObIbRsDScjXILAJ6mV5hP7Xyqkg5fcSsZbfRYypzlsAM'
|
||||||
|
HASH_ED25519_HEX_ILP = b'a9020d5b6ba6e7d0b80c1f494955c7d6282a026698186aabca59475200a97cf5'
|
||||||
|
|
||||||
|
CONDITION_THRESHOLD_ED25519_ILP_2 = 'cc:1:c:IZgoTeE1Weg6tfGMLWGe2JmS-waBN-CUrlbhtI9GBcQ:230'
|
||||||
|
FULFILLMENT_THRESHOLD_ED25519_ILP_2 = \
|
||||||
|
'cf:1:4:AgIBCCDsFyuTrV5WO_STLHDhJFA0w1Rn7y79TWTr-BloNGfivwxIZWxsbyB3b3JsZCEgFSBDb25kaXRpb25zIGFyZSBoZXJlIUBDW' \
|
||||||
|
'6ped9T2wiZUVLyoz-epNFyiTDqyBqNheurnrk7UZ2KyQdrdmbbXX1zOIMw__O3h9Z2U6buK05AMfNYUnacCAQgg7Bcrk61eVjv0kyxw4SRQN' \
|
||||||
|
'MNUZ-8u_U1k6_gZaDRn4r8MSGVsbG8gd29ybGQhIBUgQ29uZGl0aW9ucyBhcmUgaGVyZSFAQ1uqXnfU9sImVFS8qM_nqTRcokw6sgajYXrq5' \
|
||||||
|
'65O1GdiskHa3Zm2119cziDMP_zt4fWdlOm7itOQDHzWFJ2nAgEBCCD9bNOse8We_gj4fRwApZnpDeDdjQO7dpbTWcixoCyKj3Q'
|
||||||
|
|
||||||
|
def create_fulfillment_ed25519sha256(self):
|
||||||
|
sk = ED25519PrivateKey(self.PRIVATE_B58_ILP)
|
||||||
|
vk = ED25519PublicKey(self.PUBLIC_B58_ILP)
|
||||||
|
|
||||||
|
fulfillment = Ed25519Sha256Fulfillment()
|
||||||
|
fulfillment.public_key = vk
|
||||||
|
fulfillment.message_prefix = 'Hello world!'
|
||||||
|
fulfillment.max_dynamic_message_length = 32 # defaults to 0
|
||||||
|
fulfillment.message = ' Conditions are here!'
|
||||||
|
fulfillment.sign(sk)
|
||||||
|
return fulfillment
|
||||||
|
|
||||||
|
def test_serialize_condition_and_validate_fulfillment(self):
|
||||||
|
ilp_fulfillment = Fulfillment.from_uri(self.FULFILLMENT_ED25519_ILP)
|
||||||
|
ilp_fulfillment_2 = Fulfillment.from_uri(self.FULFILLMENT_ED25519_ILP_2)
|
||||||
|
|
||||||
|
assert ilp_fulfillment.validate() == True
|
||||||
|
assert ilp_fulfillment_2.validate() == True
|
||||||
|
|
||||||
|
THRESHOLD = 2
|
||||||
|
|
||||||
|
# Create a threshold condition
|
||||||
|
fulfillment = ThresholdSha256Fulfillment()
|
||||||
|
fulfillment.add_subfulfillment(ilp_fulfillment_2)
|
||||||
|
fulfillment.add_subfulfillment(ilp_fulfillment)
|
||||||
|
fulfillment.add_subfulfillment(ilp_fulfillment)
|
||||||
|
fulfillment.threshold = THRESHOLD # defaults to subconditions.length
|
||||||
|
|
||||||
|
assert fulfillment.condition.serialize_uri() == self.CONDITION_THRESHOLD_ED25519_ILP_2
|
||||||
|
# Note: If there are more than enough fulfilled subconditions, shorter
|
||||||
|
# fulfillments will be chosen over longer ones.
|
||||||
|
# thresholdFulfillmentUri.length === 65
|
||||||
|
assert fulfillment.serialize_uri() == self.FULFILLMENT_THRESHOLD_ED25519_ILP_2
|
||||||
|
assert fulfillment.validate()
|
||||||
|
|
||||||
|
def test_deserialize_fulfillment(self):
|
||||||
|
NUM_FULFILLMENTS = 3
|
||||||
|
THRESHOLD = 2
|
||||||
|
|
||||||
|
fulfillment = Fulfillment.from_uri(self.FULFILLMENT_THRESHOLD_ED25519_ILP_2)
|
||||||
|
assert fulfillment.threshold == THRESHOLD
|
||||||
|
assert len(fulfillment.subfulfillments) == THRESHOLD
|
||||||
|
assert len(fulfillment.get_all_subconditions()) == NUM_FULFILLMENTS
|
||||||
|
assert fulfillment.serialize_uri() == self.FULFILLMENT_THRESHOLD_ED25519_ILP_2
|
||||||
|
assert fulfillment.validate()
|
||||||
|
|
||||||
|
def test_serialize_deserialize_fulfillment(self):
|
||||||
|
ilp_fulfillment = Fulfillment.from_uri(self.FULFILLMENT_ED25519_ILP)
|
||||||
|
NUM_FULFILLMENTS = 100
|
||||||
|
THRESHOLD = ceil(NUM_FULFILLMENTS * 2 / 3)
|
||||||
|
|
||||||
|
# Create a threshold condition
|
||||||
|
fulfillment = ThresholdSha256Fulfillment()
|
||||||
|
for i in range(NUM_FULFILLMENTS):
|
||||||
|
fulfillment.add_subfulfillment(ilp_fulfillment)
|
||||||
|
fulfillment.threshold = THRESHOLD
|
||||||
|
|
||||||
|
fulfillment_uri = fulfillment.serialize_uri()
|
||||||
|
|
||||||
|
assert fulfillment.validate()
|
||||||
|
deserialized_fulfillment = Fulfillment.from_uri(fulfillment_uri)
|
||||||
|
|
||||||
|
assert deserialized_fulfillment.threshold == THRESHOLD
|
||||||
|
assert len(deserialized_fulfillment.subfulfillments) == THRESHOLD
|
||||||
|
assert len(deserialized_fulfillment.get_all_subconditions()) == NUM_FULFILLMENTS
|
||||||
|
assert deserialized_fulfillment.serialize_uri() == fulfillment_uri
|
||||||
|
assert deserialized_fulfillment.validate()
|
||||||
|
|
||||||
|
def test_fulfillment_didnt_reach_threshold(self):
|
||||||
|
ilp_fulfillment = Fulfillment.from_uri(self.FULFILLMENT_ED25519_ILP)
|
||||||
|
THRESHOLD = 10
|
||||||
|
|
||||||
|
# Create a threshold condition
|
||||||
|
fulfillment = ThresholdSha256Fulfillment()
|
||||||
|
fulfillment.threshold = THRESHOLD
|
||||||
|
|
||||||
|
for i in range(THRESHOLD - 1):
|
||||||
|
fulfillment.add_subfulfillment(ilp_fulfillment)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
fulfillment.serialize_uri()
|
||||||
|
|
||||||
|
assert fulfillment.validate() is False
|
||||||
|
|
||||||
|
fulfillment.add_subfulfillment(ilp_fulfillment)
|
||||||
|
|
||||||
|
fulfillment_uri = fulfillment.serialize_uri()
|
||||||
|
assert fulfillment.validate()
|
||||||
|
|
||||||
|
deserialized_fulfillment = Fulfillment.from_uri(fulfillment_uri)
|
||||||
|
|
||||||
|
assert deserialized_fulfillment.threshold == THRESHOLD
|
||||||
|
assert len(deserialized_fulfillment.subfulfillments) == THRESHOLD
|
||||||
|
assert len(deserialized_fulfillment.get_all_subconditions()) == THRESHOLD
|
||||||
|
assert deserialized_fulfillment.serialize_uri() == fulfillment_uri
|
||||||
|
assert deserialized_fulfillment.validate()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user