diff --git a/.app.py.swp b/.app.py.swp new file mode 100644 index 00000000..8aabc4ae Binary files /dev/null and b/.app.py.swp differ diff --git a/.dockerignore b/.dockerignore index 4f37e5ab..ba79feef 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,4 @@ .tox *egg-info venv +pk diff --git a/.gitignore b/.gitignore index 14ef34ad..978ba581 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ venv*/ virtual\ usb/ secrets.py __pycache__ - +pk certs_dbdump.csv data/unsigned_certificates/ diff --git a/.travis.yml b/.travis.yml index 923b0188..beaa2d02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ sudo: false language: python python: - - "3.4" + - "3.6" install: pip install tox-travis script: tox diff --git a/Dockerfilebloxberg b/Dockerfilebloxberg new file mode 100644 index 00000000..858f7d4f --- /dev/null +++ b/Dockerfilebloxberg @@ -0,0 +1,12 @@ +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 + +EXPOSE 80 + +COPY ./app /app/app +COPY ./cert_issuer /app/cert_issuer +COPY ./sample_data /app/sample_data +COPY ethereum_smart_contract_requirements.txt . +COPY conf.ini . + +RUN pip install -r ethereum_smart_contract_requirements.txt + diff --git a/README.md b/README.md index fc66f11d..d884bdac 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ If you are using a local bitcoin node, you can create addresses by command line. ### Create an Ethereum issuing address -Currently Blockcerts just supports issuing to the Ropsten Ethereum testnet, and the Ethereum mainnet. In Ethereum a public/private key pair is the same accross all test/main networks. +Currently Blockcerts just supports issuing to the Bloxberg Ethereum testnet, and the Ethereum mainnet. In Ethereum a public/private key pair is the same accross all test/main networks. __These steps involve storing secure information on a USB. Do not plug in this USB when your computer's wifi is on.__ @@ -265,7 +265,10 @@ Edit your conf.ini file (the config file for this application). ``` issuing_address = -chain= +# issuer URL / DID +verification_method = + +chain= usb_name = key_file = @@ -298,7 +301,7 @@ python cert-issuer -c conf.ini - The Blockchain Certificates will be located in data/blockchain_certificates. - If you ran in the mainnet or testnet mode, you can also see your transaction on a live blockchain explorer. - For Bitcoin, Blockr.io has explorers for both [testnet](https://tbtc.blockr.io/) and [mainnet](https://blockr.io/). - - For Ethereum, Etherscan has explorers for [ropsten](https://ropsten.etherscan.io/) and [mainnet](https://etherscan.io/) + - For Ethereum, Etherscan has explorers for [bloxberg](https://bloxberg.etherscan.io/) and [mainnet](https://etherscan.io/) - The transaction id is located in the Blockchain Certificate under `signature.anchors[0].sourceId` @@ -397,7 +400,7 @@ For an Ethereum transaction, you'll need to use a different explorer, which migh output. To view a transaction in a web browser, you might try something like this: - Ethereum Mainnet: https://etherscan.io/tx/0xf537d81667c8011e34e1f450e18fd1c5a8a10c770cd0acdc91a79746696f36a3 -- Ethereum Ropsten (testnet): https://ropsten.etherscan.io/tx/0xf537d81667c8011e34e1f450e18fd1c5a8a10c770cd0acdc91a79746696f36a3 +- Ethereum Bloxberg (testnet): https://bloxberg.etherscan.io/tx/0xf537d81667c8011e34e1f450e18fd1c5a8a10c770cd0acdc91a79746696f36a3 ## Mac scrypt problems diff --git a/app.py b/app.py index 16aae656..d3d03a8f 100755 --- a/app.py +++ b/app.py @@ -4,7 +4,7 @@ from subprocess import call import cert_issuer.config -from cert_issuer.blockchain_handlers import bitcoin +from cert_issuer.blockchain_handlers import ethereum_sc import cert_issuer.issue_certificates app = Flask(__name__) @@ -20,7 +20,9 @@ def get_config(): def issue(): config = get_config() certificate_batch_handler, transaction_handler, connector = \ - bitcoin.instantiate_blockchain_handlers(config, False) + ethereum_sc.instantiate_blockchain_handlers(config) + #Removed File mode from ethereum_sc + #bitcoin.instantiate_blockchain_handlers(config, False) certificate_batch_handler.set_certificates_in_batch(request.json) cert_issuer.issue_certificates.issue(config, certificate_batch_handler, transaction_handler) return json.dumps(certificate_batch_handler.proof) diff --git a/app/main.py b/app/main.py new file mode 100644 index 00000000..0a179337 --- /dev/null +++ b/app/main.py @@ -0,0 +1,48 @@ +from typing import List, Optional +from functools import lru_cache +from fastapi import Depends, FastAPI +from cert_tools import instantiate_v3_alpha_certificate_batch, create_v3_alpha_certificate_template +from pydantic import BaseModel +import configargparse +import os + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + +class Batch(BaseModel): + publicKey: str + recipient_name: Optional[str] + email: Optional[str] + SHA256: List[str] + + class Config: + schema_extra = { + "example": { + "publicKey": "0x69575606E8b8F0cAaA5A3BD1fc5D032024Bb85AF", + "recipient_name": "Albert Einstein", + "email": "einstein@mpg.de", + "SHA256": ["0x0e4ded5319861c8daac00d425c53a16bd180a7d01a340a0e00f7dede40d2c9f6", "0xfda3124d5319861c8daac00d425c53a16bd180a7d01a340a0e00f7dede40d2c9f6"], + } + } + + +@app.post("/createUnsignedCertificateBatch") +def createUnsignedCertificate(batch: Batch): + #create_v3_alpha_certificate_template(recipient_name, email) + conf = create_v3_alpha_certificate_template.get_config() + create_v3_alpha_certificate_template.write_certificate_template(conf, batch.recipient_name, batch.email) + conf_instantiate = instantiate_v3_alpha_certificate_batch.get_config() + instantiate_v3_alpha_certificate_batch.instantiate_batch(conf_instantiate, batch.publicKey, batch.recipient_name, batch.email, batch.SHA256) + + return {"Unsigned Certificate Created!"} + +@app.post("/createCertificateBatch") +def createCertificateBatch(batch: Batch): + conf = instantiate_v3_alpha_certificate_batch.get_config() + instantiate_v3_alpha_certificate_batch.instantiate_batch(conf, batch.publicKey, batch.recipient_name, batch.email, batch.SHA256) + + return {"Batch Created!"} diff --git a/cert_issuer/__init__.py b/cert_issuer/__init__.py index b03a62d0..7ccf291d 100644 --- a/cert_issuer/__init__.py +++ b/cert_issuer/__init__.py @@ -1 +1 @@ -__version__ = '2.0.20' +__version__ = '3.0.0a3' diff --git a/cert_issuer/blockchain_handlers/__init__.py b/cert_issuer/blockchain_handlers/__init__.py index e69de29b..b03a62d0 100644 --- a/cert_issuer/blockchain_handlers/__init__.py +++ b/cert_issuer/blockchain_handlers/__init__.py @@ -0,0 +1 @@ +__version__ = '2.0.20' diff --git a/cert_issuer/blockchain_handlers/bitcoin/__init__.py b/cert_issuer/blockchain_handlers/bitcoin/__init__.py index 4280bde3..8b34a87b 100644 --- a/cert_issuer/blockchain_handlers/bitcoin/__init__.py +++ b/cert_issuer/blockchain_handlers/bitcoin/__init__.py @@ -7,7 +7,7 @@ from cert_issuer.blockchain_handlers.bitcoin.connectors import BitcoinServiceProviderConnector, MockServiceProviderConnector from cert_issuer.blockchain_handlers.bitcoin.signer import BitcoinSigner from cert_issuer.blockchain_handlers.bitcoin.transaction_handlers import BitcoinTransactionHandler -from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV2Handler, CertificateBatchWebHandler, CertificateWebV2Handler +from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV3Handler, CertificateBatchWebHandler, CertificateWebV3Handler from cert_issuer.merkle_tree_generator import MerkleTreeGenerator from cert_issuer.models import MockTransactionHandler from cert_issuer.signer import FileSecretManager @@ -50,12 +50,14 @@ def instantiate_blockchain_handlers(app_config, file_mode=True): if file_mode: certificate_batch_handler = CertificateBatchHandler(secret_manager=secret_manager, - certificate_handler=CertificateV2Handler(), - merkle_tree=MerkleTreeGenerator()) + certificate_handler=CertificateV3Handler(), + merkle_tree=MerkleTreeGenerator(), + config=app_config) else: certificate_batch_handler = CertificateBatchWebHandler(secret_manager=secret_manager, - certificate_handler=CertificateWebV2Handler(), - merkle_tree=MerkleTreeGenerator()) + certificate_handler=CertificateWebV3Handler(), + merkle_tree=MerkleTreeGenerator(), + config=app_config) if chain == Chain.mockchain: transaction_handler = MockTransactionHandler() connector = MockServiceProviderConnector() diff --git a/cert_issuer/blockchain_handlers/ethereum/__init__.py b/cert_issuer/blockchain_handlers/ethereum/__init__.py index c2b37ca3..5d2d3291 100644 --- a/cert_issuer/blockchain_handlers/ethereum/__init__.py +++ b/cert_issuer/blockchain_handlers/ethereum/__init__.py @@ -4,7 +4,7 @@ from cert_core import BlockchainType from cert_core import Chain, UnknownChainError -from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV2Handler +from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV3Handler from cert_issuer.blockchain_handlers.ethereum.connectors import EthereumServiceProviderConnector from cert_issuer.blockchain_handlers.ethereum.signer import EthereumSigner from cert_issuer.blockchain_handlers.ethereum.transaction_handlers import EthereumTransactionHandler @@ -54,12 +54,13 @@ def instantiate_blockchain_handlers(app_config): chain = app_config.chain secret_manager = initialize_signer(app_config) certificate_batch_handler = CertificateBatchHandler(secret_manager=secret_manager, - certificate_handler=CertificateV2Handler(), - merkle_tree=MerkleTreeGenerator()) + certificate_handler=CertificateV3Handler(), + merkle_tree=MerkleTreeGenerator(), + config=app_config) if chain == Chain.mockchain: transaction_handler = MockTransactionHandler() # ethereum chains - elif chain == Chain.ethereum_mainnet or chain == Chain.ethereum_ropsten: + elif chain == Chain.ethereum_mainnet or chain == Chain.ethereum_bloxberg: cost_constants = EthereumTransactionCostConstants(app_config.gas_price, app_config.gas_limit) connector = EthereumServiceProviderConnector(chain, app_config.api_token) transaction_handler = EthereumTransactionHandler(connector, cost_constants, secret_manager, diff --git a/cert_issuer/blockchain_handlers/ethereum/connectors.py b/cert_issuer/blockchain_handlers/ethereum/connectors.py index 1f75e84b..89b7ee6f 100644 --- a/cert_issuer/blockchain_handlers/ethereum/connectors.py +++ b/cert_issuer/blockchain_handlers/ethereum/connectors.py @@ -95,12 +95,14 @@ def broadcast_tx(self, tx, api_token): broadcast_url = self.base_url + '?module=proxy&action=eth_sendRawTransaction' if api_token: - '&apikey=%s' % api_token + broadcast_url += '&apikey=%s' % api_token response = requests.post(broadcast_url, data={'hex': tx_hex}) if 'error' in response.json(): logging.error("Etherscan returned an error: %s", response.json()['error']) raise BroadcastError(response.json()['error']) if int(response.status_code) == 200: + if response.json().get('message', None) == 'NOTOK': + raise BroadcastError(response.json().get('result', None)) tx_id = response.json().get('result', None) logging.info("Transaction ID obtained from broadcast through Etherscan: %s", tx_id) return tx_id @@ -116,9 +118,11 @@ def get_balance(self, address, api_token): broadcast_url += '&address=%s' % address broadcast_url += '&tag=latest' if api_token: - '&apikey=%s' % api_token + broadcast_url += '&apikey=%s' % api_token response = requests.get(broadcast_url) if int(response.status_code) == 200: + if response.json().get('message', None) == 'NOTOK': + raise BroadcastError(response.json().get('result', None)) balance = int(response.json().get('result', None)) logging.info('Balance check succeeded: %s', response.json()) return balance @@ -133,10 +137,11 @@ def get_address_nonce(self, address, api_token): broadcast_url += '&address=%s' % address broadcast_url += '&tag=latest' if api_token: - '&apikey=%s' % api_token + broadcast_url += '&apikey=%s' % api_token response = requests.get(broadcast_url, ) if int(response.status_code) == 200: - # the int(res, 0) transforms the hex nonce to int + if response.json().get('message', None) == 'NOTOK': + raise BroadcastError(response.json().get('result', None)) nonce = int(response.json().get('result', None), 0) logging.info('Nonce check went correct: %s', response.json()) return nonce @@ -219,11 +224,11 @@ def get_address_nonce(self, address, api_token): eth_provider_list.append(MyEtherWalletBroadcaster('https://api.myetherwallet.com/eth')) connectors[Chain.ethereum_mainnet] = eth_provider_list -# Configure Ethereum Ropsten testnet connectors +# Configure Ethereum Bloxberg testnet connectors rop_provider_list = [] -rop_provider_list.append(EtherscanBroadcaster('https://ropsten.etherscan.io/api')) +rop_provider_list.append(EtherscanBroadcaster('https://blockexplorer.bloxberg.org/api')) rop_provider_list.append(MyEtherWalletBroadcaster('https://api.myetherwallet.com/rop')) -connectors[Chain.ethereum_ropsten] = rop_provider_list +connectors[Chain.ethereum_bloxberg] = rop_provider_list def get_providers_for_chain(chain, local_node=False): diff --git a/cert_issuer/blockchain_handlers/ethereum/signer.py b/cert_issuer/blockchain_handlers/ethereum/signer.py index fe6358d2..e6619f6f 100644 --- a/cert_issuer/blockchain_handlers/ethereum/signer.py +++ b/cert_issuer/blockchain_handlers/ethereum/signer.py @@ -12,7 +12,7 @@ def __init__(self, ethereum_chain): # Netcode ensures replay protection (see EIP155) if ethereum_chain.external_display_value == 'ethereumMainnet': self.netcode = 1 - elif ethereum_chain.external_display_value == 'ethereumRopsten': + elif ethereum_chain.external_display_value == 'ethereumBloxberg': self.netcode = 3 else: self.netcode = None diff --git a/cert_issuer/blockchain_handlers/ethereum/transaction_handlers.py b/cert_issuer/blockchain_handlers/ethereum/transaction_handlers.py index 8a483f5f..cdf1898b 100644 --- a/cert_issuer/blockchain_handlers/ethereum/transaction_handlers.py +++ b/cert_issuer/blockchain_handlers/ethereum/transaction_handlers.py @@ -54,7 +54,7 @@ def ensure_balance(self): logging.error(error_message) raise InsufficientFundsError(error_message) - def issue_transaction(self, blockchain_bytes, app_config): + def issue_transaction(self, recipient_address, token_uri, blockchain_bytes, app_config): eth_data_field = b2h(blockchain_bytes) prepared_tx = self.create_transaction(blockchain_bytes) signed_tx = self.sign_transaction(prepared_tx) diff --git a/cert_issuer/blockchain_handlers/ethereum_sc/.connectors.py.swp b/cert_issuer/blockchain_handlers/ethereum_sc/.connectors.py.swp new file mode 100644 index 00000000..a52c6d26 Binary files /dev/null and b/cert_issuer/blockchain_handlers/ethereum_sc/.connectors.py.swp differ diff --git a/cert_issuer/blockchain_handlers/ethereum_sc/__init__.py b/cert_issuer/blockchain_handlers/ethereum_sc/__init__.py index d62bd75c..cdd7eb79 100644 --- a/cert_issuer/blockchain_handlers/ethereum_sc/__init__.py +++ b/cert_issuer/blockchain_handlers/ethereum_sc/__init__.py @@ -4,7 +4,7 @@ from cert_core import BlockchainType from cert_core import Chain, UnknownChainError -from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV2Handler +from cert_issuer.certificate_handlers import CertificateBatchHandler, CertificateV3Handler from cert_issuer.blockchain_handlers.ethereum_sc.connectors import EthereumSCServiceProviderConnector from cert_issuer.blockchain_handlers.ethereum_sc.ens import ENSConnector from cert_issuer.blockchain_handlers.ethereum_sc.signer import EthereumSCSigner @@ -82,12 +82,13 @@ def instantiate_blockchain_handlers(app_config): chain = app_config.chain secret_manager = initialize_signer(app_config) certificate_batch_handler = CertificateBatchHandler(secret_manager=secret_manager, - certificate_handler=CertificateV2Handler(), - merkle_tree=MerkleTreeGenerator()) + certificate_handler=CertificateV3Handler(), + merkle_tree=MerkleTreeGenerator(), + config=app_config) if chain == Chain.mockchain: transaction_handler = MockTransactionHandler() # ethereum chains - elif chain == Chain.ethereum_mainnet or chain == Chain.ethereum_ropsten: + elif chain == Chain.ethereum_mainnet or chain == Chain.ethereum_bloxberg: cost_constants = EthereumTransactionCostConstants(app_config.gas_price, app_config.gas_limit) connector = instantiate_connector(app_config, cost_constants) transaction_handler = EthereumSCTransactionHandler(connector, cost_constants, secret_manager, diff --git a/cert_issuer/blockchain_handlers/ethereum_sc/connectors.py b/cert_issuer/blockchain_handlers/ethereum_sc/connectors.py index c3ab981b..564a62aa 100644 --- a/cert_issuer/blockchain_handlers/ethereum_sc/connectors.py +++ b/cert_issuer/blockchain_handlers/ethereum_sc/connectors.py @@ -1,7 +1,7 @@ import json import os import logging -from errors import UnableToSignTxError +from cert_issuer.errors import UnableToSignTxError from cert_issuer.models import ServiceProviderConnector from web3 import Web3, HTTPProvider @@ -15,6 +15,7 @@ def get_abi(contract): directory = os.path.dirname(os.path.abspath(__file__)) path = os.path.join(directory, f"data/{contract}_abi.json") + print(path) with open(path, "r") as f: raw = f.read() @@ -49,21 +50,26 @@ def get_balance(self, address): def create_transaction(self, method, *argv): gas_limit = self.cost_constants.get_gas_limit() - estimated_gas = self._contract_obj.functions[method](*argv).estimateGas() * 2 + estimated_gas = self._contract_obj.functions[method](*argv).estimateGas() * 5 if estimated_gas > gas_limit: - logging.warning("Estimated gas of %s more than gas limit of %s, transaction might fail. Please verify on etherescan.com.", estimated_gas, gas_limit) + logging.warning("Estimated gas of %s more than gas limit of %s, transaction might fail. Please verify on blockexplorer.bloxberg.org.", estimated_gas, gas_limit) estimated_gas = gas_limit gas_price = self._w3.eth.gasPrice gas_price_limit = self.cost_constants.get_gas_price() + print("gas price: " + str(gas_price)) + print("gas_price_limit: " + str(gas_price_limit)) + print("estimated_gas: " + str(estimated_gas)) + if gas_price > gas_price_limit: logging.warning("Gas price provided by network of %s higher than gas price of %s set in config, transaction might fail. Please verify on etherescan.com.", gas_price, gas_price_limit) gas_price = gas_price_limit tx_options = { 'nonce': self._w3.eth.getTransactionCount(self._w3.eth.defaultAccount), - 'gas': estimated_gas, + 'gas': 700000, + #'gas': estimated_gas, 'gasPrice': gas_price } @@ -101,3 +107,9 @@ def _sign_transaction(self, prepared_tx): def call(self, method, *argv): return self._contract_obj.functions[method](*argv).call() + + def get_event_args(self, tx_hash, event): + tx_receipt = self._w3.eth.getTransactionReceipt(tx_hash) + logs = self._contract_obj.events[event]().processReceipt(tx_receipt) + return logs[0]['args'] + diff --git a/cert_issuer/blockchain_handlers/ethereum_sc/ens.py b/cert_issuer/blockchain_handlers/ethereum_sc/ens.py index 9edf1e76..7b62834e 100644 --- a/cert_issuer/blockchain_handlers/ethereum_sc/ens.py +++ b/cert_issuer/blockchain_handlers/ethereum_sc/ens.py @@ -11,8 +11,8 @@ def __init__(self, app_config): self._w3 = Web3(HTTPProvider()) def get_registry_address(self): - if self.app_config.chain == Chain.ethereum_ropsten: - addr = self.app_config.ens_registry_ropsten + if self.app_config.chain == Chain.ethereum_bloxberg: + addr = self.app_config.ens_registry_bloxberg else: addr = self.app_config.ens_registry_mainnet diff --git a/cert_issuer/blockchain_handlers/ethereum_sc/signer.py b/cert_issuer/blockchain_handlers/ethereum_sc/signer.py index dcddd8c5..69c54584 100644 --- a/cert_issuer/blockchain_handlers/ethereum_sc/signer.py +++ b/cert_issuer/blockchain_handlers/ethereum_sc/signer.py @@ -12,9 +12,9 @@ def __init__(self, ethereum_chain): self.ethereum_chain = ethereum_chain # Netcode ensures replay protection (see EIP155) if ethereum_chain.external_display_value == 'ethereumMainnet': - self.netcode = 1 - elif ethereum_chain.external_display_value == 'ethereumRopsten': - self.netcode = 3 + self.netcode = 8995 + elif ethereum_chain.external_display_value == 'ethereumBloxberg': + self.netcode = 8995 else: self.netcode = None diff --git a/cert_issuer/blockchain_handlers/ethereum_sc/transaction_handlers.py b/cert_issuer/blockchain_handlers/ethereum_sc/transaction_handlers.py index 60721188..8c388519 100644 --- a/cert_issuer/blockchain_handlers/ethereum_sc/transaction_handlers.py +++ b/cert_issuer/blockchain_handlers/ethereum_sc/transaction_handlers.py @@ -29,11 +29,18 @@ def ensure_balance(self): def revoke_transaction(self, blockchain_bytes, app_config): return self.make_transaction(blockchain_bytes, app_config, "revoke_hash") - def issue_transaction(self, blockchain_bytes, app_config): - return self.make_transaction(blockchain_bytes, app_config, "issue_hash") + #Outdated Function: TODO - REMOVE + #def issue_transaction(self, blockchain_bytes, app_config): + # return self.make_transaction(blockchain_bytes, app_config, "issue_hash") - def make_transaction(self, blockchain_bytes, app_config, method): - prepared_tx = self.connector.create_transaction(method, blockchain_bytes) + def issue_transaction(self, recipient_address, token_uri, blockchain_bytes, app_config): + return self.make_transaction(app_config, "createCertificate", recipient_address, token_uri, blockchain_bytes) + + def update_token_uri(self, token_id, token_uri, app_config): + return self.make_transaction(app_config, "updateTokenURI", token_id, token_uri) + + def make_transaction(self, app_config, method, *argv): + prepared_tx = self.connector.create_transaction(method, *argv) signed_tx = self.sign_transaction(prepared_tx) logging.info('Broadcasting transaction to the blockchain...') @@ -53,3 +60,6 @@ def sign_transaction(self, prepared_tx): def broadcast_transaction(self, signed_tx): txid = self.connector.broadcast_tx(signed_tx) return txid + + def get_event_args(self, tx_hash, event): + return self.connector(tx_hash, event) diff --git a/cert_issuer/certificate_handlers.py b/cert_issuer/certificate_handlers.py index fe969dc8..7b1f7bb3 100644 --- a/cert_issuer/certificate_handlers.py +++ b/cert_issuer/certificate_handlers.py @@ -1,15 +1,14 @@ import json import logging -from cert_schema import normalize_jsonld +from cert_schema import normalize_jsonld, validate_v3_alpha from cert_issuer import helpers from pycoin.serialize import b2h from cert_issuer.models import CertificateHandler, BatchHandler from cert_issuer.signer import FinalizableSigner - -class CertificateV2Handler(CertificateHandler): +class CertificateV3Handler(CertificateHandler): def get_byte_array_to_issue(self, certificate_metadata): certificate_json = self._get_certificate_to_issue(certificate_metadata) normalized = normalize_jsonld(certificate_json, detect_unmapped_fields=False) @@ -22,7 +21,7 @@ def add_proof(self, certificate_metadata, merkle_proof): :return: """ certificate_json = self._get_certificate_to_issue(certificate_metadata) - certificate_json['signature'] = merkle_proof + certificate_json['proof'] = merkle_proof with open(certificate_metadata.blockchain_cert_file_name, 'w') as out_file: out_file.write(json.dumps(certificate_json)) @@ -32,8 +31,16 @@ def _get_certificate_to_issue(self, certificate_metadata): certificate_json = json.load(unsigned_cert_file) return certificate_json + def validate_certificate(self, certificate_metadata): + certificate_json = self._get_certificate_to_issue(certificate_metadata) + return validate_v3_alpha(certificate_json) + + def sign_certificate(self, signer, certificate_metadata): + # TODO + return self.sign_certificate(signer, certificate_metadata) -class CertificateWebV2Handler(CertificateHandler): + +class CertificateWebV3Handler(CertificateHandler): def get_byte_array_to_issue(self, certificate_json): normalized = normalize_jsonld(certificate_json, detect_unmapped_fields=False) return normalized.encode('utf-8') @@ -47,11 +54,20 @@ def add_proof(self, certificate_json, merkle_proof): certificate_json['signature'] = merkle_proof return certificate_json + def validate_certificate(self, certificate_metadata): + certificate_json = self._get_certificate_to_issue(certificate_metadata) + return validate_v3_alpha(certificate_json) + + def sign_certificate(self, signer, certificate_metadata): + # TODO + return self.sign_certificate(signer, certificate_metadata) + class CertificateBatchWebHandler(BatchHandler): + #Smart contract version has app_config as variable - removed in V3 def finish_batch(self, tx_id, chain, app_config): self.proof = [] - proof_generator = self.merkle_tree.get_proof_generator(tx_id, app_config, chain) + proof_generator = self.merkle_tree.get_proof_generator(tx_id, app_config, self.config.verification_method, chain) for metadata in self.certificates_to_issue: proof = next(proof_generator) self.proof.append(self.certificate_handler.add_proof(metadata, proof)) @@ -122,7 +138,7 @@ def get_certificate_generator(self): yield data_to_issue def finish_batch(self, tx_id, chain, app_config): - proof_generator = self.merkle_tree.get_proof_generator(tx_id, app_config, chain) + proof_generator = self.merkle_tree.get_proof_generator(tx_id, app_config, self.config.verification_method, chain) for _, metadata in self.certificates_to_issue.items(): proof = next(proof_generator) self.certificate_handler.add_proof(metadata, proof) diff --git a/cert_issuer/config.py b/cert_issuer/config.py index faffa53d..aa9790c7 100644 --- a/cert_issuer/config.py +++ b/cert_issuer/config.py @@ -1,7 +1,7 @@ import logging import os -import bitcoin +#import bitcoin import configargparse from cert_core import BlockchainType, Chain, chain_to_bitcoin_network, UnknownChainError @@ -38,6 +38,7 @@ def add_arguments(p): # 'invoked' through config file p.add_argument('--issuing_address', required=True, help='issuing address', env_var='ISSUING_ADDRESS') + p.add_argument('--verification_method', required=True, help='Verification method for the Linked Data Proof', env_var='VERIFICATION_METHOD') p.add_argument('--usb_name', required=True, help='usb path to key_file', env_var='USB_NAME') p.add_argument('--key_file', required=True, help='name of file on USB containing private key', env_var='KEY_FILE') @@ -52,7 +53,7 @@ def add_arguments(p): p.add_argument('--max_retry', default=10, type=int, help='Maximum attempts to retry transaction on failure', env_var='MAX_RETRY') p.add_argument('--chain', default='bitcoin_regtest', help=('Which chain to use. Default is bitcoin_regtest (which is how the docker container is configured). Other options are ' - 'bitcoin_testnet bitcoin_mainnet, mockchain, ethereum_mainnet, ethereum_ropsten'), env_var='CHAIN') + 'bitcoin_testnet bitcoin_mainnet, mockchain, ethereum_mainnet, ethereum_bloxberg'), env_var='CHAIN') p.add_argument('--safe_mode', dest='safe_mode', default=True, action='store_true', help='Used to make sure your private key is not plugged in with the wifi.', env_var='SAFE_MODE') @@ -89,10 +90,10 @@ def add_arguments(p): help='ens_name that points to the smart contract to which to issue', env_var='ENS_NAME') p.add_argument('--revocation_list_file', required=False, help='list of certificates or batches to be revokes', env_var='REVOCATION_LIST_FILE') - p.add_argument('--ens_registry_ropsten', required=False, default="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", - help='ENS registry address on ropsten', env_var='ENS_RESGISTRY_ROPSTEN') - p.add_argument('--ens_registry_mainnet', required=False, default="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", - help='ENS registry address on ropsten', env_var='ENS_RESGISTRY_MAINNET') + p.add_argument('--ens_registry_bloxberg', required=False, default="0xde68Fcf6814D81Ee910bf35703622571718E07a7", + help='ENS registry address on bloxberg', env_var='ENS_RESGISTRY_BLOXBERG') + p.add_argument('--ens_registry_mainnet', required=False, default="0xde68Fcf6814D81Ee910bf35703622571718E07a7", + help='ENS registry address on bloxberg', env_var='ENS_RESGISTRY_MAINNET') def get_config(): configure_logger() @@ -109,11 +110,12 @@ def get_config(): parsed_config.chain = Chain.parse_from_chain(parsed_config.chain) # ensure it's a supported chain + """ if parsed_config.chain.blockchain_type != BlockchainType.bitcoin and \ parsed_config.chain.blockchain_type != BlockchainType.ethereum and \ parsed_config.chain.blockchain_type != BlockchainType.mock: raise UnknownChainError(parsed_config.chain.name) - + """ logging.info('This run will try to issue on the %s chain', parsed_config.chain.name) if parsed_config.chain.blockchain_type == BlockchainType.bitcoin: diff --git a/cert_issuer/helpers.py b/cert_issuer/helpers.py index c953e7ef..83953ec1 100644 --- a/cert_issuer/helpers.py +++ b/cert_issuer/helpers.py @@ -100,3 +100,21 @@ def to_pycoin_chain(chain): return 'BTC' else: raise UnknownChainError(chain.name) + +def tx_to_blink(chain, tx_id): + blink = 'blink:' + if chain == Chain.bitcoin_regtest: + blink += 'btc:regtest:' + elif chain == Chain.bitcoin_testnet: + blink += 'btc:testnet:' + elif chain == Chain.bitcoin_mainnet: + blink += 'btc:mainnet:' + elif chain == Chain.ethereum_bloxberg: + blink += 'eth:bloxberg:' + elif chain == Chain.ethereum_mainnet: + blink += 'eth:mainnet:' + elif chain == Chain.mockchain: + blink += 'mocknet:' + else: + raise UnknownChainError(chain.name) + return blink + tx_id \ No newline at end of file diff --git a/cert_issuer/issue_certificates.py b/cert_issuer/issue_certificates.py index feeb4345..7d29fd45 100644 --- a/cert_issuer/issue_certificates.py +++ b/cert_issuer/issue_certificates.py @@ -11,7 +11,7 @@ sys.exit(1) -def issue(app_config, certificate_batch_handler, transaction_handler): +def issue(app_config, certificate_batch_handler, transaction_handler, recipient_address): certificate_batch_handler.pre_batch_actions(app_config) transaction_handler.ensure_balance() @@ -20,10 +20,10 @@ def issue(app_config, certificate_batch_handler, transaction_handler): certificate_batch_handler=certificate_batch_handler, transaction_handler=transaction_handler, max_retry=app_config.max_retry) - tx_id = issuer.issue(app_config.chain, app_config) + (tx_id, token_id) = issuer.issue(app_config.chain, app_config, recipient_address) certificate_batch_handler.post_batch_actions(app_config) - return tx_id + return (tx_id, token_id) def revoke_certificates(app_config, transaction_handler): # revocations are executed one hash at a time - balance is ensure before each tx @@ -36,9 +36,15 @@ def revoke_certificates(app_config, transaction_handler): return tx_id +def update_token_uri(app_config, token_id, token_uri, transaction_handler): + issuer = Issuer(transaction_handler=transaction_handler, + max_retry=app_config.max_retry) + + issuer.update_token_uri(token_id, token_uri, app_config) + def main(app_config): chain = app_config.chain - if chain == Chain.ethereum_mainnet or chain == Chain.ethereum_ropsten: + if chain == Chain.ethereum_mainnet or chain == Chain.ethereum_bloxberg: if app_config.issuing_method == "smart_contract": from cert_issuer.blockchain_handlers import ethereum_sc certificate_batch_handler, transaction_handler, connector = ethereum_sc.instantiate_blockchain_handlers(app_config) diff --git a/cert_issuer/issuer.py b/cert_issuer/issuer.py index dc43b228..66c6a4b1 100644 --- a/cert_issuer/issuer.py +++ b/cert_issuer/issuer.py @@ -2,7 +2,8 @@ Base class for building blockchain transactions to issue Blockchain Certificates. """ import logging - +from eth_utils import is_checksum_address +from web3 import Web3 from cert_issuer.errors import BroadcastError MAX_TX_RETRIES = 5 @@ -14,23 +15,52 @@ def __init__(self, certificate_batch_handler, transaction_handler, max_retry=MAX self.transaction_handler = transaction_handler self.max_retry = max_retry - def issue(self, chain, app_config): + def issue(self, chain, app_config, recipient_address): """ Issue the certificates on the blockchain :return: """ blockchain_bytes = self.certificate_batch_handler.prepare_batch() - + + recipient_address = Web3.toChecksumAddress(recipient_address) + print(is_checksum_address(recipient_address)) + #change token_uri address + token_uri = "https://bloxberg.org" + #blockchain_bytes = str(blockchain_bytes, 'latin-1') + blockchain_bytes = blockchain_bytes.hex() for attempt_number in range(0, self.max_retry): try: - txid = self.transaction_handler.issue_transaction(blockchain_bytes, app_config) + txid = self.transaction_handler.issue_transaction(recipient_address, token_uri, blockchain_bytes, app_config) + event_args = self.transaction_handler.get_event_args(txid, 'Transfer') + token_id = event_args['tokenId'] + self.certificate_batch_handler.finish_batch(txid, chain, app_config) logging.info('Broadcast transaction with txid %s', txid) - return txid + return (txid, token_id) except BroadcastError: logging.warning( 'Failed broadcast reattempts. Trying to recreate transaction. This is attempt number %d', attempt_number) logging.error('All attempts to broadcast failed. Try rerunning issuer.') raise BroadcastError('All attempts to broadcast failed. Try rerunning issuer.') + + def update_token_uri(self, token_id, token_uri, app_config): + """ + Update the tokenURI for the issued certificate batch + :return: transaction id + """ + + for attempt_number in range(0, self.max_retry): + try: + txid = self.transaction_handler.update_token_uri(token_id, token_uri, app_config) + logging.info('Updating tokenURI field with ipfs link. Txid: %s', txid) + return txid + except BroadcastError: + logging.warning( + 'Failed broadcast reattempts. Trying to recreate transaction. This is attempt number %d', + attempt_number) + logging.error('All attempts to broadcast failed. Try rerunning issuer.') + raise BroadcastError('All attempts to broadcast failed. Try rerunning issuer.') + + diff --git a/cert_issuer/merkle_tree_generator.py b/cert_issuer/merkle_tree_generator.py index e6f5e3cb..b7c49386 100644 --- a/cert_issuer/merkle_tree_generator.py +++ b/cert_issuer/merkle_tree_generator.py @@ -1,9 +1,13 @@ import hashlib +import logging +from datetime import datetime from cert_core import Chain # from chainpoint3.chainpoint import MerkleTools from merkletools import MerkleTools from pycoin.serialize import h2b +from lds_merkle_proof_2019.merkle_proof_2019 import MerkleProof2019 +from cert_issuer import helpers def hash_byte_array(data): @@ -41,7 +45,7 @@ def get_blockchain_data(self): merkle_root = self.tree.get_merkle_root() return h2b(ensure_string(merkle_root)) - def get_proof_generator(self, tx_id, app_config, chain=Chain.bitcoin_mainnet): + def get_proof_generator(self, tx_id, app_config, verification_method, chain=Chain.bitcoin_mainnet): """ Returns a generator (1-time iterator) of proofs in insertion order. @@ -53,52 +57,88 @@ def get_proof_generator(self, tx_id, app_config, chain=Chain.bitcoin_mainnet): for index in range(0, node_count): proof = self.tree.get_proof(index) proof2 = [] - + #Change back to smart contract proof & verification for p in proof: dict2 = dict() for key, value in p.items(): dict2[key] = ensure_string(value) proof2.append(dict2) target_hash = ensure_string(self.tree.get_leaf(index)) + + """ + Add additional parameters for smart contract certification + """ if app_config.issuing_method == "smart_contract": - from blockchain_handlers.ethereum_sc.ens import ENSConnector + from cert_issuer.blockchain_handlers.ethereum_sc.ens import ENSConnector ens = ENSConnector(app_config) abi = ens.get_abi() - merkle_proof = { - "type": ['MerkleProof2017', 'Extension'], + mp2019 = MerkleProof2019() + print(helpers.tx_to_blink(chain, tx_id)) + merkle_json = { + "path": proof2, "merkleRoot": root, "targetHash": target_hash, - "proof": proof2, - "anchors": [{ - "sourceId": to_source_id(tx_id, chain), - "type": "ETHSmartContract", - "chain": chain.external_display_value, - "contract_address": app_config.contract_address, - "ens_name": app_config.ens_name, - "contract_abi": abi - }]} - else: + #Possibly adjust anchor to merkle_proof dict + "anchors": [ + helpers.tx_to_blink(chain, tx_id) + ] + } + logging.info('merkle_json: %s', str(merkle_json)) + + proof_value = mp2019.encode(merkle_json) merkle_proof = { - "type": ['MerkleProof2017', 'Extension'], + "type": "MerkleProof2019", + "created": datetime.now().isoformat(), + "proofValue": proof_value.decode('utf8'), + "proofPurpose": "assertionMethod", + "verificationMethod": verification_method, + #Add ENS name for issuer validation + "ens_name": app_config.ens_name + } + #Uncomment after checking verification + """ + "anchors": [{ + #helpers.tx_to_blink(chain, tx_id), + "sourceId": to_source_id(tx_id, chain), + "type": "ETHSmartContract", + "contract_address": app_config.contract_address, + "ens_name": app_config.ens_name, + "contract_abi": abi + }] + """ + + else: + mp2019 = MerkleProof2019() + merkle_json = { + "path": proof2, "merkleRoot": root, "targetHash": target_hash, - "proof": proof2, - "anchors": [{ - "sourceId": to_source_id(tx_id, chain), - "type": chain.blockchain_type.external_display_value, - "chain": chain.external_display_value - }]} + "anchors": [ + helpers.tx_to_blink(chain, tx_id) + ] + } + logging.info('merkle_json: %s', str(merkle_json)) + + proof_value = mp2019.encode(merkle_json) + merkle_proof = { + "type": "MerkleProof2019", + "created": datetime.now().isoformat(), + "proofValue": proof_value.decode('utf8'), + "proofPurpose": "assertionMethod", + "verificationMethod": verification_method + } yield merkle_proof + def to_source_id(txid, chain): # workaround return txid # previously the == operator to actually compare with 'chain' was missing - this caused the below text to be returned, breaking the tests - if chain == Chain.bitcoin_mainnet or chain == Chain.bitcoin_testnet or chain == Chain.ethereum_mainnet or chain == Chain.ethereum_ropsten: + if chain == Chain.bitcoin_mainnet or chain == Chain.bitcoin_testnet or chain == Chain.ethereum_mainnet or chain == Chain.ethereum_bloxberg: return txid else: return 'This has not been issued on a blockchain and is for testing only' diff --git a/cert_issuer/models.py b/cert_issuer/models.py index ce6279a6..099976ce 100644 --- a/cert_issuer/models.py +++ b/cert_issuer/models.py @@ -3,10 +3,11 @@ from cert_issuer.config import ESTIMATE_NUM_INPUTS class BatchHandler(object): - def __init__(self, secret_manager, certificate_handler, merkle_tree): + def __init__(self, secret_manager, certificate_handler, merkle_tree, config): self.certificate_handler = certificate_handler self.secret_manager = secret_manager self.merkle_tree = merkle_tree + self.config = config @abstractmethod def pre_batch_actions(self, config): @@ -90,7 +91,7 @@ def ensure_balance(self): pass @abstractmethod - def issue_transaction(self, blockchain_bytes): + def issue_transaction(self, recipient_address, token_uri, blockchain_bytes): pass @@ -98,7 +99,7 @@ class MockTransactionHandler(TransactionHandler): def ensure_balance(self): pass - def issue_transaction(self, op_return_bytes): + def issue_transaction(self, recipient_address, token_uri, op_return_bytes): return 'This has not been issued on a blockchain and is for testing only' diff --git a/conf_regtest.ini b/conf_regtest.ini index 08a7103c..f30ecde0 100644 --- a/conf_regtest.ini +++ b/conf_regtest.ini @@ -10,4 +10,6 @@ unsigned_certificates_dir=/etc/cert-issuer/data/unsigned_certificates blockchain_certificates_dir=/etc/cert-issuer/data/blockchain_certificates work_dir=/etc/cert-issuer/work +ipfs_batch_file=ipfsBatch.json + no_safe_mode diff --git a/conf_template.ini b/conf_template.ini index 499ab702..773ef370 100644 --- a/conf_template.ini +++ b/conf_template.ini @@ -1,5 +1,8 @@ issuing_address = +# Issuer URL / DID as the verification method +verification_method = + # put your unsigned certificates here for signing. Default is /data/unsigned_certificates unsigned_certificates_dir= # signed certificates are the output from the cert signing step; input to the cert issuing step. Default is /data/signed_certificates @@ -12,8 +15,11 @@ work_dir= usb_name = key_file = +# a list of the IPFS hashes of the uploded certificates +ipfs_batch_file= + # which blockchain; bitcoin_regtest is default chain= # this disables the wifi check, and should only be used recommended during testing -no_safe_mode \ No newline at end of file +no_safe_mode diff --git a/data/blockchain_certificates/.placeholder b/data/blockchain_certificates/.placeholder old mode 100755 new mode 100644 diff --git a/docs/ethereum_smart_contract.md b/docs/ethereum_smart_contract.md index 8fe2335e..8270604f 100644 --- a/docs/ethereum_smart_contract.md +++ b/docs/ethereum_smart_contract.md @@ -62,8 +62,8 @@ Configuration is done via the `conf_eth.ini` file. Cert-deployer's configuration `deploying_address = ` The ethereum account's address. -`chain = ` -Choice of deployment on Ethereum Mainnet or the Ropsten test network. +`chain = ` +Choice of deployment on Ethereum Mainnet or the Bloxberg test network. `node_url = ` The web3py library requires an ethereum node that is compatible with the json-rpc interface. The easiest option is to use a public node e.g. infura’s, or connect to a locally-run node such as geth or parity. diff --git a/ethereum_smart_contract_requirements.txt b/ethereum_smart_contract_requirements.txt index b9e889d4..cbdc14f8 100644 --- a/ethereum_smart_contract_requirements.txt +++ b/ethereum_smart_contract_requirements.txt @@ -1,2 +1,18 @@ coincurve==7.1.0 web3>=5 +ipfshttpclient +configargparse==0.12.0 +lds-merkle-proof-2019-bloxberg +cert-core-bloxberg==2.1.2 +cert-schema +chainpoint3>=0.0.2 +configargparse==0.12.0 +glob2==0.6 +mock==2.0.0 +requests[security]>=2.18.4 +pycoin==0.80 +pyld>=1.0.3 +pysha3>=1.0.2 +python-bitcoinlib>=0.10.1 +tox>=3.0.0 +jsonschema<3 diff --git a/examples/data-testnet/unsigned_certificates/verifiable-credential.json b/examples/data-testnet/unsigned_certificates/verifiable-credential.json new file mode 100644 index 00000000..37ab8c1d --- /dev/null +++ b/examples/data-testnet/unsigned_certificates/verifiable-credential.json @@ -0,0 +1,20 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.blockcerts.org/schema/3.0-alpha/context.json", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "urn:uuid:bbba8553-8ec1-445f-82c9-a57251dd731c", + "type": [ + "VerifiableCredential", + "BlockcertsCredential" + ], + "issuer": "did:example:23adb1f712ebc6f1c276eba4dfa", + "issuanceDate": "2010-01-01T19:33:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "alumniOf": { + "id": "did:example:c276e12ec21ebfeb1f712ebc6f1" + } + } +} diff --git a/requirements.txt b/requirements.txt index fd717916..0fd28a51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cert-core>=2.1.9 +cert-core-bloxberg==2.1.2 cert-schema>=2.1.5 chainpoint3>=0.0.2 configargparse==0.12.0 diff --git a/tests/test_certificate_handler.py b/tests/test_certificate_handler.py index 6aee45e7..ab35755c 100644 --- a/tests/test_certificate_handler.py +++ b/tests/test_certificate_handler.py @@ -6,63 +6,22 @@ from pycoin.serialize import b2h from mock import patch, mock_open -from cert_issuer.certificate_handlers import CertificateWebV2Handler, CertificateV2Handler, CertificateBatchHandler, CertificateHandler, CertificateBatchWebHandler +from cert_issuer.certificate_handlers import CertificateWebV3Handler, CertificateV3Handler, CertificateBatchHandler, CertificateHandler, CertificateBatchWebHandler from cert_issuer.merkle_tree_generator import MerkleTreeGenerator from cert_issuer import helpers +from cert_core import Chain from mock import ANY class TestCertificateHandler(unittest.TestCase): def _proof_helper(self, chain): proof = { - 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', - 'type': ['MerkleProof2017', 'Extension'], - 'targetHash': ANY, - 'anchors': [ - { - 'sourceId': ANY, - 'type': chain.blockchain_type.external_display_value, - 'chain': chain.external_display_value - } - ], - 'proof': [ - {'right': 'd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35'}, - {'right': '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'} - ], + 'type': 'MerkleProof2019', + 'created': ANY, + 'proofValue': ANY, + 'proofPurpose': 'assertionMethod', + 'verificationMethod': ANY } - - proof_1 = { - 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', - 'type': ['MerkleProof2017', 'Extension'], - 'targetHash': ANY, - 'anchors': [ - { - 'sourceId': ANY, - 'type': chain.blockchain_type.external_display_value, - 'chain': chain.external_display_value - } - ], - 'proof': [ - {'left': '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b'}, - {'right': '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'} - ] - } - - proof_2 = { - 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', - 'type': ['MerkleProof2017', 'Extension'], - 'targetHash': ANY, - 'anchors': [ - { - 'sourceId': ANY, - 'type': chain.blockchain_type.external_display_value, - 'chain': chain.external_display_value - } - ], - 'proof': [ - {'left': '4295f72eeb1e3507b8461e240e3b8d18c1e7bd2f1122b11fc9ec40a65894031a'} - ] - } - return proof, proof_1, proof_2 + return proof def _helper_mock_call(self, *args): helper_mock = mock.MagicMock() @@ -83,10 +42,14 @@ def _get_certificate_batch_web_handler(self): certificates_to_issue['2'] = mock.Mock() certificates_to_issue['3'] = mock.Mock() + config = mock.Mock() + config.issuing_address = "http://example.com" + handler = CertificateBatchWebHandler( secret_manager=secret_manager, certificate_handler=DummyCertificateHandler(), - merkle_tree=MerkleTreeGenerator()) + merkle_tree=MerkleTreeGenerator(), + config=config) return handler, certificates_to_issue @@ -97,10 +60,14 @@ def _get_certificate_batch_handler(self): certificates_to_issue['2'] = mock.Mock() certificates_to_issue['3'] = mock.Mock() + config = mock.Mock() + config.issuing_address = "http://example.com" + handler = CertificateBatchHandler( secret_manager=secret_manager, certificate_handler=DummyCertificateHandler(), - merkle_tree=MerkleTreeGenerator()) + merkle_tree=MerkleTreeGenerator(), + config=config) return handler, certificates_to_issue @@ -129,8 +96,8 @@ def test_batch_web_handler_finish_batch(self): certificate_batch_handler.set_certificates_in_batch(certificates_to_issue) result = certificate_batch_handler.prepare_batch() - chain = mock.Mock() - proof, proof_1, proof_2 = self._proof_helper(chain) + chain = Chain.bitcoin_mainnet + proof = self._proof_helper(chain) with patch.object(DummyCertificateHandler, 'add_proof', return_value= {"cert": "cert"} ) as mock_method: result = certificate_batch_handler.finish_batch( @@ -138,8 +105,6 @@ def test_batch_web_handler_finish_batch(self): ) self.assertEqual(certificate_batch_handler.proof, [{'cert': 'cert'}, {'cert': 'cert'}, {'cert': 'cert'}]) mock_method.assert_any_call(ANY, proof) - mock_method.assert_any_call(ANY, proof_1) - mock_method.assert_any_call(ANY, proof_2) def test_batch_handler_finish_batch(self): app_config = mock.Mock() @@ -150,8 +115,11 @@ def test_batch_handler_finish_batch(self): certificate_batch_handler.set_certificates_in_batch(certificates_to_issue) result = certificate_batch_handler.prepare_batch() - chain = mock.Mock() - proof, proof_1, proof_2 = self._proof_helper(chain) + chain = Chain.bitcoin_mainnet + proof = self._proof_helper(chain) + + config = mock.Mock() + config.issuing_address = "http://example.com" with patch.object(DummyCertificateHandler, 'add_proof') as mock_method: result = certificate_batch_handler.finish_batch( @@ -159,8 +127,6 @@ def test_batch_handler_finish_batch(self): ) mock_method.assert_any_call(ANY, proof) - mock_method.assert_any_call(ANY, proof_1) - mock_method.assert_any_call(ANY, proof_2) def test_pre_batch_actions(self): self.directory_count = 1 @@ -202,18 +168,18 @@ def test_pre_batch_actions_empty_directories(self): @mock.patch("builtins.open", create=True) def test_add_proof(self,mock_open): - handler = CertificateV2Handler() + handler = CertificateV3Handler() cert_to_issue = {'kek':'kek'} proof = {'a': 'merkel'} - file_call = 'call().__enter__().write(\'{"kek": "kek", "signature": {"a": "merkel"}}\')' + file_call = 'call().__enter__().write(\'{"kek": "kek", "proof": {"a": "merkel"}}\')' chain = mock.Mock() metadata = mock.Mock() metadata.blockchain_cert_file_name = 'file_path.nfo' with patch.object( - CertificateV2Handler, '_get_certificate_to_issue', return_value=cert_to_issue) as mock_method: + CertificateV3Handler, '_get_certificate_to_issue', return_value=cert_to_issue) as mock_method: handler.add_proof(metadata, proof) mock_open.assert_any_call('file_path.nfo','w') @@ -222,7 +188,7 @@ def test_add_proof(self,mock_open): assert file_call in call_strings def test_web_add_proof(self): - handler = CertificateWebV2Handler() + handler = CertificateWebV3Handler() proof = {'a': 'merkel'} chain = mock.Mock() certificate_json = {'kek': 'kek'} @@ -232,6 +198,8 @@ def test_web_add_proof(self): class DummyCertificateHandler(CertificateHandler): def __init__(self): + self.config = mock.Mock() + self.config.issuing_address = "http://example.com" self.counter = 0 def validate_certificate(self, certificate_metadata): diff --git a/tests/test_merkle_tree_generator.py b/tests/test_merkle_tree_generator.py index 2bf3d5af..922b4938 100644 --- a/tests/test_merkle_tree_generator.py +++ b/tests/test_merkle_tree_generator.py @@ -6,6 +6,8 @@ from pycoin.serialize import b2h from cert_issuer.merkle_tree_generator import MerkleTreeGenerator +from cert_issuer import helpers +from lds_merkle_proof_2019.merkle_proof_2019 import MerkleProof2019 def get_test_data_generator(): @@ -45,26 +47,54 @@ def do_test_signature(self, chain, display_chain, type): merkle_tree_generator.populate(get_test_data_generator()) _ = merkle_tree_generator.get_blockchain_data() gen = merkle_tree_generator.get_proof_generator( - '8087c03e7b7bc9ca7b355de9d9d8165cc5c76307f337f0deb8a204d002c8e582', app_config, chain) + '8087c03e7b7bc9ca7b355de9d9d8165cc5c76307f337f0deb8a204d002c8e582', 'http://example.com', chain) p1 = next(gen) _ = next(gen) p3 = next(gen) - p1_expected = {'type': ['MerkleProof2017', 'Extension'], - 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', - 'targetHash': '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', - 'proof': [{'right': 'd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35'}, - {'right': '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'}], - 'anchors': [ - {'sourceId': '8087c03e7b7bc9ca7b355de9d9d8165cc5c76307f337f0deb8a204d002c8e582', - 'type': type, - 'chain': display_chain}]} - p3_expected = {'type': ['MerkleProof2017', 'Extension'], - 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', - 'targetHash': '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', - 'proof': [{'left': '4295f72eeb1e3507b8461e240e3b8d18c1e7bd2f1122b11fc9ec40a65894031a'}], - 'anchors': [{'sourceId': '8087c03e7b7bc9ca7b355de9d9d8165cc5c76307f337f0deb8a204d002c8e582', - 'type': type, - 'chain': display_chain}]} + + p1_json_proof = { + 'path': [ + {'right': 'd4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35'}, + {'right': '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'} + ], + 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', + 'targetHash': '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b', + 'anchors': [ + helpers.tx_to_blink(chain, '8087c03e7b7bc9ca7b355de9d9d8165cc5c76307f337f0deb8a204d002c8e582') + ] + } + mp2019 = MerkleProof2019() + proof_value = mp2019.encode(p1_json_proof) + + p1_expected = { + "type": "MerkleProof2019", + "created": p1['created'], + "proofValue": proof_value.decode('utf8'), + "proofPurpose": "assertionMethod", + "verificationMethod": "http://example.com" + } + + p3_json_proof = { + 'path': [ + {'left': '4295f72eeb1e3507b8461e240e3b8d18c1e7bd2f1122b11fc9ec40a65894031a'} + ], + 'merkleRoot': '0932f1d2e98219f7d7452801e2b64ebd9e5c005539db12d9b1ddabe7834d9044', + 'targetHash': '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', + 'anchors': [ + helpers.tx_to_blink(chain, '8087c03e7b7bc9ca7b355de9d9d8165cc5c76307f337f0deb8a204d002c8e582') + ] + } + mp2019 = MerkleProof2019() + proof_value = mp2019.encode(p3_json_proof) + + p3_expected = { + "type": "MerkleProof2019", + "created": p3['created'], + "proofValue": proof_value.decode('utf8'), + "proofPurpose": "assertionMethod", + "verificationMethod": "http://example.com" + } + self.assertEqual(p1, p1_expected) self.assertEqual(p3, p3_expected) diff --git a/tox.ini b/tox.ini index f731e862..fa5e7bc5 100644 --- a/tox.ini +++ b/tox.ini @@ -11,5 +11,6 @@ envlist = py36 changedir=tests deps= pytest + -rrequirements.txt commands=py.test --basetemp={envtmpdir} {posargs} # substitute with tox' positional arguments diff --git a/work/blockchain_certificates/96e4392d-461e-473c-b751-60f0dc408136.json b/work/blockchain_certificates/96e4392d-461e-473c-b751-60f0dc408136.json new file mode 100644 index 00000000..c6e0aa06 --- /dev/null +++ b/work/blockchain_certificates/96e4392d-461e-473c-b751-60f0dc408136.json @@ -0,0 +1 @@ +{"@context": ["https://www.w3.org/2018/credentials/v1", "https://w3id.org/blockcerts/schema/3.0-alpha/context.json"], "id": "urn:uuid:96e4392d-461e-473c-b751-60f0dc408136", "type": ["VerifiableCredential", "BlockcertsCredential"], "issuer": "https://raw.githubusercontent.com/AnthonyRonning/https-github.com-labnol-files/master/issuer-eth.json", "issuanceDate": "2020-08-20T12:14:10.363378+00:00", "credentialSubject": {"id": "einstein@mpg.de", "alumniOf": {"id": "https://bloxberg.org"}}, "SHA256Hash": "0x0e4ded5319861c8daac00d425c53a16bd180a7d01a340a0e00f7dede40d2c9f6", "proof": {"type": "MerkleProof2019", "created": "2020-08-20T12:15:50.473528", "proofValue": "zTAfB64R9FnirA3SRy33KqoBNysBwHuFKYP2P5kyMMWHX6hyyMZgJJRzzPjQRMwtCPxZNyHR8eeCwXCx8gRPP1cAccZLEotjmkQYVUNfUGC92fnJBuMq5BatkuGV9GZsszwzevfEGtXdCVhXmMxFevDhS8ELPQBHoNS7B4RFmKBNGjDJmpbrARY7iDC9hQLdWqFQmX6ucyGUNKFmibBeKWNNkGuboVZpzDsokDmsRAwFjDk3vbb4EZsTtwFq4h8WsqmWZGsVBKBasL5fgY9eegoVdHFeaVG9mxy6CRtBVSSQcaXPQJ3D7isPA8PCT5k6eb4ZpHCUvrug3zh181nPFci6FomhtyPD7mgCQiDtMjQNpFMUiLYswSHFA2EWx1iCBg6gCkUodLCvKEpQcWid7LhQ", "proofPurpose": "assertionMethod", "verificationMethod": "ecdsa-koblitz-pubkey:0xD748BF41264b906093460923169643f45BDbC32e", "ens_name": "mpdl.berg"}} \ No newline at end of file diff --git a/work/blockchain_certificates/bd2a927d-9374-4b5a-a923-e52aa215a4f7.json b/work/blockchain_certificates/bd2a927d-9374-4b5a-a923-e52aa215a4f7.json new file mode 100644 index 00000000..db11e3fe --- /dev/null +++ b/work/blockchain_certificates/bd2a927d-9374-4b5a-a923-e52aa215a4f7.json @@ -0,0 +1 @@ +{"@context": ["https://www.w3.org/2018/credentials/v1", "https://w3id.org/blockcerts/schema/3.0-alpha/context.json"], "id": "urn:uuid:bd2a927d-9374-4b5a-a923-e52aa215a4f7", "type": ["VerifiableCredential", "BlockcertsCredential"], "issuer": "https://raw.githubusercontent.com/AnthonyRonning/https-github.com-labnol-files/master/issuer-eth.json", "issuanceDate": "2020-08-20T12:14:10.363378+00:00", "credentialSubject": {"id": "einstein@mpg.de", "alumniOf": {"id": "https://bloxberg.org"}}, "SHA256Hash": "0xfda3124d5319861c8daac00d425c53a16bd180a7d01a340a0e00f7dede40d2c9f6", "proof": {"type": "MerkleProof2019", "created": "2020-08-20T12:15:50.692579", "proofValue": "zTAfB64R8rnijzFj9xZceVEQsH7yrpVvrc2bDidHKGYJKPpe1K3dtYgspAdJDDsNyd3yNnbrAAM1ZXwqVzCYvcAtHhqVxFxt6v4PXrCdbF6hCaFed3tQoUwhf8yXLAhWHNtjaSYawFBvyTMSJZSeYRQqXY8iedcrDb1Qx646TsBkGMaSVz7jhTNJt6Eeo6Jq1tN2Z7kR7WCCmqBNyjBZw7K39Lvw6n9qBTuNsm88LEbMXnb8ESEbRccQeUqJJXHXmhjDshuvbhpHGfHmJZ31AMEV33qmXzipsdAD4vKhdVRjnovm1TYkgnzNWg8Wv37Smi6oXySvMDjhptE5XTYLZNxuXyAh9NMdKrnEJeAXBNHvqS3ayARHemaTBGqyGJFxJNh14xX8FkKtnQTNwbu98Dcn", "proofPurpose": "assertionMethod", "verificationMethod": "ecdsa-koblitz-pubkey:0xD748BF41264b906093460923169643f45BDbC32e", "ens_name": "mpdl.berg"}} \ No newline at end of file diff --git a/work/unsigned_certificates/.placeholder b/work/unsigned_certificates/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/work/unsigned_certificates/96e4392d-461e-473c-b751-60f0dc408136.json b/work/unsigned_certificates/96e4392d-461e-473c-b751-60f0dc408136.json new file mode 100644 index 00000000..8c7deda5 --- /dev/null +++ b/work/unsigned_certificates/96e4392d-461e-473c-b751-60f0dc408136.json @@ -0,0 +1 @@ +{"@context": ["https://www.w3.org/2018/credentials/v1", "https://w3id.org/blockcerts/schema/3.0-alpha/context.json"], "id": "urn:uuid:96e4392d-461e-473c-b751-60f0dc408136", "type": ["VerifiableCredential", "BlockcertsCredential"], "issuer": "https://raw.githubusercontent.com/AnthonyRonning/https-github.com-labnol-files/master/issuer-eth.json", "issuanceDate": "2020-08-20T12:14:10.363378+00:00", "credentialSubject": {"id": "einstein@mpg.de", "alumniOf": {"id": "https://bloxberg.org"}}, "SHA256Hash": "0x0e4ded5319861c8daac00d425c53a16bd180a7d01a340a0e00f7dede40d2c9f6"} \ No newline at end of file diff --git a/work/unsigned_certificates/bd2a927d-9374-4b5a-a923-e52aa215a4f7.json b/work/unsigned_certificates/bd2a927d-9374-4b5a-a923-e52aa215a4f7.json new file mode 100644 index 00000000..ff25841a --- /dev/null +++ b/work/unsigned_certificates/bd2a927d-9374-4b5a-a923-e52aa215a4f7.json @@ -0,0 +1 @@ +{"@context": ["https://www.w3.org/2018/credentials/v1", "https://w3id.org/blockcerts/schema/3.0-alpha/context.json"], "id": "urn:uuid:bd2a927d-9374-4b5a-a923-e52aa215a4f7", "type": ["VerifiableCredential", "BlockcertsCredential"], "issuer": "https://raw.githubusercontent.com/AnthonyRonning/https-github.com-labnol-files/master/issuer-eth.json", "issuanceDate": "2020-08-20T12:14:10.363378+00:00", "credentialSubject": {"id": "einstein@mpg.de", "alumniOf": {"id": "https://bloxberg.org"}}, "SHA256Hash": "0xfda3124d5319861c8daac00d425c53a16bd180a7d01a340a0e00f7dede40d2c9f6"} \ No newline at end of file