pism.py 9.3 KB

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