blob: f8b0448a44486d9f7f9a0046d8e11056ed06b1fc [file] [log] [blame]
#!/usr/bin/env python3
"""
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
"""
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from amcl import core_utils, mpc, schnorr, factoring_zk, commitments
seed_hex = "78d0fb6705ce77dee47d03eb5b9c5d30"
def generate_key_material(rng, player):
""" Generate Paillier and ECDSA Key Pairs
Generate key material and commitment to the ECDSA PK.
The key material dictionary has keys:
* paillier_sk
* paillier_pk
* ecdsa_sk
* ecdsa_pk
Args::
rng: pointer to CSPRNG
Returns::
key_material: dictionary with the generated key material
"""
# Paillier keys
paillier_pk, paillier_sk = mpc.paillier_key_pair(rng)
# ECDSA keys
ecdsa_pk, ecdsa_sk = mpc.mpc_ecdsa_key_pair_generate(rng)
rc = mpc.ecp_secp256k1_public_key_validate(ecdsa_pk)
assert rc == 0, f"[{player}] Invalid ECDSA public key. rc {rc}"
key_material = {
'paillier_pk' : paillier_pk,
'paillier_sk' : paillier_sk,
'ecdsa_pk' : ecdsa_pk,
'ecdsa_sk' : ecdsa_sk
}
return key_material
def generate_key_material_zkp(rng, key_material, ID, AD=None):
""" Generate ZK Proofs for key material
Generate a commitment to the ECDSA PK, a Schnorr's
Proof for the ECDSA PK and a factoring Proof for the
Paillier PK
The key material dictionary must have keys:
* paillier_sk
* paillier_pk
* ecdsa_sk
* ecdsa_pk
Args::
rng: pointer to CSPRNG
key_material: dictionary with the key material
Returns::
r: secret value for the ECDSA PK commitment
c: commitment for the ECDSA PK
sc: commitment for the Schnorr's Proof
sp: Schnorr's Proof
fe: Factoring Proof. First component
fy: Factoring Proof. Second component
"""
# Commit to ECDSA PK
r, c = commitments.nm_commit(rng, key_material['ecdsa_pk'])
# Generate Schnorr's proof for ECDSA PK
sr, sc = schnorr.commit(rng)
e = schnorr.challenge(key_material['ecdsa_pk'], sc, ID, AD=AD)
sp = schnorr.prove(sr, e, key_material['ecdsa_sk'])
# Generate ZKP of knowledge of factorization for
# Paillier key pair
psk_p, psk_q = mpc.mpc_dump_paillier_sk(key_material['paillier_sk'])
fe, fy = factoring_zk.prove(rng, psk_p, psk_q, ID, ad=AD)
return r, c, sc, sp, fe, fy
def verify_key_material(key_material, r, c, sc, sp, fe, fy, player, ID, AD=None):
""" Verify key material
Verify the conunterparty key material using the
proof received
The key material dictionary must have keys:
* paillier_pk
* ecdsa_pk
Args::
key_material: dictionary with the key material
r: secret value for the ECDSA PK commitment
c: commitment for the ECDSA PK
sc: commitment for the Schnorr's Proof
sp: Schnorr's Proof
fe: Factoring Proof. First component
fy: Factoring Proof. Second component
Returns::
"""
# Decommit ECDSA PK
rc = commitments.nm_decommit(key_material['ecdsa_pk'], r, c)
assert rc == commitments.OK, f"[{player}] Failure decommitting ecdsa_pk. rc {rc}"
# Verify ECDSA PK Schnorr's proof
e = schnorr.challenge(key_material['ecdsa_pk'], sc, ID, AD=AD)
rc = schnorr.verify(key_material['ecdsa_pk'], sc, e, sp)
assert rc == schnorr.OK, f"[{player}] Invalid ECDSA PK Schnorr Proof. rc {rc}"
# Verify factoring ZKP
n = mpc.paillier_pk_to_octet(key_material['paillier_pk'])
rc = factoring_zk.verify(n, fe, fy, ID, ad=AD)
assert rc == factoring_zk.OK, f"[{player}] Invalid Factoring ZKP. rc {rc}"
if __name__ == "__main__":
seed = bytes.fromhex(seed_hex)
rng = core_utils.create_csprng(seed)
alice_id = b"alice_unique_identifier"
bob_id = b"bob_unique_identifier"
### Key setup ###
print("Setup Key Material\n")
# Generate key material
key_material1 = generate_key_material(rng, "Alice")
key_material2 = generate_key_material(rng, "Bob")
# Generate and exchange nonce for liveliness
alice_ad = core_utils.generate_random(rng, 32)
bob_ad = core_utils.generate_random(rng, 32)
print("[Alice] Generate ECDSA and Paillier key pairs")
print("[Bob] Generate ECDSA and Paillier key pairs")
# Generate key material ZKP
r1, c1, sc1, sp1, fe1, fy1 = generate_key_material_zkp(rng, key_material1, alice_id, AD = alice_ad)
r2, c2, sc2, sp2, fe2, fy2 = generate_key_material_zkp(rng, key_material2, bob_id, AD = bob_ad)
print("[Alice] Generate commitment to ECDSA PK and ZKPs")
print("[Bob] Generate commitment to ECDSA PK and ZKPs")
# Commit to ECDSA PK by transmitting c
print("[Alice] Commit to ECDSA PK. Transmit c")
print("[Bob] Commit to ECDSA PK. Transmit c")
# Transmit decommitment and ZKP
print("[Alice] Transmit decommitment for ECDSA PK and ZKPs")
print("[Bob] Transmit decommitment for ECDSA PK and ZKPs")
# Verify decommitment and ZKP
c_key_material1 = {
'paillier_pk' : key_material2['paillier_pk'],
'ecdsa_pk' : key_material2['ecdsa_pk'],
}
c_key_material2 = {
'paillier_pk' : key_material1['paillier_pk'],
'ecdsa_pk' : key_material1['ecdsa_pk'],
}
print("[Alice] Verify ZKP")
verify_key_material(c_key_material1, r2, c2, sc2, sp2, fe2, fy2, "Alice", bob_id, AD=bob_ad)
print("[Bob] Verify ZKP")
verify_key_material(c_key_material2, r1, c1, sc1, sp1, fe1, fy1, "Bob", alice_id, AD=alice_ad)
# Recombine full ECDSA PK
rc, ecdsa_full_pk1 = mpc.mpc_sum_pk(key_material1['ecdsa_pk'], c_key_material1['ecdsa_pk'])
assert rc == 0, '[Alice] Error recombining full ECDSA PK'
rc, ecdsa_full_pk2 = mpc.mpc_sum_pk(key_material2['ecdsa_pk'], c_key_material2['ecdsa_pk'])
assert rc == 0, '[Bob] Error recombining full ECDSA PK'
### Signature ###
# Message
M = b'test message'
print(f"\nSign message '{M.decode('utf-8')}'")
# Generate k, gamma and gamma.G
print("[Alice] Generate k, gamma and gamma.G")
GAMMA1, gamma1 = mpc.mpc_ecdsa_key_pair_generate(rng)
k1 = mpc.mpc_k_generate(rng)
print("[Bob] Generate k, gamma and gamma.G")
GAMMA2, gamma2 = mpc.mpc_ecdsa_key_pair_generate(rng)
k2 = mpc.mpc_k_generate(rng)
## Commit to GAMMA1, GAMMA2 and exchange nonces for liveliness
print("[Alice] Commit to GAMMA1")
GAMMAR1, GAMMAC1 = commitments.nm_commit(rng, GAMMA1)
bob_ad = core_utils.generate_random(rng, 32)
print("[Bob] Commit to GAMMA2")
GAMMAR2, GAMMAC2 = commitments.nm_commit(rng, GAMMA2)
alice_ad = core_utils.generate_random(rng, 32)
## Engage in MTA with k_i, gamma_j
# k1, gamma2
print("[Alice] Engage in MTA with shares k1, gamma2")
ca = mpc.mpc_mta_client1(rng, key_material1['paillier_pk'], k1)
cb, beta2 = mpc.mpc_mta_server(rng, c_key_material2['paillier_pk'], gamma2, ca)
alpha1 = mpc.mpc_mta_client2(key_material1['paillier_sk'], cb)
# k2, gamma1
print("[Bob] Engage in MTA with shares k2, gamma1")
ca = mpc.mpc_mta_client1(rng, key_material2['paillier_pk'], k2)
cb, beta1 = mpc.mpc_mta_server(rng, c_key_material1['paillier_pk'], gamma1, ca)
alpha2 = mpc.mpc_mta_client2(key_material2['paillier_sk'], cb)
# Partial sums
print("[Alice] Combine partial sum delta1 for kgamma")
delta1 = mpc.mpc_sum_mta(k1, gamma1, alpha1, beta1)
print("[Bob] Combine partial sum delta2 for kgamma")
delta2 = mpc.mpc_sum_mta(k2, gamma2, alpha2, beta2)
## Engage in MTA with k_i, sk_j
# k1, sk2
print("[Alice] Engage in MTA with k1, s2")
ca = mpc.mpc_mta_client1(rng, key_material1['paillier_pk'], k1)
cb, beta2 = mpc.mpc_mta_server(rng, c_key_material2['paillier_pk'], key_material2['ecdsa_sk'], ca)
alpha1 = mpc.mpc_mta_client2(key_material1['paillier_sk'], cb)
# k2, sk1
print("[Bob] Engage in MTA with k2, s1")
ca = mpc.mpc_mta_client1(rng, key_material2['paillier_pk'], k2)
cb, beta1 = mpc.mpc_mta_server(rng, c_key_material1['paillier_pk'], key_material1['ecdsa_sk'], ca)
alpha2 = mpc.mpc_mta_client2(key_material2['paillier_sk'], cb)
# Partial sums
print("[Alice] Combine partial sum sigma1 for kw")
sigma1 = mpc.mpc_sum_mta(k1, key_material1['ecdsa_sk'], alpha1, beta1)
print("[Bob] Combine partial sum sigma2 for kw")
sigma2 = mpc.mpc_sum_mta(k2, key_material2['ecdsa_sk'], alpha2, beta2)
## Decommitment and Proofs for R component
# Generate Schnorr's Proofs
print("[Alice] Generate Schnorr's Proof")
GAMMA_schnorr_r1, GAMMA_schnorr_c1 = schnorr.commit(rng)
GAMMA_schnorr_e1 = schnorr.challenge(GAMMA1, GAMMA_schnorr_c1, alice_id, AD = alice_ad)
GAMMA_schnorr_p1 = schnorr.prove(GAMMA_schnorr_r1, GAMMA_schnorr_e1, gamma1)
print("[Bob] Generate Schnorr's Proof")
GAMMA_schnorr_r2, GAMMA_schnorr_c2 = schnorr.commit(rng)
GAMMA_schnorr_e2 = schnorr.challenge(GAMMA2, GAMMA_schnorr_c2, bob_id, AD = bob_ad)
GAMMA_schnorr_p2 = schnorr.prove(GAMMA_schnorr_r2, GAMMA_schnorr_e2, gamma2)
print("[Alice] Transmit decommitment and Schnorr Proof for GAMMA1")
print("[Bob] Transmit decommitment and Schnorr Proof for GAMMA2")
# Decommit GAMMAi and verify Schnorr Proof
rc = commitments.nm_decommit(GAMMA2, GAMMAR2, GAMMAC2)
assert rc == commitments.OK, f'[Alice] Error decommitting GAMMA2. rc {rc}'
GAMMA_schnorr_e2 = schnorr.challenge(GAMMA2, GAMMA_schnorr_c2, bob_id, AD=bob_ad)
rc = schnorr.verify(GAMMA2, GAMMA_schnorr_c2, GAMMA_schnorr_e2, GAMMA_schnorr_p2)
assert rc == schnorr.OK, f'[Alice] Error verifying Schnorr proof for GAMMA2'
rc = commitments.nm_decommit(GAMMA1, GAMMAR1, GAMMAC1)
assert rc == commitments.OK, f'[Bob] Error decommitting GAMMA1. rc {rc}'
GAMMA_schnorr_e1 = schnorr.challenge(GAMMA1, GAMMA_schnorr_c1, alice_id, AD=alice_ad)
rc = schnorr.verify(GAMMA1, GAMMA_schnorr_c1, GAMMA_schnorr_e1, GAMMA_schnorr_p1)
assert rc == schnorr.OK, f'[Bob] Error verifying Schnorr proof for GAMMA1'
## Reconcile R component
print("[Alice] Recombine kgamma^(-1)")
ikgamma1 = mpc.mpc_invkgamma(delta1, delta2)
rc, R1, _ = mpc.mpc_r(ikgamma1, GAMMA1, GAMMA2)
assert rc == 0, f'[Alice] Error reconciling R. rc {rc}'
print("[Bob] Recombine kgamma^(-1)")
ikgamma2 = mpc.mpc_invkgamma(delta1, delta2)
rc, R2, _ = mpc.mpc_r(ikgamma2, GAMMA1, GAMMA2)
assert rc == 0, f'[Bob] Error reconciling R. rc {rc}'
## Compute signature shares
hm = mpc.mpc_hash(M)
rc, s1 = mpc.mpc_s(hm, R1, k1, sigma1)
assert rc == 0, f'[Alice] Error computing signature share s1'
rc, s2 = mpc.mpc_s(hm, R2, k2, sigma2)
assert rc == 0, f'[Bob] Error computing signature share s1'
## Reconcile S component
# Commit to signature shares
print("[Alice] Transmit commitment signature share s1")
SR1, SC1 = commitments.nm_commit(rng, s1)
print("[Bob] Transmit commitment signature share s2")
SR2, SC2 = commitments.nm_commit(rng, s2)
# Decommit signature shares and combine
print("[Alice] Decommit s2")
rc = commitments.nm_decommit(s2, SR2, SC2)
assert rc == 0, f'[Alice] Error decommitting s2. rc {rc}'
print("[Bob] Decommit s1")
rc = commitments.nm_decommit(s1, SR1, SC1)
assert rc == 0, f'[Bob] Error decommitting s1. rc {rc}'
print("[Alice] Recombine S component")
S1 = mpc.mpc_sum_s(s1, s2)
print("[Bob] Recombine S component")
S2 = mpc.mpc_sum_s(s1, s2)
rc = mpc.mpc_ecdsa_verify(hm, ecdsa_full_pk1, R1, S1)
assert rc == 0, f'[Alice] Invalid reconstructed signature'
rc = mpc.mpc_ecdsa_verify(hm, ecdsa_full_pk2, R2, S2)
assert rc == 0, f'[Bob] Invalid reconstructed signature'
assert R1 == R2, f'R component is different:\n{R1}\n{R2}'
assert S1 == S2, f'S component is different:\n{S1}\n{S2}'
print('\nReconstructed signature')
print(f'\tR = {R1.hex()}')
print(f'\tS = {S1.hex()}')