zkrunner.py 5.4 KB

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