zkrunner.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env python3
  2. # This file is part of DarkFi (https://dark.fi)
  3. #
  4. # Copyright (C) 2020-2023 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. from darkfi_sdk.pasta import Fp, Fq, Ep
  24. from darkfi_sdk.zkas import (MockProver, ZkBinary, ZkCircuit, ProvingKey,
  25. Proof, VerifyingKey)
  26. def main(witness_file, source_file, mock=False):
  27. """main zkrunner logic"""
  28. # We will first attempt to decode the witnesses from the JSON file.
  29. # Refer to the `witness_gen.py` file to see what the format of this
  30. # file should be.
  31. print("Decoding witnesses...")
  32. with open(witness_file, "r", encoding="utf-8") as json_file:
  33. witness_data = json.load(json_file)
  34. # Then we attempt to compile the given zkas code and create a
  35. # zkVM circuit. This compiling logic happens in the Python bindings'
  36. # `ZkBinary::new` function, and should be equivalent to the actual
  37. # `zkas` binary provided in the DarkFi codebase.
  38. print("Compiling zkas code...")
  39. with open(source_file, "r", encoding="utf-8") as zkas_file:
  40. zkas_source = zkas_file.read()
  41. # This line will compile the source code
  42. zkbin = ZkBinary(source_file, zkas_source)
  43. # Construct the initial circuit object.
  44. circuit = ZkCircuit(zkbin)
  45. # If we want to build an actual proof, we'll need a proving key
  46. # and a verifying key.
  47. # circuit.verifier_build() is called so that the inital circuit
  48. # (which contains no witnesses) actually calls empty_witnesses()
  49. # in order to have the correct code path when the circuit gets
  50. # synthesized.
  51. if not mock:
  52. print("Building proving key...")
  53. proving_key = ProvingKey.build(zkbin.k(), circuit.verifier_build())
  54. print("Building verifying key...")
  55. verifying_key = VerifyingKey.build(zkbin.k(), circuit.verifier_build())
  56. # Now we scan through the parsed JSON witness file and
  57. # build our "heap". These will be appended to the initial
  58. # circuit and decide the code path for the prover.
  59. for witness in witness_data["witnesses"]:
  60. assert len(witness) == 1
  61. if value := witness.get("EcPoint"):
  62. circuit.witness_ecpoint(Ep(value))
  63. elif value := witness.get("EcNiPoint"):
  64. assert len(value) == 2
  65. xcoord, ycoord = Fp(value[0]), Fp(value[1])
  66. circuit.witness_ecnipoint(Ep(xcoord, ycoord))
  67. elif value := witness.get("Base"):
  68. circuit.witness_base(Fp(value))
  69. elif value := witness.get("Scalar"):
  70. circuit.witness_scalar(Fq(value))
  71. elif value := witness.get("MerklePath"):
  72. path = [Fp(i) for i in value]
  73. assert len(path) == 32
  74. circuit.witness_merklepath(path)
  75. elif value := witness.get("Uint32"):
  76. circuit.witness_uint32(value)
  77. elif value := witness.get("Uint64"):
  78. circuit.witness_uint64(value)
  79. else:
  80. raise ValueError(f"Invalid Witness type for witness {witness}")
  81. # circuit.prover_build() will actually construct the circuit
  82. # with the values witnessed above.
  83. circuit = circuit.prover_build()
  84. # Instances are our public inputs for the proof and they're also
  85. # part of the JSON file.
  86. instances = []
  87. for instance in witness_data["instances"]:
  88. instances.append(Fp(instance))
  89. # If we're building an actual proof, we'll use the ProvingKey to
  90. # prove and our VerifyingKey to verify the proof.
  91. if not mock:
  92. print("Proving knowledge of witnesses...")
  93. proof = Proof.create(proving_key, [circuit], instances)
  94. print("Verifying ZK proof...")
  95. proof.verify(verifying_key, instances)
  96. # Otherwise, we'll simply run the MockProver:
  97. else:
  98. print("Running MockProver...")
  99. proof = MockProver.run(zkbin.k(), circuit, instances)
  100. print("Verifying MockProver...")
  101. proof.verify()
  102. print("Proof verified successfully!")
  103. if __name__ == "__main__":
  104. from argparse import ArgumentParser
  105. parser = ArgumentParser(
  106. prog="zkrunner",
  107. description="Python util for running zk proofs",
  108. epilog="This tool is only for prototyping purposes",
  109. )
  110. parser.add_argument(
  111. "SOURCE",
  112. help="Path to zkas source code",
  113. )
  114. parser.add_argument(
  115. "-w",
  116. "--witness",
  117. required=True,
  118. help="Path to JSON file holding witnesses",
  119. )
  120. parser.add_argument(
  121. "--prove",
  122. action="store_true",
  123. help="Actually create a real proof instead of using MockProver",
  124. )
  125. args = parser.parse_args()
  126. main(args.witness, args.SOURCE, mock=not args.prove)