Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4/4] In-memory issuing: signed certificate creation. #156

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions cert_issuer/simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import copy

from cert_core import Chain
from cert_issuer.blockchain_handlers.ethereum import initialize_signer, EthereumTransactionCostConstants
from cert_issuer.blockchain_handlers.ethereum.connectors import EthereumServiceProviderConnector
from cert_issuer.blockchain_handlers.ethereum.transaction_handlers import EthereumTransactionHandler
from cert_issuer.merkle_tree_generator import MerkleTreeGenerator
from cert_schema import normalize_jsonld


class SimplifiedCertificateBatchIssuer():
"""
Class to issue blockcerts without relying on filesystem usage.
(except for the Private Key which remains a file until the overall key handling issue is solved in theory)

Please note that it currently only supports anchoring to Ethereum.
"""

def __init__(self, config: 'AttrDict', unsigned_certs: dict):
# 1- Prepare config and unsigned certs (These come from my latest changes in cert-tools
self.config = config
self.config.chain = Chain.parse_from_chain(self.config.chain)

self.secret_manager = initialize_signer(self.config)
self.cost_constants = EthereumTransactionCostConstants(self.config.gas_price, self.config.gas_limit)
self.connector = EthereumServiceProviderConnector(self.config.chain, self.config.api_token)

self.unsigned_certs = unsigned_certs
self.cert_generator = self._create_cert_generator()

# 2- Calculate Merkle Proof and Root
self.merkle_tree_generator = MerkleTreeGenerator()
self.merkle_tree_generator.populate(self.cert_generator)
self.merkle_root = self.merkle_tree_generator.get_blockchain_data()

def issue(self):
"""Anchor the merkle root in a blockchain transaction and add the tx id and merkle proof to each cert."""
transaction_handler = EthereumTransactionHandler(
self.connector,
self.cost_constants,
self.secret_manager,
issuing_address=self.config.issuing_address
)
transaction_handler.ensure_balance()
tx_id = transaction_handler.issue_transaction(self.merkle_root)

proof_generator = self.merkle_tree_generator.get_proof_generator(tx_id, self.config.chain)
signed_certs = copy.deepcopy(self.unsigned_certs)
for _, cert in signed_certs.items():
proof = next(proof_generator)
cert['signature'] = proof

# Check cert integrity
self._ensure_successful_issuing(tx_id, signed_certs)
return tx_id, signed_certs

@staticmethod
def _ensure_successful_issuing(tx_id: str, signed_certs: dict):
assert tx_id != "0xfail"
for uid, cert in signed_certs.items():
assert cert['signature']['anchors'][0]['sourceId'] == tx_id

def _create_cert_generator(self):
for uid, cert in self.unsigned_certs.items():
normalized = normalize_jsonld(cert, detect_unmapped_fields=False)
yield normalized.encode('utf-8')
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pyld>=1.0.3
pysha3>=1.0.2
python-bitcoinlib>=0.10.1
tox>=3.0.0
jsonschema<3.0.0
attrdict==2.0.1
pytest==5.1.1
jsonschema<3.0.0
84 changes: 84 additions & 0 deletions tests/conftest.py

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions tests/test_no_fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from cert_issuer.simple import SimplifiedCertificateBatchIssuer


def test_simplfied_issuing_process(config, unsigned_certs, write_private_key_file):
"""Please note this test actually anchors to Ropsten so you need internet access and funds in the given account."""
simple_certificate_batch_issuer = SimplifiedCertificateBatchIssuer(config, unsigned_certs)
tx_id, signed_certs = simple_certificate_batch_issuer.issue()
one_cert_id = list(signed_certs.keys())[0]
merkle_root = signed_certs[one_cert_id]['signature']['merkleRoot']
print(f'Check https://ropsten.etherscan.io/tx/{tx_id} to confirm it contains the merkle root "{merkle_root}"')
assert merkle_root == 'cffe57bac8b8f47df9f5bb89e88dda893774b45b77d6600d5f1836d309505b61'
# This was already executed when issuing, just putting it here to be very explicit on what's being checked.
SimplifiedCertificateBatchIssuer._ensure_successful_issuing(tx_id, signed_certs)