pism.py 12 KB

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