mirror of
https://github.com/planetmint/planetmint.git
synced 2025-09-14 12:00:11 +00:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7f80b0f306 | ||
![]() |
d1bb726e9a | ||
![]() |
99e1edf880 | ||
![]() |
9f66450c7c | ||
![]() |
a061e773c9 | ||
![]() |
6d0af34aa2 | ||
![]() |
ea05872927 | ||
![]() |
a35f705865 | ||
![]() |
bab55e2803 | ||
![]() |
7aa3228844 | ||
![]() |
57f8529303 | ||
![]() |
f3791cfd8f | ||
![]() |
28869d5a88 | ||
![]() |
4fd071adeb | ||
![]() |
b394831e39 | ||
![]() |
ca4a9cf949 | ||
![]() |
3f28ddd990 | ||
![]() |
ebabd3de7d | ||
![]() |
ecb828f1d6 | ||
![]() |
7237df59ba | ||
![]() |
83cd391028 | ||
![]() |
b455434aac |
@ -25,6 +25,10 @@ For reference, the possible headings are:
|
|||||||
* **Known Issues**
|
* **Known Issues**
|
||||||
* **Notes**
|
* **Notes**
|
||||||
|
|
||||||
|
## [0.9.7] - 2022-06-17
|
||||||
|
|
||||||
|
### Feature Update
|
||||||
|
Deep Zenroom integration
|
||||||
|
|
||||||
## [0.9.6] - 2022-06-08
|
## [0.9.6] - 2022-06-08
|
||||||
|
|
||||||
|
@ -9,56 +9,10 @@ RUN apt-get install -y vim zsh build-essential cmake
|
|||||||
RUN mkdir -p /src
|
RUN mkdir -p /src
|
||||||
RUN /usr/local/bin/python -m pip install --upgrade pip
|
RUN /usr/local/bin/python -m pip install --upgrade pip
|
||||||
RUN pip install --upgrade meson ninja
|
RUN pip install --upgrade meson ninja
|
||||||
RUN pip install zenroom==2.0.0.dev1644927841
|
|
||||||
RUN pip install --upgrade \
|
RUN pip install --upgrade \
|
||||||
pycco \
|
pycco \
|
||||||
websocket-client~=0.47.0 \
|
websocket-client~=0.47.0 \
|
||||||
pytest~=3.0 \
|
pytest~=3.0 \
|
||||||
#git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \
|
planetmint-cryptoconditions>=0.9.9\
|
||||||
#git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \
|
planetmint-driver>=0.9.2 \
|
||||||
planetmint-cryptoconditions>=0.9.4\
|
|
||||||
planetmint-driver>=0.9.0 \
|
|
||||||
blns
|
blns
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#FROM python:3.9
|
|
||||||
#
|
|
||||||
#RUN apt-get update && apt-get install -y vim zsh
|
|
||||||
#RUN apt-get update \
|
|
||||||
# && apt-get install -y git zsh\
|
|
||||||
# && pip install -U pip \
|
|
||||||
# && apt-get autoremove \
|
|
||||||
# && apt-get clean
|
|
||||||
#RUN apt install sudo
|
|
||||||
#RUN apt-get install -y python3 openssl ca-certificates git python3-dev
|
|
||||||
#RUN apt-get install zsh gcc
|
|
||||||
#RUN apt-get install libffi-dev
|
|
||||||
#RUN apt-get install build-essential cmake -y
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#RUN mkdir -p /src
|
|
||||||
#RUN pip install --upgrade \
|
|
||||||
# pycco \
|
|
||||||
# websocket-client~=0.47.0 \
|
|
||||||
# pytest~=3.0 \
|
|
||||||
# planetmint-driver>=0.9.0 \
|
|
||||||
# blns \
|
|
||||||
# git+https://github.com/planetmint/cryptoconditions.git@gitzenroom >=0.9.0 \
|
|
||||||
# chardet==3.0.4 \
|
|
||||||
# aiohttp==3.7.4 \
|
|
||||||
# abci==0.8.3 \
|
|
||||||
# #planetmint-cryptoconditions>=0.9.0\
|
|
||||||
# flask-cors==3.0.10 \
|
|
||||||
# flask-restful==0.3.9 \
|
|
||||||
# flask==2.0.1 \
|
|
||||||
# gunicorn==20.1.0 \
|
|
||||||
# jsonschema==3.2.0 \
|
|
||||||
# logstats==0.3.0 \
|
|
||||||
# packaging>=20.9 \
|
|
||||||
# pymongo==3.11.4 \
|
|
||||||
# pyyaml==5.4.1 \
|
|
||||||
# requests==2.25.1 \
|
|
||||||
# setproctitle==1.2.2
|
|
||||||
#
|
|
||||||
|
@ -5,37 +5,41 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
GENERATE_KEYPAIR = \
|
CONDITION_SCRIPT = """
|
||||||
"""Rule input encoding base58
|
Scenario 'ecdh': create the signature of an object
|
||||||
Rule output encoding base58
|
Given I have the 'keyring'
|
||||||
Scenario 'ecdh': Create the keypair
|
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
||||||
Given that I am known as 'Pippo'
|
When I create the signature of 'houses'
|
||||||
When I create the ecdh key
|
Then print the 'signature'"""
|
||||||
When I create the testnet key
|
|
||||||
Then print data"""
|
|
||||||
|
|
||||||
# secret key to public key
|
|
||||||
SK_TO_PK = \
|
|
||||||
"""Rule input encoding base58
|
|
||||||
Rule output encoding base58
|
|
||||||
Scenario 'ecdh': Create the keypair
|
|
||||||
Given that I am known as '{}'
|
|
||||||
Given I have the 'keys'
|
|
||||||
When I create the ecdh public key
|
|
||||||
When I create the testnet address
|
|
||||||
Then print my 'ecdh public key'
|
|
||||||
Then print my 'testnet address'"""
|
|
||||||
|
|
||||||
FULFILL_SCRIPT = \
|
FULFILL_SCRIPT = \
|
||||||
"""Rule input encoding base58
|
"""Scenario 'ecdh': Bob verifies the signature from Alice
|
||||||
Rule output encoding base58
|
|
||||||
Scenario 'ecdh': Bob verifies the signature from Alice
|
|
||||||
Given I have a 'ecdh public key' from 'Alice'
|
Given I have a 'ecdh public key' from 'Alice'
|
||||||
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
||||||
Given I have a 'signature' named 'data.signature' inside 'result'
|
Given I have a 'signature' named 'signature' inside 'result'
|
||||||
When I verify the 'houses' has a signature in 'data.signature' by 'Alice'
|
When I verify the 'houses' has a signature in 'signature' by 'Alice'
|
||||||
Then print the string 'ok'"""
|
Then print the string 'ok'"""
|
||||||
|
|
||||||
|
SK_TO_PK = \
|
||||||
|
"""Scenario 'ecdh': Create the keypair
|
||||||
|
Given that I am known as '{}'
|
||||||
|
Given I have the 'keyring'
|
||||||
|
When I create the ecdh public key
|
||||||
|
When I create the bitcoin address
|
||||||
|
Then print my 'ecdh public key'
|
||||||
|
Then print my 'bitcoin address'"""
|
||||||
|
|
||||||
|
GENERATE_KEYPAIR = \
|
||||||
|
"""Scenario 'ecdh': Create the keypair
|
||||||
|
Given that I am known as 'Pippo'
|
||||||
|
When I create the ecdh key
|
||||||
|
When I create the bitcoin key
|
||||||
|
Then print data"""
|
||||||
|
|
||||||
|
ZENROOM_DATA = {
|
||||||
|
'also': 'more data'
|
||||||
|
}
|
||||||
|
|
||||||
HOUSE_ASSETS = {
|
HOUSE_ASSETS = {
|
||||||
"data": {
|
"data": {
|
||||||
"houses": [
|
"houses": [
|
||||||
@ -51,19 +55,11 @@ HOUSE_ASSETS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZENROOM_DATA = {
|
metadata = {
|
||||||
'also': 'more data'
|
'units': 300,
|
||||||
|
'type': 'KG'
|
||||||
}
|
}
|
||||||
|
|
||||||
CONDITION_SCRIPT = """Rule input encoding base58
|
|
||||||
Rule output encoding base58
|
|
||||||
Scenario 'ecdh': create the signature of an object
|
|
||||||
Given I have the 'keys'
|
|
||||||
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
|
||||||
When I create the signature of 'houses'
|
|
||||||
When I rename the 'signature' to 'data.signature'
|
|
||||||
Then print the 'data.signature'"""
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def gen_key_zencode():
|
def gen_key_zencode():
|
||||||
return GENERATE_KEYPAIR
|
return GENERATE_KEYPAIR
|
||||||
|
@ -1,68 +1,73 @@
|
|||||||
# GOAL:
|
import os
|
||||||
# In this script I tried to implement the ECDSA signature using zenroom
|
|
||||||
|
|
||||||
# However, the scripts are customizable and so with the same procedure
|
|
||||||
# we can implement more complex smart contracts
|
|
||||||
|
|
||||||
# PUBLIC IDENTITY
|
|
||||||
# The public identity of the users in this script (Bob and Alice)
|
|
||||||
# is the pair (ECDH public key, Testnet address)
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import base58
|
||||||
|
from hashlib import sha3_256
|
||||||
|
from cryptoconditions.types.ed25519 import Ed25519Sha256
|
||||||
|
from cryptoconditions.types.zenroom import ZenroomSha256
|
||||||
|
from zenroom import zencode_exec
|
||||||
|
from planetmint_driver import Planetmint
|
||||||
|
from planetmint_driver.crypto import generate_keypair
|
||||||
|
|
||||||
import hashlib
|
|
||||||
from cryptoconditions import ZenroomSha256
|
|
||||||
from json.decoder import JSONDecodeError
|
|
||||||
|
|
||||||
def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode,
|
|
||||||
condition_script_zencode, zenroom_data, zenroom_house_assets):
|
|
||||||
alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
|
|
||||||
bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
|
|
||||||
|
|
||||||
zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'),
|
|
||||||
keys={'keys': alice}).output)
|
|
||||||
zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'),
|
|
||||||
keys={'keys': bob}).output))
|
|
||||||
|
|
||||||
# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer
|
|
||||||
zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data)
|
def test_zenroom_signing(gen_key_zencode, secret_key_to_private_key_zencode,
|
||||||
|
fulfill_script_zencode, zenroom_data, zenroom_house_assets,
|
||||||
|
condition_script_zencode):
|
||||||
|
|
||||||
|
biolabs = generate_keypair()
|
||||||
|
version = '2.0'
|
||||||
|
|
||||||
|
alice = json.loads(zencode_exec(gen_key_zencode).output)['keyring']
|
||||||
|
bob = json.loads(zencode_exec(gen_key_zencode).output)['keyring']
|
||||||
|
|
||||||
|
zen_public_keys = json.loads(zencode_exec(secret_key_to_private_key_zencode.format('Alice'),
|
||||||
|
keys=json.dumps({'keyring': alice})).output)
|
||||||
|
zen_public_keys.update(json.loads(zencode_exec(secret_key_to_private_key_zencode.format('Bob'),
|
||||||
|
keys=json.dumps({'keyring': bob})).output))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
zenroomscpt = ZenroomSha256(script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys)
|
||||||
|
print(F'zenroom is: {zenroomscpt.script}')
|
||||||
|
|
||||||
# CRYPTO-CONDITIONS: generate the condition uri
|
# CRYPTO-CONDITIONS: generate the condition uri
|
||||||
condition_uri = zenSha.condition.serialize_uri()
|
condition_uri_zen = zenroomscpt.condition.serialize_uri()
|
||||||
|
print(F'\nzenroom condition URI: {condition_uri_zen}')
|
||||||
|
|
||||||
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
|
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
|
||||||
unsigned_fulfillment_dict = {
|
unsigned_fulfillment_dict_zen = {
|
||||||
'type': zenSha.TYPE_NAME,
|
'type': zenroomscpt.TYPE_NAME,
|
||||||
'script': fulfill_script_zencode,
|
'public_key': base58.b58encode(biolabs.public_key).decode(),
|
||||||
'keys': zen_public_keys,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
'amount': '1000',
|
'amount': '10',
|
||||||
'condition': {
|
'condition': {
|
||||||
'details': unsigned_fulfillment_dict,
|
'details': unsigned_fulfillment_dict_zen,
|
||||||
'uri': condition_uri,
|
'uri': condition_uri_zen,
|
||||||
|
|
||||||
},
|
},
|
||||||
'data': zenroom_data,
|
'public_keys': [biolabs.public_key,],
|
||||||
'script': fulfill_script_zencode,
|
|
||||||
'conf': '',
|
|
||||||
'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
input_ = {
|
input_ = {
|
||||||
'fulfillment': None,
|
'fulfillment': None,
|
||||||
'fulfills': None,
|
'fulfills': None,
|
||||||
'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ),
|
'owners_before': [biolabs.public_key,]
|
||||||
|
}
|
||||||
|
metadata = {
|
||||||
|
"result": {
|
||||||
|
"output": ["ok"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
token_creation_tx = {
|
token_creation_tx = {
|
||||||
'operation': 'CREATE',
|
'operation': 'CREATE',
|
||||||
'asset': zenroom_house_assets,
|
'asset': zenroom_house_assets,
|
||||||
'metadata': None,
|
'metadata': metadata,
|
||||||
'outputs': (output,),
|
'outputs': [output,],
|
||||||
'inputs': (input_,),
|
'inputs': [input_,],
|
||||||
'version': '2.0',
|
'version': version,
|
||||||
'id': None,
|
'id': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,12 +79,36 @@ condition_script_zencode, zenroom_data, zenroom_house_assets):
|
|||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
# major workflow:
|
||||||
assert(not zenSha.validate(message=message))
|
# we store the fulfill script in the transaction/message (zenroom-sha)
|
||||||
except JSONDecodeError:
|
# the condition script is used to fulfill the transaction and create the signature
|
||||||
pass
|
#
|
||||||
except ValueError:
|
# the server should ick the fulfill script and recreate the zenroom-sha and verify the signature
|
||||||
pass
|
|
||||||
|
|
||||||
message = zenSha.sign(message, condition_script_zencode, alice)
|
|
||||||
assert(zenSha.validate(message=message))
|
|
||||||
|
message = zenroomscpt.sign(message, condition_script_zencode, alice)
|
||||||
|
assert(zenroomscpt.validate(message=message))
|
||||||
|
|
||||||
|
message = json.loads(message)
|
||||||
|
fulfillment_uri_zen = zenroomscpt.serialize_uri()
|
||||||
|
|
||||||
|
message['inputs'][0]['fulfillment'] = fulfillment_uri_zen
|
||||||
|
tx = message
|
||||||
|
tx['id'] = None
|
||||||
|
json_str_tx = json.dumps(
|
||||||
|
tx,
|
||||||
|
sort_keys=True,
|
||||||
|
skipkeys=False,
|
||||||
|
separators=(',', ':')
|
||||||
|
)
|
||||||
|
# SHA3: hash the serialized id-less transaction to generate the id
|
||||||
|
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
|
||||||
|
message['id'] = shared_creation_txid
|
||||||
|
|
||||||
|
|
||||||
|
# `https://example.com:9984`
|
||||||
|
plntmnt = Planetmint(os.environ.get('PLANETMINT_ENDPOINT'))
|
||||||
|
sent_transfer_tx = plntmnt.transactions.send_commit(message)
|
||||||
|
|
||||||
|
print( f"\n\nstatus and result : + {sent_transfer_tx}")
|
||||||
|
@ -13,10 +13,8 @@ RUN mkdir -p /src
|
|||||||
RUN pip install --upgrade meson ninja
|
RUN pip install --upgrade meson ninja
|
||||||
RUN pip install --upgrade \
|
RUN pip install --upgrade \
|
||||||
pytest~=6.2.5 \
|
pytest~=6.2.5 \
|
||||||
planetmint-driver~=0.9.0 \
|
|
||||||
pycco \
|
pycco \
|
||||||
websocket-client~=0.47.0 \
|
websocket-client~=0.47.0 \
|
||||||
#git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \
|
planetmint-cryptoconditions>=0.9.9\
|
||||||
#git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \
|
planetmint-driver>=0.9.2 \
|
||||||
blns
|
blns
|
||||||
|
|
||||||
|
@ -5,37 +5,41 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
GENERATE_KEYPAIR = \
|
CONDITION_SCRIPT = """
|
||||||
"""Rule input encoding base58
|
Scenario 'ecdh': create the signature of an object
|
||||||
Rule output encoding base58
|
Given I have the 'keyring'
|
||||||
Scenario 'ecdh': Create the keypair
|
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
||||||
Given that I am known as 'Pippo'
|
When I create the signature of 'houses'
|
||||||
When I create the ecdh key
|
Then print the 'signature'"""
|
||||||
When I create the testnet key
|
|
||||||
Then print data"""
|
|
||||||
|
|
||||||
# secret key to public key
|
|
||||||
SK_TO_PK = \
|
|
||||||
"""Rule input encoding base58
|
|
||||||
Rule output encoding base58
|
|
||||||
Scenario 'ecdh': Create the keypair
|
|
||||||
Given that I am known as '{}'
|
|
||||||
Given I have the 'keys'
|
|
||||||
When I create the ecdh public key
|
|
||||||
When I create the testnet address
|
|
||||||
Then print my 'ecdh public key'
|
|
||||||
Then print my 'testnet address'"""
|
|
||||||
|
|
||||||
FULFILL_SCRIPT = \
|
FULFILL_SCRIPT = \
|
||||||
"""Rule input encoding base58
|
"""Scenario 'ecdh': Bob verifies the signature from Alice
|
||||||
Rule output encoding base58
|
|
||||||
Scenario 'ecdh': Bob verifies the signature from Alice
|
|
||||||
Given I have a 'ecdh public key' from 'Alice'
|
Given I have a 'ecdh public key' from 'Alice'
|
||||||
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
||||||
Given I have a 'signature' named 'data.signature' inside 'result'
|
Given I have a 'signature' named 'signature' inside 'result'
|
||||||
When I verify the 'houses' has a signature in 'data.signature' by 'Alice'
|
When I verify the 'houses' has a signature in 'signature' by 'Alice'
|
||||||
Then print the string 'ok'"""
|
Then print the string 'ok'"""
|
||||||
|
|
||||||
|
SK_TO_PK = \
|
||||||
|
"""Scenario 'ecdh': Create the keypair
|
||||||
|
Given that I am known as '{}'
|
||||||
|
Given I have the 'keyring'
|
||||||
|
When I create the ecdh public key
|
||||||
|
When I create the bitcoin address
|
||||||
|
Then print my 'ecdh public key'
|
||||||
|
Then print my 'bitcoin address'"""
|
||||||
|
|
||||||
|
GENERATE_KEYPAIR = \
|
||||||
|
"""Scenario 'ecdh': Create the keypair
|
||||||
|
Given that I am known as 'Pippo'
|
||||||
|
When I create the ecdh key
|
||||||
|
When I create the bitcoin key
|
||||||
|
Then print data"""
|
||||||
|
|
||||||
|
ZENROOM_DATA = {
|
||||||
|
'also': 'more data'
|
||||||
|
}
|
||||||
|
|
||||||
HOUSE_ASSETS = {
|
HOUSE_ASSETS = {
|
||||||
"data": {
|
"data": {
|
||||||
"houses": [
|
"houses": [
|
||||||
@ -51,45 +55,31 @@ HOUSE_ASSETS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZENROOM_DATA = {
|
metadata = {
|
||||||
'also': 'more data'
|
'units': 300,
|
||||||
|
'type': 'KG'
|
||||||
}
|
}
|
||||||
|
|
||||||
CONDITION_SCRIPT = """Rule input encoding base58
|
|
||||||
Rule output encoding base58
|
|
||||||
Scenario 'ecdh': create the signature of an object
|
|
||||||
Given I have the 'keys'
|
|
||||||
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
|
||||||
When I create the signature of 'houses'
|
|
||||||
When I rename the 'signature' to 'data.signature'
|
|
||||||
Then print the 'data.signature'"""
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def gen_key_zencode():
|
def gen_key_zencode():
|
||||||
return GENERATE_KEYPAIR
|
return GENERATE_KEYPAIR
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def secret_key_to_private_key_zencode():
|
def secret_key_to_private_key_zencode():
|
||||||
return SK_TO_PK
|
return SK_TO_PK
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fulfill_script_zencode():
|
def fulfill_script_zencode():
|
||||||
return FULFILL_SCRIPT
|
return FULFILL_SCRIPT
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def condition_script_zencode():
|
def condition_script_zencode():
|
||||||
return CONDITION_SCRIPT
|
return CONDITION_SCRIPT
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def zenroom_house_assets():
|
def zenroom_house_assets():
|
||||||
return HOUSE_ASSETS
|
return HOUSE_ASSETS
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def zenroom_data():
|
def zenroom_data():
|
||||||
return ZENROOM_DATA
|
return ZENROOM_DATA
|
||||||
|
@ -1,84 +1,123 @@
|
|||||||
# GOAL:
|
|
||||||
# In this script I tried to implement the ECDSA signature using zenroom
|
|
||||||
|
|
||||||
# However, the scripts are customizable and so with the same procedure
|
|
||||||
# we can implement more complex smart contracts
|
|
||||||
|
|
||||||
# PUBLIC IDENTITY
|
|
||||||
# The public identity of the users in this script (Bob and Alice)
|
|
||||||
# is the pair (ECDH public key, Testnet address)
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import base58
|
||||||
from cryptoconditions import ZenroomSha256
|
from hashlib import sha3_256
|
||||||
from json.decoder import JSONDecodeError
|
from cryptoconditions.types.zenroom import ZenroomSha256
|
||||||
|
from planetmint_driver.crypto import generate_keypair
|
||||||
|
from .helper.hosts import Hosts
|
||||||
|
from zenroom import zencode_exec
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode,
|
def test_zenroom_signing(
|
||||||
condition_script_zencode, zenroom_data, zenroom_house_assets):
|
gen_key_zencode,
|
||||||
alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
|
secret_key_to_private_key_zencode,
|
||||||
bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys']
|
fulfill_script_zencode,
|
||||||
|
zenroom_data,
|
||||||
|
zenroom_house_assets,
|
||||||
|
condition_script_zencode,
|
||||||
|
):
|
||||||
|
|
||||||
zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'),
|
biolabs = generate_keypair()
|
||||||
keys={'keys': alice}).output)
|
version = "2.0"
|
||||||
zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'),
|
|
||||||
keys={'keys': bob}).output))
|
|
||||||
|
|
||||||
# CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer
|
alice = json.loads(zencode_exec(gen_key_zencode).output)["keyring"]
|
||||||
zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data)
|
bob = json.loads(zencode_exec(gen_key_zencode).output)["keyring"]
|
||||||
|
|
||||||
|
zen_public_keys = json.loads(
|
||||||
|
ZenroomSha256.run_zenroom(
|
||||||
|
secret_key_to_private_key_zencode.format("Alice"),
|
||||||
|
keys=json.dumps({"keyring": alice}),
|
||||||
|
).output
|
||||||
|
)
|
||||||
|
zen_public_keys.update(
|
||||||
|
json.loads(
|
||||||
|
ZenroomSha256.run_zenroom(
|
||||||
|
secret_key_to_private_key_zencode.format("Bob"),
|
||||||
|
keys=json.dumps({"keyring": bob}),
|
||||||
|
).output
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
zenroomscpt = ZenroomSha256(
|
||||||
|
script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys
|
||||||
|
)
|
||||||
|
print(f"zenroom is: {zenroomscpt.script}")
|
||||||
|
|
||||||
# CRYPTO-CONDITIONS: generate the condition uri
|
# CRYPTO-CONDITIONS: generate the condition uri
|
||||||
condition_uri = zenSha.condition.serialize_uri()
|
condition_uri_zen = zenroomscpt.condition.serialize_uri()
|
||||||
|
print(f"\nzenroom condition URI: {condition_uri_zen}")
|
||||||
|
|
||||||
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
|
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
|
||||||
unsigned_fulfillment_dict = {
|
unsigned_fulfillment_dict_zen = {
|
||||||
'type': zenSha.TYPE_NAME,
|
"type": zenroomscpt.TYPE_NAME,
|
||||||
'script': fulfill_script_zencode,
|
"public_key": base58.b58encode(biolabs.public_key).decode(),
|
||||||
'keys': zen_public_keys,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
'amount': '1000',
|
"amount": "10",
|
||||||
'condition': {
|
"condition": {
|
||||||
'details': unsigned_fulfillment_dict,
|
"details": unsigned_fulfillment_dict_zen,
|
||||||
'uri': condition_uri,
|
"uri": condition_uri_zen,
|
||||||
},
|
},
|
||||||
'data': zenroom_data,
|
"public_keys": [
|
||||||
'script': fulfill_script_zencode,
|
biolabs.public_key,
|
||||||
'conf': '',
|
],
|
||||||
'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input_ = {
|
input_ = {
|
||||||
'fulfillment': None,
|
"fulfillment": None,
|
||||||
'fulfills': None,
|
"fulfills": None,
|
||||||
'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ),
|
"owners_before": [
|
||||||
|
biolabs.public_key,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
metadata = {"result": {"output": ["ok"]}}
|
||||||
token_creation_tx = {
|
token_creation_tx = {
|
||||||
'operation': 'CREATE',
|
"operation": "CREATE",
|
||||||
'asset': zenroom_house_assets,
|
"asset": zenroom_house_assets,
|
||||||
'metadata': None,
|
"metadata": metadata,
|
||||||
'outputs': (output,),
|
"outputs": [
|
||||||
'inputs': (input_,),
|
output,
|
||||||
'version': '2.0',
|
],
|
||||||
'id': None,
|
"inputs": [
|
||||||
|
input_,
|
||||||
|
],
|
||||||
|
"version": version,
|
||||||
|
"id": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
# JSON: serialize the transaction-without-id to a json formatted string
|
# JSON: serialize the transaction-without-id to a json formatted string
|
||||||
message = json.dumps(
|
message = json.dumps(
|
||||||
token_creation_tx,
|
token_creation_tx,
|
||||||
sort_keys=True,
|
sort_keys=True,
|
||||||
separators=(',', ':'),
|
separators=(",", ":"),
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
# major workflow:
|
||||||
assert(not zenSha.validate(message=message))
|
# we store the fulfill script in the transaction/message (zenroom-sha)
|
||||||
except JSONDecodeError:
|
# the condition script is used to fulfill the transaction and create the signature
|
||||||
pass
|
#
|
||||||
except ValueError:
|
# the server should ick the fulfill script and recreate the zenroom-sha and verify the signature
|
||||||
pass
|
|
||||||
|
|
||||||
message = zenSha.sign(message, condition_script_zencode, alice)
|
message = zenroomscpt.sign(message, condition_script_zencode, alice)
|
||||||
assert(zenSha.validate(message=message))
|
assert zenroomscpt.validate(message=message)
|
||||||
|
|
||||||
|
message = json.loads(message)
|
||||||
|
fulfillment_uri_zen = zenroomscpt.serialize_uri()
|
||||||
|
|
||||||
|
message["inputs"][0]["fulfillment"] = fulfillment_uri_zen
|
||||||
|
tx = message
|
||||||
|
tx["id"] = None
|
||||||
|
json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":"))
|
||||||
|
# SHA3: hash the serialized id-less transaction to generate the id
|
||||||
|
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
|
||||||
|
message["id"] = shared_creation_txid
|
||||||
|
|
||||||
|
hosts = Hosts("/shared/hostnames")
|
||||||
|
pm_alpha = hosts.get_connection()
|
||||||
|
|
||||||
|
sent_transfer_tx = pm_alpha.transactions.send_commit(message)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Assert that transaction is stored on both planetmint nodes
|
||||||
|
hosts.assert_transaction(shared_creation_txid)
|
||||||
|
print(f"\n\nstatus and result : + {sent_transfer_tx}")
|
||||||
|
@ -36,26 +36,29 @@ BANNER = """
|
|||||||
|
|
||||||
def start(args):
|
def start(args):
|
||||||
# Exchange object for event stream api
|
# Exchange object for event stream api
|
||||||
logger.info('Starting Planetmint')
|
logger.info("Starting Planetmint")
|
||||||
exchange = Exchange()
|
exchange = Exchange()
|
||||||
# start the web api
|
# start the web api
|
||||||
app_server = server.create_server(
|
app_server = server.create_server(
|
||||||
settings=planetmint.config['server'],
|
settings=planetmint.config["server"],
|
||||||
log_config=planetmint.config['log'],
|
log_config=planetmint.config["log"],
|
||||||
planetmint_factory=Planetmint)
|
planetmint_factory=Planetmint,
|
||||||
p_webapi = Process(name='planetmint_webapi', target=app_server.run, daemon=True)
|
)
|
||||||
|
p_webapi = Process(name="planetmint_webapi", target=app_server.run, daemon=True)
|
||||||
p_webapi.start()
|
p_webapi.start()
|
||||||
|
|
||||||
logger.info(BANNER.format(planetmint.config['server']['bind']))
|
logger.info(BANNER.format(planetmint.config["server"]["bind"]))
|
||||||
|
|
||||||
# start websocket server
|
# start websocket server
|
||||||
p_websocket_server = Process(name='planetmint_ws',
|
p_websocket_server = Process(
|
||||||
|
name="planetmint_ws",
|
||||||
target=websocket_server.start,
|
target=websocket_server.start,
|
||||||
daemon=True,
|
daemon=True,
|
||||||
args=(exchange.get_subscriber_queue(EventTypes.BLOCK_VALID),))
|
args=(exchange.get_subscriber_queue(EventTypes.BLOCK_VALID),),
|
||||||
|
)
|
||||||
p_websocket_server.start()
|
p_websocket_server.start()
|
||||||
|
|
||||||
p_exchange = Process(name='planetmint_exchange', target=exchange.run, daemon=True)
|
p_exchange = Process(name="planetmint_exchange", target=exchange.run, daemon=True)
|
||||||
p_exchange.start()
|
p_exchange.start()
|
||||||
|
|
||||||
# We need to import this after spawning the web server
|
# We need to import this after spawning the web server
|
||||||
@ -63,7 +66,7 @@ def start(args):
|
|||||||
# for gevent.
|
# for gevent.
|
||||||
from abci.server import ABCIServer
|
from abci.server import ABCIServer
|
||||||
|
|
||||||
setproctitle.setproctitle('planetmint')
|
setproctitle.setproctitle("planetmint")
|
||||||
|
|
||||||
# Start the ABCIServer
|
# Start the ABCIServer
|
||||||
# abci = ABCI(TmVersion(planetmint.config['tendermint']['version']))
|
# abci = ABCI(TmVersion(planetmint.config['tendermint']['version']))
|
||||||
@ -82,5 +85,5 @@ def start(args):
|
|||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
start()
|
start()
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256
|
from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
|
||||||
|
from cryptoconditions import Fulfillment
|
||||||
|
|
||||||
from planetmint.transactions.common.exceptions import AmountError
|
from planetmint.transactions.common.exceptions import AmountError
|
||||||
from .utils import _fulfillment_to_details, _fulfillment_from_details
|
from .utils import _fulfillment_to_details, _fulfillment_from_details
|
||||||
@ -24,7 +25,7 @@ class Output(object):
|
|||||||
owners before a Transaction was confirmed.
|
owners before a Transaction was confirmed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MAX_AMOUNT = 9 * 10 ** 18
|
MAX_AMOUNT = 9 * 10**18
|
||||||
|
|
||||||
def __init__(self, fulfillment, public_keys=None, amount=1):
|
def __init__(self, fulfillment, public_keys=None, amount=1):
|
||||||
"""Create an instance of a :class:`~.Output`.
|
"""Create an instance of a :class:`~.Output`.
|
||||||
@ -41,13 +42,13 @@ class Output(object):
|
|||||||
TypeError: if `public_keys` is not instance of `list`.
|
TypeError: if `public_keys` is not instance of `list`.
|
||||||
"""
|
"""
|
||||||
if not isinstance(public_keys, list) and public_keys is not None:
|
if not isinstance(public_keys, list) and public_keys is not None:
|
||||||
raise TypeError('`public_keys` must be a list instance or None')
|
raise TypeError("`public_keys` must be a list instance or None")
|
||||||
if not isinstance(amount, int):
|
if not isinstance(amount, int):
|
||||||
raise TypeError('`amount` must be an int')
|
raise TypeError("`amount` must be an int")
|
||||||
if amount < 1:
|
if amount < 1:
|
||||||
raise AmountError('`amount` must be greater than 0')
|
raise AmountError("`amount` must be greater than 0")
|
||||||
if amount > self.MAX_AMOUNT:
|
if amount > self.MAX_AMOUNT:
|
||||||
raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT)
|
raise AmountError("`amount` must be <= %s" % self.MAX_AMOUNT)
|
||||||
|
|
||||||
self.fulfillment = fulfillment
|
self.fulfillment = fulfillment
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
@ -71,19 +72,20 @@ class Output(object):
|
|||||||
# and fulfillment!
|
# and fulfillment!
|
||||||
condition = {}
|
condition = {}
|
||||||
try:
|
try:
|
||||||
condition['details'] = _fulfillment_to_details(self.fulfillment)
|
# TODO verify if a script is returned in case of zenroom fulfillments
|
||||||
|
condition["details"] = _fulfillment_to_details(self.fulfillment)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
condition['uri'] = self.fulfillment.condition_uri
|
condition["uri"] = self.fulfillment.condition_uri
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
condition['uri'] = self.fulfillment
|
condition["uri"] = self.fulfillment
|
||||||
|
|
||||||
output = {
|
output = {
|
||||||
'public_keys': self.public_keys,
|
"public_keys": self.public_keys,
|
||||||
'condition': condition,
|
"condition": condition,
|
||||||
'amount': str(self.amount),
|
"amount": str(self.amount),
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
@ -113,25 +115,24 @@ class Output(object):
|
|||||||
"""
|
"""
|
||||||
threshold = len(public_keys)
|
threshold = len(public_keys)
|
||||||
if not isinstance(amount, int):
|
if not isinstance(amount, int):
|
||||||
raise TypeError('`amount` must be a int')
|
raise TypeError("`amount` must be a int")
|
||||||
if amount < 1:
|
if amount < 1:
|
||||||
raise AmountError('`amount` needs to be greater than zero')
|
raise AmountError("`amount` needs to be greater than zero")
|
||||||
if not isinstance(public_keys, list):
|
if not isinstance(public_keys, list):
|
||||||
raise TypeError('`public_keys` must be an instance of list')
|
raise TypeError("`public_keys` must be an instance of list")
|
||||||
if len(public_keys) == 0:
|
if len(public_keys) == 0:
|
||||||
raise ValueError('`public_keys` needs to contain at least one'
|
raise ValueError("`public_keys` needs to contain at least one" "owner")
|
||||||
'owner')
|
|
||||||
elif len(public_keys) == 1 and not isinstance(public_keys[0], list):
|
elif len(public_keys) == 1 and not isinstance(public_keys[0], list):
|
||||||
if isinstance(public_keys[0], Fulfillment):
|
if isinstance(public_keys[0], Fulfillment):
|
||||||
ffill = public_keys[0]
|
ffill = public_keys[0]
|
||||||
|
elif isinstance(public_keys[0], ZenroomSha256):
|
||||||
|
ffill = ZenroomSha256(public_key=base58.b58decode(public_keys[0]))
|
||||||
else:
|
else:
|
||||||
ffill = Ed25519Sha256(
|
ffill = Ed25519Sha256(public_key=base58.b58decode(public_keys[0]))
|
||||||
public_key=base58.b58decode(public_keys[0]))
|
|
||||||
return cls(ffill, public_keys, amount=amount)
|
return cls(ffill, public_keys, amount=amount)
|
||||||
else:
|
else:
|
||||||
initial_cond = ThresholdSha256(threshold=threshold)
|
initial_cond = ThresholdSha256(threshold=threshold)
|
||||||
threshold_cond = reduce(cls._gen_condition, public_keys,
|
threshold_cond = reduce(cls._gen_condition, public_keys, initial_cond)
|
||||||
initial_cond)
|
|
||||||
return cls(threshold_cond, public_keys, amount=amount)
|
return cls(threshold_cond, public_keys, amount=amount)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -161,7 +162,7 @@ class Output(object):
|
|||||||
ffill = ThresholdSha256(threshold=threshold)
|
ffill = ThresholdSha256(threshold=threshold)
|
||||||
reduce(cls._gen_condition, new_public_keys, ffill)
|
reduce(cls._gen_condition, new_public_keys, ffill)
|
||||||
elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1:
|
elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1:
|
||||||
raise ValueError('Sublist cannot contain single owner')
|
raise ValueError("Sublist cannot contain single owner")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
new_public_keys = new_public_keys.pop()
|
new_public_keys = new_public_keys.pop()
|
||||||
@ -176,8 +177,7 @@ class Output(object):
|
|||||||
if isinstance(new_public_keys, Fulfillment):
|
if isinstance(new_public_keys, Fulfillment):
|
||||||
ffill = new_public_keys
|
ffill = new_public_keys
|
||||||
else:
|
else:
|
||||||
ffill = Ed25519Sha256(
|
ffill = Ed25519Sha256(public_key=base58.b58decode(new_public_keys))
|
||||||
public_key=base58.b58decode(new_public_keys))
|
|
||||||
initial.add_subfulfillment(ffill)
|
initial.add_subfulfillment(ffill)
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
@ -198,12 +198,12 @@ class Output(object):
|
|||||||
:class:`~planetmint.transactions.common.transaction.Output`
|
:class:`~planetmint.transactions.common.transaction.Output`
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
fulfillment = _fulfillment_from_details(data['condition']['details'])
|
fulfillment = _fulfillment_from_details(data["condition"]["details"])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# NOTE: Hashlock condition case
|
# NOTE: Hashlock condition case
|
||||||
fulfillment = data['condition']['uri']
|
fulfillment = data["condition"]["uri"]
|
||||||
try:
|
try:
|
||||||
amount = int(data['amount'])
|
amount = int(data["amount"])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise AmountError('Invalid amount: %s' % data['amount'])
|
raise AmountError("Invalid amount: %s" % data["amount"])
|
||||||
return cls(fulfillment, data['public_keys'], amount)
|
return cls(fulfillment, data["public_keys"], amount)
|
||||||
|
@ -100,8 +100,8 @@ definitions:
|
|||||||
uri:
|
uri:
|
||||||
type: string
|
type: string
|
||||||
pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\
|
pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\
|
||||||
(fpt=(ed25519|threshold)-sha-256(&)?|cost=[0-9]+(&)?|\
|
(fpt=(ed25519|threshold|zenroom)-sha-256(&)?|cost=[0-9]+(&)?|\
|
||||||
subtypes=ed25519-sha-256(&)?){2,3}$"
|
subtypes=(ed25519|zenroom)-sha-256(&)?){2,3}$"
|
||||||
public_keys:
|
public_keys:
|
||||||
"$ref": "#/definitions/public_keys"
|
"$ref": "#/definitions/public_keys"
|
||||||
input:
|
input:
|
||||||
@ -147,7 +147,7 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
pattern: "^ed25519-sha-256$"
|
pattern: "^(ed25519|zenroom)-sha-256$"
|
||||||
public_key:
|
public_key:
|
||||||
"$ref": "#/definitions/base58"
|
"$ref": "#/definitions/base58"
|
||||||
- type: object
|
- type: object
|
||||||
|
@ -17,9 +17,9 @@ from functools import lru_cache
|
|||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
import base58
|
import base58
|
||||||
from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256
|
from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256, ZenroomSha256
|
||||||
from cryptoconditions.exceptions import (
|
from cryptoconditions.exceptions import ParsingError, ASN1DecodeError, ASN1EncodeError
|
||||||
ParsingError, ASN1DecodeError, ASN1EncodeError)
|
|
||||||
try:
|
try:
|
||||||
from hashlib import sha3_256
|
from hashlib import sha3_256
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -27,8 +27,14 @@ except ImportError:
|
|||||||
|
|
||||||
from planetmint.transactions.common.crypto import PrivateKey, hash_data
|
from planetmint.transactions.common.crypto import PrivateKey, hash_data
|
||||||
from planetmint.transactions.common.exceptions import (
|
from planetmint.transactions.common.exceptions import (
|
||||||
KeypairMismatchException, InputDoesNotExist, DoubleSpend,
|
KeypairMismatchException,
|
||||||
InvalidHash, InvalidSignature, AmountError, AssetIdMismatch)
|
InputDoesNotExist,
|
||||||
|
DoubleSpend,
|
||||||
|
InvalidHash,
|
||||||
|
InvalidSignature,
|
||||||
|
AmountError,
|
||||||
|
AssetIdMismatch,
|
||||||
|
)
|
||||||
from planetmint.transactions.common.utils import serialize
|
from planetmint.transactions.common.utils import serialize
|
||||||
from .memoize import memoize_from_dict, memoize_to_dict
|
from .memoize import memoize_from_dict, memoize_to_dict
|
||||||
from .input import Input
|
from .input import Input
|
||||||
@ -36,15 +42,16 @@ from .output import Output
|
|||||||
from .transaction_link import TransactionLink
|
from .transaction_link import TransactionLink
|
||||||
|
|
||||||
UnspentOutput = namedtuple(
|
UnspentOutput = namedtuple(
|
||||||
'UnspentOutput', (
|
"UnspentOutput",
|
||||||
|
(
|
||||||
# TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode())
|
# TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode())
|
||||||
# 'utxo_hash', # noqa
|
# 'utxo_hash', # noqa
|
||||||
'transaction_id',
|
"transaction_id",
|
||||||
'output_index',
|
"output_index",
|
||||||
'amount',
|
"amount",
|
||||||
'asset_id',
|
"asset_id",
|
||||||
'condition_uri',
|
"condition_uri",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -71,13 +78,22 @@ class Transaction(object):
|
|||||||
version (string): Defines the version number of a Transaction.
|
version (string): Defines the version number of a Transaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CREATE = 'CREATE'
|
CREATE = "CREATE"
|
||||||
TRANSFER = 'TRANSFER'
|
TRANSFER = "TRANSFER"
|
||||||
ALLOWED_OPERATIONS = (CREATE, TRANSFER)
|
ALLOWED_OPERATIONS = (CREATE, TRANSFER)
|
||||||
VERSION = '2.0'
|
VERSION = "2.0"
|
||||||
|
|
||||||
def __init__(self, operation, asset, inputs=None, outputs=None,
|
def __init__(
|
||||||
metadata=None, version=None, hash_id=None, tx_dict=None):
|
self,
|
||||||
|
operation,
|
||||||
|
asset,
|
||||||
|
inputs=None,
|
||||||
|
outputs=None,
|
||||||
|
metadata=None,
|
||||||
|
version=None,
|
||||||
|
hash_id=None,
|
||||||
|
tx_dict=None,
|
||||||
|
):
|
||||||
"""The constructor allows to create a customizable Transaction.
|
"""The constructor allows to create a customizable Transaction.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
@ -98,30 +114,41 @@ class Transaction(object):
|
|||||||
hash_id (string): Hash id of the transaction.
|
hash_id (string): Hash id of the transaction.
|
||||||
"""
|
"""
|
||||||
if operation not in self.ALLOWED_OPERATIONS:
|
if operation not in self.ALLOWED_OPERATIONS:
|
||||||
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
|
allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
|
||||||
raise ValueError('`operation` must be one of {}'
|
raise ValueError("`operation` must be one of {}".format(allowed_ops))
|
||||||
.format(allowed_ops))
|
|
||||||
|
|
||||||
# Asset payloads for 'CREATE' operations must be None or
|
# Asset payloads for 'CREATE' operations must be None or
|
||||||
# dicts holding a `data` property. Asset payloads for 'TRANSFER'
|
# dicts holding a `data` property. Asset payloads for 'TRANSFER'
|
||||||
# operations must be dicts holding an `id` property.
|
# operations must be dicts holding an `id` property.
|
||||||
if (operation == self.CREATE and
|
if (
|
||||||
asset is not None and not (isinstance(asset, dict) and 'data' in asset)):
|
operation == self.CREATE
|
||||||
raise TypeError(('`asset` must be None or a dict holding a `data` '
|
and asset is not None
|
||||||
" property instance for '{}' Transactions".format(operation)))
|
and not (isinstance(asset, dict) and "data" in asset)
|
||||||
elif (operation == self.TRANSFER and
|
):
|
||||||
not (isinstance(asset, dict) and 'id' in asset)):
|
raise TypeError(
|
||||||
raise TypeError(('`asset` must be a dict holding an `id` property '
|
(
|
||||||
'for \'TRANSFER\' Transactions'))
|
"`asset` must be None or a dict holding a `data` "
|
||||||
|
" property instance for '{}' Transactions".format(operation)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif operation == self.TRANSFER and not (
|
||||||
|
isinstance(asset, dict) and "id" in asset
|
||||||
|
):
|
||||||
|
raise TypeError(
|
||||||
|
(
|
||||||
|
"`asset` must be a dict holding an `id` property "
|
||||||
|
"for 'TRANSFER' Transactions"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if outputs and not isinstance(outputs, list):
|
if outputs and not isinstance(outputs, list):
|
||||||
raise TypeError('`outputs` must be a list instance or None')
|
raise TypeError("`outputs` must be a list instance or None")
|
||||||
|
|
||||||
if inputs and not isinstance(inputs, list):
|
if inputs and not isinstance(inputs, list):
|
||||||
raise TypeError('`inputs` must be a list instance or None')
|
raise TypeError("`inputs` must be a list instance or None")
|
||||||
|
|
||||||
if metadata is not None and not isinstance(metadata, dict):
|
if metadata is not None and not isinstance(metadata, dict):
|
||||||
raise TypeError('`metadata` must be a dict or None')
|
raise TypeError("`metadata` must be a dict or None")
|
||||||
|
|
||||||
self.version = version if version is not None else self.VERSION
|
self.version = version if version is not None else self.VERSION
|
||||||
self.operation = operation
|
self.operation = operation
|
||||||
@ -141,14 +168,17 @@ class Transaction(object):
|
|||||||
if self.operation == self.CREATE:
|
if self.operation == self.CREATE:
|
||||||
self._asset_id = self._id
|
self._asset_id = self._id
|
||||||
elif self.operation == self.TRANSFER:
|
elif self.operation == self.TRANSFER:
|
||||||
self._asset_id = self.asset['id']
|
self._asset_id = self.asset["id"]
|
||||||
return (UnspentOutput(
|
return (
|
||||||
|
UnspentOutput(
|
||||||
transaction_id=self._id,
|
transaction_id=self._id,
|
||||||
output_index=output_index,
|
output_index=output_index,
|
||||||
amount=output.amount,
|
amount=output.amount,
|
||||||
asset_id=self._asset_id,
|
asset_id=self._asset_id,
|
||||||
condition_uri=output.fulfillment.condition_uri,
|
condition_uri=output.fulfillment.condition_uri,
|
||||||
) for output_index, output in enumerate(self.outputs))
|
)
|
||||||
|
for output_index, output in enumerate(self.outputs)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spent_outputs(self):
|
def spent_outputs(self):
|
||||||
@ -156,10 +186,7 @@ class Transaction(object):
|
|||||||
is represented as a dictionary containing a transaction id and
|
is represented as a dictionary containing a transaction id and
|
||||||
output index.
|
output index.
|
||||||
"""
|
"""
|
||||||
return (
|
return (input_.fulfills.to_dict() for input_ in self.inputs if input_.fulfills)
|
||||||
input_.fulfills.to_dict()
|
|
||||||
for input_ in self.inputs if input_.fulfills
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serialized(self):
|
def serialized(self):
|
||||||
@ -199,9 +226,11 @@ class Transaction(object):
|
|||||||
# as inputs.
|
# as inputs.
|
||||||
indices = indices or range(len(self.outputs))
|
indices = indices or range(len(self.outputs))
|
||||||
return [
|
return [
|
||||||
Input(self.outputs[idx].fulfillment,
|
Input(
|
||||||
|
self.outputs[idx].fulfillment,
|
||||||
self.outputs[idx].public_keys,
|
self.outputs[idx].public_keys,
|
||||||
TransactionLink(self.id, idx))
|
TransactionLink(self.id, idx),
|
||||||
|
)
|
||||||
for idx in indices
|
for idx in indices
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -213,7 +242,7 @@ class Transaction(object):
|
|||||||
Input`): An Input to be added to the Transaction.
|
Input`): An Input to be added to the Transaction.
|
||||||
"""
|
"""
|
||||||
if not isinstance(input_, Input):
|
if not isinstance(input_, Input):
|
||||||
raise TypeError('`input_` must be a Input instance')
|
raise TypeError("`input_` must be a Input instance")
|
||||||
self.inputs.append(input_)
|
self.inputs.append(input_)
|
||||||
|
|
||||||
def add_output(self, output):
|
def add_output(self, output):
|
||||||
@ -225,7 +254,7 @@ class Transaction(object):
|
|||||||
Transaction.
|
Transaction.
|
||||||
"""
|
"""
|
||||||
if not isinstance(output, Output):
|
if not isinstance(output, Output):
|
||||||
raise TypeError('`output` must be an Output instance or None')
|
raise TypeError("`output` must be an Output instance or None")
|
||||||
self.outputs.append(output)
|
self.outputs.append(output)
|
||||||
|
|
||||||
def sign(self, private_keys):
|
def sign(self, private_keys):
|
||||||
@ -236,6 +265,7 @@ class Transaction(object):
|
|||||||
currently:
|
currently:
|
||||||
- Ed25519Fulfillment
|
- Ed25519Fulfillment
|
||||||
- ThresholdSha256
|
- ThresholdSha256
|
||||||
|
- ZenroomSha256
|
||||||
Furthermore, note that all keys required to fully sign the
|
Furthermore, note that all keys required to fully sign the
|
||||||
Transaction have to be passed to this method. A subset of all
|
Transaction have to be passed to this method. A subset of all
|
||||||
will cause this method to fail.
|
will cause this method to fail.
|
||||||
@ -251,7 +281,7 @@ class Transaction(object):
|
|||||||
# TODO: Singing should be possible with at least one of all private
|
# TODO: Singing should be possible with at least one of all private
|
||||||
# keys supplied to this method.
|
# keys supplied to this method.
|
||||||
if private_keys is None or not isinstance(private_keys, list):
|
if private_keys is None or not isinstance(private_keys, list):
|
||||||
raise TypeError('`private_keys` must be a list instance')
|
raise TypeError("`private_keys` must be a list instance")
|
||||||
|
|
||||||
# NOTE: Generate public keys from private keys and match them in a
|
# NOTE: Generate public keys from private keys and match them in a
|
||||||
# dictionary:
|
# dictionary:
|
||||||
@ -268,8 +298,10 @@ class Transaction(object):
|
|||||||
# to decode to convert the bytestring into a python str
|
# to decode to convert the bytestring into a python str
|
||||||
return public_key.decode()
|
return public_key.decode()
|
||||||
|
|
||||||
key_pairs = {gen_public_key(PrivateKey(private_key)):
|
key_pairs = {
|
||||||
PrivateKey(private_key) for private_key in private_keys}
|
gen_public_key(PrivateKey(private_key)): PrivateKey(private_key)
|
||||||
|
for private_key in private_keys
|
||||||
|
}
|
||||||
|
|
||||||
tx_dict = self.to_dict()
|
tx_dict = self.to_dict()
|
||||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||||
@ -290,7 +322,7 @@ class Transaction(object):
|
|||||||
currently:
|
currently:
|
||||||
- Ed25519Fulfillment
|
- Ed25519Fulfillment
|
||||||
- ThresholdSha256.
|
- ThresholdSha256.
|
||||||
|
- ZenroomSha256
|
||||||
Args:
|
Args:
|
||||||
input_ (:class:`~planetmint.transactions.common.transaction.
|
input_ (:class:`~planetmint.transactions.common.transaction.
|
||||||
Input`) The Input to be signed.
|
Input`) The Input to be signed.
|
||||||
@ -298,15 +330,51 @@ class Transaction(object):
|
|||||||
key_pairs (dict): The keys to sign the Transaction with.
|
key_pairs (dict): The keys to sign the Transaction with.
|
||||||
"""
|
"""
|
||||||
if isinstance(input_.fulfillment, Ed25519Sha256):
|
if isinstance(input_.fulfillment, Ed25519Sha256):
|
||||||
return cls._sign_simple_signature_fulfillment(input_, message,
|
return cls._sign_simple_signature_fulfillment(input_, message, key_pairs)
|
||||||
key_pairs)
|
|
||||||
elif isinstance(input_.fulfillment, ThresholdSha256):
|
elif isinstance(input_.fulfillment, ThresholdSha256):
|
||||||
return cls._sign_threshold_signature_fulfillment(input_, message,
|
return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs)
|
||||||
key_pairs)
|
elif isinstance(input_.fulfillment, ZenroomSha256):
|
||||||
|
return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Fulfillment couldn\'t be matched to '
|
"Fulfillment couldn't be matched to "
|
||||||
'Cryptocondition fulfillment type.')
|
"Cryptocondition fulfillment type."
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _sign_zenroom_fulfillment(cls, input_, message, key_pairs):
|
||||||
|
"""Signs a Zenroomful.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_ (:class:`~planetmint.transactions.common.transaction.
|
||||||
|
Input`) The input to be signed.
|
||||||
|
message (str): The message to be signed
|
||||||
|
key_pairs (dict): The keys to sign the Transaction with.
|
||||||
|
"""
|
||||||
|
# NOTE: To eliminate the dangers of accidentally signing a condition by
|
||||||
|
# reference, we remove the reference of input_ here
|
||||||
|
# intentionally. If the user of this class knows how to use it,
|
||||||
|
# this should never happen, but then again, never say never.
|
||||||
|
input_ = deepcopy(input_)
|
||||||
|
public_key = input_.owners_before[0]
|
||||||
|
message = sha3_256(message.encode())
|
||||||
|
if input_.fulfills:
|
||||||
|
message.update(
|
||||||
|
"{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
|
# message to sign or verify. It only accepts bytestrings
|
||||||
|
input_.fulfillment.sign(
|
||||||
|
message.digest(), base58.b58decode(key_pairs[public_key].encode())
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
raise KeypairMismatchException(
|
||||||
|
"Public key {} is not a pair to "
|
||||||
|
"any of the private keys".format(public_key)
|
||||||
|
)
|
||||||
|
return input_
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs):
|
def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs):
|
||||||
@ -326,18 +394,21 @@ class Transaction(object):
|
|||||||
public_key = input_.owners_before[0]
|
public_key = input_.owners_before[0]
|
||||||
message = sha3_256(message.encode())
|
message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update('{}{}'.format(
|
message.update(
|
||||||
input_.fulfills.txid, input_.fulfills.output).encode())
|
"{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# cryptoconditions makes no assumptions of the encoding of the
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
# message to sign or verify. It only accepts bytestrings
|
# message to sign or verify. It only accepts bytestrings
|
||||||
input_.fulfillment.sign(
|
input_.fulfillment.sign(
|
||||||
message.digest(), base58.b58decode(key_pairs[public_key].encode()))
|
message.digest(), base58.b58decode(key_pairs[public_key].encode())
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeypairMismatchException('Public key {} is not a pair to '
|
raise KeypairMismatchException(
|
||||||
'any of the private keys'
|
"Public key {} is not a pair to "
|
||||||
.format(public_key))
|
"any of the private keys".format(public_key)
|
||||||
|
)
|
||||||
return input_
|
return input_
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -353,8 +424,9 @@ class Transaction(object):
|
|||||||
input_ = deepcopy(input_)
|
input_ = deepcopy(input_)
|
||||||
message = sha3_256(message.encode())
|
message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update('{}{}'.format(
|
message.update(
|
||||||
input_.fulfills.txid, input_.fulfills.output).encode())
|
"{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
|
||||||
|
)
|
||||||
|
|
||||||
for owner_before in set(input_.owners_before):
|
for owner_before in set(input_.owners_before):
|
||||||
# TODO: CC should throw a KeypairMismatchException, instead of
|
# TODO: CC should throw a KeypairMismatchException, instead of
|
||||||
@ -367,24 +439,24 @@ class Transaction(object):
|
|||||||
# TODO FOR CC: `get_subcondition` is singular. One would not
|
# TODO FOR CC: `get_subcondition` is singular. One would not
|
||||||
# expect to get a list back.
|
# expect to get a list back.
|
||||||
ccffill = input_.fulfillment
|
ccffill = input_.fulfillment
|
||||||
subffills = ccffill.get_subcondition_from_vk(
|
subffills = ccffill.get_subcondition_from_vk(base58.b58decode(owner_before))
|
||||||
base58.b58decode(owner_before))
|
|
||||||
if not subffills:
|
if not subffills:
|
||||||
raise KeypairMismatchException('Public key {} cannot be found '
|
raise KeypairMismatchException(
|
||||||
'in the fulfillment'
|
"Public key {} cannot be found "
|
||||||
.format(owner_before))
|
"in the fulfillment".format(owner_before)
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
private_key = key_pairs[owner_before]
|
private_key = key_pairs[owner_before]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeypairMismatchException('Public key {} is not a pair '
|
raise KeypairMismatchException(
|
||||||
'to any of the private keys'
|
"Public key {} is not a pair "
|
||||||
.format(owner_before))
|
"to any of the private keys".format(owner_before)
|
||||||
|
)
|
||||||
|
|
||||||
# cryptoconditions makes no assumptions of the encoding of the
|
# cryptoconditions makes no assumptions of the encoding of the
|
||||||
# message to sign or verify. It only accepts bytestrings
|
# message to sign or verify. It only accepts bytestrings
|
||||||
for subffill in subffills:
|
for subffill in subffills:
|
||||||
subffill.sign(
|
subffill.sign(message.digest(), base58.b58decode(private_key.encode()))
|
||||||
message.digest(), base58.b58decode(private_key.encode()))
|
|
||||||
return input_
|
return input_
|
||||||
|
|
||||||
def inputs_valid(self, outputs=None):
|
def inputs_valid(self, outputs=None):
|
||||||
@ -409,15 +481,14 @@ class Transaction(object):
|
|||||||
# to check for outputs, we're just submitting dummy
|
# to check for outputs, we're just submitting dummy
|
||||||
# values to the actual method. This simplifies it's logic
|
# values to the actual method. This simplifies it's logic
|
||||||
# greatly, as we do not have to check against `None` values.
|
# greatly, as we do not have to check against `None` values.
|
||||||
return self._inputs_valid(['dummyvalue'
|
return self._inputs_valid(["dummyvalue" for _ in self.inputs])
|
||||||
for _ in self.inputs])
|
|
||||||
elif self.operation == self.TRANSFER:
|
elif self.operation == self.TRANSFER:
|
||||||
return self._inputs_valid([output.fulfillment.condition_uri
|
return self._inputs_valid(
|
||||||
for output in outputs])
|
[output.fulfillment.condition_uri for output in outputs]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS)
|
allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS)
|
||||||
raise TypeError('`operation` must be one of {}'
|
raise TypeError("`operation` must be one of {}".format(allowed_ops))
|
||||||
.format(allowed_ops))
|
|
||||||
|
|
||||||
def _inputs_valid(self, output_condition_uris):
|
def _inputs_valid(self, output_condition_uris):
|
||||||
"""Validates an Input against a given set of Outputs.
|
"""Validates an Input against a given set of Outputs.
|
||||||
@ -435,21 +506,22 @@ class Transaction(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self.inputs) != len(output_condition_uris):
|
if len(self.inputs) != len(output_condition_uris):
|
||||||
raise ValueError('Inputs and '
|
raise ValueError(
|
||||||
'output_condition_uris must have the same count')
|
"Inputs and " "output_condition_uris must have the same count"
|
||||||
|
)
|
||||||
|
|
||||||
tx_dict = self.tx_dict if self.tx_dict else self.to_dict()
|
tx_dict = self.tx_dict if self.tx_dict else self.to_dict()
|
||||||
tx_dict = Transaction._remove_signatures(tx_dict)
|
tx_dict = Transaction._remove_signatures(tx_dict)
|
||||||
tx_dict['id'] = None
|
tx_dict["id"] = None
|
||||||
tx_serialized = Transaction._to_str(tx_dict)
|
tx_serialized = Transaction._to_str(tx_dict)
|
||||||
|
|
||||||
def validate(i, output_condition_uri=None):
|
def validate(i, output_condition_uri=None):
|
||||||
"""Validate input against output condition URI"""
|
"""Validate input against output condition URI"""
|
||||||
return self._input_valid(self.inputs[i], self.operation,
|
return self._input_valid(
|
||||||
tx_serialized, output_condition_uri)
|
self.inputs[i], self.operation, tx_serialized, output_condition_uri
|
||||||
|
)
|
||||||
|
|
||||||
return all(validate(i, cond)
|
return all(validate(i, cond) for i, cond in enumerate(output_condition_uris))
|
||||||
for i, cond in enumerate(output_condition_uris))
|
|
||||||
|
|
||||||
@lru_cache(maxsize=16384)
|
@lru_cache(maxsize=16384)
|
||||||
def _input_valid(self, input_, operation, message, output_condition_uri=None):
|
def _input_valid(self, input_, operation, message, output_condition_uri=None):
|
||||||
@ -473,8 +545,20 @@ class Transaction(object):
|
|||||||
ccffill = input_.fulfillment
|
ccffill = input_.fulfillment
|
||||||
try:
|
try:
|
||||||
parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri())
|
parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri())
|
||||||
except (TypeError, ValueError,
|
except TypeError as e:
|
||||||
ParsingError, ASN1DecodeError, ASN1EncodeError):
|
print(f"Exception TypeError : {e}")
|
||||||
|
return False
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Exception ValueError : {e}")
|
||||||
|
return False
|
||||||
|
except ParsingError as e:
|
||||||
|
print(f"Exception ParsingError : {e}")
|
||||||
|
return False
|
||||||
|
except ASN1DecodeError as e:
|
||||||
|
print(f"Exception ASN1DecodeError : {e}")
|
||||||
|
return False
|
||||||
|
except ASN1EncodeError as e:
|
||||||
|
print(f"Exception ASN1EncodeError : {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if operation == self.CREATE:
|
if operation == self.CREATE:
|
||||||
@ -484,10 +568,15 @@ class Transaction(object):
|
|||||||
else:
|
else:
|
||||||
output_valid = output_condition_uri == ccffill.condition_uri
|
output_valid = output_condition_uri == ccffill.condition_uri
|
||||||
|
|
||||||
|
ffill_valid = False
|
||||||
|
if isinstance(parsed_ffill, ZenroomSha256):
|
||||||
|
ffill_valid = parsed_ffill.validate(message=message)
|
||||||
|
else:
|
||||||
message = sha3_256(message.encode())
|
message = sha3_256(message.encode())
|
||||||
if input_.fulfills:
|
if input_.fulfills:
|
||||||
message.update('{}{}'.format(
|
message.update(
|
||||||
input_.fulfills.txid, input_.fulfills.output).encode())
|
"{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode()
|
||||||
|
)
|
||||||
|
|
||||||
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout
|
# NOTE: We pass a timestamp to `.validate`, as in case of a timeout
|
||||||
# condition we'll have to validate against it
|
# condition we'll have to validate against it
|
||||||
@ -509,13 +598,13 @@ class Transaction(object):
|
|||||||
dict: The Transaction as an alternative serialization format.
|
dict: The Transaction as an alternative serialization format.
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
'inputs': [input_.to_dict() for input_ in self.inputs],
|
"inputs": [input_.to_dict() for input_ in self.inputs],
|
||||||
'outputs': [output.to_dict() for output in self.outputs],
|
"outputs": [output.to_dict() for output in self.outputs],
|
||||||
'operation': str(self.operation),
|
"operation": str(self.operation),
|
||||||
'metadata': self.metadata,
|
"metadata": self.metadata,
|
||||||
'asset': self.asset,
|
"asset": self.asset,
|
||||||
'version': self.version,
|
"version": self.version,
|
||||||
'id': self._id,
|
"id": self._id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -533,12 +622,12 @@ class Transaction(object):
|
|||||||
# NOTE: We remove the reference since we need `tx_dict` only for the
|
# NOTE: We remove the reference since we need `tx_dict` only for the
|
||||||
# transaction's hash
|
# transaction's hash
|
||||||
tx_dict = deepcopy(tx_dict)
|
tx_dict = deepcopy(tx_dict)
|
||||||
for input_ in tx_dict['inputs']:
|
for input_ in tx_dict["inputs"]:
|
||||||
# NOTE: Not all Cryptoconditions return a `signature` key (e.g.
|
# NOTE: Not all Cryptoconditions return a `signature` key (e.g.
|
||||||
# ThresholdSha256), so setting it to `None` in any
|
# ThresholdSha256), so setting it to `None` in any
|
||||||
# case could yield incorrect signatures. This is why we only
|
# case could yield incorrect signatures. This is why we only
|
||||||
# set it to `None` if it's set in the dict.
|
# set it to `None` if it's set in the dict.
|
||||||
input_['fulfillment'] = None
|
input_["fulfillment"] = None
|
||||||
return tx_dict
|
return tx_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -550,7 +639,7 @@ class Transaction(object):
|
|||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
def to_hash(self):
|
def to_hash(self):
|
||||||
return self.to_dict()['id']
|
return self.to_dict()["id"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_str(value):
|
def _to_str(value):
|
||||||
@ -586,14 +675,19 @@ class Transaction(object):
|
|||||||
transactions = [transactions]
|
transactions = [transactions]
|
||||||
|
|
||||||
# create a set of the transactions' asset ids
|
# create a set of the transactions' asset ids
|
||||||
asset_ids = {tx.id if tx.operation == tx.CREATE
|
asset_ids = {
|
||||||
else tx.asset['id']
|
tx.id if tx.operation == tx.CREATE else tx.asset["id"]
|
||||||
for tx in transactions}
|
for tx in transactions
|
||||||
|
}
|
||||||
|
|
||||||
# check that all the transasctions have the same asset id
|
# check that all the transasctions have the same asset id
|
||||||
if len(asset_ids) > 1:
|
if len(asset_ids) > 1:
|
||||||
raise AssetIdMismatch(('All inputs of all transactions passed'
|
raise AssetIdMismatch(
|
||||||
' need to have the same asset id'))
|
(
|
||||||
|
"All inputs of all transactions passed"
|
||||||
|
" need to have the same asset id"
|
||||||
|
)
|
||||||
|
)
|
||||||
return asset_ids.pop()
|
return asset_ids.pop()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -608,18 +702,20 @@ class Transaction(object):
|
|||||||
tx_body = rapidjson.loads(rapidjson.dumps(tx_body))
|
tx_body = rapidjson.loads(rapidjson.dumps(tx_body))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proposed_tx_id = tx_body['id']
|
proposed_tx_id = tx_body["id"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidHash('No transaction id found!')
|
raise InvalidHash("No transaction id found!")
|
||||||
|
|
||||||
tx_body['id'] = None
|
tx_body["id"] = None
|
||||||
|
|
||||||
tx_body_serialized = Transaction._to_str(tx_body)
|
tx_body_serialized = Transaction._to_str(tx_body)
|
||||||
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
valid_tx_id = Transaction._to_hash(tx_body_serialized)
|
||||||
|
|
||||||
if proposed_tx_id != valid_tx_id:
|
if proposed_tx_id != valid_tx_id:
|
||||||
err_msg = ("The transaction's id '{}' isn't equal to "
|
err_msg = (
|
||||||
"the hash of its body, i.e. it's not valid.")
|
"The transaction's id '{}' isn't equal to "
|
||||||
|
"the hash of its body, i.e. it's not valid."
|
||||||
|
)
|
||||||
raise InvalidHash(err_msg.format(proposed_tx_id))
|
raise InvalidHash(err_msg.format(proposed_tx_id))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -633,17 +729,29 @@ class Transaction(object):
|
|||||||
Returns:
|
Returns:
|
||||||
:class:`~planetmint.transactions.common.transaction.Transaction`
|
:class:`~planetmint.transactions.common.transaction.Transaction`
|
||||||
"""
|
"""
|
||||||
operation = tx.get('operation', Transaction.CREATE) if isinstance(tx, dict) else Transaction.CREATE
|
operation = (
|
||||||
|
tx.get("operation", Transaction.CREATE)
|
||||||
|
if isinstance(tx, dict)
|
||||||
|
else Transaction.CREATE
|
||||||
|
)
|
||||||
cls = Transaction.resolve_class(operation)
|
cls = Transaction.resolve_class(operation)
|
||||||
|
|
||||||
if not skip_schema_validation:
|
if not skip_schema_validation:
|
||||||
cls.validate_id(tx)
|
cls.validate_id(tx)
|
||||||
cls.validate_schema(tx)
|
cls.validate_schema(tx)
|
||||||
|
|
||||||
inputs = [Input.from_dict(input_) for input_ in tx['inputs']]
|
inputs = [Input.from_dict(input_) for input_ in tx["inputs"]]
|
||||||
outputs = [Output.from_dict(output) for output in tx['outputs']]
|
outputs = [Output.from_dict(output) for output in tx["outputs"]]
|
||||||
return cls(tx['operation'], tx['asset'], inputs, outputs,
|
return cls(
|
||||||
tx['metadata'], tx['version'], hash_id=tx['id'], tx_dict=tx)
|
tx["operation"],
|
||||||
|
tx["asset"],
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
tx["metadata"],
|
||||||
|
tx["version"],
|
||||||
|
hash_id=tx["id"],
|
||||||
|
tx_dict=tx,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_db(cls, planet, tx_dict_list):
|
def from_db(cls, planet, tx_dict_list):
|
||||||
@ -669,22 +777,22 @@ class Transaction(object):
|
|||||||
tx_map = {}
|
tx_map = {}
|
||||||
tx_ids = []
|
tx_ids = []
|
||||||
for tx in tx_dict_list:
|
for tx in tx_dict_list:
|
||||||
tx.update({'metadata': None})
|
tx.update({"metadata": None})
|
||||||
tx_map[tx['id']] = tx
|
tx_map[tx["id"]] = tx
|
||||||
tx_ids.append(tx['id'])
|
tx_ids.append(tx["id"])
|
||||||
|
|
||||||
assets = list(planet.get_assets(tx_ids))
|
assets = list(planet.get_assets(tx_ids))
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
if asset is not None:
|
if asset is not None:
|
||||||
tx = tx_map[asset['id']]
|
tx = tx_map[asset["id"]]
|
||||||
del asset['id']
|
del asset["id"]
|
||||||
tx['asset'] = asset
|
tx["asset"] = asset
|
||||||
|
|
||||||
tx_ids = list(tx_map.keys())
|
tx_ids = list(tx_map.keys())
|
||||||
metadata_list = list(planet.get_metadata(tx_ids))
|
metadata_list = list(planet.get_metadata(tx_ids))
|
||||||
for metadata in metadata_list:
|
for metadata in metadata_list:
|
||||||
tx = tx_map[metadata['id']]
|
tx = tx_map[metadata["id"]]
|
||||||
tx.update({'metadata': metadata.get('metadata')})
|
tx.update({"metadata": metadata.get("metadata")})
|
||||||
|
|
||||||
if return_list:
|
if return_list:
|
||||||
tx_list = []
|
tx_list = []
|
||||||
@ -725,14 +833,13 @@ class Transaction(object):
|
|||||||
input_tx = ctxn
|
input_tx = ctxn
|
||||||
|
|
||||||
if input_tx is None:
|
if input_tx is None:
|
||||||
raise InputDoesNotExist("input `{}` doesn't exist"
|
raise InputDoesNotExist("input `{}` doesn't exist".format(input_txid))
|
||||||
.format(input_txid))
|
|
||||||
|
|
||||||
spent = planet.get_spent(input_txid, input_.fulfills.output,
|
spent = planet.get_spent(
|
||||||
current_transactions)
|
input_txid, input_.fulfills.output, current_transactions
|
||||||
|
)
|
||||||
if spent:
|
if spent:
|
||||||
raise DoubleSpend('input `{}` was already spent'
|
raise DoubleSpend("input `{}` was already spent".format(input_txid))
|
||||||
.format(input_txid))
|
|
||||||
|
|
||||||
output = input_tx.outputs[input_.fulfills.output]
|
output = input_tx.outputs[input_.fulfills.output]
|
||||||
input_conditions.append(output)
|
input_conditions.append(output)
|
||||||
@ -745,21 +852,32 @@ class Transaction(object):
|
|||||||
|
|
||||||
# validate asset id
|
# validate asset id
|
||||||
asset_id = self.get_asset_id(input_txs)
|
asset_id = self.get_asset_id(input_txs)
|
||||||
if asset_id != self.asset['id']:
|
if asset_id != self.asset["id"]:
|
||||||
raise AssetIdMismatch(('The asset id of the input does not'
|
raise AssetIdMismatch(
|
||||||
' match the asset id of the'
|
(
|
||||||
' transaction'))
|
"The asset id of the input does not"
|
||||||
|
" match the asset id of the"
|
||||||
|
" transaction"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
input_amount = sum([input_condition.amount for input_condition in input_conditions])
|
input_amount = sum(
|
||||||
output_amount = sum([output_condition.amount for output_condition in self.outputs])
|
[input_condition.amount for input_condition in input_conditions]
|
||||||
|
)
|
||||||
|
output_amount = sum(
|
||||||
|
[output_condition.amount for output_condition in self.outputs]
|
||||||
|
)
|
||||||
|
|
||||||
if output_amount != input_amount:
|
if output_amount != input_amount:
|
||||||
raise AmountError(('The amount used in the inputs `{}`'
|
raise AmountError(
|
||||||
' needs to be same as the amount used'
|
(
|
||||||
' in the outputs `{}`')
|
"The amount used in the inputs `{}`"
|
||||||
.format(input_amount, output_amount))
|
" needs to be same as the amount used"
|
||||||
|
" in the outputs `{}`"
|
||||||
|
).format(input_amount, output_amount)
|
||||||
|
)
|
||||||
|
|
||||||
if not self.inputs_valid(input_conditions):
|
if not self.inputs_valid(input_conditions):
|
||||||
raise InvalidSignature('Transaction signature is invalid.')
|
raise InvalidSignature("Transaction signature is invalid.")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -10,7 +10,7 @@ import rapidjson
|
|||||||
|
|
||||||
import planetmint
|
import planetmint
|
||||||
from planetmint.transactions.common.exceptions import ValidationError
|
from planetmint.transactions.common.exceptions import ValidationError
|
||||||
from cryptoconditions import ThresholdSha256, Ed25519Sha256
|
from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256
|
||||||
from planetmint.transactions.common.exceptions import ThresholdTooDeep
|
from planetmint.transactions.common.exceptions import ThresholdTooDeep
|
||||||
from cryptoconditions.exceptions import UnsupportedTypeError
|
from cryptoconditions.exceptions import UnsupportedTypeError
|
||||||
|
|
||||||
@ -43,8 +43,7 @@ def serialize(data):
|
|||||||
str: JSON formatted string
|
str: JSON formatted string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False,
|
return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True)
|
||||||
sort_keys=True)
|
|
||||||
|
|
||||||
|
|
||||||
def deserialize(data):
|
def deserialize(data):
|
||||||
@ -76,9 +75,9 @@ def validate_txn_obj(obj_name, obj, key, validation_fun):
|
|||||||
Raises:
|
Raises:
|
||||||
ValidationError: `validation_fun` will raise exception on failure
|
ValidationError: `validation_fun` will raise exception on failure
|
||||||
"""
|
"""
|
||||||
backend = planetmint.config['database']['backend']
|
backend = planetmint.config["database"]["backend"]
|
||||||
|
|
||||||
if backend == 'localmongodb':
|
if backend == "localmongodb":
|
||||||
data = obj.get(key, {})
|
data = obj.get(key, {})
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
validate_all_keys_in_obj(obj_name, data, validation_fun)
|
validate_all_keys_in_obj(obj_name, data, validation_fun)
|
||||||
@ -162,10 +161,12 @@ def validate_key(obj_name, key):
|
|||||||
Raises:
|
Raises:
|
||||||
ValidationError: will raise exception in case of regex match.
|
ValidationError: will raise exception in case of regex match.
|
||||||
"""
|
"""
|
||||||
if re.search(r'^[$]|\.|\x00', key):
|
if re.search(r"^[$]|\.|\x00", key):
|
||||||
error_str = ('Invalid key name "{}" in {} object. The '
|
error_str = (
|
||||||
'key name cannot contain characters '
|
'Invalid key name "{}" in {} object. The '
|
||||||
'".", "$" or null characters').format(key, obj_name)
|
"key name cannot contain characters "
|
||||||
|
'".", "$" or null characters'
|
||||||
|
).format(key, obj_name)
|
||||||
raise ValidationError(error_str)
|
raise ValidationError(error_str)
|
||||||
|
|
||||||
|
|
||||||
@ -176,21 +177,26 @@ def _fulfillment_to_details(fulfillment):
|
|||||||
fulfillment: Crypto-conditions Fulfillment object
|
fulfillment: Crypto-conditions Fulfillment object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if fulfillment.type_name == 'ed25519-sha-256':
|
if fulfillment.type_name == "ed25519-sha-256":
|
||||||
return {
|
return {
|
||||||
'type': 'ed25519-sha-256',
|
"type": "ed25519-sha-256",
|
||||||
'public_key': base58.b58encode(fulfillment.public_key).decode(),
|
"public_key": base58.b58encode(fulfillment.public_key).decode(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if fulfillment.type_name == 'threshold-sha-256':
|
if fulfillment.type_name == "threshold-sha-256":
|
||||||
subconditions = [
|
subconditions = [
|
||||||
_fulfillment_to_details(cond['body'])
|
_fulfillment_to_details(cond["body"]) for cond in fulfillment.subconditions
|
||||||
for cond in fulfillment.subconditions
|
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
'type': 'threshold-sha-256',
|
"type": "threshold-sha-256",
|
||||||
'threshold': fulfillment.threshold,
|
"threshold": fulfillment.threshold,
|
||||||
'subconditions': subconditions,
|
"subconditions": subconditions,
|
||||||
|
}
|
||||||
|
if fulfillment.type_name == "zenroom-sha-256":
|
||||||
|
return {
|
||||||
|
"type": "zenroom-sha-256",
|
||||||
|
"public_key": base58.b58encode(fulfillment.public_key).decode(),
|
||||||
|
"script": base58.b58encode(fulfillment.script).decode(),
|
||||||
}
|
}
|
||||||
|
|
||||||
raise UnsupportedTypeError(fulfillment.type_name)
|
raise UnsupportedTypeError(fulfillment.type_name)
|
||||||
@ -205,15 +211,22 @@ def _fulfillment_from_details(data, _depth=0):
|
|||||||
if _depth == 100:
|
if _depth == 100:
|
||||||
raise ThresholdTooDeep()
|
raise ThresholdTooDeep()
|
||||||
|
|
||||||
if data['type'] == 'ed25519-sha-256':
|
if data["type"] == "ed25519-sha-256":
|
||||||
public_key = base58.b58decode(data['public_key'])
|
public_key = base58.b58decode(data["public_key"])
|
||||||
return Ed25519Sha256(public_key=public_key)
|
return Ed25519Sha256(public_key=public_key)
|
||||||
|
|
||||||
if data['type'] == 'threshold-sha-256':
|
if data["type"] == "threshold-sha-256":
|
||||||
threshold = ThresholdSha256(data['threshold'])
|
threshold = ThresholdSha256(data["threshold"])
|
||||||
for cond in data['subconditions']:
|
for cond in data["subconditions"]:
|
||||||
cond = _fulfillment_from_details(cond, _depth + 1)
|
cond = _fulfillment_from_details(cond, _depth + 1)
|
||||||
threshold.add_subfulfillment(cond)
|
threshold.add_subfulfillment(cond)
|
||||||
return threshold
|
return threshold
|
||||||
|
|
||||||
raise UnsupportedTypeError(data.get('type'))
|
if data["type"] == "zenroom-sha-256":
|
||||||
|
public_key = base58.b58decode(data["public_key"])
|
||||||
|
script = base58.b58decode(data["script"])
|
||||||
|
# zenroom = ZenroomSha256(script=script, data=None, keys={public_key})
|
||||||
|
# TODO: assign to zenroom and evaluate the outcome
|
||||||
|
ZenroomSha256(script=script, data=None, keys={public_key})
|
||||||
|
|
||||||
|
raise UnsupportedTypeError(data.get("type"))
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
|
||||||
# Code is Apache-2.0 and docs are CC-BY-4.0
|
# Code is Apache-2.0 and docs are CC-BY-4.0
|
||||||
|
|
||||||
__version__ = '0.9.3'
|
__version__ = '0.9.7'
|
||||||
__short_version__ = '0.9'
|
__short_version__ = '0.9'
|
||||||
|
|
||||||
# Supported Tendermint versions
|
# Supported Tendermint versions
|
||||||
|
209
setup.py
209
setup.py
@ -14,134 +14,167 @@ import sys
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
if sys.version_info < (3, 9):
|
if sys.version_info < (3, 9):
|
||||||
sys.exit('Please use Python version 3.9 or higher.')
|
sys.exit("Please use Python version 3.9 or higher.")
|
||||||
|
|
||||||
with open('README.md') as readme_file:
|
with open("README.md") as readme_file:
|
||||||
readme = readme_file.read()
|
readme = readme_file.read()
|
||||||
|
|
||||||
# get the version
|
# get the version
|
||||||
version = {}
|
version = {}
|
||||||
with open('planetmint/version.py') as fp:
|
with open("planetmint/version.py") as fp:
|
||||||
exec(fp.read(), version)
|
exec(fp.read(), version)
|
||||||
|
|
||||||
|
|
||||||
def check_setuptools_features():
|
def check_setuptools_features():
|
||||||
"""Check if setuptools is up to date."""
|
"""Check if setuptools is up to date."""
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
try:
|
try:
|
||||||
list(pkg_resources.parse_requirements('foo~=1.0'))
|
list(pkg_resources.parse_requirements("foo~=1.0"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
sys.exit('Your Python distribution comes with an incompatible version '
|
sys.exit(
|
||||||
'of `setuptools`. Please run:\n'
|
"Your Python distribution comes with an incompatible version "
|
||||||
' $ pip3 install --upgrade setuptools\n'
|
"of `setuptools`. Please run:\n"
|
||||||
'and then run this command again')
|
" $ pip3 install --upgrade setuptools\n"
|
||||||
|
"and then run this command again"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
with pathlib.Path('docs/root/requirements.txt').open() as requirements_txt:
|
docs_require = [
|
||||||
docs_require= [
|
"aafigure==0.6",
|
||||||
str(requirement)
|
"alabaster==0.7.12",
|
||||||
for requirement
|
"Babel==2.10.1",
|
||||||
in pkg_resources.parse_requirements(requirements_txt)
|
"certifi==2021.10.8",
|
||||||
]
|
"charset-normalizer==2.0.12",
|
||||||
|
"commonmark==0.9.1",
|
||||||
|
"docutils==0.17.1",
|
||||||
|
"idna",
|
||||||
|
"imagesize==1.3.0",
|
||||||
|
"importlib-metadata==4.11.3",
|
||||||
|
"Jinja2==3.0.0",
|
||||||
|
"markdown-it-py==2.1.0",
|
||||||
|
"MarkupSafe==2.1.1",
|
||||||
|
"mdit-py-plugins==0.3.0",
|
||||||
|
"mdurl==0.1.1",
|
||||||
|
"myst-parser==0.17.2",
|
||||||
|
"packaging==21.3",
|
||||||
|
"pockets==0.9.1",
|
||||||
|
"Pygments==2.12.0",
|
||||||
|
"pyparsing==3.0.8",
|
||||||
|
"pytz==2022.1",
|
||||||
|
"PyYAML>=5.4.0",
|
||||||
|
"requests>=2.25i.1",
|
||||||
|
"six==1.16.0",
|
||||||
|
"snowballstemmer==2.2.0",
|
||||||
|
"Sphinx==4.5.0",
|
||||||
|
"sphinx-rtd-theme==1.0.0",
|
||||||
|
"sphinxcontrib-applehelp==1.0.2",
|
||||||
|
"sphinxcontrib-devhelp==1.0.2",
|
||||||
|
"sphinxcontrib-htmlhelp==2.0.0",
|
||||||
|
"sphinxcontrib-httpdomain==1.8.0",
|
||||||
|
"sphinxcontrib-jsmath==1.0.1",
|
||||||
|
"sphinxcontrib-napoleon==0.7",
|
||||||
|
"sphinxcontrib-qthelp==1.0.3",
|
||||||
|
"sphinxcontrib-serializinghtml==1.1.5",
|
||||||
|
"urllib3==1.26.9",
|
||||||
|
"wget==3.2",
|
||||||
|
"zipp==3.8.0",
|
||||||
|
"nest-asyncio==1.5.5",
|
||||||
|
"sphinx-press-theme==0.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
check_setuptools_features()
|
check_setuptools_features()
|
||||||
|
|
||||||
dev_require = [
|
dev_require = ["ipdb", "ipython", "watchdog", "logging_tree", "pre-commit", "twine"]
|
||||||
'ipdb',
|
|
||||||
'ipython',
|
|
||||||
'watchdog',
|
|
||||||
'logging_tree',
|
|
||||||
'pre-commit',
|
|
||||||
'twine'
|
|
||||||
]
|
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
'coverage',
|
"coverage",
|
||||||
'pep8',
|
"pep8",
|
||||||
'flake8',
|
"flake8",
|
||||||
'flake8-quotes==0.8.1',
|
"flake8-quotes==0.8.1",
|
||||||
'hypothesis>=5.3.0',
|
"hypothesis>=5.3.0",
|
||||||
'pytest>=3.0.0',
|
"pytest>=3.0.0",
|
||||||
'pytest-cov==2.8.1',
|
"pytest-cov==2.8.1",
|
||||||
'pytest-mock',
|
"pytest-mock",
|
||||||
'pytest-xdist',
|
"pytest-xdist",
|
||||||
'pytest-flask',
|
"pytest-flask",
|
||||||
'pytest-aiohttp',
|
"pytest-aiohttp",
|
||||||
'pytest-asyncio',
|
"pytest-asyncio",
|
||||||
'tox',
|
"tox",
|
||||||
] + docs_require
|
] + docs_require
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
'chardet==3.0.4',
|
"chardet==3.0.4",
|
||||||
'aiohttp==3.8.1',
|
"aiohttp==3.8.1",
|
||||||
'abci==0.8.3',
|
"abci==0.8.3",
|
||||||
'planetmint-cryptoconditions>=0.9.4',
|
"planetmint-cryptoconditions>=0.9.9",
|
||||||
'flask-cors==3.0.10',
|
"flask-cors==3.0.10",
|
||||||
'flask-restful==0.3.9',
|
"flask-restful==0.3.9",
|
||||||
'flask==2.0.1',
|
"flask==2.0.1",
|
||||||
'gunicorn==20.1.0',
|
"gunicorn==20.1.0",
|
||||||
'jsonschema==3.2.0',
|
"jsonschema==3.2.0",
|
||||||
'logstats==0.3.0',
|
"logstats==0.3.0",
|
||||||
'packaging>=20.9',
|
"packaging>=20.9",
|
||||||
# TODO Consider not installing the db drivers, or putting them in extras.
|
# TODO Consider not installing the db drivers, or putting them in extras.
|
||||||
'pymongo==3.11.4',
|
"protobuf==3.20.1",
|
||||||
'python-rapidjson==1.0',
|
"pymongo==3.11.4",
|
||||||
'pyyaml==5.4.1',
|
"python-rapidjson==1.0",
|
||||||
'requests==2.25.1',
|
"pyyaml==5.4.1",
|
||||||
'setproctitle==1.2.2',
|
"requests>=2.25.1",
|
||||||
'werkzeug==2.0.3',
|
"setproctitle==1.2.2",
|
||||||
'nest-asyncio==1.5.5',
|
"werkzeug==2.0.3",
|
||||||
'protobuf==3.20.1'
|
"nest-asyncio==1.5.5",
|
||||||
|
"protobuf==3.20.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
if sys.version_info < (3, 6):
|
||||||
install_requires.append('pysha3~=1.0.2')
|
install_requires.append("pysha3~=1.0.2")
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Planetmint',
|
name="Planetmint",
|
||||||
version=version['__version__'],
|
version=version["__version__"],
|
||||||
description='Planetmint: The Blockchain Database',
|
description="Planetmint: The Blockchain Database",
|
||||||
long_description=readme,
|
long_description=readme,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type="text/markdown",
|
||||||
url='https://github.com/Planetmint/planetmint/',
|
url="https://github.com/Planetmint/planetmint/",
|
||||||
author='Planetmint Contributors',
|
author="Planetmint Contributors",
|
||||||
author_email='contact@ipdb.global',
|
author_email="contact@ipdb.global",
|
||||||
license='AGPLv3',
|
license="AGPLv3",
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
python_requires='>=3.9',
|
python_requires=">=3.9",
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
"Development Status :: 4 - Beta",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'Topic :: Database',
|
"Topic :: Database",
|
||||||
'Topic :: Database :: Database Engines/Servers',
|
"Topic :: Database :: Database Engines/Servers",
|
||||||
'Topic :: Software Development',
|
"Topic :: Software Development",
|
||||||
'Natural Language :: English',
|
"Natural Language :: English",
|
||||||
'License :: OSI Approved :: Apache Software License',
|
"License :: OSI Approved :: Apache Software License",
|
||||||
'Programming Language :: Python :: 3.9',
|
"Programming Language :: Python :: 3.9",
|
||||||
'Operating System :: MacOS :: MacOS X',
|
"Operating System :: MacOS :: MacOS X",
|
||||||
'Operating System :: POSIX :: Linux',
|
"Operating System :: POSIX :: Linux",
|
||||||
],
|
],
|
||||||
|
packages=find_packages(exclude=["tests*"]),
|
||||||
packages=find_packages(exclude=['tests*']),
|
scripts=["pkg/scripts/planetmint-monit-config"],
|
||||||
|
|
||||||
scripts=['pkg/scripts/planetmint-monit-config'],
|
|
||||||
|
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
"console_scripts": ["planetmint=planetmint.commands.planetmint:main"],
|
||||||
'planetmint=planetmint.commands.planetmint:main'
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
setup_requires=['pytest-runner'],
|
setup_requires=["pytest-runner"],
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
extras_require={
|
extras_require={
|
||||||
'test': tests_require,
|
"test": tests_require,
|
||||||
'dev': dev_require + tests_require + docs_require,
|
"dev": dev_require + tests_require + docs_require,
|
||||||
'docs': docs_require,
|
"docs": docs_require,
|
||||||
},
|
},
|
||||||
package_data={
|
package_data={
|
||||||
'planetmint.transactions.common.schema': ['v1.0/*.yaml','v2.0/*.yaml','v3.0/*.yaml' ],
|
"planetmint.transactions.common.schema": [
|
||||||
|
"v1.0/*.yaml",
|
||||||
|
"v2.0/*.yaml",
|
||||||
|
"v3.0/*.yaml",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
172
tests/assets/test_zenroom_signing.py
Normal file
172
tests/assets/test_zenroom_signing.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
import base58
|
||||||
|
from hashlib import sha3_256
|
||||||
|
from zenroom import zencode_exec
|
||||||
|
from cryptoconditions.types.ed25519 import Ed25519Sha256
|
||||||
|
from cryptoconditions.types.zenroom import ZenroomSha256
|
||||||
|
from planetmint.transactions.common.crypto import generate_key_pair
|
||||||
|
|
||||||
|
CONDITION_SCRIPT = """
|
||||||
|
Scenario 'ecdh': create the signature of an object
|
||||||
|
Given I have the 'keyring'
|
||||||
|
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
||||||
|
When I create the signature of 'houses'
|
||||||
|
Then print the 'signature'"""
|
||||||
|
|
||||||
|
FULFILL_SCRIPT = """Scenario 'ecdh': Bob verifies the signature from Alice
|
||||||
|
Given I have a 'ecdh public key' from 'Alice'
|
||||||
|
Given that I have a 'string dictionary' named 'houses' inside 'asset'
|
||||||
|
Given I have a 'signature' named 'signature' inside 'result'
|
||||||
|
When I verify the 'houses' has a signature in 'signature' by 'Alice'
|
||||||
|
Then print the string 'ok'"""
|
||||||
|
|
||||||
|
SK_TO_PK = """Scenario 'ecdh': Create the keypair
|
||||||
|
Given that I am known as '{}'
|
||||||
|
Given I have the 'keyring'
|
||||||
|
When I create the ecdh public key
|
||||||
|
When I create the bitcoin address
|
||||||
|
Then print my 'ecdh public key'
|
||||||
|
Then print my 'bitcoin address'"""
|
||||||
|
|
||||||
|
GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair
|
||||||
|
Given that I am known as 'Pippo'
|
||||||
|
When I create the ecdh key
|
||||||
|
When I create the bitcoin key
|
||||||
|
Then print data"""
|
||||||
|
|
||||||
|
ZENROOM_DATA = {"also": "more data"}
|
||||||
|
|
||||||
|
HOUSE_ASSETS = {
|
||||||
|
"data": {
|
||||||
|
"houses": [
|
||||||
|
{
|
||||||
|
"name": "Harry",
|
||||||
|
"team": "Gryffindor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Draco",
|
||||||
|
"team": "Slytherin",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = {"units": 300, "type": "KG"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_zenroom_signing():
|
||||||
|
|
||||||
|
biolabs = generate_key_pair()
|
||||||
|
version = "2.0"
|
||||||
|
|
||||||
|
alice = json.loads(zencode_exec(GENERATE_KEYPAIR).output)["keyring"]
|
||||||
|
bob = json.loads(zencode_exec(GENERATE_KEYPAIR).output)["keyring"]
|
||||||
|
|
||||||
|
zen_public_keys = json.loads(
|
||||||
|
zencode_exec(
|
||||||
|
SK_TO_PK.format("Alice"), keys=json.dumps({"keyring": alice})
|
||||||
|
).output
|
||||||
|
)
|
||||||
|
zen_public_keys.update(
|
||||||
|
json.loads(
|
||||||
|
zencode_exec(
|
||||||
|
SK_TO_PK.format("Bob"), keys=json.dumps({"keyring": bob})
|
||||||
|
).output
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
zenroomscpt = ZenroomSha256(
|
||||||
|
script=FULFILL_SCRIPT, data=ZENROOM_DATA, keys=zen_public_keys
|
||||||
|
)
|
||||||
|
print(f"zenroom is: {zenroomscpt.script}")
|
||||||
|
|
||||||
|
# CRYPTO-CONDITIONS: generate the condition uri
|
||||||
|
condition_uri_zen = zenroomscpt.condition.serialize_uri()
|
||||||
|
print(f"\nzenroom condition URI: {condition_uri_zen}")
|
||||||
|
|
||||||
|
# CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary
|
||||||
|
unsigned_fulfillment_dict_zen = {
|
||||||
|
"type": zenroomscpt.TYPE_NAME,
|
||||||
|
"public_key": base58.b58encode(biolabs.public_key).decode(),
|
||||||
|
}
|
||||||
|
output = {
|
||||||
|
"amount": "10",
|
||||||
|
"condition": {
|
||||||
|
"details": unsigned_fulfillment_dict_zen,
|
||||||
|
"uri": condition_uri_zen,
|
||||||
|
},
|
||||||
|
"public_keys": [
|
||||||
|
biolabs.public_key,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
input_ = {
|
||||||
|
"fulfillment": None,
|
||||||
|
"fulfills": None,
|
||||||
|
"owners_before": [
|
||||||
|
biolabs.public_key,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
metadata = {
|
||||||
|
"result": {
|
||||||
|
"output": ["ok"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
token_creation_tx = {
|
||||||
|
"operation": "CREATE",
|
||||||
|
"asset": HOUSE_ASSETS,
|
||||||
|
"metadata": metadata,
|
||||||
|
"outputs": [
|
||||||
|
output,
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
input_,
|
||||||
|
],
|
||||||
|
"version": version,
|
||||||
|
"id": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# JSON: serialize the transaction-without-id to a json formatted string
|
||||||
|
message = json.dumps(
|
||||||
|
token_creation_tx,
|
||||||
|
sort_keys=True,
|
||||||
|
separators=(",", ":"),
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# major workflow:
|
||||||
|
# we store the fulfill script in the transaction/message (zenroom-sha)
|
||||||
|
# the condition script is used to fulfill the transaction and create the signature
|
||||||
|
#
|
||||||
|
# the server should ick the fulfill script and recreate the zenroom-sha and verify the signature
|
||||||
|
|
||||||
|
message = zenroomscpt.sign(message, CONDITION_SCRIPT, alice)
|
||||||
|
assert zenroomscpt.validate(message=message)
|
||||||
|
|
||||||
|
message = json.loads(message)
|
||||||
|
fulfillment_uri_zen = zenroomscpt.serialize_uri()
|
||||||
|
|
||||||
|
message["inputs"][0]["fulfillment"] = fulfillment_uri_zen
|
||||||
|
tx = message
|
||||||
|
tx["id"] = None
|
||||||
|
json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":"))
|
||||||
|
# SHA3: hash the serialized id-less transaction to generate the id
|
||||||
|
shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest()
|
||||||
|
message["id"] = shared_creation_txid
|
||||||
|
|
||||||
|
from planetmint.models import Transaction
|
||||||
|
from planetmint.transactions.common.exceptions import (
|
||||||
|
SchemaValidationError,
|
||||||
|
ValidationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
tx_obj = Transaction.from_dict(message)
|
||||||
|
except SchemaValidationError:
|
||||||
|
assert ()
|
||||||
|
except ValidationError as e:
|
||||||
|
print(e)
|
||||||
|
assert ()
|
||||||
|
|
||||||
|
print(f"VALIDATED : {tx_obj}")
|
||||||
|
assert (tx_obj == False) is False
|
2
tox.ini
2
tox.ini
@ -25,7 +25,7 @@ extras = None
|
|||||||
commands = flake8 planetmint tests
|
commands = flake8 planetmint tests
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore = E126 E127 W504 E302 E126 E305
|
ignore = E126 E127 W504 E302 E126 E305 W503 E712 F401
|
||||||
|
|
||||||
[testenv:docsroot]
|
[testenv:docsroot]
|
||||||
basepython = {[base]basepython}
|
basepython = {[base]basepython}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user