Merge 46cd3323e8deb71028284b0533b56f5d805303fe into 55ad60097da46da44a33892d9c4f1f8ce797e774

This commit is contained in:
Dimitri De Jonghe 2016-03-18 15:58:20 +00:00
commit 4636434b1f
24 changed files with 1554 additions and 98 deletions

2
.gitignore vendored
View File

@ -65,3 +65,5 @@ target/
# pyenv
.python-version
# IDE related
.idea

View File

@ -1,10 +1,10 @@
import requests
import bigchaindb
from bigchaindb import util
from bigchaindb import config_utils
from bigchaindb import exceptions
from bigchaindb import crypto
from bigchaindb import util
from bigchaindb.crypto import asymmetric
class Client:
@ -92,6 +92,6 @@ def temp_client():
A client initialized with a keypair generated on the fly.
"""
private_key, public_key = crypto.generate_key_pair()
private_key, public_key = asymmetric.generate_key_pair()
return Client(private_key=private_key, public_key=public_key, api_endpoint='http://localhost:5000/api/v1')

View File

@ -1,19 +1,17 @@
"""Command line interface for the `bigchain` command."""
import os
import logging
import argparse
import copy
import logging
import os
import bigchaindb
import bigchaindb.config_utils
from bigchaindb import db
from bigchaindb.exceptions import DatabaseAlreadyExists
from bigchaindb.commands.utils import base_parser, start
from bigchaindb.crypto import asymmetric
from bigchaindb.exceptions import DatabaseAlreadyExists
from bigchaindb.processes import Processes
from bigchaindb import crypto
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@ -52,7 +50,7 @@ def run_configure(args, skip_if_exists=False):
conf = copy.deepcopy(bigchaindb._config)
print('Generating keypair')
conf['keypair']['private'], conf['keypair']['public'] = crypto.generate_key_pair()
conf['keypair']['private'], conf['keypair']['public'] = asymmetric.generate_key_pair()
if not args.yes:
for key in ('host', 'port', 'name'):

View File

@ -1,17 +1,15 @@
import rethinkdb as r
import random
import json
import rapidjson
import rethinkdb as r
import bigchaindb
from bigchaindb import util
from bigchaindb import config_utils
from bigchaindb import exceptions
from bigchaindb import crypto
from bigchaindb import util
from bigchaindb.crypto import asymmetric
from bigchaindb.monitor import Monitor
monitor = Monitor()
@ -98,7 +96,7 @@ class Bigchain(object):
signature = data.pop('signature')
public_key_base58 = signed_transaction['transaction']['current_owner']
public_key = crypto.PublicKey(public_key_base58)
public_key = asymmetric.PublicKey(public_key_base58)
return public_key.verify(util.serialize(data), signature)
@monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate'])
@ -331,8 +329,8 @@ class Bigchain(object):
# Calculate the hash of the new block
block_data = util.serialize(block)
block_hash = crypto.hash_data(block_data)
block_signature = crypto.PrivateKey(self.me_private).sign(block_data)
block_hash = asymmetric.hash_data(block_data)
block_signature = asymmetric.PrivateKey(self.me_private).sign(block_data)
block = {
'id': block_hash,
@ -357,7 +355,7 @@ class Bigchain(object):
"""
# 1. Check if current hash is correct
calculated_hash = crypto.hash_data(util.serialize(block['block']))
calculated_hash = asymmetric.hash_data(util.serialize(block['block']))
if calculated_hash != block['id']:
raise exceptions.InvalidHash()
@ -452,7 +450,7 @@ class Bigchain(object):
}
vote_data = util.serialize(vote)
signature = crypto.PrivateKey(self.me_private).sign(vote_data)
signature = asymmetric.PrivateKey(self.me_private).sign(vote_data)
vote_signed = {
'node_pubkey': self.me,

View File

View File

@ -0,0 +1,69 @@
# Separate all crypto code so that we can easily test several implementations
from abc import ABCMeta, abstractmethod
import sha3
class PrivateKey(metaclass=ABCMeta):
"""
PrivateKey instance
"""
@abstractmethod
def sign(self, data):
"""
Sign data with private key
"""
raise NotImplementedError
@staticmethod
@abstractmethod
def encode(private_value):
"""
Encode the decimal number private_value to base58
"""
raise NotImplementedError
@staticmethod
@abstractmethod
def decode(key):
"""
Decode the base58 private_value to decimale
"""
raise NotImplementedError
class PublicKey(metaclass=ABCMeta):
@abstractmethod
def verify(self, data, signature):
raise NotImplementedError
@staticmethod
@abstractmethod
def encode(public_value_x, public_value_y):
"""
Encode the public key represented by the decimal values x and y to base58
"""
raise NotImplementedError
@staticmethod
@abstractmethod
def decode(public_value_compressed_base58):
"""
Decode the base58 public_value to the decimal x and y values
"""
raise NotImplementedError
def hash_data(data):
"""Hash the provided data using SHA3-256"""
return sha3.sha3_256(data.encode()).hexdigest()
from bigchaindb.crypto.ecdsa import ECDSAPrivateKey, ECDSAPublicKey, ecdsa_generate_key_pair
PrivateKey = ECDSAPrivateKey
PublicKey = ECDSAPublicKey
generate_key_pair = ecdsa_generate_key_pair

View File

@ -0,0 +1,56 @@
from bigchaindb.crypto.iostream import MAX_SAFE_INTEGER
class BitmaskRegistry:
registered_types = []
@staticmethod
def get_class_from_typebit(bitmask):
"""
Determine fulfillment implementation class from a bitmask.
Returns the class implementing a fulfillment type that matches a certain bitmask.
Args:
bitmask (int): fulfillment bitmask
Return:
Class implementing the given fulfillment type.
"""
# Determine type of condition
if bitmask > MAX_SAFE_INTEGER:
raise ValueError('Bitmask {} is not supported'.format(bitmask))
for registered_type in BitmaskRegistry.registered_types:
if bitmask == registered_type['bitmask']:
return registered_type['class']
raise ValueError('Bitmask {} is not supported'.format(bitmask))
@staticmethod
def register_type(cls):
"""
Add a new fulfillment type.
This can be used to extend this cryptocondition implementation with new
fulfillment types that it does not yet support. But mostly it is used
internally to register the built-in types.
In this method, we expect a regular fulfillment type, for information on
registering meta types please see `registerMetaType`.
Args:
cls: Implementation of a fulfillment type.
"""
# TODO Do some sanity checks on Class
BitmaskRegistry.registered_types.append(
{
'bitmask': cls().bitmask,
'class': cls
})
from bigchaindb.crypto.fulfillments.ed25519_sha256 import Ed25519Sha256Fulfillment
BitmaskRegistry.register_type(Ed25519Sha256Fulfillment)

View File

@ -0,0 +1,163 @@
import base64
import re
from abc import ABCMeta
from six import string_types
from bigchaindb.crypto.iostream import base64_add_padding, base64_remove_padding
CONDITION_REGEX = r'^cc:1:[1-9a-f][0-9a-f]{0,2}:[a-zA-Z0-9_-]{43}:[1-9][0-9]{0,50}$'
class Condition(metaclass=ABCMeta):
_bitmask = None
_hash = None
_max_fulfillment_length = None
@staticmethod
def from_uri(serialized_condition):
"""
Create a Condition object from a URI.
This method will parse a condition URI and construct a corresponding Condition object.
Args:
serialized_condition (str): URI representing the condition
Returns:
Condition: Resulting object
"""
if not isinstance(serialized_condition, string_types):
raise TypeError('Serialized condition must be a string')
pieces = serialized_condition.split(':')
if not pieces[0] == 'cc':
raise ValueError('Serialized condition must start with "cc:"')
if not pieces[1] == '1':
raise ValueError('Condition must be version 1')
if not re.match(CONDITION_REGEX, serialized_condition):
raise ValueError('Invalid condition format')
condition = Condition()
condition.bitmask = int(pieces[2])
condition.hash = base64.urlsafe_b64decode(base64_add_padding(pieces[3]))
condition.max_fulfillment_length = int(pieces[4])
return condition
@property
def bitmask(self):
"""
Return the bitmask of this condition.
For simple condition types this is simply the bit representing this type.
For meta-conditions, these are the bits representing the types of the subconditions.
Return:
int: Bitmask corresponding to this condition.
"""
return self._bitmask
@bitmask.setter
def bitmask(self, value):
"""
Set the bitmask.
Sets the required bitmask to validate a fulfillment for this condition.
Args:
value (int): representation of bitmask.
"""
self._bitmask = value
@property
def hash(self):
"""
Return the hash of the condition.
A primary component of all conditions is the hash. It encodes the static
properties of the condition. This method enables the conditions to be
constant size, no matter how complex they actually are. The data used to
generate the hash consists of all the static properties of the condition
and is provided later as part of the fulfillment.
Return:
Hash of the condition
"""
if not self._hash:
raise ValueError
return self._hash
@hash.setter
def hash(self, value):
"""
Validate and set the hash of this condition.
Typically conditions are generated from fulfillments and the hash is
calculated automatically. However, sometimes it may be necessary to
construct a condition URI from a known hash. This method enables that case.
Args:
value (Buffer): Hash as binary.
"""
# TODO: value must be Buffer
# if not isinstance(value, Buffer):
# raise ValueError
self._hash = value
@property
def max_fulfillment_length(self):
"""
Return the maximum fulfillment length.
The maximum fulfillment length is the maximum allowed length for any
fulfillment payload to fulfill this condition.
The condition defines a maximum fulfillment length which all
implementations will enforce. This allows implementations to verify that
their local maximum fulfillment size is guaranteed to accomodate any
possible fulfillment for this condition.
Otherwise an attacker could craft a fulfillment which exceeds the maximum
size of one implementation, but meets the maximum size of another, thereby
violating the fundamental property that fulfillments are either valid
everywhere or nowhere.
Return:
(int) Maximum length (in bytes) of any fulfillment payload that fulfills this condition..
"""
if not self._max_fulfillment_length:
raise ValueError
return self._max_fulfillment_length
@max_fulfillment_length.setter
def max_fulfillment_length(self, value):
"""
Set the maximum fulfillment length.
The maximum fulfillment length is normally calculated automatically, when
calling `Fulfillment#getCondition`. However, when
Args:
value (int): Maximum fulfillment payload length in bytes.
"""
self._max_fulfillment_length = value
def serialize_uri(self):
"""
Generate the URI form encoding of this condition.
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.
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)

View File

@ -1,18 +1,18 @@
# Separate all crypto code so that we can easily test several implementations
import binascii
import base58
import sha3
import bitcoin
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from bigchaindb.crypto.asymmetric import PrivateKey, PublicKey
class PrivateKey(object):
class ECDSAPrivateKey(PrivateKey):
"""
PrivateKey instance
"""
@ -65,7 +65,7 @@ class PrivateKey(object):
Return an instance of cryptography PrivateNumbers from the decimal private_value
"""
public_value_x, public_value_y = self._private_value_to_public_values(private_value)
public_numbers = PublicKey._public_values_to_cryptography_public_numbers(public_value_x, public_value_y)
public_numbers = ECDSAPublicKey._public_values_to_cryptography_public_numbers(public_value_x, public_value_y)
private_numbers = ec.EllipticCurvePrivateNumbers(private_value, public_numbers)
return private_numbers
@ -77,7 +77,7 @@ class PrivateKey(object):
return private_numbers.private_key(default_backend())
class PublicKey(object):
class ECDSAPublicKey(PublicKey):
def __init__(self, key):
"""
@ -85,7 +85,7 @@ class PublicKey(object):
"""
public_value_x, public_value_y = self.decode(key)
public_numbers = self._public_values_to_cryptography_public_numbers(public_value_x, public_value_y)
self.public_key = self._criptography_public_key_from_public_numbers(public_numbers)
self.public_key = self._cryptography_public_key_from_public_numbers(public_numbers)
def verify(self, data, signature):
verifier = self.public_key.verifier(binascii.unhexlify(signature), ec.ECDSA(hashes.SHA256()))
@ -123,33 +123,25 @@ class PublicKey(object):
public_numbers = ec.EllipticCurvePublicNumbers(public_value_x, public_value_y, ec.SECP256K1())
return public_numbers
def _criptography_public_key_from_public_numbers(self, public_numbers):
def _cryptography_public_key_from_public_numbers(self, public_numbers):
"""
Return an instance of cryptography PublicKey from a cryptography instance of PublicNumbers
"""
return public_numbers.public_key(default_backend())
def generate_key_pair():
def ecdsa_generate_key_pair():
"""
Generate a new key pair and return the pair encoded in base58
"""
# Private key
private_key = ec.generate_private_key(ec.SECP256K1, default_backend())
private_value = private_key.private_numbers().private_value
private_value_base58 = PrivateKey.encode(private_value)
private_value_base58 = ECDSAPrivateKey.encode(private_value)
# Public key
public_key = private_key.public_key()
public_value_x, public_value_y = public_key.public_numbers().x, public_key.public_numbers().y
public_value_compressed_base58 = PublicKey.encode(public_value_x, public_value_y)
public_value_compressed_base58 = ECDSAPublicKey.encode(public_value_x, public_value_y)
return (private_value_base58, public_value_compressed_base58)
def hash_data(data):
"""Hash the provided data using SHA3-256"""
return sha3.sha3_256(data.encode()).hexdigest()

View File

@ -0,0 +1,104 @@
# Separate all crypto code so that we can easily test several implementations
import base64
import base58
import ed25519
from bigchaindb.crypto.asymmetric import PrivateKey, PublicKey
class ED25519PrivateKey(PrivateKey):
"""
PrivateKey instance
"""
def __init__(self, key):
"""
Instantiate the private key with the private_value encoded in base58
"""
private_base64 = self.decode(key)
self.private_key = self._private_key_from_private_base64(private_base64)
def sign(self, data, encoding="base64"):
"""
Sign data with private key
"""
if not isinstance(data, bytes):
data = data.encode('utf-8')
return self.private_key.sign(data, encoding=encoding)
@staticmethod
def encode(private_base64):
"""
Encode the base64 number private_base64 to base58
"""
return base58.b58encode(base64.b64decode(private_base64))
@staticmethod
def decode(key):
"""
Decode the base58 private_value to base64
"""
return base64.b64encode(base58.b58decode(key))
@staticmethod
def _private_key_from_private_base64(private_base64):
"""
Return an instance of a ED25519 SignigKey from a base64 key
"""
return ed25519.SigningKey(private_base64, encoding='base64')
class ED25519PublicKey(PublicKey):
def __init__(self, key):
"""
Instantiate the public key with the compressed public value encoded in base58
"""
public_base64 = self.decode(key)
self.public_key = self._public_key_from_public_base64(public_base64)
def verify(self, data, signature, encoding='base64'):
try:
if encoding:
data = data.encode('utf-8')
self.public_key.verify(signature, data, encoding=encoding)
except ed25519.BadSignatureError:
return False
return True
@staticmethod
def encode(public_base64):
"""
Encode the public key represented by base64 to base58
"""
return ED25519PrivateKey.encode(public_base64)
@staticmethod
def decode(public_value_compressed_base58):
"""
Decode the base58 public_value to base64
"""
return ED25519PrivateKey.decode(public_value_compressed_base58)
def _public_key_from_public_base64(self, public_base64):
"""
Return an instance of ED25519 VerifyingKey from a base64
"""
return ed25519.VerifyingKey(public_base64, encoding='base64')
def ed25519_generate_key_pair():
"""
Generate a new key pair and return the pair encoded in base58
"""
sk, vk = ed25519.create_keypair()
# Private key
private_value_base58 = base58.b58encode(sk.to_bytes())
# Public key
public_value_compressed_base58 = base58.b58encode(vk.to_bytes())
return (private_value_base58, public_value_compressed_base58)

View File

@ -0,0 +1,161 @@
import base64
import re
from abc import ABCMeta, abstractmethod
from six import string_types
from bigchaindb.crypto.condition import Condition
from bigchaindb.crypto.iostream import Writer, base64_remove_padding, Reader, base64_add_padding
FULFILLMENT_REGEX = r'^cf:1:[1-9a-f][0-9a-f]{0,2}:[a-zA-Z0-9_-]+$'
class Fulfillment(metaclass=ABCMeta):
_bitmask = None
@staticmethod
def from_uri(serialized_fulfillment):
"""
Create a Fulfillment object from a URI.
This method will parse a fulfillment URI and construct a corresponding Fulfillment object.
Args:
serialized_fulfillment (str): URI representing the fulfillment
Return:
Fulfillment: Resulting object
"""
if not isinstance(serialized_fulfillment, string_types):
raise TypeError('Serialized fulfillment must be a string')
pieces = serialized_fulfillment.split(':')
if not pieces[0] == 'cf':
raise ValueError('Serialized fulfillment must start with "cf:"')
if not pieces[1] == '1':
raise ValueError('Fulfillment must be version 1')
if not re.match(FULFILLMENT_REGEX, serialized_fulfillment):
raise ValueError('Invalid fulfillment format')
bitmask = int(pieces[2])
from bigchaindb.crypto.bitmark_registry import BitmaskRegistry
cls = BitmaskRegistry.get_class_from_typebit(bitmask)
fulfillment = cls()
payload = Reader.from_source(
base64.urlsafe_b64decode(
base64_add_padding(pieces[3])))
fulfillment.parse_payload(payload)
return fulfillment
@property
def bitmask(self):
"""
Return the bitmask of this fulfillment.
For simple fulfillment types this is simply the bit representing this type.
For meta-fulfillments, these are the bits representing the types of the subconditions.
Returns:
int: Bitmask corresponding to this fulfillment.
"""
return self._bitmask
@property
def condition(self):
"""
Generate condition corresponding to this fulfillment.
An important property of crypto-conditions is that the condition can always
be derived from the fulfillment. This makes it very easy to post
fulfillments to a system without having to specify which condition the
relate to. The system can keep an index of conditions and look up any
matching events related to that condition.
Return:
Condition: Condition corresponding to this fulfillment.
"""
condition = Condition()
condition.bitmask = self.bitmask
condition.hash = self.generate_hash()
condition.max_fulfillment_length = self.calculate_max_fulfillment_length()
return condition
def serialize_uri(self):
"""
Generate the URI form encoding of this fulfillment.
Turns the fulfillment into a URI containing only URL-safe characters. This
format is convenient for passing around fulfillments in URLs, JSON and
other text-based formats.
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'))
def serialize_payload(self):
"""
Return the fulfillment payload as a buffer.
Note that the fulfillment payload is not the standard format for passing
fulfillments in binary protocols. Use `serializeBinary` for that. The
fulfillment payload is purely the type-specific data and does not include the bitmask.
Return:
Buffer: Fulfillment payload
"""
return self.write_payload(Writer())
def calculate_max_fulfillment_length(self):
"""
Calculate the maximum length of the fulfillment payload.
This implementation works by measuring the length of the fulfillment.
Condition types that do not have a constant length will override this
method with one that calculates the maximum possible length.
Return:
{Number} Maximum fulfillment length
"""
# TODO: Predictor
# predictor = Predictor()
predictor = None
self.write_payload(predictor)
return predictor.size
@abstractmethod
def write_payload(self, writer):
raise NotImplementedError
@abstractmethod
def parse_payload(self, reader):
raise NotImplementedError
@abstractmethod
def generate_hash(self):
"""
Generate the hash of the fulfillment.
This method is a stub and will be overridden by subclasses.
"""
raise NotImplementedError
@abstractmethod
def validate(self):
raise NotImplementedError

View File

@ -0,0 +1,25 @@
from abc import abstractmethod
from bigchaindb.crypto.fulfillment import Fulfillment
from bigchaindb.crypto.iostream import Hasher
class BaseSha256Fulfillment(Fulfillment):
def generate_hash(self):
"""
Calculate condition hash.
This method is called internally by `condition`. It calculates the
condition hash by hashing the hash payload.
Return:
Buffer: Result from hashing the hash payload.
"""
hasher = Hasher('sha256')
self.write_hash_payload(hasher)
return hasher.digest() # remove padding
@abstractmethod
def write_hash_payload(self, hasher):
raise NotImplementedError

View File

@ -0,0 +1,254 @@
import base58
from bigchaindb.crypto.ed25519 import ED25519PublicKey
from bigchaindb.crypto.fulfillments.base_sha256 import BaseSha256Fulfillment
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
@property
def message_prefix(self):
return self._message_prefix
@message_prefix.setter
def message_prefix(self, value):
"""
Set the fixed message prefix.
The fixed prefix is the portion of the message that is determined when the
condition is first created.
Args:
value (Buffer): Static portion of the message
"""
if not isinstance(value, bytes):
value = value.encode()
self._message_prefix = value
@property
def max_dynamic_message_length(self):
return self._max_dynamic_message_length
@max_dynamic_message_length.setter
def max_dynamic_message_length(self, value):
"""
Set the maximum length of the dynamic message component.
The dynamic message is the part of the signed message that is determined at
fulfillment time. However, when the condition is first created, we need to
know the maximum fulfillment length, which in turn requires us to put a
limit on the length of the dynamic message component.
If this method is not called, the maximum dynamic message length defaults to zero.
Args:
value (int): Maximum length in bytes
"""
self._max_dynamic_message_length = value
@property
def public_key(self):
return self._public_key
@public_key.setter
def public_key(self, value):
"""
Set the public publicKey.
This is the Ed25519 public key. It has to be provided as a buffer.
Args:
value (Buffer): publicKey Public Ed25519 publicKey
"""
# TODO: Buffer
# if not isinstance(value, Buffer):
# raise ValueError("public key must be a Buffer")
if not isinstance(value, ED25519PublicKey):
raise TypeError
self._public_key = value
@property
def message(self):
return self._message
@message.setter
def message(self, value):
"""
Set the dynamic message portion.
Part of the signed message (the suffix) can be determined when the condition is being fulfilled.
Length may not exceed the maximum dynamic message length.
Args:
value (Buffer): Binary form of dynamic message.
"""
# TODO: Buffer
# if not isinstance(value, Buffer):
# raise ValueError("message must be a Buffer")
if not isinstance(value, bytes):
value = value.encode()
self._message = value
@property
def signature(self):
return self._signature
@signature.setter
def signature(self, value):
"""
Set the signature.
Instead of using the private key to sign using the sign() method, we can also generate the signature elsewhere
and pass it in.
Args:
value (Buffer): 64-byte signature.
"""
# TODO: Buffer
# if not isinstance(value, Buffer):
# raise ValueError("signature must be a Buffer")
self._signature = value
def write_common_header(self, writer):
"""
Write static header fields.
Some fields are common between the hash and the fulfillment payload. This
method writes those field to anything implementing the Writer interface.
It is used internally when generating the hash of the condition, when
generating the fulfillment payload and when calculating the maximum fulfillment size.
Args:
writer (Writer|Hasher|Predictor): Target for outputting the header.
"""
if not self.public_key:
raise ValueError
writer.write_var_bytes(bytearray(self.public_key.public_key.to_bytes()))
writer.write_var_bytes(self.message_prefix)
writer.write_var_uint(self.max_dynamic_message_length)
return writer
def parse_payload(self, reader):
"""
Parse the payload of an Ed25519 fulfillment.
Read a fulfillment payload from a Reader and populate this object with that fulfillment.
Args:
reader (Reader): Source to read the fulfillment payload from.
"""
self.public_key = \
ED25519PublicKey(
base58.b58encode(
reader.read_var_bytes()))
self.message_prefix = reader.read_var_bytes()
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):
"""
Generate the fulfillment payload.
This writes the fulfillment payload to a Writer.
Args:
writer (Writer): Subject for writing the fulfillment payload.
"""
self.write_common_header(writer)
writer.write_var_bytes(self.message)
writer.write_var_bytes(self.signature)
return writer
def write_hash_payload(self, hasher):
"""
Generate the contents of the condition hash.
Writes the contents of the condition hash to a Hasher. Used internally by `condition`.
Args:
hasher (Hasher): Destination where the hash payload will be written.
"""
hasher.write_var_uint(self.bitmask)
return self.write_common_header(hasher)
def calculate_max_fulfillment_length(self):
"""
Calculates the longest possible fulfillment length.
The longest fulfillment for an Ed25519 condition is the length of a
fulfillment where the dynamic message length equals its maximum length.
Return:
Maximum length of the fulfillment payload
"""
predictor = Predictor()
if not self.public_key:
raise ValueError('Requires a public key')
# Calculate the length that the common header would have
self.write_common_header(predictor)
# Message suffix
predictor.write_var_uint(self.max_dynamic_message_length)
predictor.skip(self.max_dynamic_message_length)
# Signature
predictor.write_var_bytes(self.public_key.public_key.to_bytes())
return predictor.size
def sign(self, private_key):
"""
Sign the message.
This method will take the currently configured values for the message
prefix and suffix and create a signature using the provided Ed25519 private key.
Args:
private_key (string) Ed25519 private key
"""
# TODO: Buffer
sk = private_key
vk = ED25519PublicKey(
base58.b58encode(
sk.private_key.get_verifying_key().to_bytes()))
self.public_key = vk
message = self.message_prefix + self.message
# This would be the Ed25519ph version (JavaScript ES7):
# const message = crypto.createHash('sha512')
# .update(Buffer.concat([this.messagePrefix, this.message]))
# .digest()
self.signature = sk.sign(message, encoding=None)
def validate(self):
"""
Verify the signature of this Ed25519 fulfillment.
The signature of this Ed25519 fulfillment is verified against the provided message and public key.
Return:
boolean: Whether this fulfillment is valid.
"""
if not (self.message and self.signature):
return False
message = self.message_prefix + self.message
return self.public_key.verify(message, self.signature, encoding=None)

View File

@ -0,0 +1,437 @@
import base64
import hashlib
from math import ceil
import binascii
from six import string_types
MSB = 0x80
REST = 0x7F
MSBALL = ~REST
INT = 2**31
# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
MAX_SAFE_INTEGER = 2**53-1
class UnsignedLEB128:
"""
Adapter for DWARF unsigned LEB128.
A VARUINT is a variable length integer encoded as base128 where the highest
bit indicates that another byte is following. The first byte contains the
seven least significant bits of the number represented.
see: https://en.wikipedia.org/wiki/LEB128 (ULEB128)
see http://grokbase.com/t/python/python-list/112e5jpc16/encoding
"""
@staticmethod
def encode(obj):
out = []
value = int(obj)
while value > INT:
out.append((value & REST) | MSB)
value /= 7
while value & MSBALL:
out.append((value & REST) | MSB)
value >>= 7
out.append(value | 0)
return out
@staticmethod
def decode(obj):
value = 0
for b in reversed(obj):
value = value * 128 + (ord(b) & 0x7F)
return value
class Writer:
def __init__(self):
self.components = []
def write_var_uint(self, value):
"""
Write a VARUINT to the stream.
Args:
value (int): Integer to represent.
"""
out = UnsignedLEB128.encode(value)
self.write(out)
def write_var_bytes(self, buffer):
"""
Write a VARBYTES.
A VARBYTES field consists of a VARUINT followed by that many bytes.
Args:
buffer (Buffer): Contents of the VARBYTES.
"""
self.write_var_uint(len(buffer))
self.write(buffer)
def write(self, in_bytes):
"""
Write a series of raw bytes.
Adds the given bytes to the output buffer.
Args:
in_bytes (Buffer): Bytes to write.
"""
out = in_bytes
if isinstance(out, (list, bytearray)):
out = binascii.unhexlify(''.join('{:02x}'.format(x) for x in out))
if not isinstance(out, bytes):
out = out.encode('utf-8')
self.components.append(out)
class Hasher(Writer):
hash = None
def __init__(self, algorithm):
if algorithm == 'sha256':
self.hash = hashlib.sha256()
else:
raise NotImplementedError
super(Writer, self).__init__()
def write(self, in_bytes):
"""
Adds bytes to the hash input.
The hasher will pass these bytes into the hashing function. By overriding
the Writer class and implementing this method, the Hasher supports any of
the datatypes that a Writer can write.
Args:
in_bytes (Buffer): Bytes to add to the hash.
"""
out = in_bytes
if isinstance(out, (list, bytearray)):
out = binascii.unhexlify(''.join('{:02x}'.format(x) for x in out))
if not isinstance(out, bytes):
out = out.encode('utf-8')
self.hash.update(out)
def digest(self):
"""
Return the hash.
Returns the finished hash based on what has been written to the Hasher so far.
Return:
Buffer: Resulting hash.
"""
return self.hash.digest()
@staticmethod
def length(algorithm):
"""
Get digest length for hashing algorithm.
Args:
algorithm (string): Hashing algorithm identifier.
Return:
int: Digest length in bytes.
"""
return len(Hasher(algorithm).digest())
class Predictor:
size = 0
def write_var_uint(self, val):
"""
Calculate the size of a VARUINT.
A VARUINT is a variable length integer encoded as base128 where the highest
bit indicates that another byte is following. The first byte contains the
seven least significant bits of the number represented.
Args:
val (int): Integer to be encoded
"""
if val == 0:
self.size += 1
elif val < 0:
raise ValueError('Variable length integer cannot be negative')
elif val > MAX_SAFE_INTEGER:
raise ValueError('Variable length integer too large')
else:
# Calculate number of bits divided by seven
self.size += ceil(len('{:02b}'.format(val)) / 7)
def write_var_bytes(self, val):
"""
Calculate the size of a VARBYTES.
A VARBYTES field consists of a VARUINT followed by that many bytes.
Args:
val (varbytes): Contents for VARBYTES
"""
self.write_var_uint(len(val))
self.size += len(val)
def skip(self, in_bytes):
"""
Add this many bytes to the predicted size.
Args:
in_bytes (int): Number of bytes to pretend to write.
"""
self.size += in_bytes
class Reader:
def __init__(self, buffer):
self.buffer = buffer
self.cursor = 0
self.bookmarks = []
@staticmethod
def from_source(source):
"""
Create a Reader from a source of bytes.
Currently, this method only allows the creation of a Reader from a Buffer.
If the object provided is already a Reader, that reader is returned as is.
Args:
source (Reader|Buffer): Source of binary data.
Return:
Reader: Instance of Reader
"""
# if (Buffer.isBuffer(source)) {
# return new Reader(source)
# } else {
# throw new Error('Reader must be given a Buffer')
if isinstance(source, Reader):
return source
return Reader(source)
def bookmark(self):
"""
Store the current cursor position on a stack.
"""
self.bookmarks.append(self.cursor)
def restore(self):
"""
Pop the most recently bookmarked cursor position off the stack.
"""
self.cursor = self.bookmarks.pop()
def ensure_available(self, num_bytes):
"""
Ensure this number of bytes is buffered.
This method checks that the given number of bytes is buffered and available
for reading. If insufficient bytes are available, the method throws an `OverflowError`.
Args:
num_bytes (int): Number of bytes that should be available.
"""
if len(self.buffer) < self.cursor + num_bytes:
raise OverflowError('Tried to read {} bytes, but only {} bytes available'
.format(num_bytes, len(self.buffer.length) - self.cursor))
def read_uint8(self):
"""
Read a single unsigned 8 byte integer.
Return: {Number} Contents of next byte.
"""
self.ensure_available(1)
value = self.buffer[self.cursor]
self.cursor += 1
return value
def peek_uint8(self):
"""
Look at the next byte, but don't advance the cursor.
Return: {Number} Contents of the next byte.
"""
self.ensure_available(1)
return self.buffer.read_uint8(self.cursor)
def skip_uint8(self):
"""
Advance cursor by one byte.
"""
self.cursor += 1
def read_var_uint(self):
"""
Read a VARUINT at the cursor position.
A VARUINT is a variable length integer encoded as base128 where the highest
bit indicates that another byte is following. The first byte contains the
seven least significant bits of the number represented.
Return the VARUINT and advances the cursor accordingly.
Return: {Number} Value of the VARUINT.
"""
shift = 0
result = 0
while True:
in_byte = self.read_uint8()
result += (in_byte & REST) << shift if shift < 28 else (in_byte & REST) * (2 ** shift)
shift += 7
# Don't allow numbers greater than Number.MAX_SAFE_INTEGER
if shift > 45:
raise ValueError('Too large variable integer')
if not (in_byte & MSB):
break
return result
def peek_var_uint(self):
"""
Read the next VARUINT, but don't advance the cursor.
Return: {Number} VARUINT at the cursor position.
"""
self.bookmark()
value = self.read_var_uint()
self.restore()
return value
def skip_var_uint(self):
"""
Skip past the VARUINT at the cursor position.
"""
# Read variable integer and ignore output
self.read_var_uint()
def read_var_bytes(self):
"""
Read a VARBYTES.
A VARBYTES field consists of a VARUINT followed by that many bytes.
Return: {Buffer} Contents of the VARBYTES.
"""
return self.read(self.read_var_uint())
def peek_var_bytes(self):
"""
Read a VARBYTES, but do not advance cursor position.
Return: {Buffer} Contents of the VARBYTES.
"""
self.bookmark()
value = self.read_var_bytes()
self.restore()
return value
def skip_var_bytes(self):
"""
Skip a VARBYTES.
"""
self.skip(len(self.read_var_bytes()))
def read(self, num_bytes):
"""
Read a given number of bytes.
Returns this many bytes starting at the cursor position and advances the
cursor.
Args:
num_bytes (int): Number of bytes to read.
Return:
Contents of bytes read.
"""
self.ensure_available(num_bytes)
value = self.buffer[self.cursor:self.cursor + num_bytes]
self.cursor += num_bytes
return value
def peek(self, num_bytes):
"""
Read bytes, but do not advance cursor.
Args:
num_bytes (int): Number of bytes to read.
Return:
Contents of bytes read.
"""
self.ensure_available(num_bytes)
return self.buffer.slice(self.cursor, self.cursor + num_bytes)
def skip(self, num_bytes):
"""
Skip a number of bytes.
Advances the cursor by this many bytes.
Args:
num_bytes (int): Number of bytes to advance the cursor by.
"""
self.ensure_available(num_bytes)
self.cursor += num_bytes
def base64_add_padding(data):
"""
Add enough padding for base64 encoding such that length is a multiple of 4
Args:
data: unpadded string or bytes
Return:
bytes: The padded bytes
"""
if isinstance(data, string_types):
data = data.encode('utf-8')
missing_padding = 4 - len(data) % 4
if missing_padding:
data += b'=' * missing_padding
return data
def base64_remove_padding(data):
"""
Remove padding from base64 encoding
Args:
data: fully padded base64 data
Return:
base64: Unpadded base64 bytes
"""
if isinstance(data, string_types):
data = data.encode('utf-8')
return data.replace(b'=', b'')

View File

@ -1,12 +1,11 @@
import json
import time
import multiprocessing as mp
import time
from datetime import datetime
import bigchaindb
from bigchaindb import exceptions
from bigchaindb.crypto import PrivateKey, PublicKey, hash_data
from bigchaindb.crypto.asymmetric import PrivateKey, PublicKey, hash_data
class ProcessGroup(object):

View File

@ -76,6 +76,7 @@ setup(
'bitcoin==1.1.42',
'flask==0.10.1',
'requests==2.9',
'ed25519==1.4',
],
setup_requires=['pytest-runner'],
tests_require=tests_require,

0
tests/crypto/__init__.py Normal file
View File

121
tests/crypto/test_crypto.py Normal file
View File

@ -0,0 +1,121 @@
import base64
from bigchaindb.crypto.ecdsa import ECDSAPrivateKey, ECDSAPublicKey, ecdsa_generate_key_pair
from bigchaindb.crypto.ed25519 import ED25519PrivateKey, ED25519PublicKey, ed25519_generate_key_pair
class TestBigchainCryptoED25519(object):
PRIVATE_B64 = b'xSrGKGxeJYVlVrk84f29wcPazoTV+y8fzM7P0iFsSdg='
PRIVATE_BYTES = b'\xc5*\xc6(l^%\x85eV\xb9<\xe1\xfd\xbd\xc1\xc3\xda\xce\x84\xd5\xfb/\x1f\xcc\xce\xcf\xd2!lI\xd8v\x08\xfb\x03\x15y/&\xcd^O\xa9\xb8\xd2\x8a\x89\x8d\xf94\x9b\xbe\xb1\xe7\xdb~\x95!o\xde\xa2{\xa5'
PRIVATE_B58 = 'EGf9UJzryLpZaBguyf5f4QAefFnairNbHLkhht8BZ57m'
PUBLIC_B64 = b'dgj7AxV5LybNXk+puNKKiY35NJu+sefbfpUhb96ie6U='
PUBLIC_BYTES = b'v\x08\xfb\x03\x15y/&\xcd^O\xa9\xb8\xd2\x8a\x89\x8d\xf94\x9b\xbe\xb1\xe7\xdb~\x95!o\xde\xa2{\xa5'
PUBLIC_B58 = '8wm3wiqsoujkDJvk8FMZkHijb9eZdUqMuZsnRee4eRz4'
PUBLIC_B64_ILP = 'Lvf3YtnHLMER+VHT0aaeEJF+7WQcvp4iKZAdvMVto7c='
MSG_SHA512_ILP = 'claZQU7qkFz7smkAVtQp9ekUCc5LgoeN9W3RItIzykNEDbGSvzeHvOk9v/vrPpm+XWx5VFjd/sVbM2SLnCpxLw=='
SIG_B64_ILP = 'sd0RahwuJJgeNfg8HvWHtYf4uqNgCOqIbseERacqs8G0kXNQQnhfV6gWAnMb+0RIlY3e0mqbrQiUwbRYJvRBAw=='
def test_private_key_encode(self):
private_value_base58 = ED25519PrivateKey.encode(self.PRIVATE_B64)
assert private_value_base58 == self.PRIVATE_B58
def test_private_key_init(self):
sk = ED25519PrivateKey(self.PRIVATE_B58)
assert sk.private_key.to_ascii(encoding='base64') == self.PRIVATE_B64[:-1]
assert sk.private_key.to_bytes() == self.PRIVATE_BYTES
def test_private_key_decode(self):
private_value = ED25519PrivateKey.decode(self.PRIVATE_B58)
assert private_value == self.PRIVATE_B64
def test_public_key_encode(self):
public_value_base58 = ED25519PublicKey.encode(self.PUBLIC_B64)
assert public_value_base58 == self.PUBLIC_B58
def test_public_key_init(self):
vk = ED25519PublicKey(self.PUBLIC_B58)
assert vk.public_key.to_ascii(encoding='base64') == self.PUBLIC_B64[:-1]
assert vk.public_key.to_bytes() == self.PUBLIC_BYTES
def test_public_key_decode(self):
public_value = ED25519PublicKey.decode(self.PUBLIC_B58)
assert public_value == self.PUBLIC_B64
def test_sign_verify(self):
message = 'Hello World!'
sk = ED25519PrivateKey(self.PRIVATE_B58)
vk = ED25519PublicKey(self.PUBLIC_B58)
assert vk.verify(message, sk.sign(message)) is True
assert vk.verify(message, sk.sign(message + 'dummy')) is False
assert vk.verify(message + 'dummy', sk.sign(message)) is False
vk = ED25519PublicKey(ED25519PublicKey.encode(self.PUBLIC_B64_ILP))
assert vk.verify(message, sk.sign(message)) is False
def test_valid_condition_valid_signature_ilp(self):
vk = ED25519PublicKey(ED25519PublicKey.encode(self.PUBLIC_B64_ILP))
msg = self.MSG_SHA512_ILP
sig = self.SIG_B64_ILP
assert vk.verify(base64.b64decode(msg), base64.b64decode(sig), encoding=None) is True
def test_valid_condition_invalid_signature_ilp(self):
vk = ED25519PublicKey(ED25519PublicKey.encode(self.PUBLIC_B64_ILP))
msg = self.MSG_SHA512_ILP
sig = self.MSG_SHA512_ILP
assert vk.verify(base64.b64decode(msg), base64.b64decode(sig), encoding=None) is False
def test_generate_key_pair(self):
sk, vk = ed25519_generate_key_pair()
assert ED25519PrivateKey.encode(ED25519PrivateKey.decode(sk)) == sk
assert ED25519PublicKey.encode(ED25519PublicKey.decode(vk)) == vk
def test_generate_sign_verify(self):
sk, vk = ed25519_generate_key_pair()
sk = ED25519PrivateKey(sk)
vk = ED25519PublicKey(vk)
message = 'Hello World!'
assert vk.verify(message, sk.sign(message)) is True
assert vk.verify(message, sk.sign(message + 'dummy')) is False
assert vk.verify(message + 'dummy', sk.sign(message)) is False
vk = ED25519PublicKey(ED25519PublicKey.encode(self.PUBLIC_B64_ILP))
assert vk.verify(message, sk.sign(message)) is False
class TestBigchainCryptoECDSA(object):
PRIVATE_VALUE = 64328150571824492670917070117568709277186368319388887463636481841106388379832
PUBLIC_VALUE_X = 48388170575736684074633245566225141536152842355597159440179742847497614196929
PUBLIC_VALUE_Y = 65233479152484407841598798165960909560839872511163322973341535484598825150846
PRIVATE_VALUE_B58 = 'AaAp4xBavbe6VGeQF2mWdSKNM1r6HfR2Z1tAY6aUkwdq'
PUBLIC_VALUE_COMPRESSED_B58 = 'ifEi3UuTDT4CqUUKiS5omgeDodhu2aRFHVp6LoahbEVe'
def test_private_key_encode(self):
private_value_base58 = ECDSAPrivateKey.encode(self.PRIVATE_VALUE)
assert private_value_base58 == self.PRIVATE_VALUE_B58
def test_private_key_decode(self):
private_value = ECDSAPrivateKey.decode(self.PRIVATE_VALUE_B58)
assert private_value == self.PRIVATE_VALUE
def test_public_key_encode(self):
public_value_compressed_base58 = ECDSAPublicKey.encode(self.PUBLIC_VALUE_X, self.PUBLIC_VALUE_Y)
assert public_value_compressed_base58 == self.PUBLIC_VALUE_COMPRESSED_B58
def test_public_key_decode(self):
public_value_x, public_value_y = ECDSAPublicKey.decode(self.PUBLIC_VALUE_COMPRESSED_B58)
assert public_value_x == self.PUBLIC_VALUE_X
assert public_value_y == self.PUBLIC_VALUE_Y
def test_sign_verify(self):
message = 'Hello World!'
public_key = ECDSAPublicKey(self.PUBLIC_VALUE_COMPRESSED_B58)
private_key = ECDSAPrivateKey(self.PRIVATE_VALUE_B58)
assert public_key.verify(message, private_key.sign(message)) is True
def test_generate_key_pair(self):
private_value_base58, public_value_compressed_base58 = ecdsa_generate_key_pair()
assert ECDSAPrivateKey.encode(
ECDSAPrivateKey.decode(private_value_base58)) == private_value_base58
assert ECDSAPublicKey.encode(
*ECDSAPublicKey.decode(public_value_compressed_base58)) == public_value_compressed_base58

View File

@ -0,0 +1,115 @@
import binascii
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
class TestBigchainILPFulfillmentEd25519Sha256:
PUBLIC_HEX_ILP = b'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf'
PUBLIC_B64_ILP = b'7Bcrk61eVjv0kyxw4SRQNMNUZ+8u/U1k6/gZaDRn4r8'
PUBLIC_B58_ILP = 'Gtbi6WQDB6wUePiZm8aYs5XZ5pUqx9jMMLvRVHPESTjU'
PRIVATE_HEX_ILP = b'833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42'
PRIVATE_B64_ILP = b'gz/mJAkje51i7HdYdSCRHpp1nOwdGXVbfakBuW3KPUI'
PRIVATE_B58_ILP = '9qLvREC54mhKYivr88VpckyVWdAFmifJpGjbvV5AiTRs'
CONDITION_ED25519_ILP = 'cc:1:8:qQINW2um59C4DB9JSVXH1igqAmaYGGqryllHUgCpfPU:113'
FULFILLMENT_ED25519_ILP = \
'cf:1:8:IOwXK5OtXlY79JMscOEkUDTDVGfvLv1NZOv4GWg0Z-K_DEhlbGxvIHdvcmxkISAVIENvbmRpdGlvbnMgYXJlIGhlcmUhQENbql531' \
'PbCJlRUvKjP56k0XKJMOrIGo2F66ueuTtRnYrJB2t2ZttdfXM4gzD_87eH1nZTpu4rTkAx81hSdpwI'
HASH_ED25519_HEX_ILP = b'a9020d5b6ba6e7d0b80c1f494955c7d6282a026698186aabca59475200a97cf5'
def test_ilp_keys(self):
sk = ED25519PrivateKey(self.PRIVATE_B58_ILP)
assert sk.private_key.to_ascii(encoding='base64') == self.PRIVATE_B64_ILP
assert binascii.hexlify(sk.private_key.to_bytes()[:32]) == self.PRIVATE_HEX_ILP
vk = ED25519PublicKey(self.PUBLIC_B58_ILP)
assert vk.public_key.to_ascii(encoding='base64') == self.PUBLIC_B64_ILP
assert binascii.hexlify(vk.public_key.to_bytes()) == self.PUBLIC_HEX_ILP
def test_serialize_condition_and_validate_fulfillment(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
assert fulfillment.condition.serialize_uri() == self.CONDITION_ED25519_ILP
assert binascii.hexlify(fulfillment.condition.hash) == self.HASH_ED25519_HEX_ILP
fulfillment.message = ' Conditions are here!'
# ED25519-SHA256 condition not fulfilled
assert fulfillment.validate() == False
# Fulfill an ED25519-SHA256 condition
fulfillment.sign(sk)
assert fulfillment.serialize_uri() == self.FULFILLMENT_ED25519_ILP
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
assert binascii.hexlify(deserialized_condition.hash) == self.HASH_ED25519_HEX_ILP
def test_serialize_deserialize_condition(self):
vk = ED25519PublicKey(self.PUBLIC_B58_ILP)
fulfillment = Ed25519Sha256Fulfillment()
fulfillment.public_key = vk
fulfillment.message_prefix = 'Hello world!'
fulfillment.max_dynamic_message_length = 32
condition = fulfillment.condition
deserialized_condition = Condition.from_uri(condition.serialize_uri())
assert deserialized_condition.bitmask == condition.bitmask
assert deserialized_condition.hash == condition.hash
assert deserialized_condition.max_fulfillment_length == condition.max_fulfillment_length
assert deserialized_condition.serialize_uri() == condition.serialize_uri()
def test_deserialize_fulfillment(self):
fulfillment = Fulfillment.from_uri(self.FULFILLMENT_ED25519_ILP)
assert fulfillment.serialize_uri() == self.FULFILLMENT_ED25519_ILP
assert fulfillment.condition.serialize_uri() == self.CONDITION_ED25519_ILP
assert binascii.hexlify(fulfillment.condition.hash) == self.HASH_ED25519_HEX_ILP
assert fulfillment.public_key.public_key.to_ascii(encoding='hex') == self.PUBLIC_HEX_ILP
assert fulfillment.validate()
def test_serializer_deserialize_fulfillment(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)
assert fulfillment.validate()
deserialized_fulfillment = Fulfillment.from_uri(fulfillment.serialize_uri())
assert deserialized_fulfillment.serialize_uri() == fulfillment.serialize_uri()
assert deserialized_fulfillment.condition.serialize_uri() == fulfillment.condition.serialize_uri()
assert deserialized_fulfillment.public_key.public_key.to_bytes() == fulfillment.public_key.public_key.to_bytes()
assert deserialized_fulfillment.validate()
class TestBigchainILPConditionSha256:
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

View File

@ -6,12 +6,11 @@ import pytest
import rethinkdb as r
import bigchaindb
from bigchaindb import util
from bigchaindb import exceptions
from bigchaindb.crypto import PrivateKey, PublicKey, generate_key_pair, hash_data
from bigchaindb.voter import Voter
from bigchaindb import util
from bigchaindb.block import Block
from bigchaindb.crypto.asymmetric import PrivateKey, PublicKey, generate_key_pair, hash_data
from bigchaindb.voter import Voter
@pytest.mark.skipif(reason='Some tests throw a ResourceWarning that might result in some weird '
@ -408,45 +407,6 @@ class TestBlockValidation(object):
assert b.is_valid_block(block)
class TestBigchainCrypto(object):
PRIVATE_VALUE = 64328150571824492670917070117568709277186368319388887463636481841106388379832
PUBLIC_VALUE_X = 48388170575736684074633245566225141536152842355597159440179742847497614196929
PUBLIC_VALUE_Y = 65233479152484407841598798165960909560839872511163322973341535484598825150846
PRIVATE_VALUE_B58 = 'AaAp4xBavbe6VGeQF2mWdSKNM1r6HfR2Z1tAY6aUkwdq'
PUBLIC_VALUE_COMPRESSED_B58 = 'ifEi3UuTDT4CqUUKiS5omgeDodhu2aRFHVp6LoahbEVe'
def test_private_key_encode(self):
private_value_base58 = PrivateKey.encode(self.PRIVATE_VALUE)
assert private_value_base58 == self.PRIVATE_VALUE_B58
def test_private_key_decode(self):
private_value = PrivateKey.decode(self.PRIVATE_VALUE_B58)
assert private_value == self.PRIVATE_VALUE
def test_public_key_encode(self):
public_value_compressed_base58 = PublicKey.encode(self.PUBLIC_VALUE_X, self.PUBLIC_VALUE_Y)
assert public_value_compressed_base58 == self.PUBLIC_VALUE_COMPRESSED_B58
def test_public_key_decode(self):
public_value_x, public_value_y = PublicKey.decode(self.PUBLIC_VALUE_COMPRESSED_B58)
assert public_value_x == self.PUBLIC_VALUE_X
assert public_value_y == self.PUBLIC_VALUE_Y
def test_sign_verify(self):
message = 'Hello World!'
public_key = PublicKey(self.PUBLIC_VALUE_COMPRESSED_B58)
private_key = PrivateKey(self.PRIVATE_VALUE_B58)
assert public_key.verify(message, private_key.sign(message)) is True
def test_generate_key_pair(self):
private_value_base58, public_value_compressed_base58 = generate_key_pair()
assert PrivateKey.encode(
PrivateKey.decode(private_value_base58)) == private_value_base58
assert PublicKey.encode(
*PublicKey.decode(public_value_compressed_base58)) == public_value_compressed_base58
class TestBigchainVoter(object):
def test_valid_block_voting(self, b):

View File

@ -1,12 +1,12 @@
import pytest
import time
import rethinkdb as r
import multiprocessing as mp
import time
import pytest
import rethinkdb as r
from bigchaindb import util
from bigchaindb.crypto.asymmetric import PublicKey, generate_key_pair
from bigchaindb.voter import Voter, BlockStream
from bigchaindb.crypto import PublicKey, generate_key_pair
class TestBigchainVoter(object):

View File

@ -52,7 +52,7 @@ def mock_rethink_db_drop(monkeypatch):
@pytest.fixture
def mock_generate_key_pair(monkeypatch):
monkeypatch.setattr('bigchaindb.crypto.generate_key_pair', lambda: ('privkey', 'pubkey'))
monkeypatch.setattr('bigchaindb.crypto.asymmetric.generate_key_pair', lambda: ('privkey', 'pubkey'))
@pytest.fixture

View File

@ -1,8 +1,9 @@
import json
import pytest
from bigchaindb import crypto
from bigchaindb import util
from bigchaindb.crypto import asymmetric
TX_ENDPOINT = '/api/v1/transactions/'
@ -17,7 +18,7 @@ def test_get_transaction_endpoint(b, client, user_public_key):
def test_post_create_transaction_endpoint(b, client):
keypair = crypto.generate_key_pair()
keypair = asymmetric.generate_key_pair()
tx = util.create_and_sign_tx(keypair[0], keypair[1], keypair[1], None, 'CREATE')
@ -27,8 +28,8 @@ def test_post_create_transaction_endpoint(b, client):
def test_post_transfer_transaction_endpoint(b, client):
from_keypair = crypto.generate_key_pair()
to_keypair = crypto.generate_key_pair()
from_keypair = asymmetric.generate_key_pair()
to_keypair = asymmetric.generate_key_pair()
tx = util.create_and_sign_tx(from_keypair[0], from_keypair[1], from_keypair[1], None, 'CREATE')
res = client.post(TX_ENDPOINT, data=json.dumps(tx))