# Phase 5: Secure Payment
# Zero-Knowledge Proof
# noknow library - https://github.com/GoodiesHQ/noknow-python
# Author: Norbert Lovinger; xlovin00@vutbr.cz
# Date: 16th May 2023

from noknow.core import ZK, ZKSignature, ZKParameters, ZKData, ZKProof
from queue import Queue
from threading import Thread

# Received amount of money for the using the service to pay.
payment_amout = "100"

# User class is one part of the Zero-Knowledge proof payment process.
# Class represents a whole process from the User side, as in the main() 
# function these classes are started simultaneously.
# User firstly creates a new instance of ZK using 
# Elliptic curves (secp256k1 = Koblitz curves) and SHA3 hashing fuction.
def User(iq: Queue, oq: Queue):
    user_zk = ZK.new(curve_name="secp256k1", hash_alg="sha3_256")

    # Create signature and send to service operator
    signature = user_zk.create_signature(payment_amout)
    oq.put(signature.dump())

    # Receive the token from the service operator
    token = iq.get()

    # Create a proof that signs the provided token and sends to service operator
    proof = user_zk.sign(payment_amout, token).dump()

    # Send the token and proof to the service operator
    oq.put(proof)

    # Wait for service operator response!
    print("Success!" if iq.get() else "Failure!")

# Service Operator class is second part of the Zero-Knowledge proof payment process.
# Class represents a whole process from the Service Operator side. Service Operator
# also creates his instance of ZK using Elliptic curves (secp384r1 = Weierstrass curve)
# and SHA3 hashing function using 512 bits.
def ServiceOperator(iq: Queue, oq: Queue):
    # Set up service operator component
    operator_password = "Password"
    operator_zk = ZK.new(curve_name="secp384r1", hash_alg="sha3_512")
    operator_signature: ZKSignature = operator_zk.create_signature(operator_password)

    # Load the received signature from the User
    sig = iq.get()
    user_signature = ZKSignature.load(sig)
    user_zk = ZK(user_signature.params)

    # Create a signed token and send to the User
    token = operator_zk.sign(operator_password, user_zk.token())
    oq.put(token.dump(separator=":"))

    # Get the token from the User
    proof = ZKData.load(iq.get())
    token = ZKData.load(proof.data, ":")

    # In this example, the service opertor signs the token so it can be sure it has not been modified
    if not operator_zk.verify(token, operator_signature):
        oq.put(False)
    else:
        oq.put(user_zk.verify(proof, user_signature, data=token)) # type: ignore

#
# Example solution
#  
def main():
    q1, q2 = Queue(), Queue()
    threads = [
        Thread(target=User, args=(q1, q2)),
        Thread(target=ServiceOperator, args=(q2, q1)),
    ]
    for func in [Thread.start, Thread.join]:
        for thread in threads:
            func(thread)

if __name__ == "__main__":
    main()
