def test_sharing_late():
    contract, nodes = setup_multiple(register=True)

    utils.run([node.share_key for node in nodes[1:]])
    utils.mine_blocks_until(lambda: not contract.in_sharing_phase())

    with pytest.raises(ValueError, match='.*key sharing failed.*'):
        nodes[0].share_key()
def test_registration_prohibt_late_submission():
    node, contract = setup_single()
    utils.mine_blocks_until(lambda: not contract.in_registration_phase())

    with pytest.raises(AssertionError):
        node.register()
    with pytest.raises(ValueError, match='.*registration failed.*'):
        node.register(check_contract_phase=False)
    events = utils.get_events(contract, EVENT_REGISTRATION)
    assert len(events) == 0
def test_init_secret_sharing():
    contract, nodes = setup_multiple()

    utils.run([node.register for node in nodes])
    utils.mine_blocks_until(contract.registrations_confirmed)

    utils.run([node.init_secret_sharing for node in nodes])

    for node in nodes:
        assert len(node.nodes) == len(nodes)
def test_verification_of_valid_shares(test_dispute=True):
    contract, nodes = setup_multiple(register=True)

    utils.run([node.share_key for node in nodes])
    utils.mine_blocks_until(contract.sharing_confirmed)

    # as everything is valid, no node should raise an exception during loading of the shares
    for node in nodes:
        node.load_shares()

    if test_dispute:
        with pytest.raises(ValueError,
                           match='.*dispute failed.*share was valid.*'):
            nodes[0].dispute(issuer_id=nodes[-1].id)
def test_verification_invalid_public_coefficients(test_dispute=True):
    contract, nodes = setup_multiple(3, register=True)
    issuer, verifier = nodes[0], nodes[2]

    # invalidate first public coefficient
    C1x, C1y = issuer.public_coefficients[0]
    issuer.public_coefficients[0] = (C1x + 1, C1y)

    utils.run([node.share_key for node in nodes])
    utils.mine_blocks_until(contract.sharing_confirmed)

    with pytest.raises(ValueError):
        verifier.load_shares()

    # dispute should succeed and not raise any error
    if test_dispute:
        verifier.dispute(issuer_id=nodes[0].id)
def test_upload_group_key():
    contract, nodes = setup_multiple(3, register=True)

    utils.run([node.share_key for node in nodes])
    utils.mine_blocks_until(contract.sharing_confirmed)

    utils.run([node.load_shares() for node in nodes])
    utils.mine_blocks_until(contract.dispute_confirmed)

    nodes[0].derive_group_keys()

    assert crypto.check_pairing(
        nodes[0].master_pk,
        crypto.G2,
        crypto.G1,
        nodes[0].master_bls_pk,
    )

    nodes[0].upload_group_key()
def test_verification_of_invalid_shares(test_dispute=True):
    contract, nodes = setup_multiple(3, register=True)
    issuer, verifier = nodes[0], nodes[2]

    share = issuer.shares[verifier.id - 1]
    invalid_share = share + 1
    invalid_encrypted_share = vss.encrypt_share(
        invalid_share, verifier.id, vss.shared_key(issuer.sk, verifier.pk))
    issuer.encrypted_shares[-1] = invalid_encrypted_share

    utils.run([node.share_key for node in nodes])
    utils.mine_blocks_until(contract.sharing_confirmed)

    with pytest.raises(ValueError):
        verifier.load_shares()

    # dispute should succeed and not raise any error
    if test_dispute:
        verifier.dispute(issuer_id=issuer.id)
def setup_multiple(num_nodes=3, register=False):
    assert num_nodes <= len(w3.eth.accounts), \
        "Each node needs a seperate account, update e.g. Ganache settings for more accounts"

    contract = utils.deploy_contract('DKG')
    nodes = [EthNode(w3.eth.accounts[i]) for i in range(num_nodes)]
    for node in nodes:
        c = utils.get_contract('DKG', contract.address)
        node.keygen()
        node.connect(c)

    if register:
        utils.run([node.register for node in nodes])
        utils.mine_blocks_until(contract.registrations_confirmed)
        utils.run([node.init_secret_sharing for node in nodes])

        # here we actually ensure that we return the nodes ordered by id, for easier tests
        nodes.sort(key=lambda node: node.id)

    return contract, nodes
def test_sharing_non_registered():

    contractA, nodes = setup_multiple(6)
    contractB = utils.deploy_contract('DKG')

    nodesA = nodes[:3]
    nodesB = nodes[3:]
    for b in nodesB:
        b.contract = contractB

    utils.run([node.register for node in nodesA])
    utils.run([node.register for node in nodesB])
    utils.mine_blocks_until(contractA.registrations_confirmed)
    utils.mine_blocks_until(contractB.registrations_confirmed)

    utils.run([node.init_secret_sharing for node in nodesA])
    utils.run([node.init_secret_sharing for node in nodesB])

    bad_node = nodesB[0]
    bad_node.contract = contractA

    with pytest.raises(ValueError, match='.*key sharing failed.*'):
        # bad_node is not registered for contract A, call to contract should therefore fail
        bad_node.share_key()
def run(num_nodes):
    contract = utils.deploy_contract('DKG')
    nodes = [EthNode(w3.eth.accounts[i]) for i in range(num_nodes)]

    tx_register = []
    tx_share_key = []
    tx_dispute = []
    tx_upload = None

    for i, node in enumerate(nodes):
        print(f'setting up node {i + 1}... ', end="", flush=True)
        c = utils.get_contract('DKG', contract.address)
        node.keygen()
        node.connect(c)
        print("done")
    print()

    for i, node in enumerate(nodes):
        print(f'registering node {i + 1}... ', end="", flush=True)
        tx = node.register()
        tx_register.append(tx)
        print("done")
    print()

    print(f'waiting for begin of key sharing phase... ', end='', flush=True)
    utils.mine_blocks_until(contract.registrations_confirmed)
    print('done\n')

    for i, node in enumerate(nodes):
        print(f'init secret sharing for node {i + 1}... ', end="", flush=True)
        node.init_secret_sharing()
        print("done")
    print()

    # invalid the share from last to first node
    # to actually also test the dispute case
    manipulate_share(nodes[-1], 1)

    for node in nodes:
        print(f'distribute key shares for node {node.id}... ', end="", flush=True)
        tx = node.share_key()
        tx_share_key.append(tx)
        print("done")
    print()

    print(f'waiting for begin of dispute phase... ', end='', flush=True)
    utils.mine_blocks_until(contract.sharing_confirmed)
    print('done\n')

    # for node in nodes:
    for node in nodes[:1]:
        print(f'loading and verififying shares for node {node.id}... ', end='', flush=True)
        try:
            node.load_shares()
        except ValueError:
            print("done (invalid share detected)")
        else:
            print("done")
    print()

    # for node in nodes:
    for node in nodes[:1]:
        dispute_ids = [n.id for n in node.nodes if hasattr(n, 'share') and not n.share_ok]
        for id in dispute_ids:
            print(f'submitting dispute from node {node.id} against node {id}... ', end="", flush=False)
            try:
                tx = node.dispute(id)
                tx_dispute.append(tx)
            except ValueError:
                print(f'done (dispute no required, node {id} already flagged as malicous)')
            else:
                print("done")
    print()

    print(f'waiting for begin of finalization phase... ', end='', flush=True)
    utils.mine_blocks_until(contract.dispute_confirmed)
    print('done\n')

    print(f'loading dispute and state information... ', end='', flush=True)
    nodes[0].verify_nodes()
    print("done\n")

    for node in nodes[0].nodes:
        t = 'OK' if node in nodes[0].group else 'FAILED'
        print(f'node {node.id}: status={t}')
    print()

    print(f'deriving master key... ', end='', flush=True)
    nodes[0].derive_group_keys()
    print("done")

    print(f'uploading master key... ', end='', flush=True)
    tx_upload = nodes[0].upload_group_key()
    print("done")

    print()
    print()
    print(f'GAS USAGE STATS FOR {num_nodes} NODES')
    print()
    print('                  |   min   |   max   |   avg')
    print('-----------------------------------------------')

    for txs, name in zip([tx_register, tx_share_key, tx_dispute], ['registration', 'key sharing', 'dispute']):
        gas_min = min(tx['gasUsed'] for tx in txs)
        gas_max = max(tx['gasUsed'] for tx in txs)
        gas_avg = math.ceil(sum(tx['gasUsed'] for tx in txs) / len(txs))
        print(f'{name:<17} | {gas_min:7} | {gas_max:7} | {gas_avg:7}')
        print('-----------------------------------------------')
    g = tx_upload['gasUsed']  # noqa
    print(f'master key upload | {g:7} | {g:7} | {g:7} ')
    print()
    print()
    print()
    print("=" * 80)
    print("=" * 80)
    print("=" * 80)
    print()
    print()
    print()