zkrunner.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 main(witness_file, source_file, mock=False, trace=False):
  41. """main zkrunner logic"""
  42. # We will first attempt to decode the witnesses from the JSON file.
  43. # Refer to the `witness_gen.py` file to see what the format of this
  44. # file should be.
  45. print("Decoding witnesses...")
  46. if witness_file == "-":
  47. witness_data = json.load(sys.stdin)
  48. else:
  49. with open(witness_file, "r", encoding="utf-8") as json_file:
  50. witness_data = json.load(json_file)
  51. # Then we attempt to compile the given zkas code and create a
  52. # zkVM circuit. This compiling logic happens in the Python bindings'
  53. # `ZkBinary::new` function, and should be equivalent to the actual
  54. # `zkas` binary provided in the DarkFi codebase.
  55. print("Compiling zkas code...")
  56. with open(source_file, "r", encoding="utf-8") as zkas_file:
  57. zkas_source = zkas_file.read()
  58. # This line will compile the source code
  59. zkbin = ZkBinary(source_file, zkas_source)
  60. # Construct the initial circuit object.
  61. circuit = ZkCircuit(zkbin)
  62. # If we want to build an actual proof, we'll need a proving key
  63. # and a verifying key.
  64. # circuit.verifier_build() is called so that the inital circuit
  65. # (which contains no witnesses) actually calls empty_witnesses()
  66. # in order to have the correct code path when the circuit gets
  67. # synthesized.
  68. if not mock:
  69. print("Building proving key...")
  70. proving_key = ProvingKey.build(zkbin.k(), circuit.verifier_build())
  71. print("Building verifying key...")
  72. verifying_key = VerifyingKey.build(zkbin.k(), circuit.verifier_build())
  73. # Now we scan through the parsed JSON witness file and
  74. # build our "heap". These will be appended to the initial
  75. # circuit and decide the code path for the prover.
  76. for witness in witness_data["witnesses"]:
  77. assert len(witness) == 1
  78. if value := witness.get("EcPoint"):
  79. circuit.witness_ecpoint(Ep(value))
  80. elif value := witness.get("EcNiPoint"):
  81. assert len(value) == 2
  82. xcoord, ycoord = Fp(value[0]), Fp(value[1])
  83. circuit.witness_ecnipoint(Ep(xcoord, ycoord))
  84. elif value := witness.get("Base"):
  85. circuit.witness_base(Fp(value))
  86. elif value := witness.get("Scalar"):
  87. circuit.witness_scalar(Fq(value))
  88. elif value := witness.get("MerklePath"):
  89. path = [Fp(i) for i in value]
  90. assert len(path) == 32
  91. circuit.witness_merklepath(path)
  92. elif value := witness.get("Uint32"):
  93. circuit.witness_uint32(value)
  94. elif value := witness.get("Uint64"):
  95. circuit.witness_uint64(value)
  96. else:
  97. eprint(f"Invalid Witness type for witness {witness}")
  98. return -1
  99. # circuit.prover_build() will actually construct the circuit
  100. # with the values witnessed above.
  101. circuit = circuit.prover_build()
  102. if trace:
  103. if mock:
  104. eprint(f"Debug trace can only be enabled with --prove")
  105. return -2
  106. circuit.enable_trace()
  107. # Instances are our public inputs for the proof and they're also
  108. # part of the JSON file.
  109. instances = []
  110. for instance in witness_data["instances"]:
  111. instances.append(Fp(instance))
  112. # If we're building an actual proof, we'll use the ProvingKey to
  113. # prove and our VerifyingKey to verify the proof.
  114. if not mock:
  115. print("Proving knowledge of witnesses...")
  116. proof = Proof.create(proving_key, [circuit], instances)
  117. if trace:
  118. show_trace(zkbin.opcodes(), circuit.opvalues())
  119. print("Verifying ZK proof...")
  120. proof.verify(verifying_key, instances)
  121. # Otherwise, we'll simply run the MockProver:
  122. else:
  123. print("Running MockProver...")
  124. proof = MockProver.run(zkbin.k(), circuit, instances)
  125. print("Verifying MockProver...")
  126. proof.verify()
  127. print("Proof verified successfully!")
  128. return 0
  129. if __name__ == "__main__":
  130. from argparse import ArgumentParser
  131. parser = ArgumentParser(
  132. prog="zkrunner",
  133. description="Python util for running zk proofs",
  134. epilog="This tool is only for prototyping purposes",
  135. )
  136. parser.add_argument(
  137. "SOURCE",
  138. help="Path to zkas source code",
  139. )
  140. parser.add_argument(
  141. "-w",
  142. "--witness",
  143. required=True,
  144. help="Path to JSON file holding witnesses",
  145. )
  146. parser.add_argument(
  147. "--prove",
  148. action="store_true",
  149. help="Actually create a real proof instead of using MockProver",
  150. )
  151. parser.add_argument(
  152. "--trace",
  153. action="store_true",
  154. help="Enable debug trace (only works with --prove enabled)",
  155. )
  156. args = parser.parse_args()
  157. sys.exit(main(args.witness, args.SOURCE, mock=not args.prove,
  158. trace=args.trace))