zkrunner.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. #!/usr/bin/env python3
  2. # This file is part of DarkFi (https://dark.fi)
  3. #
  4. # Copyright (C) 2020-2025 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")) is not None:
  55. circuit.witness_ecpoint(Ep(value))
  56. elif (value := witness.get("EcNiPoint")) is not None:
  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")) is not None:
  61. circuit.witness_base(Fp(value))
  62. elif (value := witness.get("Scalar")) is not None:
  63. circuit.witness_scalar(Fq(value))
  64. elif (value := witness.get("MerklePath")) is not None:
  65. path = [Fp(i) for i in value]
  66. assert len(path) == 32
  67. circuit.witness_merklepath(path)
  68. elif (value := witness.get("SparseMerklePath")) is not None:
  69. path = [Fp(i) for i in value]
  70. assert len(path) == 255
  71. circuit.witness_sparsemerklepath(path)
  72. elif (value := witness.get("Uint32")) is not None:
  73. print("here")
  74. circuit.witness_uint32(value)
  75. elif (value := witness.get("Uint64")) is not None:
  76. circuit.witness_uint64(value)
  77. else:
  78. eprint(f"Invalid Witness type for witness {witness}")
  79. return -1
  80. # Instances are our public inputs for the proof and they're also
  81. # part of the JSON file.
  82. instances = []
  83. for instance in witness_data["instances"]:
  84. instances.append(Fp(instance))
  85. return instances
  86. def main(witness_file, source_file, mock=False, trace=False):
  87. """main zkrunner logic"""
  88. # Then we attempt to compile the given zkas code and create a
  89. # zkVM circuit. This compiling logic happens in the Python bindings'
  90. # `ZkBinary::new` function, and should be equivalent to the actual
  91. # `zkas` binary provided in the DarkFi codebase.
  92. print("Compiling zkas code...")
  93. with open(source_file, "r", encoding="utf-8") as zkas_file:
  94. zkas_source = zkas_file.read()
  95. # This line will compile the source code
  96. zkbin = ZkBinary(source_file, zkas_source)
  97. # Construct the initial circuit object.
  98. circuit = ZkCircuit(zkbin)
  99. print("Decoding witnesses...")
  100. instances = load_circuit_witness(circuit, witness_file)
  101. # If we want to build an actual proof, we'll need a proving key
  102. # and a verifying key.
  103. # circuit.verifier_build() is called so that the inital circuit
  104. # (which contains no witnesses) actually calls empty_witnesses()
  105. # in order to have the correct code path when the circuit gets
  106. # synthesized.
  107. if not mock:
  108. print("Building proving key...")
  109. proving_key = ProvingKey.build(zkbin.k(), circuit.verifier_build())
  110. print("Building verifying key...")
  111. verifying_key = VerifyingKey.build(zkbin.k(), circuit.verifier_build())
  112. # circuit.prover_build() will actually construct the circuit
  113. # with the values witnessed above.
  114. circuit = circuit.prover_build()
  115. if trace:
  116. if mock:
  117. eprint(f"Debug trace can only be enabled with --prove")
  118. return -2
  119. circuit.enable_trace()
  120. # If we're building an actual proof, we'll use the ProvingKey to
  121. # prove and our VerifyingKey to verify the proof.
  122. if not mock:
  123. print("Proving knowledge of witnesses...")
  124. proof = Proof.create(proving_key, [circuit], instances)
  125. if proof is None:
  126. eprint(f"Proof creation failed")
  127. return -3
  128. if trace:
  129. show_trace(zkbin.opcodes(), circuit.opvalues())
  130. print("Verifying ZK proof...")
  131. verify_status = proof.verify(verifying_key, instances)
  132. # Otherwise, we'll simply run the MockProver:
  133. else:
  134. print("Running MockProver...")
  135. proof = MockProver.run(zkbin.k(), circuit, instances)
  136. print("Verifying MockProver...")
  137. verify_status = proof.verify()
  138. if not verify_status:
  139. eprint("Proof failed to verify")
  140. return -3
  141. print("Proof verified successfully!")
  142. return 0
  143. if __name__ == "__main__":
  144. from argparse import ArgumentParser
  145. parser = ArgumentParser(
  146. prog="zkrunner",
  147. description="Python util for running zk proofs",
  148. epilog="This tool is only for prototyping purposes",
  149. )
  150. parser.add_argument(
  151. "SOURCE",
  152. help="Path to zkas source code",
  153. )
  154. parser.add_argument(
  155. "-w",
  156. "--witness",
  157. required=True,
  158. help="Path to JSON file holding witnesses",
  159. )
  160. parser.add_argument(
  161. "--prove",
  162. action="store_true",
  163. help="Actually create a real proof instead of using MockProver",
  164. )
  165. parser.add_argument(
  166. "--trace",
  167. action="store_true",
  168. help="Enable debug trace (only works with --prove enabled)",
  169. )
  170. args = parser.parse_args()
  171. sys.exit(main(args.witness, args.SOURCE, mock=not args.prove,
  172. trace=args.trace))