zkrunner.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/env python3
  2. # This file is part of DarkFi (https://dark.fi)
  3. #
  4. # Copyright (C) 2020-2024 Dyne.org foundation
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as
  8. # published by the Free Software Foundation, either version 3 of the
  9. # License, or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. """
  19. Python tool to prototype zkVM proofs given zkas source code and necessary
  20. witness values in JSON format.
  21. """
  22. import json
  23. import sys
  24. from darkfi_sdk.pasta import Fp, Fq, Ep
  25. from darkfi_sdk.zkas import (MockProver, ZkBinary, ZkCircuit, ProvingKey,
  26. Proof, VerifyingKey)
  27. def eprint(fstr, *args):
  28. print("error: " + fstr, *args, file=sys.stderr)
  29. def show_trace(opcodes, trace):
  30. print(f"{'Line':<4} {'Opcode':<22} {'Type':<10} {'Values'}")
  31. for i, (opcode, (optype, args)) in enumerate(zip(opcodes, trace)):
  32. if args:
  33. args = ", ".join([str(arg) for arg in args])
  34. args = f"[{args}]"
  35. else:
  36. args = ""
  37. opcode = str(opcode)
  38. optype = str(optype)
  39. print(f"{i:<4} {opcode:<22} {optype:<10} {args}")
  40. def load_circuit_witness(circuit, witness_file):
  41. # We attempt to decode the witnesses from the JSON file.
  42. # Refer to the `witness_gen.py` file to see what the format of this
  43. # file should be.
  44. if witness_file == "-":
  45. witness_data = json.load(sys.stdin)
  46. else:
  47. with open(witness_file, "r", encoding="utf-8") as json_file:
  48. witness_data = json.load(json_file)
  49. # Now we scan through the parsed JSON witness file and
  50. # build our "heap". These will be appended to the initial
  51. # circuit and decide the code path for the prover.
  52. for witness in witness_data["witnesses"]:
  53. assert len(witness) == 1
  54. if value := witness.get("EcPoint"):
  55. circuit.witness_ecpoint(Ep(value))
  56. elif value := witness.get("EcNiPoint"):
  57. assert len(value) == 2
  58. xcoord, ycoord = Fp(value[0]), Fp(value[1])
  59. circuit.witness_ecnipoint(Ep(xcoord, ycoord))
  60. elif value := witness.get("Base"):
  61. circuit.witness_base(Fp(value))
  62. elif value := witness.get("Scalar"):
  63. circuit.witness_scalar(Fq(value))
  64. elif value := witness.get("MerklePath"):
  65. path = [Fp(i) for i in value]
  66. assert len(path) == 32
  67. circuit.witness_merklepath(path)
  68. elif value := witness.get("SparseMerklePath"):
  69. path = [Fp(i) for i in value]
  70. assert len(path) == 255
  71. circuit.witness_sparsemerklepath(path)
  72. elif value := witness.get("Uint32"):
  73. circuit.witness_uint32(value)
  74. elif value := witness.get("Uint64"):
  75. circuit.witness_uint64(value)
  76. else:
  77. eprint(f"Invalid Witness type for witness {witness}")
  78. return -1
  79. # Instances are our public inputs for the proof and they're also
  80. # part of the JSON file.
  81. instances = []
  82. for instance in witness_data["instances"]:
  83. instances.append(Fp(instance))
  84. return instances
  85. def main(witness_file, source_file, mock=False, trace=False):
  86. """main zkrunner logic"""
  87. # Then we attempt to compile the given zkas code and create a
  88. # zkVM circuit. This compiling logic happens in the Python bindings'
  89. # `ZkBinary::new` function, and should be equivalent to the actual
  90. # `zkas` binary provided in the DarkFi codebase.
  91. print("Compiling zkas code...")
  92. with open(source_file, "r", encoding="utf-8") as zkas_file:
  93. zkas_source = zkas_file.read()
  94. # This line will compile the source code
  95. zkbin = ZkBinary(source_file, zkas_source)
  96. # Construct the initial circuit object.
  97. circuit = ZkCircuit(zkbin)
  98. print("Decoding witnesses...")
  99. instances = load_circuit_witness(circuit, witness_file)
  100. # If we want to build an actual proof, we'll need a proving key
  101. # and a verifying key.
  102. # circuit.verifier_build() is called so that the inital circuit
  103. # (which contains no witnesses) actually calls empty_witnesses()
  104. # in order to have the correct code path when the circuit gets
  105. # synthesized.
  106. if not mock:
  107. print("Building proving key...")
  108. proving_key = ProvingKey.build(zkbin.k(), circuit.verifier_build())
  109. print("Building verifying key...")
  110. verifying_key = VerifyingKey.build(zkbin.k(), circuit.verifier_build())
  111. # circuit.prover_build() will actually construct the circuit
  112. # with the values witnessed above.
  113. circuit = circuit.prover_build()
  114. if trace:
  115. if mock:
  116. eprint(f"Debug trace can only be enabled with --prove")
  117. return -2
  118. circuit.enable_trace()
  119. # If we're building an actual proof, we'll use the ProvingKey to
  120. # prove and our VerifyingKey to verify the proof.
  121. if not mock:
  122. print("Proving knowledge of witnesses...")
  123. proof = Proof.create(proving_key, [circuit], instances)
  124. if proof is None:
  125. eprint(f"Proof creation failed")
  126. return -3
  127. if trace:
  128. show_trace(zkbin.opcodes(), circuit.opvalues())
  129. print("Verifying ZK proof...")
  130. verify_status = proof.verify(verifying_key, instances)
  131. # Otherwise, we'll simply run the MockProver:
  132. else:
  133. print("Running MockProver...")
  134. proof = MockProver.run(zkbin.k(), circuit, instances)
  135. print("Verifying MockProver...")
  136. verify_status = proof.verify()
  137. if not verify_status:
  138. eprint("Proof failed to verify")
  139. return -3
  140. print("Proof verified successfully!")
  141. return 0
  142. if __name__ == "__main__":
  143. from argparse import ArgumentParser
  144. parser = ArgumentParser(
  145. prog="zkrunner",
  146. description="Python util for running zk proofs",
  147. epilog="This tool is only for prototyping purposes",
  148. )
  149. parser.add_argument(
  150. "SOURCE",
  151. help="Path to zkas source code",
  152. )
  153. parser.add_argument(
  154. "-w",
  155. "--witness",
  156. required=True,
  157. help="Path to JSON file holding witnesses",
  158. )
  159. parser.add_argument(
  160. "--prove",
  161. action="store_true",
  162. help="Actually create a real proof instead of using MockProver",
  163. )
  164. parser.add_argument(
  165. "--trace",
  166. action="store_true",
  167. help="Enable debug trace (only works with --prove enabled)",
  168. )
  169. args = parser.parse_args()
  170. sys.exit(main(args.witness, args.SOURCE, mock=not args.prove,
  171. trace=args.trace))