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
|
||||
|
||||
BitmaskRegistry.register_type(ThresholdSha256Fulfillment)
|
||||
BitmaskRegistry.register_type(Ed25519Sha256Fulfillment)
|
||||
|
||||
@ -4,7 +4,7 @@ from abc import ABCMeta
|
||||
|
||||
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}$'
|
||||
|
||||
@ -47,6 +47,27 @@ class Condition(metaclass=ABCMeta):
|
||||
|
||||
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
|
||||
def bitmask(self):
|
||||
"""
|
||||
@ -152,12 +173,53 @@ class Condition(metaclass=ABCMeta):
|
||||
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.
|
||||
|
||||
"cc:" BASE10(VERSION) ":" BASE16(TYPE_BITMASK) ":" BASE64URL(HASH) ":" BASE10(MAX_FULFILLMENT_LENGTH)
|
||||
|
||||
Returns:
|
||||
string: Condition as a URI
|
||||
"""
|
||||
|
||||
return 'cc:1:{}:{}:{}'.format(self.bitmask,
|
||||
base64_remove_padding(
|
||||
base64.urlsafe_b64encode(self.hash)
|
||||
).decode('utf-8'),
|
||||
self.max_fulfillment_length)
|
||||
return 'cc:1:{:x}:{}:{}'.format(self.bitmask,
|
||||
base64_remove_padding(
|
||||
base64.urlsafe_b64encode(self.hash)
|
||||
).decode('utf-8'),
|
||||
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
|
||||
|
||||
@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
|
||||
def bitmask(self):
|
||||
"""
|
||||
@ -99,15 +123,37 @@ class Fulfillment(metaclass=ABCMeta):
|
||||
format is convenient for passing around fulfillments in URLs, JSON and
|
||||
other text-based formats.
|
||||
|
||||
"cf:" BASE10(VERSION) ":" BASE16(TYPE_BIT) ":" BASE64URL(FULFILLMENT_PAYLOAD)
|
||||
|
||||
Return:
|
||||
string: Fulfillment as a URI
|
||||
"""
|
||||
return 'cf:1:{}:{}'.format(self.bitmask,
|
||||
base64_remove_padding(
|
||||
base64.urlsafe_b64encode(
|
||||
b''.join(self.serialize_payload().components)
|
||||
)
|
||||
).decode('utf-8'))
|
||||
return 'cf:1:{:x}:{}'.format(self._bitmask,
|
||||
base64_remove_padding(
|
||||
base64.urlsafe_b64encode(
|
||||
b''.join(self.serialize_payload().components)
|
||||
)
|
||||
).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):
|
||||
"""
|
||||
|
||||
@ -8,11 +8,13 @@ from bigchaindb.crypto.iostream import Predictor
|
||||
class Ed25519Sha256Fulfillment(BaseSha256Fulfillment):
|
||||
|
||||
_bitmask = 0x08
|
||||
_message_prefix = None
|
||||
_max_dynamic_message_length = None
|
||||
_public_key = None
|
||||
_message = None
|
||||
_signature = None
|
||||
|
||||
def __init__(self):
|
||||
self._message_prefix = None
|
||||
self._max_dynamic_message_length = None
|
||||
self._public_key = None
|
||||
self._message = None
|
||||
self._signature = None
|
||||
|
||||
@property
|
||||
def message_prefix(self):
|
||||
@ -155,7 +157,6 @@ class Ed25519Sha256Fulfillment(BaseSha256Fulfillment):
|
||||
self.max_dynamic_message_length = reader.read_var_uint()
|
||||
self.message = reader.read_var_bytes()
|
||||
self.signature = reader.read_var_bytes()
|
||||
print(self.signature)
|
||||
|
||||
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):
|
||||
|
||||
hash = None
|
||||
def __init__(self):
|
||||
self.hash = None
|
||||
super().__init__()
|
||||
|
||||
def __init__(self, algorithm):
|
||||
if algorithm == 'sha256':
|
||||
@ -149,7 +151,9 @@ class Hasher(Writer):
|
||||
|
||||
|
||||
class Predictor:
|
||||
size = 0
|
||||
|
||||
def __init__(self):
|
||||
self.size = 0
|
||||
|
||||
def write_var_uint(self, val):
|
||||
"""
|
||||
|
||||
@ -1,12 +1,26 @@
|
||||
import binascii
|
||||
|
||||
from math import floor, ceil
|
||||
|
||||
import pytest
|
||||
|
||||
from bigchaindb.crypto.condition import Condition
|
||||
from bigchaindb.crypto.ed25519 import ED25519PrivateKey, ED25519PublicKey
|
||||
from bigchaindb.crypto.fulfillment import Fulfillment
|
||||
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_B64_ILP = b'7Bcrk61eVjv0kyxw4SRQNMNUZ+8u/U1k6/gZaDRn4r8'
|
||||
PUBLIC_B58_ILP = 'Gtbi6WQDB6wUePiZm8aYs5XZ5pUqx9jMMLvRVHPESTjU'
|
||||
@ -54,7 +68,6 @@ class TestBigchainILPFulfillmentEd25519Sha256:
|
||||
assert fulfillment.validate()
|
||||
|
||||
def test_deserialize_condition(self):
|
||||
|
||||
deserialized_condition = Condition.from_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.validate()
|
||||
|
||||
def test_serializer_deserialize_fulfillment(self):
|
||||
def test_serialize_deserialize_fulfillment(self):
|
||||
sk = ED25519PrivateKey(self.PRIVATE_B58_ILP)
|
||||
vk = ED25519PublicKey(self.PUBLIC_B58_ILP)
|
||||
|
||||
@ -105,11 +118,121 @@ class TestBigchainILPFulfillmentEd25519Sha256:
|
||||
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):
|
||||
example_condition = self.CONDITION_SHA256_ILP
|
||||
condition = Condition.from_uri(example_condition)
|
||||
assert condition.serialize_uri() == self.CONDITION_SHA256_ILP
|
||||
CONDITION_ED25519_ILP_2 = 'cc:1:8:_WzTrHvFnv4I-H0cAKWZ6Q3g3Y0Du3aW01nIsaAsio8:116'
|
||||
FULFILLMENT_ED25519_ILP_2 = \
|
||||
'cf:1:8:IOwXK5OtXlY79JMscOEkUDTDVGfvLv1NZOv4GWg0Z-K_D0hlbGxvIHVuaXZlcnNlISAbIENvbmRpdGlvbnMgYXJlIGV2ZXJ5d2hlc' \
|
||||
'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