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

Introducing Smart Contracts to BlockCerts #27

Open
wants to merge 91 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
372a9d1
fix requirements
Jan 12, 2020
c61de65
add gate for ens verify path
Jan 12, 2020
dde2747
Possible MVP
Jan 12, 2020
4efefa5
change names for usb and file paths
Jan 12, 2020
e886b92
some fixes regarding the ens impl
flamestro Jan 13, 2020
974b8a7
some stuff with klea
flamestro Jan 13, 2020
f030ac9
with klea: fixed wrong argument problem
flamestro Jan 13, 2020
967193d
added a verification step for ENS
kleapaci Jan 13, 2020
09ce063
fix output
flamestro Jan 13, 2020
9934ef4
Merge remote-tracking branch 'origin/flamestros_sandbox' into flamest…
flamestro Jan 13, 2020
ed8a0bd
fix output
flamestro Jan 15, 2020
0a6ab3f
fix requirements
marcplustwo Jan 16, 2020
d907e1d
with Klea: implement in Steps
flamestro Jan 16, 2020
4f533f8
Merge remote-tracking branch 'origin/flamestros_sandbox' into flamest…
flamestro Jan 16, 2020
3b72b92
with Klea: began fixing tamperedcheck
flamestro Jan 16, 2020
8718103
fix tampered check
flamestro Jan 16, 2020
0598f64
Merge pull request #1 from flamestro/flamestros_sandbox
flamestro Jan 16, 2020
c009304
fix ens verification
flamestro Jan 16, 2020
0d2f316
put abi into cert (into revokedList as placeholder)
flamestro Jan 16, 2020
a595f23
put ens as last step, to check if hashes are valid, even if ens is not
flamestro Jan 19, 2020
97ddc83
fix tests
flamestro Jan 19, 2020
63cc564
fix NormalizedJsonLdIntegrityCheckerSC
flamestro Jan 19, 2020
8304d6d
remove unused import
flamestro Jan 19, 2020
5fa95f4
adding validsc.json and check if all tests still pass
flamestro Jan 19, 2020
4b7764c
add test for validsc
flamestro Jan 19, 2020
97d9838
change cert json calls to cert model calls
flamestro Jan 20, 2020
83f457e
beautify
flamestro Jan 20, 2020
d400f8b
fix forced targethash
flamestro Jan 20, 2020
3db1870
change information places
flamestro Jan 23, 2020
6bad50e
finish changing fields
flamestro Jan 23, 2020
9b09515
remove config.py (not needed)
flamestro Jan 23, 2020
7181621
refactor
flamestro Jan 23, 2020
32da5a6
fix tampered check and add tampered test
flamestro Jan 23, 2020
4a802ba
small fixes
kleapaci Jan 25, 2020
4cf66c3
readded config.py
kleapaci Jan 25, 2020
8c98488
Merge pull request #2 from flamestro/small-fixes
flamestro Jan 25, 2020
229c4c7
refactor
flamestro Jan 27, 2020
ef82a21
remove abi file
flamestro Jan 27, 2020
f72e9b6
create new cert verif style
flamestro Jan 28, 2020
4440ac0
change config.py
kleapaci Jan 29, 2020
a2519fe
added conf.ini
kleapaci Jan 29, 2020
c68b5ec
fixed Value Error
kleapaci Jan 29, 2020
58bdec5
fix variable name
kleapaci Jan 29, 2020
2e5b2e7
small fix
kleapaci Jan 29, 2020
d4c5415
create new cert verif style
flamestro Jan 30, 2020
763df8e
extend/fix config
flamestro Jan 31, 2020
629cf4c
import json fix
flamestro Jan 31, 2020
b6cdfd0
fix tests by fixing path method - make ropsten default
flamestro Jan 31, 2020
ed36d97
refactor
flamestro Jan 31, 2020
ac0d5ba
clearer comments and outputs, also less code complexity by assumption…
flamestro Jan 31, 2020
c26cc5f
change output to be more readable
flamestro Jan 31, 2020
8e7c08c
refactor
flamestro Jan 31, 2020
35fbe72
Update Verifier.py, to match description to new impl
flamestro Feb 1, 2020
95aae03
add issuer outputs to every cert version, and print certfile name on …
flamestro Feb 3, 2020
21efc45
Merge remote-tracking branch 'origin/master'
flamestro Feb 3, 2020
fb9f1c2
checksum ens registry mainnet
kleapaci Feb 3, 2020
713dc4a
Merge pull request #3 from flamestro/small-fix
kleapaci Feb 3, 2020
e9ad72c
add testcase for revocation
flamestro Feb 3, 2020
0a0a787
Merge remote-tracking branch 'origin/master'
flamestro Feb 3, 2020
b88c980
rename testcase valid.json to ens fail -> because we connected the en…
flamestro Feb 3, 2020
d02e3ac
add fixed indexes in tests
flamestro Feb 3, 2020
307904a
add assertEqual to check if verification fails
flamestro Feb 3, 2020
a94aca7
new ens registry
kleapaci Feb 4, 2020
f32764a
add valid cert test and test file for last refinements
flamestro Feb 5, 2020
b42a2ef
Merge remote-tracking branch 'origin/master'
flamestro Feb 5, 2020
9d35764
Config.ini naming and extended readme
flamestro Feb 8, 2020
7fd2d5c
refactor README.md
flamestro Feb 8, 2020
4e3a361
refactor README.md
flamestro Feb 8, 2020
5ee371d
remove chain from config, because this should be read from certificates
flamestro Feb 13, 2020
44189be
naming resolver to registry
flamestro Feb 14, 2020
b9e54eb
remove ens registry as an mandatory config option
flamestro Feb 14, 2020
6169fc4
reduce redundancy and clean up
flamestro Feb 14, 2020
7d894d4
reduce redundancy and clean up
flamestro Feb 14, 2020
c5e04db
clarify why test would could fail
flamestro Feb 16, 2020
4ac057b
since the ens registry 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e is …
flamestro Feb 16, 2020
a4cf0b3
adjust ens registry in sample config
flamestro Feb 17, 2020
054491a
add solc to readme and fix enumeration
flamestro Feb 18, 2020
7d7ed39
remove solc from readme
flamestro Feb 18, 2020
f91ca67
refactor
flamestro Feb 18, 2020
e782bfd
format README.md
flamestro Feb 18, 2020
637c5cb
format README.md
flamestro Feb 18, 2020
1585f20
format
flamestro Feb 18, 2020
31f043b
add transaction id again
flamestro Feb 18, 2020
26e139d
adjust tests to match new contract_address field
flamestro Feb 18, 2020
9a29953
set config cwd in __init__.py
flamestro Feb 18, 2020
0472f5c
set config cwd in verifier.py
flamestro Feb 18, 2020
37ee487
fix tests by changing approach of setting the configs cwd
flamestro Feb 19, 2020
eeb6c06
remove test.json, because it is not used anymore
flamestro Feb 20, 2020
99311be
make printer less redundant
flamestro Feb 21, 2020
3bb836f
add docs for ethereum smart contract
marcplustwo Feb 25, 2020
ead0341
Update ethereum_smart_contract.md
marcplustwo Mar 6, 2020
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
26 changes: 12 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,22 @@ Library for verifying blockchain certificates.

The most common way to use this is to add the [latest cert-verifier pypi package](https://badge.fury.io/py/cert-verifier) to your project dependencies.

## Verify a certificate by command line

1. Ensure you have an python environment. [Recommendations](https://github.com/blockchain-certificates/cert-issuer/blob/master/docs/virtualenv.md)
## Configuration

2. Git clone the repository and change to the directory

```bash
git clone https://github.com/blockchain-certificates/cert-verifier.git && cd cert-verifier
```
Set the ethereum node you want to use in the verification processes in ``` cert-verifier/cert_verifier/config.ini ```.

3. Run cert-viewer setup
The Provided ones can also be used and should work out of the box, but keep in mind that they are only recommendations.

```bash
pip install .
```

4. Run the main program
If you want to change the ens nodes as well you can use the ```conf_sample.ini``` schema as a structure guideline.
## Verify a certificate by command line

1. Ensure you have an python environment. [Recommendations](https://github.com/blockchain-certificates/cert-issuer/blob/master/docs/virtualenv.md)
1. Git clone the repository and change to the directory
```
bash git clone https://github.com/blockchain-certificates/cert-verifier.git && cd cert-verifier
```
1. Run cert-viewer setup ```bash pip install . ```
1. Run the main program
```bash
cd cert_verifier
python verifier.py
Expand Down
98 changes: 97 additions & 1 deletion cert.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions cert_verifier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

unhexlify = binascii.unhexlify
hexlify = binascii.hexlify


if sys.version > '3':
def unhexlify(h): return binascii.unhexlify(h.encode('utf8'))

Expand Down
175 changes: 132 additions & 43 deletions cert_verifier/checks.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import hashlib
import json
import logging
from datetime import datetime
from json import JSONDecodeError
from threading import Lock

import bitcoin
import hashlib
import pytz
from bitcoin.signmessage import BitcoinMessage, VerifyMessage
from cert_schema import BlockcertValidationError
from cert_schema import normalize_jsonld
from cert_core import BlockcertVersion, Chain
from cert_core import chain_to_bitcoin_network
from cert_core.cert_model.model import SignatureType
from chainpoint.chainpoint import Chainpoint
from cert_schema import BlockcertValidationError
from cert_schema import normalize_jsonld
from chainpoint3 import Chainpoint
from ens import ENS

from cert_verifier import StepStatus
from cert_verifier import config
from cert_verifier.connectors import ContractConnection, MakeW3
from cert_verifier.errors import InvalidCertificateError

lock = Lock()
Expand All @@ -29,6 +33,30 @@ def hashes_match(actual_hash, expected_hash):
return actual_hash in expected_hash or actual_hash == expected_hash


def verify_hash(hash_val, certificate_model, is_batch_hash=False):
try:
sc = ContractConnection(certificate_model)
except (KeyError, JSONDecodeError):
print("Could not load smart contract")
cert_status = sc.functions.call("hashes", hash_val)
if cert_status == 0:
if not is_batch_hash:
result = True
else:
result = False
return {"validity": result,
"name": "ethcheck",
"status": " hash is not issued"}
elif cert_status == 1:
return {"validity": True,
"name": "ethcheck",
"status": " hash is valid on"}
elif cert_status == 2:
return {"validity": False,
"name": "ethcheck",
"status": " hash is revoked on"}


class VerificationCheck(object):
"""Individual task involved in verification"""

Expand Down Expand Up @@ -219,6 +247,41 @@ def do_execute(self):
return False


class EnsChecker(VerificationCheck):
def __init__(self, cert_model):
self.ens_name = cert_model.certificate_json["signature"]["anchors"][0]["ens_name"]
self.contract_address = cert_model.certificate_json["signature"]["anchors"][0]["contract_address"]
self.chain = cert_model.certificate_json["signature"]["anchors"][0]["chain"]

def do_execute(self):
registry = config.get_registry()
w3_factory = MakeW3(self.chain)
w3 = w3_factory.get_w3_obj()
ns = ENS.fromWeb3(w3, registry)
address = ns.address(self.ens_name)
return address == self.contract_address


class HashValidityChecker(VerificationCheck):
def __init__(self, merkle_root, target_hash, certificate_model):
self.merkle_root = merkle_root
self.target_hash = target_hash
self.certificate_model = certificate_model

def do_execute(self):
if self.merkle_root == self.target_hash:
targethashverif = verify_hash(self.target_hash, self.certificate_model, True)
validity = targethashverif["validity"]
else:
merkleverif = verify_hash(self.merkle_root, self.certificate_model, True)
validity = merkleverif["validity"]

targethashverif = verify_hash(self.target_hash, self.certificate_model)
validity &= targethashverif["validity"]

return validity


# Verification group creators

def create_embedded_signature_verification_group(signatures, transaction_info, chain):
Expand All @@ -232,26 +295,37 @@ def create_embedded_signature_verification_group(signatures, transaction_info, c
return VerificationGroup(steps=[signature_check], name='Checking issuer signature')


def create_anchored_data_verification_group(signatures, chain, transaction_info, detect_unmapped_fields=False):
def create_anchored_data_verification_group(certificate_model, chain, transaction_info=None,
detect_unmapped_fields=False, is_issued_on_smartcontract=False):
anchored_data_verification = None
signatures = certificate_model.signatures
for s in signatures:
if s.signature_type == SignatureType.signed_transaction:
if s.merkle_proof:
steps = [ReceiptIntegrityChecker(s.merkle_proof.proof_json),
NormalizedJsonLdIntegrityChecker(s.content_to_verify, s.merkle_proof.target_hash,
detect_unmapped_fields=detect_unmapped_fields)]
if chain != Chain.mockchain and chain != Chain.bitcoin_regtest:
steps.append(MerkleRootIntegrityChecker(s.merkle_proof.merkle_root, transaction_info.op_return))

anchored_data_verification = VerificationGroup(
steps=steps,
name='Checking certificate has not been tampered with')
else:
anchored_data_verification = VerificationGroup(
steps=[BinaryFileIntegrityChecker(s.content_to_verify, transaction_info)],
name='Checking certificate has not been tampered with')

break
if is_issued_on_smartcontract:
steps = [ReceiptIntegrityChecker(s.merkle_proof.proof_json),
NormalizedJsonLdIntegrityChecker(s.content_to_verify,
s.merkle_proof.target_hash,
detect_unmapped_fields=detect_unmapped_fields)]
anchored_data_verification = VerificationGroup(
steps=steps,
name='Checking certificate has not been tampered with')
else:
if s.signature_type == SignatureType.signed_transaction:
if s.merkle_proof:
steps = [ReceiptIntegrityChecker(s.merkle_proof.proof_json),
NormalizedJsonLdIntegrityChecker(s.content_to_verify, s.merkle_proof.target_hash,
detect_unmapped_fields=detect_unmapped_fields)]
if chain != Chain.mockchain and chain != Chain.bitcoin_regtest:
steps.append(MerkleRootIntegrityChecker(s.merkle_proof.merkle_root, transaction_info.op_return))

anchored_data_verification = VerificationGroup(
steps=steps,
name='Checking certificate has not been tampered with')
else:
anchored_data_verification = VerificationGroup(
steps=[BinaryFileIntegrityChecker(s.content_to_verify, transaction_info)],
name='Checking certificate has not been tampered with')
break
pass
return anchored_data_verification


Expand All @@ -267,13 +341,13 @@ def create_revocation_verification_group(certificate_model, issuer_info, transac
return VerificationGroup(steps=[revocation_check], name='Checking not revoked by issuer')


def create_verification_steps(certificate_model, transaction_info, issuer_info, chain):
def create_verification_steps(certificate_model, transaction_info=None, issuer_info=None, chain=None,
is_issued_on_smartcontract=False):
steps = []

v2ish = certificate_model.version == BlockcertVersion.V2 or certificate_model.version == BlockcertVersion.V2_ALPHA

# embedded signature: V1.1. and V1.2 must have this
if not v2ish:
if not v2ish and not is_issued_on_smartcontract:
embedded_signature_group = create_embedded_signature_verification_group(certificate_model.signatures,
transaction_info, chain)
if not embedded_signature_group:
Expand All @@ -282,10 +356,12 @@ def create_verification_steps(certificate_model, transaction_info, issuer_info,

# transaction-anchored data. All versions must have this. In V2 we add an extra check for unmapped fields
detect_unmapped_fields = v2ish
transaction_signature_group = create_anchored_data_verification_group(certificate_model.signatures,
chain,
transaction_info,
detect_unmapped_fields)
transaction_signature_group = create_anchored_data_verification_group(certificate_model=certificate_model,
chain=chain,
transaction_info=transaction_info,
detect_unmapped_fields=detect_unmapped_fields,
is_issued_on_smartcontract=is_issued_on_smartcontract)

if not transaction_signature_group:
raise InvalidCertificateError('Did not find transaction verification info in certificate')
steps.append(transaction_signature_group)
Expand All @@ -295,18 +371,31 @@ def create_verification_steps(certificate_model, transaction_info, issuer_info,
steps.append(VerificationGroup(steps=[expired_group],
name='Checking certificate has not expired'))

# revocation check. All versions have this
revocation_group = create_revocation_verification_group(certificate_model, issuer_info, transaction_info)
steps.append(revocation_group)

# authenticity check
if chain != Chain.mockchain and chain != Chain.bitcoin_regtest:
key_map = {k.public_key: k for k in issuer_info.issuer_keys}
authenticity_checker = AuthenticityChecker(transaction_info.signing_key, transaction_info.date_time_utc,
key_map)
steps.append(VerificationGroup(steps=[authenticity_checker],
name='Checking authenticity'))

if chain == Chain.mockchain or chain == Chain.bitcoin_regtest:
return VerificationGroup(steps=steps, name='Validation', success_status=StepStatus.mock_passed)
if is_issued_on_smartcontract:
for s in certificate_model.signatures:
# check if certificates hash is really issued and has not been revoked
hash_group = HashValidityChecker(s.merkle_proof.merkle_root,
s.merkle_proof.target_hash,
certificate_model)
steps.append(VerificationGroup(steps=[hash_group],
name='Checking if certificate is valid and has not been revoked'))

# ens check -> checks if smartcontract in signatures really belongs to the ens entry
ens_group = EnsChecker(certificate_model)
steps.append(VerificationGroup(steps=[ens_group], name='Checking if certificate is really issued by ens owner'))
else:
# revocation check. All versions have this
revocation_group = create_revocation_verification_group(certificate_model, issuer_info, transaction_info)
steps.append(revocation_group)

# authenticity check
if chain != Chain.mockchain and chain != Chain.bitcoin_regtest:
key_map = {k.public_key: k for k in issuer_info.issuer_keys}
authenticity_checker = AuthenticityChecker(transaction_info.signing_key, transaction_info.date_time_utc,
key_map)
steps.append(VerificationGroup(steps=[authenticity_checker],
name='Checking authenticity'))

if chain == Chain.mockchain or chain == Chain.bitcoin_regtest:
return VerificationGroup(steps=steps, name='Validation', success_status=StepStatus.mock_passed)
return VerificationGroup(steps=steps, name='Validation')
3 changes: 3 additions & 0 deletions cert_verifier/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# the node addresses which should be used to connect to the ethereum blockchain
node_ropsten = https://ropsten.infura.io/v3/a70de76e3fd748cbb6dbb2ed49dda183
node_mainnet = https://mainnet.infura.io/v3/a70de76e3fd748cbb6dbb2ed49dda183
54 changes: 54 additions & 0 deletions cert_verifier/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import os
import configargparse
from web3 import Web3, HTTPProvider

_CWD = None
_CONFIG = None


def add_arguments(p):
p.add('-c', '--my-config', required=False, env_var='CONFIG_FILE',
is_config_file=True, help='config file path')
p.add_argument('--node_ropsten', help='infura ropsten', env_var='INFURA_ROPSTEN')
p.add_argument('--node_mainnet', help='infura mainnet', env_var='INFURA_MAINNET')
p.add_argument('--ens_registry', required=False, default="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
help='mainnet', env_var='REGISTRY')


def read_config():
p = configargparse.getArgumentParser(default_config_files=[os.path.join(_CWD, 'config.ini')])
add_arguments(p)
parsed_config, _ = p.parse_known_args()
return vars(parsed_config)


def init_config():
global _CONFIG
if _CONFIG is None:
_CONFIG = read_config()
else:
return _CONFIG


def init_cwd(cwd):
global _CWD
if _CWD is None:
_CWD = cwd
else:
return _CWD


def get_infura(chain):
init_config()
global _CONFIG
if chain == "ethereumMainnet":
return _CONFIG["node_mainnet"]
else:
return _CONFIG["node_ropsten"]


def get_registry():
init_config()
global _CONFIG
w3 = Web3(HTTPProvider())
return w3.toChecksumAddress(_CONFIG["ens_registry"])
Loading