pism.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import json
  2. import os
  3. import sys
  4. import codegen
  5. symbol_table = {
  6. "contract": 1,
  7. "param": 2,
  8. "start": 0,
  9. "end": 0,
  10. "witness": 2,
  11. "assert_not_small_order": 1,
  12. "fr_as_binary_le": 2,
  13. "ec_mul_const": 3,
  14. "ec_add": 3,
  15. "emit_ec": 1
  16. }
  17. types_map = {
  18. "U64": "u64",
  19. "Fr": "jubjub::Fr",
  20. "Point": "jubjub::SubgroupPoint",
  21. "Scalar": "bls12_381::Scalar",
  22. "Bool": "bool"
  23. }
  24. command_desc = {
  25. "witness": (
  26. ("EdwardsPoint", True),
  27. ("Point", False)
  28. ),
  29. "assert_not_small_order": (
  30. ("EdwardsPoint", False),
  31. ),
  32. "fr_as_binary_le": (
  33. ("Vec<Boolean>", True),
  34. ("Fr", False)
  35. ),
  36. "ec_mul_const": (
  37. ("EdwardsPoint", True),
  38. ("Vec<Boolean>", False),
  39. ("FixedGenerator", False)
  40. ),
  41. "ec_add": (
  42. ("EdwardsPoint", True),
  43. ("EdwardsPoint", False),
  44. ("EdwardsPoint", False),
  45. ),
  46. "emit_ec": (
  47. ("EdwardsPoint", False),
  48. )
  49. }
  50. def eprint(*args):
  51. print(*args, file=sys.stderr)
  52. class Line:
  53. def __init__(self, text, line_number):
  54. self.text = text
  55. self.orig = text
  56. self.lineno = line_number
  57. self.clean()
  58. def clean(self):
  59. # Remove the comments
  60. self.text = self.text.split("#", 1)[0]
  61. # Remove whitespace
  62. self.text = self.text.strip()
  63. def is_empty(self):
  64. return bool(self.text)
  65. def __repr__(self):
  66. return "Line %s: %s" % (self.lineno, self.orig)
  67. def command(self):
  68. if not self.is_empty():
  69. return None
  70. return self.text.split(" ")[0]
  71. def args(self):
  72. if not self.is_empty():
  73. return None
  74. return self.text.split(" ")[1:]
  75. def clean(contents):
  76. # Split input into lines
  77. contents = contents.split("\n")
  78. contents = [Line(line, i) for i, line in enumerate(contents)]
  79. # Remove empty blank lines
  80. contents = [line for line in contents if line.is_empty()]
  81. return contents
  82. def make_segments(contents):
  83. constants = [line for line in contents if line.command() == "constant"]
  84. segments = []
  85. current_segment = []
  86. for line in contents:
  87. if line.command() == "contract":
  88. current_segment = []
  89. current_segment.append(line)
  90. if line.command() == "end":
  91. segments.append(current_segment)
  92. current_segment = []
  93. return constants, segments
  94. def build_constants_table(constants):
  95. table = {}
  96. for line in constants:
  97. args = line.args()
  98. if len(args) != 2:
  99. eprint("error: wrong number of args")
  100. eprint(line)
  101. return None
  102. name, type = args
  103. table[name] = type
  104. return table
  105. def extract(segment):
  106. assert segment
  107. # Does it have a declaration?
  108. if not segment[0].command() == "contract":
  109. eprint("error: missing contract declaration")
  110. eprint(segment[0])
  111. return None
  112. # Does it have an end?
  113. if not segment[-1].command() == "end":
  114. eprint("error: missing contract end")
  115. eprint(segment[-1])
  116. return None
  117. # Does it have a start?
  118. if not [line for line in segment if line.command() == "start"]:
  119. eprint("error: missing contract start")
  120. eprint(segment[0])
  121. return None
  122. for line in segment:
  123. command, args = line.command(), line.args()
  124. if symbol_table[command] != len(args):
  125. eprint("error: wrong number of args for command '%s'" % command)
  126. eprint(line)
  127. return None
  128. contract_name = segment[0].args()[0]
  129. start_index = [index for index, line in enumerate(segment)
  130. if line.command() == "start"]
  131. if len(start_index) > 1:
  132. eprint("error: multiple start statements in contract '%s'" %
  133. contract_name)
  134. for index in start_index:
  135. eprint(segment[index])
  136. eprint("Aborting.")
  137. return None
  138. assert len(start_index) == 1
  139. start_index = start_index[0]
  140. header = segment[1:start_index]
  141. code = segment[start_index + 1:-1]
  142. params = {}
  143. for param_decl in header:
  144. args = param_decl.args()
  145. assert len(args) == 2
  146. name, type = args
  147. params[name] = type
  148. program = []
  149. for line in code:
  150. command, args = line.command(), line.args()
  151. program.append((command, args, line))
  152. return Contract(contract_name, params, program)
  153. def to_initial_caps(snake_str):
  154. components = snake_str.split("_")
  155. return "".join(x.title() for x in components)
  156. class Contract:
  157. def __init__(self, name, params, program):
  158. self.name = name
  159. self.params = params
  160. self.program = program
  161. def _includes(self):
  162. return \
  163. r"""use bellman::{
  164. gadgets::{
  165. boolean,
  166. boolean::{AllocatedBit, Boolean},
  167. multipack,
  168. },
  169. groth16, Circuit, ConstraintSystem, SynthesisError,
  170. };
  171. use bls12_381::Bls12;
  172. use ff::{PrimeField, Field};
  173. use group::Curve;
  174. use zcash_proofs::circuit::ecc;
  175. """
  176. def _compile_header(self):
  177. code = "pub struct %s {\n" % to_initial_caps(self.name)
  178. for param_name, param_type in self.params.items():
  179. try:
  180. mapped_type = types_map[param_type]
  181. except KeyError:
  182. return None
  183. code += " pub %s: Option<%s>,\n" % (param_name, mapped_type)
  184. code += "}\n"
  185. return code
  186. def _compile_body(self):
  187. self.stack = {}
  188. code = "\n"
  189. #indent = " " * 8
  190. for command, args, line in self.program:
  191. if (code_text := self._compile_line(command, args, line)) is None:
  192. return None
  193. code += code_text + "\n"
  194. return code
  195. def _preprocess_args(self, args, line):
  196. nargs = []
  197. for arg in args:
  198. if not arg.startswith("param:"):
  199. nargs.append((arg, False))
  200. continue
  201. _, argname = arg.split(":", 1)
  202. if argname not in self.params:
  203. eprint("error: non-existant param referenced")
  204. eprint(line)
  205. return None
  206. nargs.append((argname, True))
  207. return nargs
  208. def type_checking(self, command, args, line):
  209. assert command in command_desc
  210. type_list = command_desc[command]
  211. if len(type_list) != len(args):
  212. eprint("error: wrong number of arguments!")
  213. eprint(line)
  214. return False
  215. for (expected_type, new_val), (argname, is_param) in \
  216. zip(type_list, args):
  217. # Only type check input arguments, not output values
  218. if new_val:
  219. continue
  220. if is_param:
  221. actual_type = self.params[argname]
  222. else:
  223. # Check the stack here
  224. if argname not in self.stack:
  225. eprint("error: cannot find value '%s' on the stack!" %
  226. argname)
  227. eprint(line)
  228. return False
  229. actual_type = self.stack[argname]
  230. return True
  231. def _check_args(self, command, args, line):
  232. assert command in command_desc
  233. type_list = command_desc[command]
  234. assert len(type_list) == len(args)
  235. for (_, is_new_val), (arg, is_param) in zip(type_list, args):
  236. if is_param:
  237. continue
  238. if is_new_val:
  239. continue
  240. if arg in self.stack:
  241. continue
  242. if arg in self.constants:
  243. continue
  244. eprint("error: cannot find '%s' in the stack" % arg)
  245. eprint(line)
  246. return False
  247. return True
  248. def _compile_line(self, command, args, line):
  249. if (args := self._preprocess_args(args, line)) is None:
  250. return None
  251. if not self.type_checking(command, args, line):
  252. return None
  253. if not self._check_args(command, args, line):
  254. return None
  255. self.modify_stack(command, args)
  256. args = [self.carg(arg) for arg in args]
  257. try:
  258. codegen_method = getattr(codegen, command)
  259. except AttributeError:
  260. eprint("error: missing command '%s' does not exist" % command)
  261. eprint(line)
  262. return None
  263. return codegen_method(line, *args)
  264. def carg(self, arg):
  265. argname, is_param = arg
  266. if is_param:
  267. return "self.%s" % argname
  268. if argname in self.rename_consts:
  269. return self.rename_consts[argname]
  270. return argname
  271. def modify_stack(self, command, args):
  272. type_list = command_desc[command]
  273. assert len(type_list) == len(args)
  274. for (expected_type, new_val), (argname, is_param) in \
  275. zip(type_list, args):
  276. if is_param:
  277. assert not new_val
  278. continue
  279. # Now apply the new values to the stack
  280. if new_val:
  281. self.stack[argname] = expected_type
  282. def compile(self, constants, aux):
  283. self.constants = constants
  284. code = ""
  285. code += self._includes()
  286. self.rename_consts = {}
  287. if "constants" in aux:
  288. for const_name, value in aux["constants"].items():
  289. if "module_includes" not in value:
  290. continue
  291. if "maps_to" not in value:
  292. eprint("error: bad aux config '%s', missing maps_to" %
  293. const_name)
  294. mapped_type = value["maps_to"]
  295. code += "use %s::%s;\n" % (value["module_includes"], mapped_type)
  296. self.rename_consts[const_name] = mapped_type
  297. code += "\n"
  298. if (header := self._compile_header()) is None:
  299. return None
  300. code += header
  301. code += \
  302. r"""impl Circuit<bls12_381::Scalar> for %s {
  303. fn synthesize<CS: ConstraintSystem<bls12_381::Scalar>>(
  304. self,
  305. cs: &mut CS,
  306. ) -> Result<(), SynthesisError> {
  307. """ % to_initial_caps(self.name)
  308. if (body := self._compile_body()) is None:
  309. return None
  310. code += body
  311. code += "Ok(())\n"
  312. code += " }\n"
  313. code += "}\n"
  314. return code
  315. def process(contents, aux):
  316. contents = clean(contents)
  317. constants, segments = make_segments(contents)
  318. if (constants := build_constants_table(constants)) is None:
  319. return False
  320. codes = []
  321. for segment in segments:
  322. contract = extract(segment)
  323. if (code := contract.compile(constants, aux)) is None:
  324. return False
  325. codes.append(code)
  326. # Success! Output finished product.
  327. [print(code) for code in codes]
  328. return True
  329. def main(argv):
  330. if len(argv) != 2:
  331. eprint("pism FILENAME")
  332. return -1
  333. src_filename = argv[1]
  334. basename, _ = os.path.splitext(src_filename)
  335. aux_filename = basename + ".aux"
  336. aux = json.loads(open(aux_filename).read())
  337. contents = open(src_filename).read()
  338. if not process(contents, aux):
  339. return -2
  340. return 0
  341. if __name__ == "__main__":
  342. sys.exit(main(sys.argv))