Compare commits

...

48 Commits

Author SHA1 Message Date
krlosMata
babe908d35 add header to witness bin 2020-02-27 20:58:37 +01:00
Jordi Baylina
6c1a3e7687 Isolate code generation to output different languages 2020-02-04 19:21:37 +01:00
Jordi Baylina
111c91c70d bigarray 2020-01-30 07:25:48 +01:00
Jordi Baylina
a8d597d8c5 Optimize number of getSignalOffset and getSignalSizes 2020-01-28 15:17:47 +01:00
Jordi Baylina
3a9766a008 write code in stream mode 2020-01-27 13:08:11 +07:00
Jordi Baylina
20058a38d6 toInt and isTrue in assembly 2020-01-23 09:31:29 +07:00
Jordi Baylina
f6092e3944 Integrated with asm and tested 2020-01-23 07:20:58 +07:00
Jordi Baylina
e11e6768e4 buildasm field finished 2020-01-17 17:58:25 +01:00
Jordi Baylina
63fd72cdc7 Assembly library started 2020-01-14 22:43:58 +01:00
Jordi Baylina
da969a5e16 Construction phase redone 2019-12-23 19:34:52 +01:00
Jordi Baylina
b564201170 Multithread 2019-12-20 22:01:12 +01:00
Jordi Baylina
e62c1cdbc3 Fixes and tests passed 2019-12-16 21:37:14 +01:00
Jordi Baylina
ec0e7f421b roll unrolled loops with code 2019-12-10 18:46:10 +01:00
Jordi Baylina
afa8201c2c All operators finished 2019-12-08 16:20:15 +01:00
Jordi Baylina
1f94f7f3ec All Bit and logical operators working 2019-12-08 13:39:16 +01:00
Jordi Baylina
eaf4396cb3 div operators 2019-12-07 21:47:00 +01:00
Jordi Baylina
2a45647274 If added 2019-12-07 14:15:27 +01:00
Jordi Baylina
305bc7456f Syms generarion fixed 2019-12-07 13:14:55 +01:00
Jordi Baylina
ff1c12bcc3 scopes work og in code generation 2019-12-07 12:58:11 +01:00
Jordi Baylina
fbcc753bc1 Arrays working 2019-12-06 13:26:26 +01:00
Jordi Baylina
1e3d1235cb Merge branch 'master' into c_build 2019-12-04 22:13:55 +01:00
Jordi Baylina
7b0b203c60 Merge branch 'master' of github.com:iden3/circom 2019-12-04 21:54:34 +01:00
Jordi Baylina
80846667ea 0.0.35 2019-12-04 21:54:11 +01:00
Jordi Baylina
7181c372d9 Error with bad assignments 2019-12-04 21:53:39 +01:00
Jordi Baylina
aecc28a79b Fix array assignement and not allow assign with equal 2019-12-04 17:52:52 +01:00
Jordi Baylina
0be08d67b0 Fix array assignement and not allow assign with equal 2019-12-04 17:40:48 +01:00
Jordi Baylina
6cdb006909 constants 2019-11-30 22:59:14 +01:00
Jordi Baylina
f4bbcfd90c functions added 2019-11-29 20:19:58 +01:00
Jordi Baylina
93330f065b for loops 2019-11-28 15:18:52 +01:00
Jordi Baylina
66291a0efe C generation 2019-11-23 19:12:58 +01:00
Jordi Baylina
83c95b5188 Merge pull request #42 from daira/patch-1
Yes! Thank you for the fix!
2019-10-15 19:11:02 +09:00
Daira Hopwood
13c4c81a0f Fix error in comment 2019-10-13 09:35:26 -07:00
Jordi Baylina
51ff27b9c6 Verbose added 2019-09-22 12:56:05 +02:00
Jordi Baylina
6985892f86 0.0.34 2019-09-18 17:43:39 +02:00
Jordi Baylina
bacb7afde7 Merge branch 'master' of github.com:iden3/circom 2019-09-18 17:43:26 +02:00
Jordi Baylina
d04eff6c0d Optimize optimization and fix out<==in 2019-09-18 17:43:14 +02:00
Jordi Baylina
230894921e Merge pull request #41 from k06a/fix/error-message
Fix error message, for most IDEs recognises as URI to file row and column
2019-09-16 22:10:12 +02:00
Anton Bukov
64029e1842 Fix error message, for most IDEs recognises as URI to file row and column 2019-09-15 22:57:18 +03:00
Jordi Baylina
700412f23d 0.0.33 2019-09-15 18:55:40 +02:00
Jordi Baylina
832077fbe9 Fix Optimization 2019-09-15 18:55:28 +02:00
Jordi Baylina
0df0ac712d 0.0.32 2019-09-15 10:48:02 +02:00
Jordi Baylina
67a35ee400 better info in runtime constraint assertion 2019-09-15 10:47:52 +02:00
Jordi Baylina
680e3fe139 0.0.31 2019-08-29 16:26:28 +02:00
Jordi Baylina
f05c4e1338 compute block added 2019-08-29 16:26:19 +02:00
Jordi Baylina
597deb1eaa Merge pull request #31 from Mikerah/patch-1
Typo fixes
2019-06-27 11:40:21 +02:00
Jordi Baylina
7a1c606ca6 0.0.30 2019-06-21 10:43:11 +02:00
Jordi Baylina
6642d4cf93 Fix: include allways reduce constants 2019-06-21 10:42:49 +02:00
Mikerah
a43154241e Typo fixes 2019-06-10 20:41:01 -04:00
107 changed files with 18071 additions and 2484 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

6
.gitignore vendored
View File

@@ -61,3 +61,9 @@ typings/
.next
tmp
.DS_Store
# Workspace files are user-specific
*.sublime-workspace

24
Project.sublime-project Normal file
View File

@@ -0,0 +1,24 @@
{
"folders": [
{
"path": ".",
}
],
"settings": {
"SublimeAnarchyDebug": {
"debug": {
"executable": "${project_path}/test/circuits/add",
"params": [
"addin.json",
"out.bin",
],
"path": [
],
"environment": [
],
"working_dir": "${project_path}"
}
}
}
}

View File

@@ -177,7 +177,7 @@ To waranty binary outputs:
.
.
.
out[n+e-1] * (out[n+e-1] - 1) == 0
out[n+e-1] * (out[n+e-1] - 1) === 0
*/

View File

@@ -156,7 +156,7 @@ snarkjs verify
This command will use `verification_key.json`, `proof.json` and `public.json` to verify that is valid.
Here we are veifying that we know a witness that the public inputs and the outputs matches the ones in the `public.json` file.
Here we are verifying that we know a witness that the public inputs and the outputs matches the ones in the `public.json` file.
If the proof is ok, you will see `OK` or `INVALID` if not ok.
@@ -182,7 +182,7 @@ The verifier contract deployed in the last step has a `view` function called `ve
This function will return true if the proof and the inputs are valid.
To facilitiate the call, you can use snarkjs to generate the parameters of the call by typing:
To facilitate the call, you can use snarkjs to generate the parameters of the call by typing:
```sh
snarkjs generatecall
@@ -192,7 +192,7 @@ Just cut and paste the output to the parameters field of the `verifyProof` metho
If every thing works ok, this method should return true.
If you change any bit in the parameters, the result will be veryfiable false.
If you change any bit in the parameters, the result will be verifiably false.
## Bonus track
@@ -227,19 +227,19 @@ template Multiplier() {
component main = Multiplier();
```
A nice thing of circom language is that you can split a <== into two independent acions: <-- and ===
A nice thing of the circom language is that you can split a <== into two independent actions: <-- and ===
The <-- and --> operators assign a value to a signal without creating any constraints.
The === operator adds a constraint without assigning any value to any signal.
The circuit has also another problem: the operation works in Zr, so we need to guarantee the multiplication does not overflow. This can be done by binarizing the inputs and checking the ranges, but we will reserve it for future tutorials.
The circuit also has another problem: the operation works in Zr, so we need to guarantee the multiplication does not overflow. This can be done by converting the inputs to binary and checking the ranges, but we will reserve it for future tutorials.
## Where to go from here:
You may want to read the [README](https://github.com/iden3/circom) to learn more features about circom.
You can also check a a library with many basic circuits lib binaritzations, comparators, eddsa, hashes, merkle trees etc [here](https://github.com/iden3/circomlib) (Work in progress).
You can also check a library with many basic circuits lib binarizations, comparators, eddsa, hashes, merkle trees etc [here](https://github.com/iden3/circomlib) (Work in progress).
Or a exponentiation in the Baby Jub curve [here](https://github.com/iden3/circomlib) (Work in progress).

245
c/buildasm/add.asm.ejs Normal file
View File

@@ -0,0 +1,245 @@
<% function addS1S2() { %>
xor rdx, rdx
mov edx, eax
add edx, ecx
jo add_manageOverflow ; rsi already is the 64bits result
mov [rdi], rdx ; not necessary to adjust so just save and return
ret
add_manageOverflow: ; Do the operation in 64 bits
push rsi
movsx rsi, eax
movsx rdx, ecx
add rsi, rdx
call rawCopyS2L
pop rsi
ret
<% } %>
<% function addL1S2() { %>
add rsi, 8
movsx rdx, ecx
add rdi, 8
cmp rdx, 0
<% const rawAddLabel = global.tmpLabel() %>
jns <%= rawAddLabel %>
neg rdx
call rawSubLS
sub rdi, 8
sub rsi, 8
ret
<%= rawAddLabel %>:
call rawAddLS
sub rdi, 8
sub rsi, 8
ret
<% } %>
<% function addS1L2() { %>
lea rsi, [rdx + 8]
movsx rdx, eax
add rdi, 8
cmp rdx, 0
<% const rawAddLabel = global.tmpLabel() %>
jns <%= rawAddLabel %>
neg rdx
call rawSubLS
sub rdi, 8
sub rsi, 8
ret
<%= rawAddLabel %>:
call rawAddLS
sub rdi, 8
sub rsi, 8
ret
<% } %>
<% function addL1L2() { %>
add rdi, 8
add rsi, 8
add rdx, 8
call rawAddLL
sub rdi, 8
sub rsi, 8
ret
<% } %>
;;;;;;;;;;;;;;;;;;;;;;
; add
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of any kind
; Params:
; rsi <= Pointer to element 1
; rdx <= Pointer to element 2
; rdi <= Pointer to result
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_add:
mov rax, [rsi]
mov rcx, [rdx]
bt rax, 63 ; Check if is short first operand
jc add_l1
bt rcx, 63 ; Check if is short second operand
jc add_s1l2
add_s1s2: ; Both operands are short
<%= addS1S2() %>
add_l1:
bt rcx, 63 ; Check if is short second operand
jc add_l1l2
;;;;;;;;
add_l1s2:
bt rax, 62 ; check if montgomery first
jc add_l1ms2
add_l1ns2:
<%= global.setTypeDest("0x80"); %>
<%= addL1S2(); %>
add_l1ms2:
bt rcx, 62 ; check if montgomery second
jc add_l1ms2m
add_l1ms2n:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_b() %>
<%= addL1L2() %>
add_l1ms2m:
<%= global.setTypeDest("0xC0"); %>
<%= addL1L2() %>
;;;;;;;;
add_s1l2:
bt rcx, 62 ; check if montgomery first
jc add_s1l2m
add_s1l2n:
<%= global.setTypeDest("0x80"); %>
<%= addS1L2(); %>
add_s1l2m:
bt rax, 62 ; check if montgomery second
jc add_s1ml2m
add_s1nl2m:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_a() %>
<%= addL1L2() %>
add_s1ml2m:
<%= global.setTypeDest("0xC0"); %>
<%= addL1L2() %>
;;;;
add_l1l2:
bt rax, 62 ; check if montgomery first
jc add_l1ml2
add_l1nl2:
bt rcx, 62 ; check if montgomery second
jc add_l1nl2m
add_l1nl2n:
<%= global.setTypeDest("0x80"); %>
<%= addL1L2() %>
add_l1nl2m:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_a(); %>
<%= addL1L2() %>
add_l1ml2:
bt rcx, 62 ; check if montgomery seconf
jc add_l1ml2m
add_l1ml2n:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_b(); %>
<%= addL1L2() %>
add_l1ml2m:
<%= global.setTypeDest("0xC0"); %>
<%= addL1L2() %>
;;;;;;;;;;;;;;;;;;;;;;
; rawAddLL
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of type long
; Params:
; rsi <= Pointer to the long data of element 1
; rdx <= Pointer to the long data of element 2
; rdi <= Pointer to the long data of result
; Modified Registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;
rawAddLL:
; Add component by component with carry
<% for (let i=0; i<n64; i++) { %>
mov rax, [rsi + <%=i*8%>]
<%= i==0 ? "add" : "adc" %> rax, [rdx + <%=i*8%>]
mov [rdi + <%=i*8%>], rax
<% } %>
jc rawAddLL_sq ; if overflow, substract q
; Compare with q
<% for (let i=0; i<n64; i++) { %>
<% if (i>0) { %>
mov rax, [rdi + <%= (n64-i-1)*8 %>]
<% } %>
cmp rax, [q + <%= (n64-i-1)*8 %>]
jc rawAddLL_done ; q is bigget so done.
jnz rawAddLL_sq ; q is lower
<% } %>
; If equal substract q
rawAddLL_sq:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "sub" : "sbb" %> [rdi + <%=i*8%>], rax
<% } %>
rawAddLL_done:
ret
;;;;;;;;;;;;;;;;;;;;;;
; rawAddLS
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of type long
; Params:
; rdi <= Pointer to the long data of result
; rsi <= Pointer to the long data of element 1
; rdx <= Value to be added
;;;;;;;;;;;;;;;;;;;;;;
rawAddLS:
; Add component by component with carry
add rdx, [rsi]
mov [rdi] ,rdx
<% for (let i=1; i<n64; i++) { %>
mov rdx, 0
adc rdx, [rsi + <%=i*8%>]
mov [rdi + <%=i*8%>], rdx
<% } %>
jc rawAddLS_sq ; if overflow, substract q
; Compare with q
<% for (let i=0; i<n64; i++) { %>
mov rax, [rdi + <%= (n64-i-1)*8 %>]
cmp rax, [q + <%= (n64-i-1)*8 %>]
jc rawAddLS_done ; q is bigget so done.
jnz rawAddLS_sq ; q is lower
<% } %>
; If equal substract q
rawAddLS_sq:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "sub" : "sbb" %> [rdi + <%=i*8%>], rax
<% } %>
rawAddLS_done:
ret

217
c/buildasm/binops.asm.ejs Normal file
View File

@@ -0,0 +1,217 @@
<% function binOpS1S2(op) { %>
cmp r8d, 0
<% const s1s2_solveNeg = global.tmpLabel() %>
js <%=s1s2_solveNeg%>
cmp r9d, 0
js <%=s1s2_solveNeg%>
xor rdx, rdx ; both ops are positive so do the op and return
mov edx, r8d
<%=op%> edx, r9d
mov [rdi], rdx ; not necessary to adjust so just save and return
ret
<%=s1s2_solveNeg%>:
<%= global.setTypeDest("0x80"); %>
<%= global.toLong_b() %>
<%= global.toLong_a() %>
<%= binOpL1L2(op) %>
<% } %>
<% function binOpS1L2(op) { %>
cmp r8d, 0
<% const s1l2_solveNeg = global.tmpLabel() %>
js <%=s1l2_solveNeg%>
movsx rax, r8d
<%=op%> rax, [rdx +8]
mov [rdi+8], rax
<% for (let i=1; i<n64; i++) { %>
xor rax, rax
<%=op%> rax, [rdx + <%= (i*8)+8 %>]
<% if (i== n64-1) { %>
and rax, [lboMask]
<% } %>
mov [rdi + <%= (i*8)+8 %> ], rax
<% } %>
ret
<%=s1l2_solveNeg%>:
<%= global.toLong_a() %>
<%= global.setTypeDest("0x80"); %>
<%= binOpL1L2(op) %>
<% } %>
<% function binOpL1S2(op) { %>
cmp r9d, 0
<% const l1s2_solveNeg = global.tmpLabel() %>
js <%=l1s2_solveNeg%>
movsx rax, r9d
<%=op%> rax, [rsi +8]
mov [rdi+8], rax
<% for (let i=1; i<n64; i++) { %>
xor rax, rax
<%=op%> rax, [rsi + <%= (i*8)+8 %>];
<% if (i== n64-1) { %>
and rax, [lboMask] ;
<% } %>
mov [rdi + <%= (i*8)+8 %> ], rax;
<% } %>
ret
<%=l1s2_solveNeg%>:
<%= global.toLong_b() %>
<%= global.setTypeDest("0x80"); %>
<%= binOpL1L2(op) %>
<% } %>
<% function binOpL1L2(op) { %>
<% for (let i=0; i<n64; i++) { %>
mov rax, [rsi + <%= (i*8)+8 %>]
<%=op%> rax, [rdx + <%= (i*8)+8 %>]
<% if (i== n64-1) { %>
and rax, [lboMask]
<% } %>
mov [rdi + <%= (i*8)+8 %> ], rax
<% } %>
ret
<% } %>
<% function binOp(op) { %>
;;;;;;;;;;;;;;;;;;;;;;
; b<%= op %>
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of any kind
; Params:
; rsi <= Pointer to element 1
; rdx <= Pointer to element 2
; rdi <= Pointer to result
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_b<%=op%>:
mov r8, [rsi]
mov r9, [rdx]
bt r8, 63 ; Check if is short first operand
jc <%=op%>_l1
bt r9, 63 ; Check if is short second operand
jc <%=op%>_s1l2
<%=op%>_s1s2:
<%= binOpS1S2(op) %>
<%=op%>_l1:
bt r9, 63 ; Check if is short second operand
jc <%=op%>_l1l2
<%=op%>_l1s2:
bt r8, 62 ; check if montgomery first
jc <%=op%>_l1ms2
<%=op%>_l1ns2:
<%= global.setTypeDest("0x80"); %>
<%= binOpL1S2(op) %>
<%=op%>_l1ms2:
<%= global.setTypeDest("0x80"); %>
push r9 ; r9 is used in montgomery so we need to save it
<%= global.fromMont_a() %>
pop r9
<%= binOpL1S2(op) %>
<%=op%>_s1l2:
bt r9, 62 ; check if montgomery first
jc <%=op%>_s1l2m
<%=op%>_s1l2n:
<%= global.setTypeDest("0x80"); %>
<%= binOpS1L2(op) %>
<%=op%>_s1l2m:
<%= global.setTypeDest("0x80"); %>
push r8 ; r8 is used in montgomery so we need to save it
<%= global.fromMont_b() %>
pop r8
<%= binOpS1L2(op) %>
<%=op%>_l1l2:
bt r8, 62 ; check if montgomery first
jc <%=op%>_l1ml2
bt r9, 62 ; check if montgomery first
jc <%=op%>_l1nl2m
<%=op%>_l1nl2n:
<%= global.setTypeDest("0x80"); %>
<%= binOpL1L2(op) %>
<%=op%>_l1nl2m:
<%= global.setTypeDest("0x80"); %>
<%= global.fromMont_b() %>
<%= binOpL1L2(op) %>
<%=op%>_l1ml2:
bt r9, 62 ; check if montgomery first
jc <%=op%>_l1ml2m
<%=op%>_l1ml2n:
<%= global.setTypeDest("0x80"); %>
<%= global.fromMont_a() %>
<%= binOpL1L2(op) %>
<%=op%>_l1ml2m:
<%= global.setTypeDest("0x80"); %>
<%= global.fromMont_a() %>
<%= global.fromMont_b() %>
<%= binOpL1L2(op) %>
<% } %>
<%= binOp("and") %>
<%= binOp("or") %>
<%= binOp("xor") %>
;;;;;;;;;;;;;;;;;;;;;;
; bnot
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of any kind
; Params:
; rsi <= Pointer to element 1
; rdi <= Pointer to result
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_bnot:
<%= global.setTypeDest("0x80"); %>
mov r8, [rsi]
bt r8, 63 ; Check if is long operand
jc bnot_l1
bnot_s:
<%= global.toLong_a() %>
jmp bnot_l1n
bnot_l1:
bt r8, 62 ; check if montgomery first
jnc bnot_l1n
bnot_l1m:
<%= global.fromMont_a() %>
bnot_l1n:
<% for (let i=0; i<n64; i++) { %>
mov rax, [rsi + <%= i*8 + 8 %>]
not rax
<% if (i== n64-1) { %>
and rax, [lboMask]
<% } %>
mov [rdi + <%= i*8 + 8 %>], rax
<% } %>
ret

View File

@@ -0,0 +1,72 @@
const bigInt=require("big-integer");
const path = require("path");
const util = require("util");
const renderFile = util.promisify(require("ejs").renderFile);
const runningAsScript = !module.parent;
class ZqBuilder {
constructor(q, name) {
const self = this;
this.q=bigInt(q);
this.n64 = Math.floor((this.q.bitLength() - 1) / 64)+1;
this.name = name;
this.bigInt = bigInt;
this.lastTmp=0;
this.global = {};
this.global.tmpLabel = function(label) {
self.lastTmp++;
label = label || "tmp";
return label+"_"+self.lastTmp;
};
}
constantElement(v) {
let S = "";
const mask = bigInt("FFFFFFFFFFFFFFFF", 16);
for (let i=0; i<this.n64; i++) {
if (i>0) S = S+",";
let shex = v.shiftRight(i*64).and(mask).toString(16);
while (shex.length <16) shex = "0" + shex;
S = S + "0x" + shex;
}
return S;
}
}
async function buildField(q, name) {
const builder = new ZqBuilder(q, name);
const asm = await renderFile(path.join(__dirname, "fr.asm.ejs"), builder);
const c = await renderFile(path.join(__dirname, "fr.c.ejs"), builder);
const h = await renderFile(path.join(__dirname, "fr.h.ejs"), builder);
return {asm: asm, h: h, c: c};
}
if (runningAsScript) {
const fs = require("fs");
var argv = require("yargs")
.usage("Usage: $0 -q [primeNum] -n [name] -oc [out .c file] -oh [out .h file]")
.demandOption(["q","n"])
.alias("q", "prime")
.alias("n", "name")
.argv;
const q = bigInt(argv.q);
const asmFileName = (argv.oc) ? argv.oc : argv.name.toLowerCase() + ".asm";
const hFileName = (argv.oc) ? argv.oc : argv.name.toLowerCase() + ".h";
const cFileName = (argv.oc) ? argv.oc : argv.name.toLowerCase() + ".c";
buildField(q, argv.name).then( (res) => {
fs.writeFileSync(asmFileName, res.asm, "utf8");
fs.writeFileSync(hFileName, res.h, "utf8");
fs.writeFileSync(cFileName, res.c, "utf8");
});
} else {
module.exports = buildField;
}

View File

@@ -0,0 +1,75 @@
const chai = require("chai");
const assert = chai.assert;
const fs = require("fs");
var tmp = require("tmp-promise");
const path = require("path");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const BuildZqField = require("./buildzqfield");
module.exports = testField;
async function testField(prime, test) {
tmp.setGracefulCleanup();
const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
const source = await BuildZqField(prime, "Fr");
// console.log(dir.path);
await fs.promises.writeFile(path.join(dir.path, "fr.asm"), source.asm, "utf8");
await fs.promises.writeFile(path.join(dir.path, "fr.h"), source.h, "utf8");
await fs.promises.writeFile(path.join(dir.path, "fr.c"), source.c, "utf8");
await exec(`cp ${path.join(__dirname, "tester.cpp")} ${dir.path}`);
await exec("nasm -fmacho64 --prefix _ " +
` ${path.join(dir.path, "fr.asm")}`
);
await exec("g++" +
` ${path.join(dir.path, "tester.cpp")}` +
` ${path.join(dir.path, "fr.o")}` +
` ${path.join(dir.path, "fr.c")}` +
` -o ${path.join(dir.path, "tester")}` +
" -lgmp -g"
);
const inLines = [];
for (let i=0; i<test.length; i++) {
for (let j=0; j<test[i][0].length; j++) {
inLines.push(test[i][0][j]);
}
}
inLines.push("");
await fs.promises.writeFile(path.join(dir.path, "in.tst"), inLines.join("\n"), "utf8");
await exec(`${path.join(dir.path, "tester")}` +
` <${path.join(dir.path, "in.tst")}` +
` >${path.join(dir.path, "out.tst")}`);
const res = await fs.promises.readFile(path.join(dir.path, "out.tst"), "utf8");
const resLines = res.split("\n");
for (let i=0; i<test.length; i++) {
const expected = test[i][1].toString();
const calculated = resLines[i];
if (calculated != expected) {
console.log("FAILED");
for (let j=0; j<test[i][0].length; j++) {
console.log(test[i][0][j]);
}
console.log("Should Return: " + expected);
console.log("But Returns: " + calculated);
}
assert.equal(calculated, expected);
}
}

108
c/buildasm/cmpops.asm.ejs Normal file
View File

@@ -0,0 +1,108 @@
<% function retOne() { %>
mov qword [rdi], 1
add rsp, <%= (n64+1)*8 %>
ret
<% } %>
<% function retZero() { %>
mov qword [rdi], 0
add rsp, <%= (n64+1)*8 %>
ret
<% } %>
<% function cmpLong(op, eq) { %>
<%
if (eq==true) {
if (["leq","geq"].indexOf(op) >= 0) retOne();
if (["lt","gt"].indexOf(op) >= 0) retZero();
}
%>
<% const label_gt = global.tmpLabel() %>
<% const label_lt = global.tmpLabel() %>
<% for (let i=n64-1; i>=0; i--) { %>
mov rax, [rsp + <%= 8+(i*8) %>]
cmp [half + <%= (i*8) %>], rax ; comare with (q-1)/2
jc <%=label_lt%> ; half<rax => e1-e2 is neg => e1 < e2
jnz <%=label_gt%> ; half>rax => e1 -e2 is pos => e1 > e2
<% } %>
; half == rax => e1-e2 is pos => e1 > e2
<%=label_gt%>:
<% if (["geq","gt"].indexOf(op) >= 0) retOne(); else retZero(); %>
<%=label_lt%>:
<% if (["leq","lt"].indexOf(op) >= 0) retOne(); else retZero(); %>
<% } // cmpLong%>
<% function cmpOp(op) { %>
;;;;;;;;;;;;;;;;;;;;;;
; <%= op %>
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of any kind
; Params:
; rsi <= Pointer to element 1
; rdx <= Pointer to element 2
; rdi <= Pointer to result can be zero or one.
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_<%=op%>:
sub rsp, <%= (n64+1)*8 %> ; Save space for the result of the substraction
push rdi ; Save rdi
lea rdi, [rsp+8] ; We pushed rdi so we need to add 8
call <%=name%>_sub ; Do a substraction
call <%=name%>_toNormal ; Convert it to normal
pop rdi
mov rax, [rsp] ; We already poped do no need to add 8
bt rax, 63 ; check is result is long
jc <%=op%>_longCmp
<%=op%>_shortCmp:
cmp eax, 0
je <%=op%>_s_eq
js <%=op%>_s_lt
<%=op%>_s_gt:
<% if (["geq","gt", "neq"].indexOf(op) >= 0) retOne(); else retZero(); %>
<%=op%>_s_lt:
<% if (["leq","lt", "neq"].indexOf(op) >= 0) retOne(); else retZero(); %>
<%=op%>_s_eq:
<% if (["eq","geq", "leq"].indexOf(op) >= 0) retOne(); else retZero(); %>
<%=op%>_longCmp:
<% for (let i=n64-1; i>=0; i--) { %>
cmp qword [rsp + <%= 8+(i*8) %>], 0
jnz <%=op%>_neq
<% } %>
<%=op%>_eq:
<% if (op == "eq") {
retOne();
} else if (op == "neq") {
retZero();
} else {
cmpLong(op, true);
}
%>
<%=op%>_neq:
<% if (op == "neq") {
retOne();
} else if (op == "eq") {
retZero();
} else {
cmpLong(op, false);
}
%>
<% } %>
<%= cmpOp("eq") %>
<%= cmpOp("neq") %>
<%= cmpOp("lt") %>
<%= cmpOp("gt") %>
<%= cmpOp("leq") %>
<%= cmpOp("geq") %>

133
c/buildasm/copy.asm.ejs Normal file
View File

@@ -0,0 +1,133 @@
;;;;;;;;;;;;;;;;;;;;;;
; copy
;;;;;;;;;;;;;;;;;;;;;;
; Copies
; Params:
; rsi <= the src
; rdi <= the dest
;
; Nidified registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_copy:
<% for (let i=0; i<=n64; i++) { %>
mov rax, [rsi + <%= i*8 %>]
mov [rdi + <%= i*8 %>], rax
<% } %>
ret
;;;;;;;;;;;;;;;;;;;;;;
; copy an array of integers
;;;;;;;;;;;;;;;;;;;;;;
; Copies
; Params:
; rsi <= the src
; rdi <= the dest
; rdx <= number of integers to copy
;
; Nidified registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_copyn:
<%=name%>_copyn_loop:
mov r8, rsi
mov r9, rdi
mov rax, <%= n64+1 %>
mul rdx
mov rcx, rax
cld
rep movsq
mov rsi, r8
mov rdi, r9
ret
;;;;;;;;;;;;;;;;;;;;;;
; rawCopyS2L
;;;;;;;;;;;;;;;;;;;;;;
; Convert a 64 bit integer to a long format field element
; Params:
; rsi <= the integer
; rdi <= Pointer to the overwritted element
;
; Nidified registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;;
rawCopyS2L:
mov al, 0x80
shl rax, 56
mov [rdi], rax ; set the result to LONG normal
cmp rsi, 0
js u64toLong_adjust_neg
mov [rdi + 8], rsi
xor rax, rax
<% for (let i=1; i<n64; i++) { %>
mov [rdi + <%= 8+i*8 %>], rax
<% } %>
ret
u64toLong_adjust_neg:
add rsi, [q] ; Set the first digit
mov [rdi + 8], rsi ;
mov rsi, -1 ; all ones
<% for (let i=1; i<n64; i++) { %>
mov rax, rsi ; Add to q
adc rax, [q + <%= i*8 %> ]
mov [rdi + <%= (i+1)*8 %>], rax
<% } %>
ret
;;;;;;;;;;;;;;;;;;;;;;
; toInt
;;;;;;;;;;;;;;;;;;;;;;
; Convert a 64 bit integer to a long format field element
; Params:
; rsi <= Pointer to the element
; Returs:
; rax <= The value
;;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_toInt:
mov rax, [rdi]
bt rax, 63
jc <%=name%>_long
movsx rax, eax
ret
<%=name%>_long:
mov rax, [rdi + 8]
mov rcx, rax
shr rcx, 31
jnz <%=name%>_longNeg
<% for (let i=1; i< n64; i++) { %>
mov rcx, [rdi + <%= i*8+8 %>]
test rcx, rcx
jnz <%=name%>_longNeg
<% } %>
ret
<%=name%>_longNeg:
mov rax, [rdi + 8]
sub rax, [q]
jnc <%=name%>_longErr
<% for (let i=1; i<n64; i++) { %>
mov rcx, [rdi + <%= i*8+8 %>]
sbb rcx, [q + <%= i*8 %>]
jnc <%=name%>_longErr
<% } %>
mov rcx, rax
sar rcx, 31
add rcx, 1
jnz <%=name%>_longErr
ret
<%=name%>_longErr:
push rdi
mov rdi, 0
call <%=name%>_fail
pop rdi

5841
c/buildasm/fr.asm Normal file

File diff suppressed because it is too large Load Diff

53
c/buildasm/fr.asm.ejs Normal file
View File

@@ -0,0 +1,53 @@
global <%=name%>_copy
global <%=name%>_copyn
global <%=name%>_add
global <%=name%>_sub
global <%=name%>_neg
global <%=name%>_mul
global <%=name%>_square
global <%=name%>_band
global <%=name%>_bor
global <%=name%>_bxor
global <%=name%>_bnot
global <%=name%>_eq
global <%=name%>_neq
global <%=name%>_lt
global <%=name%>_gt
global <%=name%>_leq
global <%=name%>_geq
global <%=name%>_land
global <%=name%>_lor
global <%=name%>_lnot
global <%=name%>_toNormal
global <%=name%>_toLongNormal
global <%=name%>_toMontgomery
global <%=name%>_toInt
global <%=name%>_isTrue
global <%=name%>_q
extern <%=name%>_fail
DEFAULT REL
section .text
<%- include('utils.asm.ejs'); %>
<%- include('copy.asm.ejs'); %>
<%- include('montgomery.asm.ejs'); %>
<%- include('add.asm.ejs'); %>
<%- include('sub.asm.ejs'); %>
<%- include('neg.asm.ejs'); %>
<%- include('mul.asm.ejs'); %>
<%- include('binops.asm.ejs'); %>
<%- include('cmpops.asm.ejs'); %>
<%- include('logicalops.asm.ejs'); %>
section .data
<%=name%>_q:
dd 0
dd 0x80000000
q dq <%= constantElement(q) %>
half dq <%= constantElement(q.shiftRight(1)) %>
R2 dq <%= constantElement(bigInt.one.shiftLeft(n64*64*2).mod(q)) %>
R3 dq <%= constantElement(bigInt.one.shiftLeft(n64*64*3).mod(q)) %>
lboMask dq 0x<%= bigInt("8000000000000000",16).shiftRight(n64*64 - q.bitLength()).minus(bigInt.one).toString(16) %>

185
c/buildasm/fr.c Normal file
View File

@@ -0,0 +1,185 @@
#include "fr.h"
#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <assert.h>
mpz_t q;
mpz_t zero;
mpz_t one;
mpz_t mask;
size_t nBits;
void Fr_toMpz(mpz_t r, PFrElement pE) {
Fr_toNormal(pE);
if (!(pE->type & Fr_LONG)) {
mpz_set_si(r, pE->shortVal);
if (pE->shortVal<0) {
mpz_add(r, r, q);
}
} else {
mpz_import(r, Fr_N64, -1, 8, -1, 0, (const void *)pE->longVal);
}
}
void Fr_fromMpz(PFrElement pE, mpz_t v) {
if (mpz_fits_sint_p(v)) {
pE->type = Fr_SHORT;
pE->shortVal = mpz_get_si(v);
} else {
pE->type = Fr_LONG;
for (int i=0; i<Fr_N64; i++) pE->longVal[i] = 0;
mpz_export((void *)(pE->longVal), NULL, -1, 8, -1, 0, v);
}
}
void Fr_init() {
mpz_init(q);
mpz_import(q, Fr_N64, -1, 8, -1, 0, (const void *)Fr_q.longVal);
mpz_init_set_ui(zero, 0);
mpz_init_set_ui(one, 1);
nBits = mpz_sizeinbase (q, 2);
mpz_init(mask);
mpz_mul_2exp(mask, one, nBits-1);
mpz_sub(mask, mask, one);
}
void Fr_str2element(PFrElement pE, char const *s) {
mpz_t mr;
mpz_init_set_str(mr, s, 10);
Fr_fromMpz(pE, mr);
}
char *Fr_element2str(PFrElement pE) {
mpz_t r;
if (!(pE->type & Fr_LONG)) {
if (pE->shortVal>=0) {
char *r = new char[32];
sprintf(r, "%d", pE->shortVal);
return r;
} else {
mpz_init_set_si(r, pE->shortVal);
mpz_add(r, r, q);
mpz_clear(q);
}
} else {
Fr_toNormal(pE);
mpz_init(r);
mpz_import(r, Fr_N64, -1, 8, -1, 0, (const void *)pE->longVal);
}
char *res = mpz_get_str (0, 10, r);
mpz_clear(r);
return res;
}
void Fr_idiv(PFrElement r, PFrElement a, PFrElement b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
Fr_toMpz(ma, a);
// char *s1 = mpz_get_str (0, 10, ma);
// printf("s1 %s\n", s1);
Fr_toMpz(mb, b);
// char *s2 = mpz_get_str (0, 10, mb);
// printf("s2 %s\n", s2);
mpz_fdiv_q(mr, ma, mb);
// char *sr = mpz_get_str (0, 10, mr);
// printf("r %s\n", sr);
Fr_fromMpz(r, mr);
}
void Fr_mod(PFrElement r, PFrElement a, PFrElement b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
Fr_toMpz(ma, a);
Fr_toMpz(mb, b);
mpz_fdiv_r(mr, ma, mb);
Fr_fromMpz(r, mr);
}
void Fr_shl(PFrElement r, PFrElement a, PFrElement b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
Fr_toMpz(ma, a);
Fr_toMpz(mb, b);
if (mpz_cmp_ui(mb, nBits) >= 0) {
mpz_set(mr, zero);
} else {
mpz_mul_2exp(mr, ma, mpz_get_ui(mb));
mpz_and(mr, mr, mask);
}
Fr_fromMpz(r, mr);
}
void Fr_shr(PFrElement r, PFrElement a, PFrElement b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
Fr_toMpz(ma, a);
Fr_toMpz(mb, b);
if (mpz_cmp_ui(mb, nBits) >= 0) {
mpz_set(mr, zero);
} else {
mpz_tdiv_q_2exp(mr, ma, mpz_get_ui(mb));
mpz_and(mr, mr, mask);
}
Fr_fromMpz(r, mr);
}
void Fr_pow(PFrElement r, PFrElement a, PFrElement b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
Fr_toMpz(ma, a);
Fr_toMpz(mb, b);
mpz_powm(mr, ma, mb, q);
Fr_fromMpz(r, mr);
}
void Fr_inv(PFrElement r, PFrElement a) {
mpz_t ma;
mpz_t mr;
mpz_init(ma);
mpz_init(mr);
Fr_toMpz(ma, a);
mpz_invert(mr, ma, q);
Fr_fromMpz(r, mr);
}
void Fr_div(PFrElement r, PFrElement a, PFrElement b) {
FrElement tmp;
Fr_inv(&tmp, b);
Fr_mul(r, a, &tmp);
}
void Fr_fail() {
assert(false);
}

184
c/buildasm/fr.c.ejs Normal file
View File

@@ -0,0 +1,184 @@
#include "<%=name.toLowerCase()+".h"%>"
#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <assert.h>
mpz_t q;
mpz_t zero;
mpz_t one;
mpz_t mask;
size_t nBits;
void <%=name%>_toMpz(mpz_t r, P<%=name%>Element pE) {
<%=name%>_toNormal(pE);
if (!(pE->type & <%=name%>_LONG)) {
mpz_set_si(r, pE->shortVal);
if (pE->shortVal<0) {
mpz_add(r, r, q);
}
} else {
mpz_import(r, <%=name%>_N64, -1, 8, -1, 0, (const void *)pE->longVal);
}
}
void <%=name%>_fromMpz(P<%=name%>Element pE, mpz_t v) {
if (mpz_fits_sint_p(v)) {
pE->type = <%=name%>_SHORT;
pE->shortVal = mpz_get_si(v);
} else {
pE->type = <%=name%>_LONG;
for (int i=0; i<<%=name%>_N64; i++) pE->longVal[i] = 0;
mpz_export((void *)(pE->longVal), NULL, -1, 8, -1, 0, v);
}
}
void <%=name%>_init() {
mpz_init(q);
mpz_import(q, <%=name%>_N64, -1, 8, -1, 0, (const void *)Fr_q.longVal);
mpz_init_set_ui(zero, 0);
mpz_init_set_ui(one, 1);
nBits = mpz_sizeinbase (q, 2);
mpz_init(mask);
mpz_mul_2exp(mask, one, nBits-1);
mpz_sub(mask, mask, one);
}
void <%=name%>_str2element(P<%=name%>Element pE, char const *s) {
mpz_t mr;
mpz_init_set_str(mr, s, 10);
<%=name%>_fromMpz(pE, mr);
}
char *<%=name%>_element2str(P<%=name%>Element pE) {
mpz_t r;
if (!(pE->type & <%=name%>_LONG)) {
if (pE->shortVal>=0) {
char *r = new char[32];
sprintf(r, "%d", pE->shortVal);
return r;
} else {
mpz_init_set_si(r, pE->shortVal);
mpz_add(r, r, q);
}
} else {
<%=name%>_toNormal(pE);
mpz_init(r);
mpz_import(r, <%=name%>_N64, -1, 8, -1, 0, (const void *)pE->longVal);
}
char *res = mpz_get_str (0, 10, r);
mpz_clear(r);
return res;
}
void <%=name%>_idiv(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
<%=name%>_toMpz(ma, a);
// char *s1 = mpz_get_str (0, 10, ma);
// printf("s1 %s\n", s1);
<%=name%>_toMpz(mb, b);
// char *s2 = mpz_get_str (0, 10, mb);
// printf("s2 %s\n", s2);
mpz_fdiv_q(mr, ma, mb);
// char *sr = mpz_get_str (0, 10, mr);
// printf("r %s\n", sr);
<%=name%>_fromMpz(r, mr);
}
void <%=name%>_mod(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
<%=name%>_toMpz(ma, a);
<%=name%>_toMpz(mb, b);
mpz_fdiv_r(mr, ma, mb);
<%=name%>_fromMpz(r, mr);
}
void <%=name%>_shl(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
<%=name%>_toMpz(ma, a);
<%=name%>_toMpz(mb, b);
if (mpz_cmp_ui(mb, nBits) >= 0) {
mpz_set(mr, zero);
} else {
mpz_mul_2exp(mr, ma, mpz_get_ui(mb));
mpz_and(mr, mr, mask);
}
<%=name%>_fromMpz(r, mr);
}
void <%=name%>_shr(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
<%=name%>_toMpz(ma, a);
<%=name%>_toMpz(mb, b);
if (mpz_cmp_ui(mb, nBits) >= 0) {
mpz_set(mr, zero);
} else {
mpz_tdiv_q_2exp(mr, ma, mpz_get_ui(mb));
mpz_and(mr, mr, mask);
}
<%=name%>_fromMpz(r, mr);
}
void <%=name%>_pow(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b) {
mpz_t ma;
mpz_t mb;
mpz_t mr;
mpz_init(ma);
mpz_init(mb);
mpz_init(mr);
<%=name%>_toMpz(ma, a);
<%=name%>_toMpz(mb, b);
mpz_powm(mr, ma, mb, q);
<%=name%>_fromMpz(r, mr);
}
void <%=name%>_inv(P<%=name%>Element r, P<%=name%>Element a) {
mpz_t ma;
mpz_t mr;
mpz_init(ma);
mpz_init(mr);
<%=name%>_toMpz(ma, a);
mpz_invert(mr, ma, q);
<%=name%>_fromMpz(r, mr);
}
void <%=name%>_div(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b) {
<%=name%>Element tmp;
<%=name%>_inv(&tmp, b);
<%=name%>_mul(r, a, &tmp);
}
void <%=name%>_fail() {
assert(false);
}

67
c/buildasm/fr.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef __FR_H
#define __FR_H
#include <stdint.h>
#define Fr_N64 4
#define Fr_SHORT 0x00000000
#define Fr_LONG 0x80000000
#define Fr_LONGMONTGOMERY 0xC0000000
typedef struct __attribute__((__packed__)) {
int32_t shortVal;
uint32_t type;
uint64_t longVal[Fr_N64];
} FrElement;
typedef FrElement *PFrElement;
extern FrElement Fr_q;
extern "C" void Fr_copy(PFrElement r, PFrElement a);
extern "C" void Fr_copyn(PFrElement r, PFrElement a, int n);
extern "C" void Fr_add(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_sub(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_neg(PFrElement r, PFrElement a);
extern "C" void Fr_mul(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_square(PFrElement r, PFrElement a);
extern "C" void Fr_band(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_bor(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_bxor(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_bnot(PFrElement r, PFrElement a);
extern "C" void Fr_eq(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_neq(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_lt(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_gt(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_leq(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_geq(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_land(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_lor(PFrElement r, PFrElement a, PFrElement b);
extern "C" void Fr_lnot(PFrElement r, PFrElement a);
extern "C" void Fr_toNormal(PFrElement pE);
extern "C" void Fr_toLongNormal(PFrElement pE);
extern "C" void Fr_toMontgomery(PFrElement pE);
extern "C" int Fr_isTrue(PFrElement pE);
extern "C" int Fr_toInt(PFrElement pE);
extern "C" void Fr_fail();
extern FrElement Fr_q;
// Pending functions to convert
void Fr_str2element(PFrElement pE, char const*s);
char *Fr_element2str(PFrElement pE);
void Fr_idiv(PFrElement r, PFrElement a, PFrElement b);
void Fr_mod(PFrElement r, PFrElement a, PFrElement b);
void Fr_inv(PFrElement r, PFrElement a);
void Fr_div(PFrElement r, PFrElement a, PFrElement b);
void Fr_shl(PFrElement r, PFrElement a, PFrElement b);
void Fr_shr(PFrElement r, PFrElement a, PFrElement b);
void Fr_pow(PFrElement r, PFrElement a, PFrElement b);
void Fr_init();
#endif // __FR_H

67
c/buildasm/fr.h.ejs Normal file
View File

@@ -0,0 +1,67 @@
#ifndef __<%=name.toUpperCase()%>_H
#define __<%=name.toUpperCase()%>_H
#include <stdint.h>
#define <%=name%>_N64 <%= n64 %>
#define <%=name%>_SHORT 0x00000000
#define <%=name%>_LONG 0x80000000
#define <%=name%>_LONGMONTGOMERY 0xC0000000
typedef struct __attribute__((__packed__)) {
int32_t shortVal;
uint32_t type;
uint64_t longVal[<%=name%>_N64];
} <%=name%>Element;
typedef <%=name%>Element *P<%=name%>Element;
extern <%=name%>Element <%=name%>_q;
extern "C" void <%=name%>_copy(P<%=name%>Element r, P<%=name%>Element a);
extern "C" void <%=name%>_copyn(P<%=name%>Element r, P<%=name%>Element a, int n);
extern "C" void <%=name%>_add(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_sub(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_neg(P<%=name%>Element r, P<%=name%>Element a);
extern "C" void <%=name%>_mul(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_square(P<%=name%>Element r, P<%=name%>Element a);
extern "C" void <%=name%>_band(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_bor(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_bxor(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_bnot(P<%=name%>Element r, P<%=name%>Element a);
extern "C" void <%=name%>_eq(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_neq(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_lt(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_gt(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_leq(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_geq(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_land(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_lor(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
extern "C" void <%=name%>_lnot(P<%=name%>Element r, P<%=name%>Element a);
extern "C" void <%=name%>_toNormal(P<%=name%>Element pE);
extern "C" void <%=name%>_toLongNormal(P<%=name%>Element pE);
extern "C" void <%=name%>_toMontgomery(P<%=name%>Element pE);
extern "C" int <%=name%>_isTrue(P<%=name%>Element pE);
extern "C" int <%=name%>_toInt(P<%=name%>Element pE);
extern "C" void <%=name%>_fail();
extern <%=name%>Element <%=name%>_q;
// Pending functions to convert
void <%=name%>_str2element(P<%=name%>Element pE, char const*s);
char *<%=name%>_element2str(P<%=name%>Element pE);
void <%=name%>_idiv(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
void <%=name%>_mod(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
void <%=name%>_inv(P<%=name%>Element r, P<%=name%>Element a);
void <%=name%>_div(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
void <%=name%>_shl(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
void <%=name%>_shr(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
void <%=name%>_pow(P<%=name%>Element r, P<%=name%>Element a, P<%=name%>Element b);
void <%=name%>_init();
#endif // __<%=name.toUpperCase()%>_H

BIN
c/buildasm/fr.o Normal file

Binary file not shown.

View File

@@ -0,0 +1,97 @@
<% function isTrue(resReg, srcPtrReg) { %>
<% const longIsZero = global.tmpLabel() %>
<% const retOne = global.tmpLabel("retOne") %>
<% const retZero = global.tmpLabel("retZero") %>
<% const done = global.tmpLabel("done") %>
mov rax, [<%=srcPtrReg%>]
bt rax, 63
jc <%= longIsZero %>
test eax, eax
jz <%= retZero %>
jmp <%= retOne %>
<%= longIsZero %>:
<% for (let i=0; i<n64; i++) { %>
mov rax, [<%= srcPtrReg + " + " +(i*8+8) %>]
test rax, rax
jnz <%= retOne %>
<% } %>
<%= retZero %>:
mov qword <%=resReg%>, 0
jmp <%= done %>
<%= retOne %>:
mov qword <%=resReg%>, 1
<%= done %>:
<% } %>
<% function logicalOp(op) { %>
;;;;;;;;;;;;;;;;;;;;;;
; l<%= op %>
;;;;;;;;;;;;;;;;;;;;;;
; Logical <%= op %> between two elements
; Params:
; rsi <= Pointer to element 1
; rdx <= Pointer to element 2
; rdi <= Pointer to result zero or one
; Modified Registers:
; rax, rcx, r8
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_l<%=op%>:
<%= isTrue("r8", "rsi") %>
<%= isTrue("rcx", "rdx") %>
<%=op%> rcx, r8
mov [rdi], rcx
ret
<% } %>
<% logicalOp("and"); %>
<% logicalOp("or"); %>
;;;;;;;;;;;;;;;;;;;;;;
; lnot
;;;;;;;;;;;;;;;;;;;;;;
; Do the logical not of an element
; Params:
; rsi <= Pointer to element to be tested
; rdi <= Pointer to result one if element1 is zero and zero otherwise
; Modified Registers:
; rax, rax, r8
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_lnot:
<%= isTrue("rcx", "rsi") %>
test rcx, rcx
jz lnot_retOne
lnot_retZero:
mov qword [rdi], 0
ret
lnot_retOne:
mov qword [rdi], 1
ret
;;;;;;;;;;;;;;;;;;;;;;
; isTrue
;;;;;;;;;;;;;;;;;;;;;;
; Convert a 64 bit integer to a long format field element
; Params:
; rsi <= Pointer to the element
; Returs:
; rax <= 1 if true 0 if false
;;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_isTrue:
<%= isTrue("rax", "rdi") %>
ret

64
c/buildasm/main.c Normal file
View File

@@ -0,0 +1,64 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "fr.h"
int main() {
Fr_init();
/*
FrElement a = { 0, Fr_LONGMONTGOMERY, {1,1,1,1}};
FrElement b = { 0, Fr_LONGMONTGOMERY, {2,2,2,2}};
FrElement a={0x43e1f593f0000000ULL,0x2833e84879b97091ULL,0xb85045b68181585dULL,0x30644e72e131a029ULL};
FrElement b = {3,0,0,0};
FrElement c;
*/
// Fr_add(&(c[0]), a, a);
// Fr_add(&(c[0]), c, b);
/*
for (int i=0; i<1000000000; i++) {
Fr_mul(&c, &a, &b);
}
Fr_mul(&c,&a, &b);
*/
/*
FrElement a1[10];
FrElement a2[10];
for (int i=0; i<10; i++) {
a1[i].type = Fr_LONGMONTGOMERY;
a1[i].shortVal =0;
for (int j=0; j<Fr_N64; j++) {
a2[i].longVal[j] = i;
}
}
Fr_copyn(a2, a1, 10);
for (int i=0; i<10; i++) {
char *c1 = Fr_element2str(&a1[i]);
char *c2 = Fr_element2str(&a2[i]);
printf("%s\n%s\n\n", c1, c2);
free(c1);
free(c2);
}
*/
int tests[7] = { 0, 1, 2, -1, -2, 0x7FFFFFFF, (int)0x80000000};
for (int i=0; i<7;i++) {
FrElement a = { tests[i], Fr_SHORT, {0,0,0,0}};
Fr_toLongNormal(&a);
int b = Fr_toInt(&a);
int c = Fr_isTrue(&a);
printf("%d, %d, %d\n", tests[i], b, c);
}
FrElement err = { 0, Fr_LONGMONTGOMERY, {1,1,1,1}};
Fr_toInt(&err);
// printf("%llu, %llu, %llu, %llu\n", c.longVal[0], c.longVal[1], c.longVal[2], c.longVal[3]);
}

View File

@@ -0,0 +1,342 @@
<%
//////////////////////
// montgomeryTemplate
//////////////////////
// This function creates functions with the montgomery transformation
// applied
// the round hook allows to add diferent code in the iteration
//
// All the montgomery functions modifies:
// r8, r9, 10, r11, rax, rcx
//////////////////////
function montgomeryTemplate(fnName, round) {
let r0, r1, r2;
function setR(step) {
if ((step % 3) == 0) {
r0 = "r8";
r1 = "r9";
r2 = "r10";
} else if ((step % 3) == 1) {
r0 = "r9";
r1 = "r10";
r2 = "r8";
} else {
r0 = "r10";
r1 = "r8";
r2 = "r9";
}
}
const base = bigInt.one.shiftLeft(64);
const np64 = base.minus(q.modInv(base));
%>
<%=fnName%>:
sub rsp, <%= n64*8 %> ; Reserve space for ms
mov rcx, rdx ; rdx is needed for multiplications so keep it in cx
mov r11, 0x<%= np64.toString(16) %> ; np
xor r8,r8
xor r9,r9
xor r10,r10
<%
// Main loop
for (let i=0; i<n64*2; i++) {
setR(i);
round(i, r0, r1, r2);
%>
<%
for (let j=i-1; j>=0; j--) { // All ms
if (((i-j)<n64)&&(j<n64)) {
%>
mov rax, [rsp + <%= j*8 %>]
mul qword [q + <%= (i-j)*8 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
}
} // ms
%>
<%
if (i<n64) {
%>
mov rax, <%= r0 %>
mul r11
mov [rsp + <%= i*8 %>], rax
mul qword [q]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} else {
%>
mov [rdi + <%= (i-n64)*8 %> ], <%= r0 %>
xor <%= r0 %>,<%= r0 %>
<%
}
%>
<%
} // Main Loop
%>
test <%= r1 %>, <%= r1 %>
jnz <%=fnName%>_mulM_sq
; Compare with q
<%
for (let i=0; i<n64; i++) {
%>
mov rax, [rdi + <%= (n64-i-1)*8 %>]
cmp rax, [q + <%= (n64-i-1)*8 %>]
jc <%=fnName%>_mulM_done ; q is bigget so done.
jnz <%=fnName%>_mulM_sq ; q is lower
<%
}
%>
; If equal substract q
<%=fnName%>_mulM_sq:
<%
for (let i=0; i<n64; i++) {
%>
mov rax, [q + <%= i*8 %>]
<%= i==0 ? "sub" : "sbb" %> [rdi + <%= i*8 %>], rax
<%
}
%>
<%=fnName%>_mulM_done:
mov rdx, rcx ; recover rdx to its original place.
add rsp, <%= n64*8 %> ; recover rsp
ret
<%
} // Template
%>
;;;;;;;;;;;;;;;;;;;;;;
; rawMontgomeryMul
;;;;;;;;;;;;;;;;;;;;;;
; Multiply two elements in montgomery form
; Params:
; rsi <= Pointer to the long data of element 1
; rdx <= Pointer to the long data of element 2
; rdi <= Pointer to the long data of result
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%
montgomeryTemplate("rawMontgomeryMul", function(i, r0, r1, r2) {
// Same Digit
for (let o1=Math.max(0, i-n64+1); (o1<=i)&&(o1<n64); o1++) {
const o2= i-o1;
%>
mov rax, [rsi + <%= 8*o1 %>]
mul qword [rcx + <%= 8*o2 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} // Same digit
})
%>
;;;;;;;;;;;;;;;;;;;;;;
; rawMontgomerySquare
;;;;;;;;;;;;;;;;;;;;;;
; Square an element
; Params:
; rsi <= Pointer to the long data of element 1
; rdi <= Pointer to the long data of result
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%
montgomeryTemplate("rawMontgomerySquare", function(i, r0, r1, r2) {
// Same Digit
for (let o1=Math.max(0, i-n64+1); (o1<((i+1)>>1) )&&(o1<n64); o1++) {
const o2= i-o1;
%>
mov rax, [rsi + <%= 8*o1 %>]
mul qword [rsi + <%= 8*o2 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} // Same digit
%>
<% if (i%2 == 0) { %>
mov rax, [rsi + <%= 8*(i/2) %>]
mul rax
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<% } %>
<%
})
%>
;;;;;;;;;;;;;;;;;;;;;;
; rawMontgomeryMul1
;;;;;;;;;;;;;;;;;;;;;;
; Multiply two elements in montgomery form
; Params:
; rsi <= Pointer to the long data of element 1
; rdx <= second operand
; rdi <= Pointer to the long data of result
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%
montgomeryTemplate("rawMontgomeryMul1", function(i, r0, r1, r2) {
// Same Digit
if (i<n64) {
%>
mov rax, [rsi + <%= 8*i %>]
mul rcx
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} // Same digit
})
%>
;;;;;;;;;;;;;;;;;;;;;;
; rawFromMontgomery
;;;;;;;;;;;;;;;;;;;;;;
; Multiply two elements in montgomery form
; Params:
; rsi <= Pointer to the long data of element 1
; rdi <= Pointer to the long data of result
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%
montgomeryTemplate("rawFromMontgomery", function(i, r0, r1, r2) {
// Same Digit
if (i<n64) {
%>
add <%= r0 %>, [rdi + <%= 8*i %>]
adc <%= r1 %>, 0x0
adc <%= r2 %>, 0x0
<%
} // Same digit
})
%>
;;;;;;;;;;;;;;;;;;;;;;
; toMontgomery
;;;;;;;;;;;;;;;;;;;;;;
; Convert a number to Montgomery
; rdi <= Pointer element to convert
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;
<%=name%>_toMontgomery:
mov rax, [rdi]
bts rax, 62 ; check if montgomery
jc toMontgomery_doNothing
bts rax, 63
jc toMontgomeryLong
toMontgomeryShort:
mov [rdi], rax
add rdi, 8
push rsi
lea rsi, [R2]
movsx rdx, eax
cmp rdx, 0
js negMontgomeryShort
posMontgomeryShort:
call rawMontgomeryMul1
pop rsi
sub rdi, 8
ret
negMontgomeryShort:
neg rdx ; Do the multiplication positive and then negate the result.
call rawMontgomeryMul1
mov rsi, rdi
call rawNegL
pop rsi
sub rdi, 8
ret
toMontgomeryLong:
mov [rdi], rax
add rdi, 8
push rsi
mov rdx, rdi
lea rsi, [R2]
call rawMontgomeryMul
pop rsi
sub rdi, 8
toMontgomery_doNothing:
ret
;;;;;;;;;;;;;;;;;;;;;;
; toNormal
;;;;;;;;;;;;;;;;;;;;;;
; Convert a number from Montgomery
; rdi <= Pointer element to convert
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;
<%=name%>_toNormal:
mov rax, [rdi]
btc rax, 62 ; check if montgomery
jnc toNormal_doNothing
bt rax, 63 ; if short, it means it's converted
jnc toNormal_doNothing
toNormalLong:
mov [rdi], rax
add rdi, 8
call rawFromMontgomery
sub rdi, 8
toNormal_doNothing:
ret
;;;;;;;;;;;;;;;;;;;;;;
; toLongNormal
;;;;;;;;;;;;;;;;;;;;;;
; Convert a number to long normal
; rdi <= Pointer element to convert
; Modified registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;
<%=name%>_toLongNormal:
mov rax, [rdi]
bt rax, 62 ; check if montgomery
jc toLongNormal_fromMontgomery
bt rax, 63 ; check if long
jnc toLongNormal_fromShort
ret ; It is already long
toLongNormal_fromMontgomery:
add rdi, 8
call rawFromMontgomery
sub rdi, 8
ret
toLongNormal_fromShort:
mov r8, rsi ; save rsi
movsx rsi, eax
call rawCopyS2L
mov rsi, r8 ; recover rsi
ret

275
c/buildasm/mul.asm.ejs Normal file
View File

@@ -0,0 +1,275 @@
<% function mulS1S2() { %>
xor rax, rax
mov eax, r8d
imul r9d
jo mul_manageOverflow ; rsi already is the 64bits result
mov [rdi], rax ; not necessary to adjust so just save and return
mul_manageOverflow: ; Do the operation in 64 bits
push rsi
movsx rax, r8d
movsx rcx, r9d
imul rcx
mov rsi, rax
call rawCopyS2L
pop rsi
<% } %>
<% function squareS1() { %>
xor rax, rax
mov eax, r8d
imul eax
jo square_manageOverflow ; rsi already is the 64bits result
mov [rdi], rax ; not necessary to adjust so just save and return
square_manageOverflow: ; Do the operation in 64 bits
push rsi
movsx rax, r8d
imul rax
mov rsi, rax
call rawCopyS2L
pop rsi
<% } %>
<% function mulL1S2(t) { %>
push rsi
add rsi, 8
movsx rdx, r9d
add rdi, 8
cmp rdx, 0
<% const rawPositiveLabel = global.tmpLabel() %>
jns <%= rawPositiveLabel %>
neg rdx
call rawMontgomeryMul1
mov rsi, rdi
call rawNegL
sub rdi, 8
pop rsi
<% const done = global.tmpLabel() %>
jmp <%= done %>
<%= rawPositiveLabel %>:
call rawMontgomeryMul1
sub rdi, 8
pop rsi
<%= done %>:
<% } %>
<% function mulS1L2() { %>
push rsi
lea rsi, [rdx + 8]
movsx rdx, r8d
add rdi, 8
cmp rdx, 0
<% const rawPositiveLabel = global.tmpLabel() %>
jns <%= rawPositiveLabel %>
neg rdx
call rawMontgomeryMul1
mov rsi, rdi
call rawNegL
sub rdi, 8
pop rsi
<% const done = global.tmpLabel() %>
jmp <%= done %>
<%= rawPositiveLabel %>:
call rawMontgomeryMul1
sub rdi, 8
pop rsi
<%= done %>:
<% } %>
<% function mulL1L2() { %>
add rdi, 8
add rsi, 8
add rdx, 8
call rawMontgomeryMul
sub rdi, 8
sub rsi, 8
<% } %>
<% function squareL1() { %>
add rdi, 8
add rsi, 8
call rawMontgomerySquare
sub rdi, 8
sub rsi, 8
<% } %>
<% function mulR3() { %>
push rsi
add rdi, 8
mov rsi, rdi
lea rdx, [R3]
call rawMontgomeryMul
sub rdi, 8
pop rsi
<% } %>
;;;;;;;;;;;;;;;;;;;;;;
; square
;;;;;;;;;;;;;;;;;;;;;;
; Squares a field element
; Params:
; rsi <= Pointer to element 1
; rdi <= Pointer to result
; [rdi] = [rsi] * [rsi]
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_square:
mov r8, [rsi]
bt r8, 63 ; Check if is short first operand
jc square_l1
square_s1: ; Both operands are short
<%= squareS1() %>
ret
square_l1:
bt r8, 62 ; check if montgomery first
jc square_l1m
square_l1n:
<%= global.setTypeDest("0xC0"); %>
<%= squareL1() %>
<%= mulR3() %>
ret
square_l1m:
<%= global.setTypeDest("0xC0"); %>
<%= squareL1() %>
ret
;;;;;;;;;;;;;;;;;;;;;;
; mul
;;;;;;;;;;;;;;;;;;;;;;
; Multiplies two elements of any kind
; Params:
; rsi <= Pointer to element 1
; rdx <= Pointer to element 2
; rdi <= Pointer to result
; [rdi] = [rsi] * [rdi]
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_mul:
mov r8, [rsi]
mov r9, [rdx]
bt r8, 63 ; Check if is short first operand
jc mul_l1
bt r9, 63 ; Check if is short second operand
jc mul_s1l2
mul_s1s2: ; Both operands are short
<%= mulS1S2() %>
ret
mul_l1:
bt r9, 63 ; Check if is short second operand
jc mul_l1l2
;;;;;;;;
mul_l1s2:
bt r8, 62 ; check if montgomery first
jc mul_l1ms2
mul_l1ns2:
bt r9, 62 ; check if montgomery first
jc mul_l1ns2m
mul_l1ns2n:
<%= global.setTypeDest("0xC0"); %>
<%= mulL1S2() %>
<%= mulR3() %>
ret
mul_l1ns2m:
<%= global.setTypeDest("0x80"); %>
<%= mulL1L2() %>
ret
mul_l1ms2:
bt r9, 62 ; check if montgomery second
jc mul_l1ms2m
mul_l1ms2n:
<%= global.setTypeDest("0x80"); %>
<%= mulL1S2() %>
ret
mul_l1ms2m:
<%= global.setTypeDest("0xC0"); %>
<%= mulL1L2() %>
ret
;;;;;;;;
mul_s1l2:
bt r8, 62 ; check if montgomery first
jc mul_s1ml2
mul_s1nl2:
bt r9, 62 ; check if montgomery first
jc mul_s1nl2m
mul_s1nl2n:
<%= global.setTypeDest("0xC0"); %>
<%= mulS1L2() %>
<%= mulR3() %>
ret
mul_s1nl2m:
<%= global.setTypeDest("0x80"); %>
<%= mulS1L2(); %>
ret
mul_s1ml2:
bt r9, 62 ; check if montgomery first
jc mul_s1ml2m
mul_s1ml2n:
<%= global.setTypeDest("0x80"); %>
<%= mulL1L2() %>
ret
mul_s1ml2m:
<%= global.setTypeDest("0xC0"); %>
<%= mulL1L2() %>
ret
;;;;
mul_l1l2:
bt r8, 62 ; check if montgomery first
jc mul_l1ml2
mul_l1nl2:
bt r9, 62 ; check if montgomery second
jc mul_l1nl2m
mul_l1nl2n:
<%= global.setTypeDest("0xC0"); %>
<%= mulL1L2() %>
<%= mulR3() %>
ret
mul_l1nl2m:
<%= global.setTypeDest("0x80"); %>
<%= mulL1L2() %>
ret
mul_l1ml2:
bt r9, 62 ; check if montgomery seconf
jc mul_l1ml2m
mul_l1ml2n:
<%= global.setTypeDest("0x80"); %>
<%= mulL1L2() %>
ret
mul_l1ml2m:
<%= global.setTypeDest("0xC0"); %>
<%= mulL1L2() %>
ret

78
c/buildasm/neg.asm.ejs Normal file
View File

@@ -0,0 +1,78 @@
<% function negS() { %>
neg eax
jo neg_manageOverflow ; Check if overflow. (0x80000000 is the only case)
mov [rdi], rax ; not necessary to adjust so just save and return
ret
neg_manageOverflow: ; Do the operation in 64 bits
push rsi
movsx rsi, eax
neg rsi
call rawCopyS2L
pop rsi
ret
<% } %>
<% function negL() { %>
add rdi, 8
add rsi, 8
call rawNegL
sub rdi, 8
sub rsi, 8
ret
<% } %>
;;;;;;;;;;;;;;;;;;;;;;
; neg
;;;;;;;;;;;;;;;;;;;;;;
; Adds two elements of any kind
; Params:
; rsi <= Pointer to element to be negated
; rdi <= Pointer to result
; [rdi] = -[rsi]
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_neg:
mov rax, [rsi]
bt rax, 63 ; Check if is short first operand
jc neg_l
neg_s: ; Operand is short
<%= negS() %>
neg_l:
mov [rdi], rax ; Copy the type
<%= negL() %>
;;;;;;;;;;;;;;;;;;;;;;
; rawNeg
;;;;;;;;;;;;;;;;;;;;;;
; Negates a value
; Params:
; rdi <= Pointer to the long data of result
; rsi <= Pointer to the long data of element 1
;
; [rdi] = - [rsi]
;;;;;;;;;;;;;;;;;;;;;;
rawNegL:
; Compare is zero
xor rax, rax
<% for (let i=0; i<n64; i++) { %>
cmp [rsi + <%=i*8%>], rax
jnz doNegate
<% } %>
; it's zero so just set to zero
<% for (let i=0; i<n64; i++) { %>
mov [rdi + <%=i*8%>], rax
<% } %>
ret
doNegate:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "sub" : "sbb" %> rax, [rsi + <%=i*8%>]
mov [rdi + <%=i*8%>], rax
<% } %>
ret

View File

@@ -0,0 +1,33 @@
const tester = require("../c/buildasm/buildzqfieldtester2.js");
const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
describe("basic cases", function () {
this.timeout(100000);
it("should do basic tests", async () => {
await tester(__P__, [
["add", 0, 0],
["add", 0, 1],
["add", 1, 0],
["add", 1, 1],
["add", 2, 1],
["add", 2, 10],
["add", -1, -1],
["add", -20, -10],
["add", "10604728079509999371218483608188593244163417117449316147628604036713980815027", "10604728079509999371218483608188593244163417117449316147628604036713980815027"],
["mul", 0, 0],
["mul", 0, 1],
["mul", 1, 0],
["mul", 1, 1],
["mul", 2, 1],
["mul", 2, 10],
["mul", -1, -1],
["mul", -20, -10],
["mul", "10604728079509999371218483608188593244163417117449316147628604036713980815027", "10604728079509999371218483608188593244163417117449316147628604036713980815027"],
]);
});
});

View File

@@ -0,0 +1,209 @@
const bigInt=require("big-integer");
class ZqBuilder {
constructor(q, name) {
this.q=bigInt(q);
this.h = [];
this.c = [];
this.name = name;
}
build() {
this._buildHeaders();
this._buildAdd();
this._buildMul();
this.c.push(""); this.h.push("");
return [this.h.join("\n"), this.c.join("\n")];
}
_buildHeaders() {
this.n64 = Math.floor((this.q.bitLength() - 1) / 64)+1;
this.h.push("typedef unsigned long long u64;");
this.h.push(`typedef u64 ${this.name}Element[${this.n64}];`);
this.h.push(`typedef u64 *P${this.name}Element;`);
this.h.push(`extern ${this.name}Element ${this.name}_q;`);
this.h.push(`#define ${this.name}_N64 ${this.n64}`);
this.c.push(`#include "${this.name.toLowerCase()}.h"`);
this._defineConstant(`${this.name}_q`, this.q);
this.c.push(""); this.h.push("");
}
_defineConstant(n, v) {
let S = `${this.name}Element ${n}={`;
const mask = bigInt("FFFFFFFFFFFFFFFF", 16);
for (let i=0; i<this.n64; i++) {
if (i>0) S = S+",";
let shex = v.shiftRight(i*64).and(mask).toString(16);
while (shex <16) shex = "0" + shex;
S = S + "0x" + shex + "ULL";
}
S += "};";
this.c.push(S);
}
_buildAdd() {
this.h.push(`void ${this.name}_add(P${this.name}Element r, P${this.name}Element a, P${this.name}Element b);`);
this.c.push(`void ${this.name}_add(P${this.name}Element r, P${this.name}Element a, P${this.name}Element b) {`);
this.c.push(" __asm__ __volatile__ (");
for (let i=0; i<this.n64; i++) {
this.c.push(` "movq ${i*8}(%2), %%rax;"`);
this.c.push(` "${i==0 ? "addq" : "adcq"} ${i*8}(%1), %%rax;"`);
this.c.push(` "movq %%rax, ${i*8}(%0);"`);
}
this.c.push(" \"jc SQ;\"");
for (let i=0; i<this.n64; i++) {
if (i>0) {
this.c.push(` "movq ${(this.n64 - i-1)*8}(%0), %%rax;"`);
}
this.c.push(` "cmp ${(this.n64 - i-1)*8}(%3), %%rax;"`);
this.c.push(" \"jg SQ;\"");
this.c.push(" \"jl DONE;\"");
}
this.c.push(" \"SQ:\"");
for (let i=0; i<this.n64; i++) {
this.c.push(` "movq ${i*8}(%3), %%rax;"`);
this.c.push(` "${i==0 ? "subq" : "sbbq"} %%rax, ${i*8}(%0);"`);
}
this.c.push(" \"DONE:\"");
this.c.push(` :: "r" (r), "r" (a), "r" (b), "r" (${this.name}_q) : "%rax", "memory");`);
this.c.push("}\n");
}
_buildMul() {
let r0, r1, r2;
function setR(step) {
if ((step % 3) == 0) {
r0 = "%%r8";
r1 = "%%r9";
r2 = "%%r10";
} else if ((step % 3) == 1) {
r0 = "%%r9";
r1 = "%%r10";
r2 = "%%r8";
} else {
r0 = "%%r10";
r1 = "%%r8";
r2 = "%%r9";
}
}
const base = bigInt.one.shiftLeft(64);
const np64 = base.minus(this.q.modInv(base));
this.h.push(`void ${this.name}_mul(P${this.name}Element r, P${this.name}Element a, P${this.name}Element b);`);
this.c.push(`void ${this.name}_mul(P${this.name}Element r, P${this.name}Element a, P${this.name}Element b) {`);
this.c.push(" __asm__ __volatile__ (");
this.c.push(` "subq $${this.n64*8}, %%rsp;"`);
this.c.push(` "movq $0x${np64.toString(16)}, %%r11;"`);
this.c.push(" \"movq $0x0, %%r8;\"");
this.c.push(" \"movq $0x0, %%r9;\"");
this.c.push(" \"movq $0x0, %%r10;\"");
for (let i=0; i<this.n64*2; i++) {
setR(i);
for (let o1=Math.max(0, i-this.n64+1); (o1<=i)&&(o1<this.n64); o1++) {
const o2= i-o1;
this.c.push(` "movq ${o1*8}(%1), %%rax;"`);
this.c.push(` "mulq ${o2*8}(%2);"`);
this.c.push(` "addq %%rax, ${r0};"`);
this.c.push(` "adcq %%rdx, ${r1};"`);
this.c.push(` "adcq $0x0, ${r2};"`);
}
for (let j=i-1; j>=0; j--) {
if (((i-j)<this.n64)&&(j<this.n64)) {
this.c.push(` "movq ${j*8}(%%rsp), %%rax;"`);
this.c.push(` "mulq ${(i-j)*8}(%3);"`);
this.c.push(` "addq %%rax, ${r0};"`);
this.c.push(` "adcq %%rdx, ${r1};"`);
this.c.push(` "adcq $0x0, ${r2};"`);
}
}
if (i<this.n64) {
this.c.push(` "movq ${r0}, %%rax;"`);
this.c.push(" \"mulq %%r11;\"");
this.c.push(` "movq %%rax, ${i*8}(%%rsp);"`);
this.c.push(" \"mulq (%3);\"");
this.c.push(` "addq %%rax, ${r0};"`);
this.c.push(` "adcq %%rdx, ${r1};"`);
this.c.push(` "adcq $0x0, ${r2};"`);
} else {
this.c.push(` "movq ${r0}, ${(i-this.n64)*8}(%0);"`);
this.c.push(` "movq $0, ${r0};"`);
}
}
this.c.push(` "cmp $0, ${r1};"`);
this.c.push(" \"jne SQ2;\"");
for (let i=0; i<this.n64; i++) {
this.c.push(` "movq ${(this.n64 - i-1)*8}(%0), %%rax;"`);
this.c.push(` "cmp ${(this.n64 - i-1)*8}(%3), %%rax;"`);
this.c.push(" \"jg SQ2;\"");
this.c.push(" \"jl DONE2;\"");
}
this.c.push(" \"SQ2:\"");
for (let i=0; i<this.n64; i++) {
this.c.push(` "movq ${i*8}(%3), %%rax;"`);
this.c.push(` "${i==0 ? "subq" : "sbbq"} %%rax, ${i*8}(%0);"`);
}
this.c.push(" \"DONE2:\"");
this.c.push(` "addq $${this.n64*8}, %%rsp;"`);
this.c.push(` :: "r" (r), "r" (a), "r" (b), "r" (${this.name}_q) : "%rax", "%rdx", "%r8", "%r9", "%r10", "%r11", "memory");`);
this.c.push("}\n");
}
_buildIDiv() {
this.h.push(`void ${this.name}_idiv(P${this.name}Element r, P${this.name}Element a, P${this.name}Element b);`);
this.c.push(`void ${this.name}_idiv(P${this.name}Element r, P${this.name}Element a, P${this.name}Element b) {`);
this.c.push(" __asm__ __volatile__ (");
this.c.push(" \"pxor %%xmm0, %%xmm0;\""); // Comparison Register
if (this.n64 == 1) {
this.c.push(` "mov %%rax, $${this.n64 - 8};"`);
} else {
this.c.push(` "mov %%rax, $${this.n64 -16};"`);
}
this.c.push(` :: "r" (r), "r" (a), "r" (b), "r" (${this.name}_q) : "%rax", "%rdx", "%r8", "%r9", "%r10", "%r11", "memory");`);
this.c.push("}\n");
}
}
var runningAsScript = !module.parent;
if (runningAsScript) {
const fs = require("fs");
var argv = require("yargs")
.usage("Usage: $0 -q [primeNum] -n [name] -oc [out .c file] -oh [out .h file]")
.demandOption(["q","n"])
.alias("q", "prime")
.alias("n", "name")
.argv;
const q = bigInt(argv.q);
const cFileName = (argv.oc) ? argv.oc : argv.name.toLowerCase() + ".c";
const hFileName = (argv.oh) ? argv.oh : argv.name.toLowerCase() + ".h";
const builder = new ZqBuilder(q, argv.name);
const res = builder.build();
fs.writeFileSync(hFileName, res[0], "utf8");
fs.writeFileSync(cFileName, res[1], "utf8");
} else {
module.exports = function(q, name) {
const builder = new ZqBuilder(q, name);
return builder.build();
};
}

View File

@@ -0,0 +1,68 @@
const chai = require("chai");
const assert = chai.assert;
const fs = require("fs");
var tmp = require("tmp-promise");
const path = require("path");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const bigInt = require("big-integer");
const BuildZqField = require("./buildzqfield");
const ZqField = require("fflib").ZqField;
module.exports = testField;
function toMontgomeryStr(a, prime) {
const n64 = Math.floor((prime.bitLength() - 1) / 64)+1;
return a.shiftLeft(n64*64).mod(prime).toString(10);
}
function fromMontgomeryStr(a, prime) {
const n64 = Math.floor((prime.bitLength() - 1) / 64)+1;
const R = bigInt.one.shiftLeft(n64*64).mod(prime);
const RI = R.modInv(prime);
return bigInt(a).times(RI).mod(prime);
}
async function testField(prime, test) {
tmp.setGracefulCleanup();
const F = new ZqField(prime);
const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
const [hSource, cSource] = BuildZqField(prime, "Fr");
await fs.promises.writeFile(path.join(dir.path, "fr.h"), hSource, "utf8");
await fs.promises.writeFile(path.join(dir.path, "fr.c"), cSource, "utf8");
await exec("g++" +
` ${path.join(__dirname, "tester.c")}` +
` ${path.join(dir.path, "fr.c")}` +
` -o ${path.join(dir.path, "tester")}` +
" -lgmp"
);
for (let i=0; i<test.length; i++) {
let a = bigInt(test[i][1]).mod(prime);
if (a.isNegative()) a = prime.add(a);
let b = bigInt(test[i][2]).mod(prime);
if (b.isNegative()) b = prime.add(b);
const ec = F[test[i][0]](a,b);
// console.log(toMontgomeryStr(a, prime));
// console.log(toMontgomeryStr(b, prime));
const res = await exec(`${path.join(dir.path, "tester")}` +
` ${test[i][0]}` +
` ${toMontgomeryStr(a, prime)}` +
` ${toMontgomeryStr(b, prime)}`
);
// console.log(res.stdout);
const c=fromMontgomeryStr(res.stdout, prime);
assert.equal(ec.toString(), c.toString());
}
}

View File

@@ -0,0 +1,302 @@
global <%=name%>_add
global <%=name%>_mul
global <%=name%>_q
DEFAULT REL
section .text
;;;;;;;;;;;;;;;;;;;;;;
; add
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_add:
; Add component by component with carry
<% for (let i=0; i<n64; i++) { %>
mov rax, [rsi + <%=i*8%>]
<%= i==0 ? "add" : "adc" %> rax, [rdx + <%=i*8%>]
mov [rdi + <%=i*8%>], rax
<% } %>
jc add_sq ; if overflow, substract q
; Compare with q
<% for (let i=0; i<n64; i++) { %>
<% if (i>0) { %>
mov rax, [rdi + <%= (n64-i-1)*8 %>]
<% } %>
cmp rax, [q + <%= (n64-i-1)*8 %>]
jg add_sq
jl add_done
<% } %>
; If equal substract q
add_sq:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "sub" : "sbb" %> [rdi + <%=i*8%>], rax
mov [rdx + <%=i*8%>], rax
<% } %>
add_done:
ret
;;;;;;;;;;;;;;;;;;;;;;
; mul Montgomery
;;;;;;;;;;;;;;;;;;;;;;
mulM:
<%
let r0, r1, r2;
function setR(step) {
if ((step % 3) == 0) {
r0 = "r8";
r1 = "r9";
r2 = "r10";
} else if ((step % 3) == 1) {
r0 = "r9";
r1 = "r10";
r2 = "r8";
} else {
r0 = "r10";
r1 = "r8";
r2 = "r9";
}
}
const base = bigInt.one.shiftLeft(64);
const np64 = base.minus(q.modInv(base));
%>
sub rsp, <%= n64*8 %> ; Reserve space for ms
mov rcx, rdx ; rdx is needed for multiplications so keep it in cx
mov r11, 0x<%= np64.toString(16) %> ; np
xor r8,r8
xor r9,r9
xor r10,r10
<%
// Main loop
for (let i=0; i<n64*2; i++) {
setR(i);
%>
<%
// Same Digit
for (let o1=Math.max(0, i-n64+1); (o1<=i)&&(o1<n64); o1++) {
const o2= i-o1;
%>
mov rax, [rsi + <%= 8*o1 %>]
mul qword [rcx + <%= 8*o2 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} // Same digit
%>
<%
for (let j=i-1; j>=0; j--) { // All ms
if (((i-j)<n64)&&(j<n64)) {
%>
mov rax, [rsp + <%= j*8 %>]
mul qword [q + <%= (i-j)*8 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
}
} // ms
%>
<%
if (i<n64) {
%>
mov rax, <%= r0 %>
mul r11
mov [rsp + <%= i*8 %>], rax
mul qword [q]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} else {
%>
mov [rdi + <%= (i-n64)*8 %> ], <%= r0 %>
xor <%= r0 %>,<%= r0 %>
<%
}
%>
<%
} // Main Loop
%>
cmp <%= r1 %>, 0x0
jne mulM_sq
; Compare with q
<%
for (let i=0; i<n64; i++) {
%>
mov rax, [rdi + <%= (n64-i-1)*8 %>]
cmp rax, [q + <%= (n64-i-1)*8 %>]
jg mulM_sq
jl mulM_done
<%
}
%>
; If equal substract q
mulM_sq:
<%
for (let i=0; i<n64; i++) {
%>
mov rax, [q + <%= i*8 %>]
<%= i==0 ? "sub" : "sbb" %> [rdi + <%= i*8 %>], rax
mov [rdx + <%= i*8 %>], rax
<%
}
%>
mulM_done:
add rsp, <%= n64*8 %> ; recover rsp
ret
;;;;;;;;;;;;;;;;;;;;;;
; mul MontgomeryShort
;;;;;;;;;;;;;;;;;;;;;;
mulSM:
;;;;;;;;;;;;;;;;;;;;;;
; mul
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_mul:
mov rax, [rsi]
bt rax, 63
jc l1
mov rcx, [rdx]
bt rcx, 63
jc s1l2
s1s2: ; short first and second
mul ecx
jc rs2l ; If if doesn't feed in 32 bits convert the result to long
; The shorts multiplication is done. copy the val to destination and return
mov [rdi], rax
ret
rs2l: ; The result in the multiplication doen't feed
; we have the result in edx:eax we need to convert it to long
shl rdx, 32
mov edx, eax ; pack edx:eax to rdx
xor rax, rax ; Set the format to long
bts rax, 63
mov [rdi], rax ; move the first digit
cmp rdx, 0 ; check if redx is negative.
jl rs2ln
; edx is positive.
mov [rdi + 8], rdx ; Set the firs digit
xor rax, rax ; Set the remaining digits to 0
<% for (let i=1; i<n64; i++) { %>
mov [rdi + <%= (i+1)*8 %>], rax
<% } %>
ret
; edx is negative.
rs2ln:
add rdx, [q] ; Set the firs digit
mov [rdi + 8], rdx ;
mov rdx, -1 ; all ones
<% for (let i=1; i<n64; i++) { %>
mov rax, rdx ; Add to q
adc rax, [q + <%= i*8 %> ]
mov [rdi + <%= (i+1)*8 %>], rax
<% } %>
ret
l1:
mov rcx, [rdx]
bt rcx, 63
jc ll
l1s2:
xor rdx, rdx
mov edx, ecx
bt rax, 62
jc lsM
jmp lsN
s1l2:
mov rsi, rdx
xor rdx, rdx
mov edx, eax
bt rcx, 62
jc lsM
jmp lsN
lsN:
mov byte [rdi + 3], 0xC0 ; set the result to montgomery
add rsi, 8
add rdi, 8
call mulSM
mov rdx, R3
call mulM
ret
lsM:
mov byte [rdi + 3], 0x80 ; set the result to long normal
add rsi, 8
add rdi, 8
call mulSM
ret
ll:
bt rax, 62
jc lml
bt rcx, 62
jc lnlm
lnln:
mov byte [rdi + 3], 0xC0 ; set the result to long montgomery
add rsi, 8
add rdi, 8
add rdx, 8
call mulM
mov rdi, rsi
mov rdx, R3
call mulM
ret
lml:
bt rcx, 62
jc lmlm
lnlm:
mov byte [rdi + 3], 0x80 ; set the result to long normal
add rsi, 8
add rdi, 8
add rdx, 8
call mulM
ret
lmlm:
mov byte [rdi + 3], 0xC0 ; set the result to long montgomery
add rsi, 8
add rdi, 8
add rdx, 8
call mulM
ret
section .data
<%=name%>_q:
dd 0
dd 0x80000000
q dq <%= constantElement(q) %>
R3 dq <%= constantElement(bigInt.one.shiftLeft(n64*64*3).mod(q)) %>

251
c/buildasm/old/mul.asm.ejs Normal file
View File

@@ -0,0 +1,251 @@
;;;;;;;;;;;;;;;;;;;;;;
; mul Montgomery
;;;;;;;;;;;;;;;;;;;;;;
mulM:
<%
let r0, r1, r2;
function setR(step) {
if ((step % 3) == 0) {
r0 = "r8";
r1 = "r9";
r2 = "r10";
} else if ((step % 3) == 1) {
r0 = "r9";
r1 = "r10";
r2 = "r8";
} else {
r0 = "r10";
r1 = "r8";
r2 = "r9";
}
}
const base = bigInt.one.shiftLeft(64);
const np64 = base.minus(q.modInv(base));
%>
sub rsp, <%= n64*8 %> ; Reserve space for ms
mov rcx, rdx ; rdx is needed for multiplications so keep it in cx
mov r11, 0x<%= np64.toString(16) %> ; np
xor r8,r8
xor r9,r9
xor r10,r10
<%
// Main loop
for (let i=0; i<n64*2; i++) {
setR(i);
%>
<%
// Same Digit
for (let o1=Math.max(0, i-n64+1); (o1<=i)&&(o1<n64); o1++) {
const o2= i-o1;
%>
mov rax, [rsi + <%= 8*o1 %>]
mul qword [rcx + <%= 8*o2 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} // Same digit
%>
<%
for (let j=i-1; j>=0; j--) { // All ms
if (((i-j)<n64)&&(j<n64)) {
%>
mov rax, [rsp + <%= j*8 %>]
mul qword [q + <%= (i-j)*8 %>]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
}
} // ms
%>
<%
if (i<n64) {
%>
mov rax, <%= r0 %>
mul r11
mov [rsp + <%= i*8 %>], rax
mul qword [q]
add <%= r0 %>, rax
adc <%= r1 %>, rdx
adc <%= r2 %>, 0x0
<%
} else {
%>
mov [rdi + <%= (i-n64)*8 %> ], <%= r0 %>
xor <%= r0 %>,<%= r0 %>
<%
}
%>
<%
} // Main Loop
%>
cmp <%= r1 %>, 0x0
jne mulM_sq
; Compare with q
<%
for (let i=0; i<n64; i++) {
%>
mov rax, [rdi + <%= (n64-i-1)*8 %>]
cmp rax, [q + <%= (n64-i-1)*8 %>]
jg mulM_sq
jl mulM_done
<%
}
%>
; If equal substract q
mulM_sq:
<%
for (let i=0; i<n64; i++) {
%>
mov rax, [q + <%= i*8 %>]
<%= i==0 ? "sub" : "sbb" %> [rdi + <%= i*8 %>], rax
<%
}
%>
mulM_done:
add rsp, <%= n64*8 %> ; recover rsp
ret
;;;;;;;;;;;;;;;;;;;;;;
; mul MontgomeryShort
;;;;;;;;;;;;;;;;;;;;;;
mulSM:
;;;;;;;;;;;;;;;;;;;;;;
; mul
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_mul:
mov rax, [rsi]
bt rax, 63
jc l1
mov rcx, [rdx]
bt rcx, 63
jc s1l2
s1s2: ; short first and second
mul ecx
jc rs2l ; If if doesn't feed in 32 bits convert the result to long
; The shorts multiplication is done. copy the val to destination and return
mov [rdi], rax
ret
rs2l: ; The result in the multiplication doen't feed
; we have the result in edx:eax we need to convert it to long
shl rdx, 32
mov edx, eax ; pack edx:eax to rdx
xor rax, rax ; Set the format to long
bts rax, 63
mov [rdi], rax ; move the first digit
cmp rdx, 0 ; check if redx is negative.
jl rs2ln
; edx is positive.
mov [rdi + 8], rdx ; Set the firs digit
xor rax, rax ; Set the remaining digits to 0
<% for (let i=1; i<n64; i++) { %>
mov [rdi + <%= (i+1)*8 %>], rax
<% } %>
ret
; edx is negative.
rs2ln:
add rdx, [q] ; Set the firs digit
mov [rdi + 8], rdx ;
mov rdx, -1 ; all ones
<% for (let i=1; i<n64; i++) { %>
mov rax, rdx ; Add to q
adc rax, [q + <%= i*8 %> ]
mov [rdi + <%= (i+1)*8 %>], rax
<% } %>
ret
l1:
mov rcx, [rdx]
bt rcx, 63
jc ll
l1s2:
xor rdx, rdx
mov edx, ecx
bt rax, 62
jc lsM
jmp lsN
s1l2:
mov rsi, rdx
xor rdx, rdx
mov edx, eax
bt rcx, 62
jc lsM
jmp lsN
lsN:
mov byte [rdi + 7], 0xC0 ; set the result to montgomery
add rsi, 8
add rdi, 8
call mulSM
mov rsi, rdi
lea rdx, [R3]
call mulM
ret
lsM:
mov byte [rdi + 7], 0x80 ; set the result to long normal
add rsi, 8
add rdi, 8
call mulSM
ret
ll:
bt rax, 62
jc lml
bt rcx, 62
jc lnlm
lnln:
mov byte [rdi + 7], 0xC0 ; set the result to long montgomery
add rsi, 8
add rdi, 8
add rdx, 8
call mulM
mov rsi, rdi
lea rdx, [R3]
call mulM
ret
lml:
bt rcx, 62
jc lmlm
lnlm:
mov byte [rdi + 7], 0x80 ; set the result to long normal
add rsi, 8
add rdi, 8
add rdx, 8
call mulM
ret
lmlm:
mov byte [rdi + 7], 0xC0 ; set the result to long montgomery
add rsi, 8
add rdi, 8
add rdx, 8
call mulM
ret

317
c/buildasm/sub.asm.ejs Normal file
View File

@@ -0,0 +1,317 @@
<% function subS1S2() { %>
xor rdx, rdx
mov edx, eax
sub edx, ecx
jo sub_manageOverflow ; rsi already is the 64bits result
mov [rdi], rdx ; not necessary to adjust so just save and return
ret
sub_manageOverflow: ; Do the operation in 64 bits
push rsi
movsx rsi, eax
movsx rdx, ecx
sub rsi, rdx
call rawCopyS2L
pop rsi
ret
<% } %>
<% function subL1S2(t) { %>
add rsi, 8
movsx rdx, ecx
add rdi, 8
cmp rdx, 0
<% const rawSubLabel = global.tmpLabel() %>
jns <%= rawSubLabel %>
neg rdx
call rawAddLS
sub rdi, 8
sub rsi, 8
ret
<%= rawSubLabel %>:
call rawSubLS
sub rdi, 8
sub rsi, 8
ret
<% } %>
<% function subS1L2(t) { %>
cmp eax, 0
<% const s1NegLabel = global.tmpLabel() %>
js <%= s1NegLabel %>
; First Operand is positive
push rsi
add rdi, 8
movsx rsi, eax
add rdx, 8
call rawSubSL
sub rdi, 8
pop rsi
ret
<%= s1NegLabel %>: ; First operand is negative
push rsi
lea rsi, [rdx + 8]
movsx rdx, eax
add rdi, 8
neg rdx
call rawNegLS
sub rdi, 8
pop rsi
ret
<% } %>
<% function subL1L2(t) { %>
add rdi, 8
add rsi, 8
add rdx, 8
call rawSubLL
sub rdi, 8
sub rsi, 8
ret
<% } %>
;;;;;;;;;;;;;;;;;;;;;;
; sub
;;;;;;;;;;;;;;;;;;;;;;
; Substracts two elements of any kind
; Params:
; rsi <= Pointer to element 1
; rdx <= Pointer to element 2
; rdi <= Pointer to result
; Modified Registers:
; r8, r9, 10, r11, rax, rcx
;;;;;;;;;;;;;;;;;;;;;;
<%=name%>_sub:
mov rax, [rsi]
mov rcx, [rdx]
bt rax, 63 ; Check if is long first operand
jc sub_l1
bt rcx, 63 ; Check if is long second operand
jc sub_s1l2
sub_s1s2: ; Both operands are short
<%= subS1S2() %>
sub_l1:
bt rcx, 63 ; Check if is short second operand
jc sub_l1l2
;;;;;;;;
sub_l1s2:
bt rax, 62 ; check if montgomery first
jc sub_l1ms2
sub_l1ns2:
<%= global.setTypeDest("0x80"); %>
<%= subL1S2(); %>
sub_l1ms2:
bt rcx, 62 ; check if montgomery second
jc sub_l1ms2m
sub_l1ms2n:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_b() %>
<%= subL1L2() %>
sub_l1ms2m:
<%= global.setTypeDest("0xC0"); %>
<%= subL1L2() %>
;;;;;;;;
sub_s1l2:
bt rcx, 62 ; check if montgomery first
jc sub_s1l2m
sub_s1l2n:
<%= global.setTypeDest("0x80"); %>
<%= subS1L2(); %>
sub_s1l2m:
bt rax, 62 ; check if montgomery second
jc sub_s1ml2m
sub_s1nl2m:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_a() %>
<%= subL1L2() %>
sub_s1ml2m:
<%= global.setTypeDest("0xC0"); %>
<%= subL1L2() %>
;;;;
sub_l1l2:
bt rax, 62 ; check if montgomery first
jc sub_l1ml2
sub_l1nl2:
bt rcx, 62 ; check if montgomery second
jc sub_l1nl2m
sub_l1nl2n:
<%= global.setTypeDest("0x80"); %>
<%= subL1L2() %>
sub_l1nl2m:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_a(); %>
<%= subL1L2() %>
sub_l1ml2:
bt rcx, 62 ; check if montgomery seconf
jc sub_l1ml2m
sub_l1ml2n:
<%= global.setTypeDest("0xC0"); %>
<%= global.toMont_b(); %>
<%= subL1L2() %>
sub_l1ml2m:
<%= global.setTypeDest("0xC0"); %>
<%= subL1L2() %>
;;;;;;;;;;;;;;;;;;;;;;
; rawSubLS
;;;;;;;;;;;;;;;;;;;;;;
; Substracts a short element from the long element
; Params:
; rdi <= Pointer to the long data of result
; rsi <= Pointer to the long data of element 1 where will be substracted
; rdx <= Value to be substracted
; [rdi] = [rsi] - rdx
; Modified Registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;
rawSubLS:
; Substract first digit
mov rax, [rsi]
sub rax, rdx
mov [rdi] ,rax
mov rdx, 0
<% for (let i=1; i<n64; i++) { %>
mov rax, [rsi + <%=i*8%>]
sbb rax, rdx
mov [rdi + <%=i*8%>], rax
<% } %>
jnc rawSubLS_done ; if overflow, add q
; Add q
rawSubLS_aq:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "add" : "adc" %> [rdi + <%=i*8%>], rax
<% } %>
rawSubLS_done:
ret
;;;;;;;;;;;;;;;;;;;;;;
; rawSubSL
;;;;;;;;;;;;;;;;;;;;;;
; Substracts a long element from a short element
; Params:
; rdi <= Pointer to the long data of result
; rsi <= Value from where will bo substracted
; rdx <= Pointer to long of the value to be substracted
;
; [rdi] = rsi - [rdx]
; Modified Registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;
rawSubSL:
; Substract first digit
sub rsi, [rdx]
mov [rdi] ,rsi
<% for (let i=1; i<n64; i++) { %>
mov rax, 0
sbb rax, [rdx + <%=i*8%>]
mov [rdi + <%=i*8%>], rax
<% } %>
jnc rawSubSL_done ; if overflow, add q
; Add q
rawSubSL_aq:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "add" : "adc" %> [rdi + <%=i*8%>], rax
<% } %>
rawSubSL_done:
ret
;;;;;;;;;;;;;;;;;;;;;;
; rawSubLL
;;;;;;;;;;;;;;;;;;;;;;
; Substracts a long element from a short element
; Params:
; rdi <= Pointer to the long data of result
; rsi <= Pointer to long from where substracted
; rdx <= Pointer to long of the value to be substracted
;
; [rdi] = [rsi] - [rdx]
; Modified Registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;
rawSubLL:
; Substract first digit
<% for (let i=0; i<n64; i++) { %>
mov rax, [rsi + <%=i*8%>]
<%= i==0 ? "sub" : "sbb" %> rax, [rdx + <%=i*8%>]
mov [rdi + <%=i*8%>], rax
<% } %>
jnc rawSubLL_done ; if overflow, add q
; Add q
rawSubLL_aq:
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "add" : "adc" %> [rdi + <%=i*8%>], rax
<% } %>
rawSubLL_done:
ret
;;;;;;;;;;;;;;;;;;;;;;
; rawNegLS
;;;;;;;;;;;;;;;;;;;;;;
; Substracts a long element and a short element form 0
; Params:
; rdi <= Pointer to the long data of result
; rsi <= Pointer to long from where substracted
; rdx <= short value to be substracted too
;
; [rdi] = -[rsi] - rdx
; Modified Registers:
; rax
;;;;;;;;;;;;;;;;;;;;;;
rawNegLS:
mov rax, [q]
sub rax, rdx
mov [rdi], rax
<% for (let i=1; i<n64; i++) { %>
mov rax, [q + <%=i*8%> ]
sbb rax, 0
mov [rdi + <%=i*8%>], rax
<% } %>
setc dl
<% for (let i=0; i<n64; i++) { %>
mov rax, [rdi + <%=i*8%> ]
<%= i==0 ? "sub" : "sbb" %> rax, [rsi + <%=i*8%>]
mov [rdi + <%=i*8%>], rax
<% } %>
setc dh
or dl, dh
jz rawNegSL_done
; it is a negative value, so add q
<% for (let i=0; i<n64; i++) { %>
mov rax, [q + <%=i*8%>]
<%= i==0 ? "add" : "adc" %> [rdi + <%=i*8%>], rax
<% } %>
rawNegSL_done:
ret

218
c/buildasm/tester.cpp Normal file
View File

@@ -0,0 +1,218 @@
#include <string>
#include <iostream>
#include <regex>
#include <string>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <stdio.h> /* printf, NULL */
#include <stdlib.h>
#include <cassert>
#include "fr.h"
typedef void (*Func1)(PFrElement, PFrElement);
typedef void (*Func2)(PFrElement, PFrElement, PFrElement);
typedef void *FuncAny;
typedef struct {
FuncAny fn;
int nOps;
} FunctionSpec;
std::map<std::string, FunctionSpec> functions;
std::vector<FrElement> stack;
void addFunction(std::string name, FuncAny f, int nOps) {
FunctionSpec fs;
fs.fn = f;
fs.nOps = nOps;
functions[name] = fs;
}
void fillMap() {
addFunction("add", (FuncAny)Fr_add, 2);
addFunction("sub", (FuncAny)Fr_sub, 2);
addFunction("neg", (FuncAny)Fr_neg, 1);
addFunction("mul", (FuncAny)Fr_mul, 2);
addFunction("square", (FuncAny)Fr_square, 1);
addFunction("idiv", (FuncAny)Fr_idiv, 2);
addFunction("inv", (FuncAny)Fr_inv, 1);
addFunction("div", (FuncAny)Fr_div, 2);
addFunction("band", (FuncAny)Fr_band, 2);
addFunction("bor", (FuncAny)Fr_bor, 2);
addFunction("bxor", (FuncAny)Fr_bxor, 2);
addFunction("bnot", (FuncAny)Fr_bnot, 1);
addFunction("eq", (FuncAny)Fr_eq, 2);
addFunction("neq", (FuncAny)Fr_neq, 2);
addFunction("lt", (FuncAny)Fr_lt, 2);
addFunction("gt", (FuncAny)Fr_gt, 2);
addFunction("leq", (FuncAny)Fr_leq, 2);
addFunction("geq", (FuncAny)Fr_geq, 2);
addFunction("land", (FuncAny)Fr_land, 2);
addFunction("lor", (FuncAny)Fr_lor, 2);
addFunction("lnot", (FuncAny)Fr_lnot, 1);
}
u_int64_t readInt(std::string &s) {
if (s.rfind("0x", 0) == 0) {
return std::stoull(s.substr(2), 0, 16);
} else {
return std::stoull(s, 0, 10);
}
}
void pushNumber(std::vector<std::string> &v) {
u_int64_t a;
if ((v.size()<1) || (v.size() > (Fr_N64+1))) {
printf("Invalid Size: %d - %d \n", v.size(), Fr_N64);
throw std::runtime_error("Invalid number of parameters for number");
}
FrElement e;
a = readInt(v[0]);
*(u_int64_t *)(&e) = a;
for (int i=0; i<Fr_N64; i++) {
if (i+1 < v.size()) {
a = readInt(v[i+1]);
} else {
a = 0;
}
e.longVal[i] = a;
}
stack.push_back(e);
}
void callFunction(FunctionSpec fs) {
if (stack.size() < fs.nOps) {
throw new std::runtime_error("Not enough elements in stack");
}
if (fs.nOps == 1) {
FrElement a = stack.back();
stack.pop_back();
FrElement c;
(*(Func1)fs.fn)(&c, &a);
stack.push_back(c);
} else if (fs.nOps == 2) {
FrElement b = stack.back();
stack.pop_back();
FrElement a = stack.back();
stack.pop_back();
FrElement c;
(*(Func2)fs.fn)(&c, &a, &b);
stack.push_back(c);
} else {
assert(false);
}
}
void processLine(std::string &line) {
std::regex re("(\\s*[,;]\\s*)|\\s+"); // whitespace
std::sregex_token_iterator begin( line.begin(), line.end(), re ,-1);
std::sregex_token_iterator end;
std::vector<std::string> tokens;
std::copy(begin, end, std::back_inserter(tokens));
// Remove initial empty tokens
while ((tokens.size() > 0)&&(tokens[0] == "")) {
tokens.erase(tokens.begin());
}
// Empty lines are valid but are not processed
if (tokens.size() == 0) return;
auto search = functions.find(tokens[0]);
if (search == functions.end()) {
pushNumber(tokens);
} else {
if (tokens.size() != 1) {
throw std::runtime_error("Functions does not accept parameters");
}
callFunction(search->second);
}
}
int main(void)
{
Fr_init();
fillMap();
std::string line;
int i=0;
while (std::getline(std::cin, line)) {
processLine(line);
// if (i%1000 == 0) printf("%d\n", i);
// printf("%d\n", i);
i++;
}
// Print the elements in the stack
//
for (int i=0; i<stack.size(); i++) {
char *s;
s = Fr_element2str(&stack[i]);
printf("%s\n", s);
free(s);
}
return EXIT_SUCCESS;
}
/*
#include <stdlib.h>
#include <string.h>
#include "fr.h"
typedef void (*Func2)(PFrElement, PFrElement, PFrElement);
typedef struct {
const char *fnName;
Func2 fn;
} FN;
#define NFN 2
FN fns[NFN] = {
{"add", Fr_add},
{"mul", Fr_mul},
};
int main(int argc, char **argv) {
if (argc <= 1) {
fprintf( stderr, "invalid number of parameters");
return 1;
}
for (int i=0; i< NFN;i++) {
if (strcmp(argv[1], fns[i].fnName) == 0) {
if (argc != 4) {
fprintf( stderr, "invalid number of parameters");
return 1;
}
FrElement a;
FrElement b;
Fr_str2element(&a, argv[2]);
Fr_str2element(&b, argv[3]);
FrElement c;
fns[i].fn(&c, &a, &b);
char *s;
s = Fr_element2str(&c);
printf("%s", s);
free(s);
return 0;
}
}
fprintf( stderr, "invalid operation %s", argv[1]);
return 1;
}
*/

73
c/buildasm/utils.asm.ejs Normal file
View File

@@ -0,0 +1,73 @@
<% global.setTypeDest = function (t) {
return (
` mov r11b, ${t}
shl r11, 56
mov [rdi], r11`);
} %>
<% global.toMont_a = function () {
return (
` push rdi
mov rdi, rsi
mov rsi, rdx
call ${name}_toMontgomery
mov rdx, rsi
mov rsi, rdi
pop rdi`);
} %>
<% global.toMont_b = function() {
return (
` push rdi
mov rdi, rdx
call ${name}_toMontgomery
mov rdx, rdi
pop rdi`);
} %>
<% global.fromMont_a = function () {
return (
` push rdi
mov rdi, rsi
mov rsi, rdx
call ${name}_toNormal
mov rdx, rsi
mov rsi, rdi
pop rdi`);
} %>
<% global.fromMont_b = function() {
return (
` push rdi
mov rdi, rdx
call ${name}_toNormal
mov rdx, rdi
pop rdi`);
} %>
<% global.toLong_a = function () {
return (
` push rdi
push rdx
mov rdi, rsi
movsx rsi, r8d
call rawCopyS2L
mov rsi, rdi
pop rdx
pop rdi`);
} %>
<% global.toLong_b = function() {
return (
` push rdi
push rsi
mov rdi, rdx
movsx rsi, r9d
call rawCopyS2L
mov rdx, rdi
pop rsi
pop rdi`);
} %>

229
c/calcwit.cpp Normal file
View File

@@ -0,0 +1,229 @@
#include <string>
#include <stdexcept>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <thread>
#include "calcwit.h"
#include "utils.h"
Circom_CalcWit::Circom_CalcWit(Circom_Circuit *aCircuit) {
circuit = aCircuit;
#ifdef SANITY_CHECK
signalAssigned = new bool[circuit->NSignals];
signalAssigned[0] = true;
#endif
mutexes = new std::mutex[NMUTEXES];
cvs = new std::condition_variable[NMUTEXES];
inputSignalsToTrigger = new int[circuit->NComponents];
signalValues = new FrElement[circuit->NSignals];
// Set one signal
Fr_copy(&signalValues[0], circuit->constants + 1);
reset();
}
Circom_CalcWit::~Circom_CalcWit() {
#ifdef SANITY_CHECK
delete signalAssigned;
#endif
delete[] cvs;
delete[] mutexes;
delete[] signalValues;
delete[] inputSignalsToTrigger;
}
void Circom_CalcWit::syncPrintf(const char *format, ...) {
va_list args;
va_start(args, format);
printf_mutex.lock();
vprintf(format, args);
printf_mutex.unlock();
va_end(args);
}
void Circom_CalcWit::reset() {
#ifdef SANITY_CHECK
for (int i=1; i<circuit->NComponents; i++) signalAssigned[i] = false;
#endif
for (int i=0; i<circuit->NComponents; i++) {
inputSignalsToTrigger[i] = circuit->components[i].inputSignals;
if (inputSignalsToTrigger[i] == 0) triggerComponent(i);
}
}
int Circom_CalcWit::getSubComponentOffset(int cIdx, u64 hash) {
int hIdx;
for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) {
if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash));
}
int entryPos = circuit->components[cIdx].hashTable[hIdx].pos;
if (circuit->components[cIdx].entries[entryPos].type != _typeComponent) {
throw std::runtime_error("invalid type");
}
return circuit->components[cIdx].entries[entryPos].offset;
}
Circom_Sizes Circom_CalcWit::getSubComponentSizes(int cIdx, u64 hash) {
int hIdx;
for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) {
if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash));
}
int entryPos = circuit->components[cIdx].hashTable[hIdx].pos;
if (circuit->components[cIdx].entries[entryPos].type != _typeComponent) {
throw std::runtime_error("invalid type");
}
return circuit->components[cIdx].entries[entryPos].sizes;
}
int Circom_CalcWit::getSignalOffset(int cIdx, u64 hash) {
int hIdx;
for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) {
if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash));
}
int entryPos = circuit->components[cIdx].hashTable[hIdx].pos;
if (circuit->components[cIdx].entries[entryPos].type != _typeSignal) {
throw std::runtime_error("invalid type");
}
return circuit->components[cIdx].entries[entryPos].offset;
}
Circom_Sizes Circom_CalcWit::getSignalSizes(int cIdx, u64 hash) {
int hIdx;
for(hIdx = int(hash & 0xFF); hash!=circuit->components[cIdx].hashTable[hIdx].hash; hIdx++) {
if (!circuit->components[cIdx].hashTable[hIdx].hash) throw std::runtime_error("hash not found: " + int_to_hex(hash));
}
int entryPos = circuit->components[cIdx].hashTable[hIdx].pos;
if (circuit->components[cIdx].entries[entryPos].type != _typeSignal) {
throw std::runtime_error("invalid type");
}
return circuit->components[cIdx].entries[entryPos].sizes;
}
void Circom_CalcWit::getSignal(int currentComponentIdx, int cIdx, int sIdx, PFrElement value) {
// syncPrintf("getSignal: %d\n", sIdx);
if ((circuit->components[cIdx].newThread)&&(currentComponentIdx != cIdx)) {
std::unique_lock<std::mutex> lk(mutexes[cIdx % NMUTEXES]);
while (inputSignalsToTrigger[cIdx] != -1) {
cvs[cIdx % NMUTEXES].wait(lk);
}
// cvs[cIdx % NMUTEXES].wait(lk, [&]{return inputSignalsToTrigger[cIdx] == -1;});
lk.unlock();
}
#ifdef SANITY_CHECK
if (signalAssigned[sIdx] == false) {
fprintf(stderr, "Accessing a not assigned signal: %d\n", sIdx);
assert(false);
}
#endif
Fr_copy(value, signalValues + sIdx);
/*
char *valueStr = mpz_get_str(0, 10, *value);
syncPrintf("%d, Get %d --> %s\n", currentComponentIdx, sIdx, valueStr);
free(valueStr);
*/
}
void Circom_CalcWit::finished(int cIdx) {
{
std::lock_guard<std::mutex> lk(mutexes[cIdx % NMUTEXES]);
inputSignalsToTrigger[cIdx] = -1;
}
// syncPrintf("Finished: %d\n", cIdx);
cvs[cIdx % NMUTEXES].notify_all();
}
void Circom_CalcWit::setSignal(int currentComponentIdx, int cIdx, int sIdx, PFrElement value) {
// syncPrintf("setSignal: %d\n", sIdx);
#ifdef SANITY_CHECK
if (signalAssigned[sIdx] == true) {
fprintf(stderr, "Signal assigned twice: %d\n", sIdx);
assert(false);
}
signalAssigned[sIdx] = true;
#endif
// Log assignement
/*
char *valueStr = mpz_get_str(0, 10, *value);
syncPrintf("%d, Set %d --> %s\n", currentComponentIdx, sIdx, valueStr);
free(valueStr);
*/
Fr_copy(signalValues + sIdx, value);
if ( BITMAP_ISSET(circuit->mapIsInput, sIdx) ) {
if (inputSignalsToTrigger[cIdx]>0) {
inputSignalsToTrigger[cIdx]--;
if (inputSignalsToTrigger[cIdx] == 0) triggerComponent(cIdx);
}
}
}
void Circom_CalcWit::checkConstraint(int currentComponentIdx, PFrElement value1, PFrElement value2, char const *err) {
#ifdef SANITY_CHECK
FrElement tmp;
Fr_eq(&tmp, value1, value2);
if (!Fr_isTrue(&tmp)) {
char *pcV1 = Fr_element2str(value1);
char *pcV2 = Fr_element2str(value2);
// throw std::runtime_error(std::to_string(currentComponentIdx) + std::string(", Constraint doesn't match, ") + err + ". " + sV1 + " != " + sV2 );
fprintf(stderr, "Constraint doesn't match, %s: %s != %s", err, pcV1, pcV2);
free(pcV1);
free(pcV2);
assert(false);
}
#endif
}
void Circom_CalcWit::triggerComponent(int newCIdx) {
//int oldCIdx = cIdx;
// cIdx = newCIdx;
if (circuit->components[newCIdx].newThread) {
// syncPrintf("Triggered: %d\n", newCIdx);
std::thread t(circuit->components[newCIdx].fn, this, newCIdx);
// t.join();
t.detach();
} else {
(*(circuit->components[newCIdx].fn))(this, newCIdx);
}
// cIdx = oldCIdx;
}
void Circom_CalcWit::log(PFrElement value) {
char *pcV = Fr_element2str(value);
syncPrintf("Log: %s\n", pcV);
free(pcV);
}
void Circom_CalcWit::join() {
for (int i=0; i<circuit->NComponents; i++) {
std::unique_lock<std::mutex> lk(mutexes[i % NMUTEXES]);
while (inputSignalsToTrigger[i] != -1) {
cvs[i % NMUTEXES].wait(lk);
}
// cvs[i % NMUTEXES].wait(lk, [&]{return inputSignalsToTrigger[i] == -1;});
lk.unlock();
// syncPrintf("Joined: %d\n", i);
}
}

73
c/calcwit.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef CIRCOM_CALCWIT_H
#define CIRCOM_CALCWIT_H
#include "circom.h"
#include "fr.h"
#include <mutex>
#include <condition_variable>
#define NMUTEXES 128
class Circom_CalcWit {
#ifdef SANITY_CHECK
bool *signalAssigned;
#endif
// componentStatus -> For each component
// >0 Signals required to trigger
// == 0 Component triggered
// == -1 Component finished
int *inputSignalsToTrigger;
std::mutex *mutexes;
std::condition_variable *cvs;
std::mutex printf_mutex;
FrElement *signalValues;
void triggerComponent(int newCIdx);
void calculateWitness(void *input, void *output);
void syncPrintf(const char *format, ...);
public:
Circom_Circuit *circuit;
// Functions called by the circuit
Circom_CalcWit(Circom_Circuit *aCircuit);
~Circom_CalcWit();
int getSubComponentOffset(int cIdx, u64 hash);
Circom_Sizes getSubComponentSizes(int cIdx, u64 hash);
int getSignalOffset(int cIdx, u64 hash);
Circom_Sizes getSignalSizes(int cIdx, u64 hash);
void getSignal(int currentComponentIdx, int cIdx, int sIdx, PFrElement value);
void setSignal(int currentComponentIdx, int cIdx, int sIdx, PFrElement value);
void checkConstraint(int currentComponentIdx, PFrElement value1, PFrElement value2, char const *err);
void log(PFrElement value);
void finished(int cIdx);
void join();
// Public functions
inline void setInput(int idx, PFrElement val) {
setSignal(0, 0, circuit->wit2sig[idx], val);
}
inline void getWitness(int idx, PFrElement val) {
Fr_copy(val, &signalValues[circuit->wit2sig[idx]]);
}
void reset();
};
#endif // CIRCOM_CALCWIT_H

58
c/circom.h Normal file
View File

@@ -0,0 +1,58 @@
#ifndef __CIRCOM_H
#define __CIRCOM_H
#include <gmp.h>
#include <stdint.h>
#include "fr.h"
class Circom_CalcWit;
typedef unsigned long long u64;
typedef uint32_t u32;
typedef uint8_t u8;
typedef int Circom_Size;
typedef Circom_Size *Circom_Sizes;
struct Circom_HashEntry {
u64 hash;
int pos;
};
typedef Circom_HashEntry *Circom_HashTable;
typedef enum { _typeSignal, _typeComponent} Circom_EntryType;
struct Circom_ComponentEntry {
int offset;
Circom_Sizes sizes;
Circom_EntryType type;
};
typedef Circom_ComponentEntry *Circom_ComponentEntries;
typedef void (*Circom_ComponentFunction)(Circom_CalcWit *ctx, int __cIdx);
struct Circom_Component {
Circom_HashTable hashTable;
Circom_ComponentEntries entries;
Circom_ComponentFunction fn;
int inputSignals;
bool newThread;
};
class Circom_Circuit {
public:
int NSignals;
int NComponents;
int NInputs;
int NOutputs;
int NVars;
int *wit2sig;
Circom_Component *components;
u32 *mapIsInput;
PFrElement constants;
const char *P;
};
#define BITMAP_ISSET(m, b) (m[b>>5] & (1 << (b&0x1F)))
extern struct Circom_Circuit _circuit;
#endif

1
c/fr.c Symbolic link
View File

@@ -0,0 +1 @@
buildasm/fr.c

1
c/fr.h Symbolic link
View File

@@ -0,0 +1 @@
buildasm/fr.h

1
c/fr.o Symbolic link
View File

@@ -0,0 +1 @@
buildasm/fr.o

214
c/main.cpp Normal file
View File

@@ -0,0 +1,214 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <iomanip>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#include "calcwit.h"
#include "circom.h"
#include "utils.h"
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
void loadBin(Circom_CalcWit *ctx, std::string filename) {
int fd;
struct stat sb;
// map input
fd = open(filename.c_str(), O_RDONLY);
if (fd == -1)
handle_error("open");
if (fstat(fd, &sb) == -1) /* To obtain file size */
handle_error("fstat");
u8 *in;
in = (u8 *)mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (in == MAP_FAILED)
handle_error("mmap");
close(fd);
FrElement v;
u8 *p = in;
for (int i=0; i<_circuit.NInputs; i++) {
v.type = Fr_LONG;
for (int j=0; j<Fr_N64; j++) {
v.longVal[j] = *(u64 *)p;
}
p += 8;
ctx->setSignal(0, 0, _circuit.wit2sig[1 + _circuit.NOutputs + i], &v);
}
}
typedef void (*ItFunc)(Circom_CalcWit *ctx, int idx, json val);
void iterateArr(Circom_CalcWit *ctx, int o, Circom_Sizes sizes, json jarr, ItFunc f) {
if (!jarr.is_array()) {
assert((sizes[0] == 1)&&(sizes[1] == 0));
f(ctx, o, jarr);
} else {
int n = sizes[0] / sizes[1];
for (int i=0; i<n; i++) {
iterateArr(ctx, o + i*sizes[1], sizes+1, jarr[i], f);
}
}
}
void itFunc(Circom_CalcWit *ctx, int o, json val) {
FrElement v;
std::string s;
if (val.is_string()) {
s = val.get<std::string>();
} else if (val.is_number()) {
double vd = val.get<double>();
std::stringstream stream;
stream << std::fixed << std::setprecision(0) << vd;
s = stream.str();
} else {
handle_error("Invalid JSON type");
}
Fr_str2element (&v, s.c_str());
ctx->setSignal(0, 0, o, &v);
}
void loadJson(Circom_CalcWit *ctx, std::string filename) {
std::ifstream inStream(filename);
json j;
inStream >> j;
for (json::iterator it = j.begin(); it != j.end(); ++it) {
// std::cout << it.key() << " => " << it.value() << '\n';
u64 h = fnv1a(it.key());
int o;
try {
o = ctx->getSignalOffset(0, h);
} catch (std::runtime_error e) {
std::ostringstream errStrStream;
errStrStream << "Error loadin variable: " << it.key() << "\n" << e.what();
throw std::runtime_error(errStrStream.str() );
}
Circom_Sizes sizes = ctx->getSignalSizes(0, h);
iterateArr(ctx, o, sizes, it.value(), itFunc);
}
}
void writeOutBin(Circom_CalcWit *ctx, std::string filename) {
FILE *write_ptr;
write_ptr = fopen(filename.c_str(),"wb");
// Add header
// 4 bytes --> total number of constraints
uint32_t witnessLength = _circuit.NVars;
// 4 bytes --> witness element size (measured in number of 32bit words)
uint32_t witnessSize = 8; // witness size = 8*32 = 256 bits
// 8bytes --> empty
uint64_t otherField = 0;
fwrite(&witnessLength, 4, 1, write_ptr);
fwrite(&witnessSize, 4, 1, write_ptr);
fwrite(&otherField, 8, 1, write_ptr);
FrElement v;
u8 buffOut[256];
for (int i=0;i<_circuit.NVars;i++) {
size_t size=256;
ctx->getWitness(i, &v);
Fr_toLongNormal(&v);
fwrite(v.longVal, Fr_N64*8, 1, write_ptr);
}
fclose(write_ptr);
}
void writeOutJson(Circom_CalcWit *ctx, std::string filename) {
std::ofstream outFile;
outFile.open (filename);
outFile << "[\n";
FrElement v;
for (int i=0;i<_circuit.NVars;i++) {
ctx->getWitness(i, &v);
char *pcV = Fr_element2str(&v);
std::string sV = std::string(pcV);
outFile << (i ? "," : " ") << "\"" << sV << "\"\n";
free(pcV);
}
outFile << "]\n";
outFile.close();
}
bool hasEnding (std::string const &fullString, std::string const &ending) {
if (fullString.length() >= ending.length()) {
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
} else {
return false;
}
}
int main(int argc, char *argv[]) {
Fr_init();
if (argc!=3) {
std::string cl = argv[0];
std::string base_filename = cl.substr(cl.find_last_of("/\\") + 1);
std::cout << "Usage: " << base_filename << " <input.<bin|json>> <output.<bin|json>>\n";
} else {
// open output
Circom_CalcWit *ctx = new Circom_CalcWit(&_circuit);
std::string infilename = argv[1];
if (hasEnding(infilename, std::string(".bin"))) {
loadBin(ctx, infilename);
} else if (hasEnding(infilename, std::string(".json"))) {
loadJson(ctx, infilename);
} else {
handle_error("Invalid input extension (.bin / .json)");
}
ctx->join();
printf("Finished!\n");
std::string outfilename = argv[2];
if (hasEnding(outfilename, std::string(".bin"))) {
writeOutBin(ctx, outfilename);
} else if (hasEnding(outfilename, std::string(".json"))) {
writeOutJson(ctx, outfilename);
} else {
handle_error("Invalid output extension (.bin / .json)");
}
delete ctx;
exit(EXIT_SUCCESS);
}
}

BIN
c/mainjson Normal file

Binary file not shown.

47
c/mainjson.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
#include "utils.h"
#include "circom.h"
#include "calcwit.h"
auto j = R"(
{
"in": "314"
}
)"_json;
typedef void (*ItFunc)(int idx, json val);
void iterateArr(int o, Circom_Sizes sizes, json jarr, ItFunc f) {
if (!jarr.is_array()) {
assert((sizes[0] == 1)&&(sizes[1] == 0));
f(o, jarr);
} else {
int n = sizes[0] / sizes[1];
for (int i=0; i<n; i++) {
iterateArr(o + i*sizes[1], sizes+1, jarr[i], f);
}
}
}
void itFunc(int o, json v) {
std::cout << o << " <-- " << v << '\n';
}
int main(int argc, char **argv) {
Circom_CalcWit *ctx = new Circom_CalcWit(&_circuit);
for (json::iterator it = j.begin(); it != j.end(); ++it) {
// std::cout << it.key() << " => " << it.value() << '\n';
u64 h = fnv1a(it.key());
int o = ctx->getSignalOffset(0, h);
Circom_Sizes sizes = ctx->getSignalSizes(0, h);
iterateArr(o, sizes, it.value(), itFunc);
}
}

25
c/utils.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <stdlib.h>
#include "utils.h"
std::string int_to_hex( u64 i )
{
std::stringstream stream;
stream << "0x"
<< std::setfill ('0') << std::setw(16)
<< std::hex << i;
return stream.str();
}
u64 fnv1a(std::string s) {
u64 hash = 0xCBF29CE484222325LL;
for(char& c : s) {
hash ^= u64(c);
hash *= 0x100000001B3LL;
}
return hash;
}

10
c/utils.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef __UTILS_H
#define __UTILS_H
#include "circom.h"
std::string int_to_hex( u64 i );
u64 fnv1a(std::string s);
#endif // __UTILS_H

199
c/zqfield.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include "zqfield.h"
ZqField::ZqField(PBigInt ap) {
mpz_init_set(p, *ap);
mpz_init_set_ui(zero, 0);
mpz_init_set_ui(one, 1);
nBits = mpz_sizeinbase (p, 2);
mpz_init(mask);
mpz_mul_2exp(mask, one, nBits-1);
mpz_sub(mask, mask, one);
}
ZqField::~ZqField() {
mpz_clear(p);
mpz_clear(zero);
mpz_clear(one);
}
void ZqField::add(PBigInt r, PBigInt a, PBigInt b) {
mpz_add(*r,*a,*b);
if (mpz_cmp(*r, p) >= 0) {
mpz_sub(*r, *r, p);
}
}
void ZqField::sub(PBigInt r, PBigInt a, PBigInt b) {
if (mpz_cmp(*a, *b) >= 0) {
mpz_sub(*r, *a, *b);
} else {
mpz_sub(*r, *b, *a);
mpz_sub(*r, p, *r);
}
}
void ZqField::neg(PBigInt r, PBigInt a) {
if (mpz_sgn(*a) > 0) {
mpz_sub(*r, p, *a);
} else {
mpz_set(*r, *a);
}
}
void ZqField::mul(PBigInt r, PBigInt a, PBigInt b) {
mpz_t tmp;
mpz_init(tmp);
mpz_mul(tmp,*a,*b);
mpz_fdiv_r(*r, tmp, p);
mpz_clear(tmp);
}
void ZqField::div(PBigInt r, PBigInt a, PBigInt b) {
mpz_t tmp;
mpz_init(tmp);
mpz_invert(tmp, *b, p);
mpz_mul(tmp,*a,tmp);
mpz_fdiv_r(*r, tmp, p);
mpz_clear(tmp);
}
void ZqField::idiv(PBigInt r, PBigInt a, PBigInt b) {
mpz_fdiv_q(*r, *a, *b);
}
void ZqField::mod(PBigInt r, PBigInt a, PBigInt b) {
mpz_fdiv_r(*r, *a, *b);
}
void ZqField::pow(PBigInt r, PBigInt a, PBigInt b) {
mpz_powm(*r, *a, *b, p);
}
void ZqField::lt(PBigInt r, PBigInt a, PBigInt b) {
int c = mpz_cmp(*a, *b);
if (c<0) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::eq(PBigInt r, PBigInt a, PBigInt b) {
int c = mpz_cmp(*a, *b);
if (c==0) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::gt(PBigInt r, PBigInt a, PBigInt b) {
int c = mpz_cmp(*a, *b);
if (c>0) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::leq(PBigInt r, PBigInt a, PBigInt b) {
int c = mpz_cmp(*a, *b);
if (c<=0) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::geq(PBigInt r, PBigInt a, PBigInt b) {
int c = mpz_cmp(*a, *b);
if (c>=0) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::neq(PBigInt r, PBigInt a, PBigInt b) {
int c = mpz_cmp(*a, *b);
if (c!=0) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::land(PBigInt r, PBigInt a, PBigInt b) {
if (mpz_sgn(*a) && mpz_sgn(*b)) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::lor(PBigInt r, PBigInt a, PBigInt b) {
if (mpz_sgn(*a) || mpz_sgn(*b)) {
mpz_set(*r, one);
} else {
mpz_set(*r, zero);
}
}
void ZqField::lnot(PBigInt r, PBigInt a) {
if (mpz_sgn(*a)) {
mpz_set(*r, zero);
} else {
mpz_set(*r, one);
}
}
int ZqField::isTrue(PBigInt a) {
return mpz_sgn(*a);
}
void ZqField::copyn(PBigInt a, PBigInt b, int n) {
for (int i=0;i<n; i++) mpz_set(a[i], b[i]);
}
void ZqField::band(PBigInt r, PBigInt a, PBigInt b) {
mpz_and(*r, *a, *b);
mpz_and(*r, *r, mask);
}
void ZqField::bor(PBigInt r, PBigInt a, PBigInt b) {
mpz_ior(*r, *a, *b);
mpz_and(*r, *r, mask);
}
void ZqField::bxor(PBigInt r, PBigInt a, PBigInt b) {
mpz_xor(*r, *a, *b);
mpz_and(*r, *r, mask);
}
void ZqField::bnot(PBigInt r, PBigInt a) {
mpz_xor(*r, *a, mask);
mpz_and(*r, *r, mask);
}
void ZqField::shl(PBigInt r, PBigInt a, PBigInt b) {
if (mpz_cmp_ui(*b, nBits) >= 0) {
mpz_set(*r, zero);
} else {
mpz_mul_2exp(*r, *a, mpz_get_ui(*b));
mpz_and(*r, *r, mask);
}
}
void ZqField::shr(PBigInt r, PBigInt a, PBigInt b) {
if (mpz_cmp_ui(*b, nBits) >= 0) {
mpz_set(*r, zero);
} else {
mpz_tdiv_q_2exp(*r, *a, mpz_get_ui(*b));
mpz_and(*r, *r, mask);
}
}
int ZqField::toInt(PBigInt a) {
return mpz_get_si (*a);
}

49
c/zqfield.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef ZQFIELD_H
#define ZQFIELD_H
#include "circom.h"
class ZqField {
public:
BigInt p;
BigInt one;
BigInt zero;
size_t nBits;
BigInt mask;
ZqField(PBigInt ap);
~ZqField();
void copyn(PBigInt a, PBigInt b, int n);
void add(PBigInt r,PBigInt a, PBigInt b);
void sub(PBigInt r,PBigInt a, PBigInt b);
void neg(PBigInt r,PBigInt a);
void mul(PBigInt r,PBigInt a, PBigInt b);
void div(PBigInt r,PBigInt a, PBigInt b);
void idiv(PBigInt r,PBigInt a, PBigInt b);
void mod(PBigInt r,PBigInt a, PBigInt b);
void pow(PBigInt r,PBigInt a, PBigInt b);
void lt(PBigInt r, PBigInt a, PBigInt b);
void eq(PBigInt r, PBigInt a, PBigInt b);
void gt(PBigInt r, PBigInt a, PBigInt b);
void leq(PBigInt r, PBigInt a, PBigInt b);
void geq(PBigInt r, PBigInt a, PBigInt b);
void neq(PBigInt r, PBigInt a, PBigInt b);
void land(PBigInt r, PBigInt a, PBigInt b);
void lor(PBigInt r, PBigInt a, PBigInt b);
void lnot(PBigInt r, PBigInt a);
void band(PBigInt r, PBigInt a, PBigInt b);
void bor(PBigInt r, PBigInt a, PBigInt b);
void bxor(PBigInt r, PBigInt a, PBigInt b);
void bnot(PBigInt r, PBigInt a);
void shl(PBigInt r, PBigInt a, PBigInt b);
void shr(PBigInt r, PBigInt a, PBigInt b);
int isTrue(PBigInt a);
int toInt(PBigInt a);
};
#endif // ZQFIELD_H

80
cli.js
View File

@@ -30,12 +30,25 @@ const version = require("./package").version;
const argv = require("yargs")
.version(version)
.usage("circom [input source circuit file] -o [output definition circuit file]")
.usage("circom [input source circuit file] -o [output definition circuit file] -c [output c file]")
.alias("o", "output")
.alias("c", "csource")
.alias("w", "wasm")
.alias("s", "sym")
.alias("r", "r1cs")
.alias("n", "newThreadTemplates")
.help("h")
.alias("h", "help")
.alias("v", "verbose")
.alias("f", "fast")
.option("verbose", {
alias: "v",
type: "boolean",
description: "Run with verbose logging"
})
.option("fast", {
alias: "f",
type: "boolean",
description: "Do not optimize constraints"
})
.epilogue(`Copyright (C) 2018 0kims association
This program comes with ABSOLUTELY NO WARRANTY;
This is free software, and you are welcome to redistribute it
@@ -55,11 +68,66 @@ if (argv._.length == 0) {
}
const fullFileName = path.resolve(process.cwd(), inputFile);
const outName = argv.output ? argv.output : "circuit.json";
const fileName = path.basename(fullFileName, ".circom");
const cSourceName = typeof(argv.csource) === "string" ? argv.csource : fileName + ".cpp";
const wasmName = typeof(argv.wasm) === "string" ? argv.wasm : fileName + ".wasm";
const r1csName = typeof(argv.r1cs) === "string" ? argv.r1cs : fileName + ".r1cs";
const symName = typeof(argv.sym) === "string" ? argv.sym : fileName + ".sym";
compiler(fullFileName, {reduceConstraints: !argv.fast}).then( (cir) => {
fs.writeFileSync(outName, JSON.stringify(cir, null, 1), "utf8");
const options = {};
options.reduceConstraints = !argv.fast;
options.verbose = argv.verbose || false;
if (argv.csource) {
options.cSourceWriteStream = fs.createWriteStream(cSourceName);
}
if (argv.wasm) {
options.wasmWriteStream = fs.createWriteStream(wasmName);
}
if (argv.r1cs) {
options.r1csFileName = r1csName;
}
if (argv.sym) {
options.symWriteStream = fs.createWriteStream(symName);
}
if (argv.newThreadTemplates) {
options.newThreadTemplates = new RegExp(argv.newThreadTemplates);
}
compiler(fullFileName, options).then( () => {
let cSourceDone = false;
let wasmDone = false;
let symDone = false;
if (options.cSourceWriteStream) {
options.cSourceWriteStream.on("finish", () => {
cSourceDone = true;
finishIfDone();
});
} else {
cSourceDone = true;
}
if (options.wasmWriteStream) {
options.wasmWriteStream.on("finish", () => {
wasmDone = true;
finishIfDone();
});
} else {
wasmDone = true;
}
if (options.symWriteStream) {
options.symWriteStream.on("finish", () => {
symDone = true;
finishIfDone();
});
} else {
symDone = true;
}
function finishIfDone() {
if ((cSourceDone)&&(symDone)&&(wasmDone)) {
setTimeout(() => {
process.exit(0);
}, 300);
}
}
}, (err) => {
// console.log(err);
console.log(err.stack);

BIN
doc/lc_example.monopic Normal file

Binary file not shown.

683
doc/r1cs_bin_format.md Normal file
View File

@@ -0,0 +1,683 @@
# Binary format for R1CS
---
eip:
title: r1cs binary format
author: Jordi Baylina <jordi@baylina.cat>
discussions-to:
status: draft
type: Standards Track
category: ERC
created: 2019-09-24
requires:
---
## Simple Summary
This standard defines a standard format for a binery representation of a r1cs constraint system.
## Abstract
## Motivation
The zero knowledge primitives, requires the definition of a statment that wants to be proved. This statment can be expressed as a deterministric program or an algebraic circuit. Lots of primitives like zkSnarks, bulletProofs or aurora, requires to convert this statment to a rank-one constraint system.
This standard specifies a format for a r1cs and allows the to connect a set of tools that compiles a program or a circuit to r1cs that can be used for the zksnarks or bulletproofs primitives.
## Specification
### General considerations
The standard extension is `.r1cs`
A deterministic program (or circuit) is a program that generates a set of deterministic values given an input. All those values are labeled from l_{0} to l_{n_labels}
This file defines a map beween l_{i} -> w_{j} and defines a series a R1CS of the form
$$
\left\{ \begin{array}{rclclcl}
(a_{0,0}w_0 + a_{0,1}w_1 + ... + a_{0,n}w_{n}) &\cdot& (b_{0,0} w_0 + b_{0,1} w_1 + ... + b_{0,n} w_{n}) &-& (c_{0,0} w_0 + c_{0,1} w_1 + ... + c_{0,n}w_{n}) &=& 0 \\
(a_{1,0}w_0 + a_{1,1}w_1 + ... + a_{1,n}w_{n}) &\cdot& (b_{1,0} w_0 + b_{1,1} w_1 + ... + b_{1,n} w_{n}) &-& (c_{1,0} w_0 + c_{1,1}w_1 + ... + c_{1,n}w_{n}) &=& 0 \\
...\\
(a_{m-1,0}w_0 + a_{m-1,1}w_1 + ... + a_{m-1,n}w_{n}) &\cdot& (b_{m-1,0} w_0 + b_{m-1,1} w_1 + ... + b_{m-1,n} w_{n}) &-& (c_{m-1,0} w_0 + c_{m-1,1}w_1 + ... + c_{m-1,n}w_{n}) &=& 0
\end{array} \right.
$$
Wire 0 must be always mapped to label 0 and it's an input forced to value "1" implicitly
### Format of the file
````
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ 4 │ 72 31 63 73 ┃ Magic "r1cs"
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ 4 │ 01 00 00 00 ┃ Version 1
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ 4 │ 03 00 00 00 ┃ Number of Sections
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 4 │ sectionType ┃ 8 │ SectionSize ┃
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ┃
┃ ┃
┃ Section Content ┃
┃ ┃
┃ ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 4 │ sectionType ┃ 8 │ SectionSize ┃
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ ┃
┃ ┃
┃ Section Content ┃
┃ ┃
┃ ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━┛
...
...
...
````
#### Magic Number
Size: 4 bytes
The file start with a constant 4 bytes (magic number) "r1cs"
```
0x72 0x31 0x63 0x73
```
#### Version
Size: 4 bytes
Format: Little-Endian
For this standard it's fixed to
```
0x01 0x00 0x00 0x00
```
#### Number of Sections
Size: 4 bytes
Format: Little-Endian
Number of sections contained in the file
#### SectionType
Size: 4 bytes
Format: Little-Endian
Type of the section.
Currently there are 3 types of sections defined:
* 0x00000001 : Header Section
* 0x00000002 : Constraint Section
* 0x00000003 : Wire2LabelId Map Section
If the file contain other types, the format is valid, but they MUST be ignored.
Any order of the section must be accepted.
Example:
```
0x01 0x00 0x00 0x00
```
#### SectionSize
Size: `ws` bytes
Format: Little-Endian
Size in bytes of the section
### Header Section
Section Type: 0x01
````
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 4 │ FieldDefSize ┃ FieldDef ┃ field Id
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ 4 │ 00 00 00 00 ┃ bigInt Format
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ 4 │ is ┃ Id size ( Normally 4 (32bits))
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ is │ 01 00 00 00 ┃ nWires
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ is │ 01 00 00 00 ┃ nPubOut
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ is │ 01 00 00 00 ┃ nPubIn
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ is │ 01 00 00 00 ┃ nPrvIn
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ is │ 01 00 00 00 ┃ nLabels
┗━━━━┻━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓
┃ is │ 01 00 00 00 ┃ mConstraints
┗━━━━┻━━━━━━━━━━━━━━━━━┛
````
#### fieldDefSize
Size: 4 bytes
Format: Little-Endian
Size of the field Definition
Example:
```
0x00 0x0 0x00 0x00
```
#### fieldDef
Field dfinition the first 4 bytes are the type in LE. 0x0000001 Ar prime fields.
For the prime fields, the next bytes are the prime in variable length LE base 256 format.
NOTE: This number is independent of the bigInt Format defined next
#### bigInt Format
Size: 4 bytes
Format: Little-Endian
0 Means that the Big Int are variable size LE.
That is the First byte indicates the size and the remaining bytes are the number in little enfian (LSB first) base 256.
Numbers from 1 to 16383 are fixed size Litle endian format base 256.
Example:
```
0x00 0x00 0x00 0x00
```
#### Id Size (is)
Size: 4 bytes
Format: Little-Endian
Size of the identifiers for wires, labels and constraints. In small circuits this is going to be 4 (32 bits)
but can be increaset to 8 for bigger circiuits.
The only possible numbers are 4 or 8
#### Number of wires
Size: `is` bytes
Format: Little-Endian
Total Number of wires including ONE signal (Index 0).
#### Number of public outputs
Size: `is` bytes
Format: Little-Endian
Total Number of wires public output wires. They should be starting at idx 1
#### Number of public inputs
Size: `is` bytes
Format: Little-Endian
Total Number of wires public input wires. They should be starting just after the public output
#### Number of private inputs
Size: `is` bytes
Format: Little-Endian
Total Number of wires private input wires. They should be starting just after the public inputs
#### Number of constraints (m)
Size: `ìs` bytes
Format: Little-Endian
Total Number of constraints
### Constraints section
Section Type: 0x02
````
┏━━━━┳━━━━━━━━━━━━━━━━━┓ ╲
┃ is │ nA ┃ ╲
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ╲
┃ is │ wireId_1 ┃ V │ a_{0,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │
┃ is │ wireId_2 ┃ V │ a_{0,wireId_2} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
... ... │
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nA ┃ V │ a_{0,wireId_nA} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
┏━━━━┳━━━━━━━━━━━━━━━━━┓ │
┃ is │ nB ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_1 ┃ V │ b_{0,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ ╲
┃ is │ wireId_2 ┃ V │ b_{0,wireId_2} ┃ ╲
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ Constraint_0
... ...
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nB ┃ V │ b_{0,wireId_nB} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
┏━━━━┳━━━━━━━━━━━━━━━━━┓ │
┃ is │ nC ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_1 ┃ V │ c_{0,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │
┃ is │ wireId_2 ┃ V │ c_{0,wireId_2} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
... ... │
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nC ┃ V │ c_{0,wireId_nC} ┃
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━┳━━━━━━━━━━━━━━━━━┓ ╲
┃ is │ nA ┃ ╲
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ╲
┃ is │ wireId_1 ┃ V │ a_{1,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │
┃ is │ wireId_2 ┃ V │ a_{1,wireId_2} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
... ... │
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nA ┃ V │ a_{1,wireId_nA} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
┏━━━━┳━━━━━━━━━━━━━━━━━┓ │
┃ is │ nB ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_1 ┃ V │ b_{1,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ ╲
┃ is │ wireId_2 ┃ V │ b_{1,wireId_2} ┃ ╲
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ Constraint_1
... ...
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nB ┃ V │ b_{1,wireId_nB} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
┏━━━━┳━━━━━━━━━━━━━━━━━┓ │
┃ is │ nC ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_1 ┃ V │ c_{1,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │
┃ is │ wireId_2 ┃ V │ c_{1,wireId_2} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
... ... │
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nC ┃ V │ c_{1,wireId_nC} ┃
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
...
...
...
┏━━━━┳━━━━━━━━━━━━━━━━━┓ ╲
┃ is │ nA ┃ ╲
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ ╲
┃ is │ wireId_1 ┃ V │ a_{m-1,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │
┃ is │ wireId_2 ┃ V │ a_{m-1,wireId_2} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
... ... │
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nA ┃ V │ a_{m-1,wireId_nA} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
┏━━━━┳━━━━━━━━━━━━━━━━━┓ │
┃ is │ nB ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_1 ┃ V │ b_{m-1,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ ╲
┃ is │ wireId_2 ┃ V │ b_{m-1,wireId_2} ┃ ╲
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ Constraint_{m-1}
... ...
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nB ┃ V │ b_{m-1,wireId_nB} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
┏━━━━┳━━━━━━━━━━━━━━━━━┓ │
┃ is │ nC ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_1 ┃ V │ c_{m-1,wireId_1} ┃ │
┣━━━━╋━━━━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━┫ │
┃ is │ wireId_2 ┃ V │ c_{m-1,wireId_2} ┃ │
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛ │
... ... │
┏━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ │
┃ is │ wireId_nC ┃ V │ c_{m-1,wireId_nC} ┃
┗━━━━┻━━━━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━┛
````
#### Constraints
Each constraint contains 3 linear combinations A, B, C.
The constraint is such that:
```
A*B-C = 0
```
#### Linear combination
Each linear combination is of the form:
$$
a_{j,0}w_0 + a_{j,1}w_1 + ... + a_{j,n}w_{n}
$$
#### Number of nonZero Factors
Size: `ìs` bytes
Format: Little-Endian
Total number of non Zero factors in the linear compination.
The factors MUST be sorted in ascending order.
#### Factor
For each factor we have the index of the factor and the value of the factor.
#### WireId of the factor
Size: `is` bytes
Format: Little-Endian
WireId of the nonZero Factor
#### Value of the factor
The first byte indicate the length N in bytes of the number in the upcoming bytes.
The next N bytes represent the value in Little Endian format.
For example, to represent the linear combination:
$$
5w_4 +8w_5 + 260w_{886}
$$
The linear combination would be represented as:
````
┏━━━━━━━━━━━━━━━━━┓
┃ 03 00 00 00 ┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┓
┃ 04 00 00 00 ┃ 01 05 ┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫
┃ 05 00 00 00 ┃ 01 08 ┃
┣━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫
┃ 76 03 00 00 ┃ 02 04 01 ┃
┗━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┛
````
### WireId2LabelId Map Section
Section Type: 0x03
````
┏━━┳━━━━━━━━━━━━━━━━━━━┳━━┳━━━━━━━━━━━━━━━━━━━┓ ┏━━┳━━━━━━━━━━━━━━━━━━━┓
┃is│ labelId of Wire_0 ┃is│ labelId of Wire_1 ┃ ... ┃is│ labelId of Wire_n ┃
┗━━┻━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━━━━━━━━━━━━━━━┛ ┗━━┻━━━━━━━━━━━━━━━━━━━┛
````
## Rationale
Variable size for field elements allows to shrink the size of the file and allows to work with any field.
Version allows to update the format.
Have a very good comprasion ratio for sparse r1cs as it's the normal case.
The motivation of having a map between l and w is that this allows optimizers to calculate equivalent r1cs systems but keeping the original values geneated by the circuit.
## Backward Compatibility
N.A.
## Test Cases
### Example
Given this r1cs in a 256 bit Field:
$$
\left\{ \begin{array}{rclclcl}
(3w_5 + 8w_6) &\cdot& (2w_0 + 20w_2 + 12w_3) &-& (5w_0 + 7w_2) &=& 0 \\
(4w_1 + 8w_4 + 3w_5) &\cdot& (6w_6 + 44w_3) && &=& 0 \\
(4w_6) &\cdot& (6w_0 + 5w_3 + 11s_2) &-& (600w_6) &=& 0
\end{array} \right.
$$
And a Wire to label map.
$$
w_0 := l_0 \\
w_1 := l_3 \\
w_2 := l_{10} \\
w_3 := l_{11} \\
w_4 := l_{12} \\
w_5 := l_{15} \\
w_6 := l_{324} \\
$$
The format will be:
````
┏━━━━━━━━━━━━━━┓
┃ 72 31 63 77 ┃ Magic
┣━━━━━━━━━━━━━━┫
┃ 01 00 00 00 ┃ Version
┣━━━━━━━━━━━━━━┫
┃ 03 00 00 00 ┃ nSections
┗━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ 01 00 00 00 ┃ 49 00 00 00 ┃ SectionType: Header
┗━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ 25 00 00 00 ┃ 10 00 00 00 ┃ FieldDefSize FieldDef
┣━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 20 010000f0 93f5e143 9170b979 48e83328 5d588181 b64550b8 29a031e1 724e6430┃
┣━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┃ 00 00 00 00 ┃ Big Int format
┣━━━━━━━━━━━━━━┫
┃ 04 00 00 00 ┃ Id Size
┣━━━━━━━━━━━━━━┫
┃ 07 00 00 00 ┃ # of wires
┣━━━━━━━━━━━━━━┫
┃ 01 00 00 00 ┃ # Public Outs
┣━━━━━━━━━━━━━━┫
┃ 02 00 00 00 ┃ # Public Ins
┣━━━━━━━━━━━━━━┫
┃ 03 00 00 00 ┃ # Private Ins
┣━━━━━━━━━━━━━━┫
┃ e8 03 00 00 ┃ # Labels
┣━━━━━━━━━━━━━━┫
┃ 03 00 00 00 ┃ # Constraints
┗━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ 02 00 00 00 ┃ 8b 00 00 00 ┃ SectionType: Constraints
┗━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓ Constraint 0: (3w_5 + 8w_6) * (2w_0 + 20w_2 + 12w_3) - (5w_0 + 7w_2) = 0
┃ 02 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┓
┃ 05 00 00 00 ┃ 01 03 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┫
┃ 06 00 00 00 ┃ 01 08 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 03 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┓
┃ 00 00 00 00 ┃ 01 02 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┫
┃ 02 00 00 00 ┃ 01 14 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┫
┃ 03 00 00 00 ┃ 01 0C ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 02 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┓
┃ 00 00 00 00 ┃ 01 05 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━┫
┃ 02 00 00 00 ┃ 01 07 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━┛
┏━━━━━━━━━━━━━━┓ Constraint 1: (4w_1 + 8w_4 + 3w_5) * (6w_6 + 44w_3) = 0
┃ 03 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┓
┃ 01 00 00 00 ┃ 01 04 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┫
┃ 04 00 00 00 ┃ 01 08 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┫
┃ 05 00 00 00 ┃ 01 03 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 02 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┓
┃ 03 00 00 00 ┃ 01 2C ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┫
┃ 06 00 00 00 ┃ 01 06 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 00 00 00 00 ┃
┗━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓ Constraint 2: (4w_6) * (6w_0 + 5w_3 + 11w_2) - (600w_6) = 0
┃ 01 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┓
┃ 06 00 00 00 ┃ 01 04 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 03 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┓
┃ 00 00 00 00 ┃ 01 06 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┫
┃ 02 00 00 00 ┃ 01 0B ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━┫
┃ 03 00 00 00 ┃ 01 05 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 01 00 00 00 ┃
┣━━━━━━━━━━━━━━╋━━━━━━━━━━━━━┓
┃ 06 00 00 00 ┃ 02 58 02 ┃
┗━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ 03 00 00 00 ┃ 1c 00 00 00 ┃ Wire to Label Map
┗━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━┓
┃ 00 00 00 00 ┃
┣━━━━━━━━━━━━━━┫
┃ 03 00 00 00 ┃
┣━━━━━━━━━━━━━━┫
┃ 0a 00 00 00 ┃
┣━━━━━━━━━━━━━━┫
┃ 0b 00 00 00 ┃
┣━━━━━━━━━━━━━━┫
┃ 0c 00 00 00 ┃
┣━━━━━━━━━━━━━━┫
┃ 0f 00 00 00 ┃
┣━━━━━━━━━━━━━━┫
┃ 44 01 00 00 ┃
┗━━━━━━━━━━━━━━┛
````
And the binary representation in Hex:
````
72 31 63 77
01 00 00 00
03 00 00 00
01 00 00 00 49 00 00 00
25 00 00 00 10 00 00 00
20 010000f0 93f5e143 9170b979 48e83328 5d588181 b64550b8 29a031e1 724e6430
00 00 00 00
04 00 00 00
07 00 00 00
01 00 00 00
02 00 00 00
03 00 00 00
e8 03 00 00
03 00 00 00
02 00 00 00 8b 00 00 00
02 00 00 00
05 00 00 00 01 03
06 00 00 00 01 08
03 00 00 00
00 00 00 00 01 02
02 00 00 00 01 14
03 00 00 00 01 0C
02 00 00 00
00 00 00 00 01 05
02 00 00 00 01 07
03 00 00 00
01 00 00 00 01 04
04 00 00 00 01 08
05 00 00 00 01 03
02 00 00 00
03 00 00 00 01 2C
06 00 00 00 01 06
00 00 00 00
01 00 00 00
06 00 00 00 01 04
03 00 00 00
00 00 00 00 01 06
02 00 00 00 01 0B
03 00 00 00 01 05
01 00 00 00
06 00 00 00 02 58 02
03 00 00 00 1c 00 00 00
00 00 00 00
03 00 00 00
0a 00 00 00
0b 00 00 00
0c 00 00 00
0f 00 00 00
44 01 00 00
````
## Implementation
circom will output this format.
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

BIN
doc/r1cs_bin_format.monopic Normal file

Binary file not shown.

BIN
doc/r1cs_example.monopic Normal file

Binary file not shown.

View File

@@ -1 +1,3 @@
module.exports = require("./src/compiler.js");
module.exports.compiler = require("./src/compiler.js");
module.exports.c_tester = require("./src/c_tester.js");
module.exports.tester = require("./src/c_tester.js");

90
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "circom",
"version": "0.0.29",
"version": "0.0.35",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -111,8 +111,7 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"big-integer": {
"version": "1.6.43",
@@ -132,7 +131,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -249,8 +247,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"cross-spawn": {
"version": "6.0.5",
@@ -308,6 +305,11 @@
"integrity": "sha1-zR9rpHfFY4xAyX7ZtXLbW6tdgzE=",
"dev": true
},
"ejs": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.0.1.tgz",
"integrity": "sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw=="
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@@ -432,10 +434,13 @@
}
},
"eslint-utils": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz",
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==",
"dev": true
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
"integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.0.0"
}
},
"eslint-visitor-keys": {
"version": "1.0.0",
@@ -533,6 +538,21 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fflib": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/fflib/-/fflib-0.0.2.tgz",
"integrity": "sha512-TvQ3nQjJwdyrFBZAz+GTWz1mv9hHwRiQmZX3T2G0P+cBAlyw42lm4W62i+Ofj1ZOW1XNrEEhFhxHPnutTsJBwA==",
"requires": {
"big-integer": "^1.6.48"
},
"dependencies": {
"big-integer": {
"version": "1.6.48",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
}
}
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -582,11 +602,15 @@
"integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==",
"dev": true
},
"fnv-plus": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/fnv-plus/-/fnv-plus-1.3.1.tgz",
"integrity": "sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw=="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"functional-red-black-tree": {
"version": "1.0.1",
@@ -617,7 +641,6 @@
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -674,7 +697,6 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -683,8 +705,7 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"inquirer": {
"version": "6.2.2",
@@ -868,9 +889,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"map-age-cleaner": {
@@ -908,7 +929,6 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -1104,8 +1124,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-is-inside": {
"version": "1.0.2",
@@ -1193,11 +1212,18 @@
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"rimraf-promise": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/rimraf-promise/-/rimraf-promise-2.0.0.tgz",
"integrity": "sha1-PdvkN4wa3slmvDZt37yYUUPHaVI=",
"requires": {
"rimraf": "^2.4.3"
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
@@ -1402,6 +1428,24 @@
"os-tmpdir": "~1.0.2"
}
},
"tmp-promise": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-2.0.2.tgz",
"integrity": "sha512-zl71nFWjPKW2KXs+73gEk8RmqvtAeXPxhWDkTUoa3MSMkjq3I+9OeknjF178MQoMYsdqL730hfzvNfEkePxq9Q==",
"requires": {
"tmp": "0.1.0"
},
"dependencies": {
"tmp": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
"integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
"requires": {
"rimraf": "^2.6.3"
}
}
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "circom",
"version": "0.0.29",
"version": "0.0.35",
"description": "Language to generate logic circuits",
"main": "index.js",
"directories": {
@@ -30,7 +30,12 @@
},
"dependencies": {
"big-integer": "^1.6.32",
"ejs": "^3.0.1",
"fflib": "0.0.2",
"fnv-plus": "^1.3.1",
"optimist": "^0.6.1",
"rimraf-promise": "^2.0.0",
"tmp-promise": "^2.0.2",
"yargs": "^12.0.2"
},
"devDependencies": {

View File

@@ -40,6 +40,7 @@ if { return 'if'; }
else { return 'else'; }
for { return 'for'; }
while { return 'while'; }
compute { return 'compute'; }
do { return 'do'; }
return { return 'return'; }
include { return 'include'; }
@@ -84,6 +85,7 @@ include { return 'include'; }
\& { return '&'; }
\| { return '|'; }
\! { return '!'; }
\~ { return '~'; }
\< { return '<'; }
\> { return '>'; }
\! { return '!'; }
@@ -198,6 +200,10 @@ statment
{
$$ = $1;
}
| computeStatment
{
$$ = $1;
}
| returnStatment
{
$$ = $1;
@@ -302,6 +308,14 @@ doWhileStatment
}
;
computeStatment
: 'compute' statment
{
$$ = { type: "COMPUTE", body: $2 };
setLines($$, @1, @2);
}
;
returnStatment
: 'return' expression ';'
{

File diff suppressed because one or more lines are too long

59
src/bigarray.js Normal file
View File

@@ -0,0 +1,59 @@
const SUBARRAY_SIZE = 0x10000;
const BigArrayHandler = {
get: function(obj, prop) {
if (!isNaN(prop)) {
return obj.getElement(prop);
} else return obj[prop];
},
set: function(obj, prop, value) {
if (!isNaN(prop)) {
return obj.setElement(prop, value);
} else {
obj[prop] = value;
return true;
}
}
};
class _BigArray {
constructor (initSize) {
this.length = initSize || 0;
this.arr = [];
for (let i=0; i<initSize; i+=SUBARRAY_SIZE) {
this.arr[i/SUBARRAY_SIZE] = new Array(Math.min(SUBARRAY_SIZE, initSize - i));
}
return this;
}
push (element) {
this.setElement (this.length, element);
}
getElement(idx) {
idx = parseInt(idx);
const idx1 = Math.floor(idx / SUBARRAY_SIZE);
const idx2 = idx % SUBARRAY_SIZE;
return this.arr[idx1] ? this.arr[idx1][idx2] : undefined;
}
setElement(idx, value) {
idx = parseInt(idx);
const idx1 = Math.floor(idx / SUBARRAY_SIZE);
if (!this.arr[idx1]) {
this.arr[idx1] = [];
}
const idx2 = idx % SUBARRAY_SIZE;
this.arr[idx1][idx2] = value;
if (idx >= this.length) this.length = idx+1;
return true;
}
}
class BigArray {
constructor( initSize ) {
const obj = new _BigArray(initSize);
const extObj = new Proxy(obj, BigArrayHandler);
return extObj;
}
}
module.exports = BigArray;

468
src/build.js Normal file
View File

@@ -0,0 +1,468 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
const assert = require("assert");
const bigInt = require("big-integer");
const utils = require("./utils");
const gen = require("./gencode").gen;
const createRefs = require("./gencode").createRefs;
module.exports = build;
function build(ctx) {
ctx.definedFunctions = {};
ctx.functionCodes = [];
ctx.buildFunction = buildFunction;
ctx.conditionalCodeHeader = "";
ctx.codes_sizes = [];
ctx.definedSizes = {};
ctx.addSizes = addSizes;
ctx.constantsMap = {};
ctx.addConstant = addConstant;
ctx.addConstant(bigInt.zero);
ctx.addConstant(bigInt.one);
buildHeader(ctx);
buildEntryTables(ctx);
ctx.globalNames = ctx.uniqueNames;
buildCode(ctx);
buildComponentsArray(ctx);
buildMapIsInput(ctx);
buildWit2Sig(ctx);
}
function buildEntryTables(ctx) {
const codes_hashMaps = [];
const codes_componentEntries = [];
const definedHashMaps = {};
for (let i=0; i<ctx.components.length; i++) {
const {htName, htMap} = addHashTable(i);
let code = "";
const componentEntriesTableName = ctx.getUniqueName("_entryTable" + ctx.components[i].template);
const componentEntriesTable = [];
for (let j=0; j<htMap.length; j++) {
const entry = ctx.components[i].names.o[htMap[j]];
const sizeName = ctx.addSizes(entry.sizes);
componentEntriesTable.push({
offset: entry.offset,
sizeName: sizeName,
type: entry.type
});
}
ctx.builder.addComponentEntriesTable(componentEntriesTableName, componentEntriesTable);
code += `Circom_ComponentEntry ${componentEntriesTableName}[${htMap.length}] = {\n`;
for (let j=0; j<htMap.length; j++) {
const entry = ctx.components[i].names.o[htMap[j]];
code += j>0 ? " ," : " ";
const sizeName = ctx.addSizes(entry.sizes);
const ty = entry.type == "S" ? "_typeSignal" : "_typeComponent";
code += `{${entry.offset},${sizeName}, ${ty}}\n`;
}
code += "};\n";
codes_componentEntries.push(code);
ctx.components[i].htName = htName;
ctx.components[i].etName = componentEntriesTableName;
}
return [
"// HashMaps\n" ,
codes_hashMaps , "\n" ,
"\n" ,
"// Component Entries\n" ,
codes_componentEntries , "\n" ,
"\n"
];
function addHashTable(cIdx) {
const keys = Object.keys(ctx.components[cIdx].names.o);
assert(keys.length<128);
keys.sort((a,b) => ((a>b) ? 1 : -1));
const h = utils.fnvHash(keys.join(","));
if (definedHashMaps[h]) return definedHashMaps[h];
definedHashMaps[h] = {};
definedHashMaps[h].htName = ctx.getUniqueName("_ht"+ctx.components[cIdx].template);
definedHashMaps[h].htMap = [];
const t = [];
for (let i=0; i<keys.length; i++) {
definedHashMaps[h].htMap[i] = keys[i];
const h2 = utils.fnvHash(keys[i]);
let pos = parseInt(h2.slice(-2), 16);
while (t[pos]) pos = (pos + 1) % 256;
t[pos] = [h2, i, keys[i]];
}
ctx.builder.addHashMap(definedHashMaps[h].htName, t);
return definedHashMaps[h];
}
}
function buildCode(ctx) {
const fDefined = {};
const fnComponents = [];
for (let i=0; i<ctx.components.length; i++) {
const {h, instanceDef} = hashComponentCall(ctx, i);
const fName = ctx.components[i].template+"_"+h;
if (!fDefined[fName]) {
ctx.scopes = [{}];
ctx.conditionalCode = false;
ctx.fnBuilder = ctx.builder.newComponentFunctionBuilder(fName, instanceDef);
ctx.codeBuilder = ctx.fnBuilder.newCodeBuilder();
ctx.uniqueNames = Object.assign({},ctx.globalNames);
ctx.refs = [];
ctx.fileName = ctx.templates[ctx.components[i].template].fileName;
ctx.filePath = ctx.templates[ctx.components[i].template].filePath;
ctx.getSignalSizesCache = {};
ctx.getSignalOffsetCache = {};
for (let p in ctx.components[i].params) {
if (ctx.scopes[0][p]) return ctx.throwError(`Repeated parameter at ${ctx.components[i].template}: ${p}`);
const refId = ctx.refs.length;
ctx.refs.push({
type: "BIGINT",
used: false,
value: utils.flatArray(ctx.components[i].params[p]),
sizes: utils.accSizes(utils.extractSizes(ctx.components[i].params[p])),
label: ctx.getUniqueName(p)
});
ctx.scopes[0][p] = refId;
}
createRefs(ctx, ctx.templates[ctx.components[i].template].block);
if (ctx.error) return;
gen(ctx, ctx.templates[ctx.components[i].template].block);
if (ctx.error) return;
ctx.fnBuilder.setBody(ctx.codeBuilder);
ctx.builder.addFunction(ctx.fnBuilder);
fDefined[fName] = true;
}
ctx.components[i].fnName = fName;
}
return fnComponents;
}
function buildComponentsArray(ctx) {
for (let i=0; i< ctx.components.length; i++) {
let newThread;
if (ctx.newThreadTemplates) {
if (ctx.newThreadTemplates.test(ctx.components[i].template)) {
newThread = true;
} else {
newThread = false;
}
} else {
newThread = false;
}
ctx.builder.addComponent({
hashMapName: ctx.components[i].htName,
entryTableName: ctx.components[i].etName,
functionName: ctx.components[i].fnName,
nInSignals: ctx.components[i].nInSignals,
newThread: newThread
});
}
}
function buildHeader(ctx) {
ctx.builder.setHeader({
NSignals: ctx.signals.length,
NComponents: ctx.components.length,
NInputs: ctx.components[ ctx.getComponentIdx("main") ].nInSignals,
NOutputs: ctx.totals[ ctx.stOUTPUT ],
NVars: ctx.totals[ctx.stONE] + ctx.totals[ctx.stOUTPUT] + ctx.totals[ctx.stPUBINPUT] + ctx.totals[ctx.stPRVINPUT] + ctx.totals[ctx.stINTERNAL],
P: ctx.field.p
});
}
function buildMapIsInput(ctx) {
let i;
let map = [];
let acc = 0;
for (i=0; i<ctx.signals.length; i++) {
if (ctx.signals[i].o & ctx.IN) {
acc = acc | (1 << (i%32) );
}
if ((i+1)%32==0) {
map.push(acc);
acc = 0;
}
}
if ((i%32) != 0) {
map.push(acc);
}
ctx.builder.setMapIsInput(map);
}
function buildWit2Sig(ctx) {
const NVars =
ctx.totals[ctx.stONE] +
ctx.totals[ctx.stOUTPUT] +
ctx.totals[ctx.stPUBINPUT] +
ctx.totals[ctx.stPRVINPUT] +
ctx.totals[ctx.stINTERNAL];
const arr = Array(NVars);
for (let i=0; i<ctx.signals.length; i++) {
const outIdx = ctx.signals[i].id;
if (ctx.signals[i].e>=0) continue; // If has an alias, continue..
assert(typeof outIdx != "undefined", `Signal ${i} does not have index`);
if (outIdx>=NVars) continue; // Is a constant or a discarded variable
if (typeof arr[ctx.signals[i].id] == "undefined") {
arr[outIdx] = i;
}
}
ctx.builder.setWit2Sig(arr);
}
function addSizes(_sizes) {
const sizes = _sizes || [];
let name = "sizes";
for (let i=0; i<sizes.length;i++) {
name+="_"+sizes[i];
}
if (name=="sizes") name="sizes_0";
if (this.definedSizes[name]) return this.definedSizes[name];
const labelName = this.getUniqueName(name);
this.definedSizes[name] = labelName;
const accSizes = utils.accSizes(sizes);
this.builder.addSizes(labelName, accSizes);
let code = `Circom_Size ${labelName}[${accSizes.length}] = {`;
for (let i=0; i<accSizes.length; i++) {
if (i>0) code += ",";
code += accSizes[i];
}
code += "};\n";
this.codes_sizes.push(code);
return labelName;
}
function addConstant(c) {
c = bigInt(c);
const s = c.toString();
if (typeof (this.constantsMap[s]) !== "undefined") return this.constantsMap[s];
const cIdx = this.builder.addConstant(c);
this.constantsMap[s] = cIdx;
return cIdx;
}
function buildFunction(name, paramValues) {
const ctx = this;
const {h, instanceDef} = hashFunctionCall(ctx, name, paramValues);
if (ctx.definedFunctions[h]) return ctx.definedFunctions[h];
const res = {
fnName: `${name}_${h}`
};
const oldRefs = ctx.refs;
const oldConditionalCode = ctx.conditionalCode;
const oldCodeBuilder = ctx.codeBuilder;
const oldFnBuilder = ctx.fnBuilder;
const oldUniqueNames = ctx.uniqueNames;
const oldFileName = ctx.fileName;
const oldFilePath = ctx.oldFilePath;
const oldReturnSizes = ctx.returnSizes;
const oldReturnValue = ctx.returnValue;
ctx.scopes = [{}];
ctx.refs = [];
ctx.conditionalCode = false;
ctx.fnBuilder = ctx.builder.newFunctionBuilder(`${name}_${h}`, instanceDef);
ctx.codeBuilder = ctx.fnBuilder.newCodeBuilder();
ctx.uniqueNames = Object.assign({},ctx.globalNames);
ctx.returnValue = null;
ctx.returnSizes = null;
ctx.fileName = ctx.functions[name].fileName;
ctx.filePath = ctx.functions[name].filePath;
let paramLabels = [];
for (let i=0; i<ctx.functions[name].params.length; i++) {
if (paramValues[i].used) {
paramLabels.push(ctx.functions[name].params[i]);
const idRef = ctx.refs.length;
ctx.refs.push({
type: "BIGINT",
used: true,
sizes: paramValues[i].sizes,
label: ctx.functions[name].params[i],
});
ctx.scopes[0][ctx.functions[name].params[i]] = idRef;
} else {
const idRef = ctx.refs.length;
ctx.refs.push({
type: "BIGINT",
used: false,
sizes: paramValues[i].sizes,
label: ctx.functions[name].params[i],
value: paramValues[i].value
});
ctx.scopes[0][ctx.functions[name].params[i]] = idRef;
}
}
ctx.fnBuilder.setParams(paramLabels);
createRefs(ctx, ctx.functions[name].block);
if (ctx.error) return;
gen(ctx, ctx.functions[name].block);
if (ctx.error) return;
if (ctx.returnValue == null) {
if (ctx.returnSizes == null) assert(false, `Funciont ${name} does not return any value`);
ctx.fnBuilder.setBody(ctx.codeBuilder);
ctx.builder.addFunction(ctx.fnBuilder);
res.type = "VARVAL_CONSTSIZE";
res.returnSizes = ctx.returnSizes;
} else {
res.type = "CONSTVAL";
res.returnValue = ctx.returnValue;
res.returnSizes = ctx.returnSizes;
}
ctx.refs = oldRefs;
ctx.conditionalCode = oldConditionalCode;
ctx.codeBuilder = oldCodeBuilder;
ctx.fnBuilder = oldFnBuilder;
ctx.uniqueNames = oldUniqueNames;
ctx.fileName = oldFileName;
ctx.filePath = oldFilePath;
ctx.returnSizes = oldReturnSizes;
ctx.returnValue = oldReturnValue;
ctx.definedFunctions[h] = res;
return res;
}
function hashComponentCall(ctx, cIdx) {
// TODO: At the moment generate a diferent function for each instance of the component
const constParams = [];
for (let p in ctx.components[cIdx].params) {
constParams.push(p + "=" + value2str(ctx.components[cIdx].params[p]));
}
for (let n in ctx.components[cIdx].names.o) {
const entry = ctx.components[cIdx].names.o[n];
if ((entry.type == "S")&&(ctx.signals[entry.offset].o & ctx.IN)) {
travelSizes(n, entry.offset, entry.sizes, (prefix, offset) => {
if (utils.isDefined(ctx.signals[offset].v)) {
constParams.push(prefix + "=" + bigInt(ctx.signals[offset].value));
}
});
}
}
let instanceDef = ctx.components[cIdx].template;
if (constParams.length>0) {
instanceDef += "\n";
constParams.sort();
instanceDef += constParams.join("\n");
}
const h = utils.fnvHash(instanceDef);
return {h, instanceDef};
function travelSizes(prefix, offset, sizes, fn) {
if (sizes.length == 0) {
fn(prefix, offset);
return 1;
} else {
let o = offset;
for (let i=0; i<sizes[0]; i++) {
o += travelSizes(prefix + "[" + i + "]", o, sizes.slice(1), fn);
}
return o-offset;
}
}
}
function hashFunctionCall(ctx, name, paramValues) {
// TODO
const constParams = [];
for (let i=0; i<ctx.functions[name].params.length; i++) {
if (!paramValues[i].used) {
constParams.push(ctx.functions[name].params[i] + utils.accSizes2Str(paramValues[i].sizes) + "=" + value2str(paramValues[i].value));
}
}
let instanceDef = name;
if (constParams.length>0) {
instanceDef += "\n";
constParams.sort();
instanceDef += constParams.join("\n");
}
const h = utils.fnvHash(instanceDef);
return {h, instanceDef};
}
function value2str(v) {
if (Array.isArray(v)) {
let S="[";
for (let i=0; i<v.length; i++) {
if (i>0) S+=",";
S+=value2str(v[i]);
}
S+="]";
return S;
} else {
return bigInt(v).toString();
}
}

616
src/builder_c.js Normal file
View File

@@ -0,0 +1,616 @@
const streamFromMultiArray = require("./stream_from_multiarray");
const bigInt = require("big-integer");
const utils = require("./utils");
const assert = require("assert");
function ref2src(c) {
if (c[0] == "R") {
return c[1];
} else if (c[0] == "V") {
return c[1].toString();
} else if (c[0] == "C") {
return `(ctx->circuit->constants + ${c[1]})`;
} else if (c[0] == "CC") {
return "__cIdx";
} else {
assert(false);
}
}
class CodeBuilderC {
constructor() {
this.ops = [];
}
addComment(comment) {
this.ops.push({op: "COMMENT", comment});
}
addBlock(block) {
this.ops.push({op: "BLOCK", block});
}
calcOffset(dLabel, offsets) {
this.ops.push({op: "CALCOFFSETS", dLabel, offsets});
}
assign(dLabel, src, sOffset) {
this.ops.push({op: "ASSIGN", dLabel, src, sOffset});
}
getSubComponentOffset(dLabel, component, hash, hashLabel) {
this.ops.push({op: "GETSUBCOMPONENTOFFSET", dLabel, component, hash, hashLabel});
}
getSubComponentSizes(dLabel, component, hash, hashLabel) {
this.ops.push({op: "GETSUBCOMPONENTSIZES", dLabel, component, hash, hashLabel});
}
getSignalOffset(dLabel, component, hash, hashLabel) {
this.ops.push({op: "GETSIGNALOFFSET", dLabel, component, hash, hashLabel});
}
getSignalSizes(dLabel, component, hash, hashLabel) {
this.ops.push({op: "GETSIGNALSIZES", dLabel, component, hash, hashLabel});
}
setSignal(component, signal, value) {
this.ops.push({op: "SETSIGNAL", component, signal, value});
}
getSignal(dLabel, component, signal) {
this.ops.push({op: "GETSIGNAL", dLabel, component, signal});
}
copyN(dLabel, offset, src, n) {
this.ops.push({op: "COPYN", dLabel, offset, src, n});
}
copyNRet(src, n) {
this.ops.push({op: "COPYNRET", src, n});
}
fieldOp(dLabel, fOp, params) {
this.ops.push({op: "FOP", dLabel, fOp, params});
}
ret() {
this.ops.push({op: "RET"});
}
addLoop(condLabel, body) {
this.ops.push({op: "LOOP", condLabel, body});
}
addIf(condLabel, thenCode, elseCode) {
this.ops.push({op: "IF", condLabel, thenCode, elseCode});
}
fnCall(fnName, retLabel, params) {
this.ops.push({op: "FNCALL", fnName, retLabel, params});
}
checkConstraint(a, b, strErr) {
this.ops.push({op: "CHECKCONSTRAINT", a, b, strErr});
}
concat(cb) {
this.ops.push(...cb.ops);
}
hasCode() {
for (let i=0; i<this.ops.length; i++) {
if (this.ops[i].op != "COMMENT") return true;
}
return false;
}
_buildOffset(offsets) {
let rN=0;
let S = "";
offsets.forEach((o) => {
if ((o[0][0] == "V") && (o[1][0]== "V")) {
rN += o[0][1]*o[1][1];
return;
}
let f="";
if (o[0][0] == "V") {
if (o[0][1]==0) return;
f += o[0][1];
} else if (o[0][0] == "RI") {
if (o[0][1]==0) return;
f += o[0][1];
} else if (o[0][0] == "R") {
f += `Fr_toInt(${o[0][1]})`;
} else {
assert(false);
}
if (o[1][0] == "V") {
if (o[1][1]==0) return;
if (o[1][1]>1) {
f += "*" + o[1][1];
}
} else if (o[1][0] == "RS") {
f += `*${o[1][1]}[${o[1][2]}]`;
} else {
assert(false);
}
if (S!="") S+= " + ";
S += f;
});
if (rN>0) {
S = `${rN} + ${S}`;
}
return S;
}
build(code) {
this.ops.forEach( (o) => {
if (o.op == "COMMENT") {
code.push(`/* ${o.comment} */`);
} else if (o.op == "BLOCK") {
const codeBlock=[];
o.block.build(codeBlock);
code.push(utils.ident(codeBlock));
} else if (o.op == "CALCOFFSETS") {
code.push(`${o.dLabel} = ${this._buildOffset(o.offsets)};`);
} else if (o.op == "ASSIGN") {
const oS = ref2src(o.sOffset);
if (oS != "0") {
code.push(`${o.dLabel} = ${ref2src(o.src)} + ${oS};`);
} else {
code.push(`${o.dLabel} = ${ref2src(o.src)};`);
}
} else if (o.op == "GETSUBCOMPONENTOFFSET") {
code.push(`${o.dLabel} = ctx->getSubComponentOffset(${ref2src(o.component)}, 0x${o.hash}LL /* ${o.hashLabel} */);`);
} else if (o.op == "GETSUBCOMPONENTSIZES") {
code.push(`${o.dLabel} = ctx->getSubComponentSizes(${ref2src(o.component)}, 0x${o.hash}LL /* ${o.hashLabel} */);`);
} else if (o.op == "GETSIGNALOFFSET") {
code.push(`${o.dLabel} = ctx->getSignalOffset(${ref2src(o.component)}, 0x${o.hash}LL /* ${o.hashLabel} */);`);
} else if (o.op == "GETSIGNALSIZES") {
code.push(`${o.dLabel} = ctx->getSignalSizes(${ref2src(o.component)}, 0x${o.hash}LL /* ${o.hashLabel} */);`);
} else if (o.op == "SETSIGNAL") {
code.push(`ctx->setSignal(__cIdx, ${ref2src(o.component)}, ${ref2src(o.signal)}, ${ref2src(o.value)});`);
} else if (o.op == "GETSIGNAL") {
code.push(`ctx->getSignal(__cIdx, ${ref2src(o.component)}, ${ref2src(o.signal)}, ${o.dLabel});`);
} else if (o.op == "COPYN") {
const oS = ref2src(o.offset);
const dLabel = (oS != "0") ? (o.dLabel + "+" + oS) : o.dLabel;
code.push(`Fr_copyn(${dLabel}, ${ref2src(o.src)}, ${o.n});`);
} else if (o.op == "COPYNRET") {
code.push(`Fr_copyn(__retValue, ${ref2src(o.src)}, ${o.n});`);
} else if (o.op == "RET") {
code.push("goto returnFunc;");
} else if (o.op == "FOP") {
let paramsS = "";
for (let i=0; i<o.params.length; i++) {
if (i>0) paramsS += ", ";
paramsS += ref2src(o.params[i]);
}
code.push(`Fr_${o.fOp}(${o.dLabel}, ${paramsS});`);
} else if (o.op == "LOOP") {
code.push(`while (Fr_isTrue(${o.condLabel})) {`);
const body = [];
o.body.build(body);
code.push(utils.ident(body));
code.push("}");
} else if (o.op == "IF") {
code.push(`if (Fr_isTrue(${o.condLabel})) {`);
const thenCode = [];
o.thenCode.build(thenCode);
code.push(utils.ident(thenCode));
if (o.elseCode) {
code.push("} else {");
const elseCode = [];
o.elseCode.build(elseCode);
code.push(utils.ident(elseCode));
}
code.push("}");
} else if (o.op == "FNCALL") {
code.push(`${o.fnName}(ctx, ${o.retLabel}, ${o.params.join(",")});`);
} else if (o.op == "CHECKCONSTRAINT") {
code.push(`ctx->checkConstraint(__cIdx, ${ref2src(o.a)}, ${ref2src(o.b)}, "${o.strErr}");`);
}
});
}
}
class FunctionBuilderC {
constructor(name, instanceDef, type) {
this.name = name;
this.instanceDef = instanceDef;
this.type = type; // "COMPONENT" or "FUNCTIOM"
this.definedFrElements = [];
this.definedIntElements = [];
this.definedSizeElements = [];
this.definedPFrElements = [];
this.initializedElements = [];
this.initializedSignalOffset = [];
this.initializedSignalSizes = [];
}
defineFrElements(dLabel, size) {
this.definedFrElements.push({dLabel, size});
}
defineIntElement(dLabel) {
this.definedIntElements.push({dLabel});
}
defineSizesElement(dLabel) {
this.definedSizeElements.push({dLabel});
}
definePFrElement(dLabel) {
this.definedPFrElements.push({dLabel});
}
initializeFrElement(dLabel, offset, idConstant) {
this.initializedElements.push({dLabel, offset, idConstant});
}
initializeSignalOffset(dLabel, component, hash, hashLabel) {
this.initializedSignalOffset.push({dLabel, component, hash, hashLabel});
}
initializeSignalSizes(dLabel, component, hash, hashLabel) {
this.initializedSignalSizes.push({dLabel, component, hash, hashLabel});
}
setParams(params) {
this.params = params;
}
_buildHeader(code) {
this.definedFrElements.forEach( (o) => {
code.push(`FrElement ${o.dLabel}[${o.size}];`);
});
this.definedIntElements.forEach( (o) => {
code.push(`int ${o.dLabel};`);
});
this.definedSizeElements.forEach( (o) => {
code.push(`Circom_Sizes ${o.dLabel};`);
});
this.definedPFrElements.forEach( (o) => {
code.push(`PFrElement ${o.dLabel};`);
});
this.initializedElements.forEach( (o) => {
code.push(`Fr_copy(&(${o.dLabel}[${o.offset}]), ctx->circuit->constants +${o.idConstant});`);
});
this.initializedSignalOffset.forEach( (o) => {
code.push(`${o.dLabel} = ctx->getSignalOffset(${ref2src(o.component)}, 0x${o.hash}LL /* ${o.hashLabel} */);`);
});
this.initializedSignalSizes.forEach( (o) => {
code.push(`${o.dLabel} = ctx->getSignalSizes(${ref2src(o.component)}, 0x${o.hash}LL /* ${o.hashLabel} */);`);
});
}
_buildFooter(code) {
}
newCodeBuilder() {
return new CodeBuilderC();
}
setBody(body) {
this.body = body;
}
build(code) {
code.push(
"/*",
this.instanceDef,
"*/"
);
if (this.type=="COMPONENT") {
code.push(`void ${this.name}(Circom_CalcWit *ctx, int __cIdx) {`);
} else if (this.type=="FUNCTION") {
let sParams = "";
for (let i=0;i<this.params.length;i++ ) sParams += `, PFrElement ${this.params[i]}`;
code.push(`void ${this.name}(Circom_CalcWit *ctx, PFrElement __retValue ${sParams}) {`);
} else {
assert(false);
}
const fnCode = [];
this._buildHeader(fnCode);
this.body.build(fnCode);
if (this.type=="COMPONENT") {
fnCode.push("ctx->finished(__cIdx);");
} else if (this.type=="FUNCTION") {
fnCode.push("returnFunc: ;");
} else {
assert(false);
}
this._buildFooter(fnCode);
code.push(utils.ident(fnCode));
code.push("}");
}
}
class BuilderC {
constructor() {
this.hashMaps={};
this.componentEntriesTables={};
this.sizes ={};
this.constants = [];
this.functions = [];
this.components = [];
}
setHeader(header) {
this.header=header;
}
// ht is an array of 256 element that can be undefined or [Hash, Idx, KeyName] elements.
addHashMap(name, hm) {
this.hashMaps[name] = hm;
}
addComponentEntriesTable(name, cet) {
this.componentEntriesTables[name] = cet;
}
addSizes(name, accSizes) {
this.sizes[name] = accSizes;
}
addConstant(c) {
this.constants.push(c);
return this.constants.length - 1;
}
addFunction(fnBuilder) {
this.functions.push(fnBuilder);
}
addComponent(component) {
this.components.push(component);
}
setMapIsInput(map) {
this.mapIsInput = map;
}
setWit2Sig(wit2sig) {
this.wit2sig = wit2sig;
}
newComponentFunctionBuilder(name, instanceDef) {
return new FunctionBuilderC(name, instanceDef, "COMPONENT");
}
newFunctionBuilder(name, instanceDef) {
return new FunctionBuilderC(name, instanceDef, "FUNCTION");
}
// Body functions
_buildHeader(code) {
code.push(
"#include \"circom.h\"",
"#include \"calcwit.h\"",
`#define NSignals ${this.header.NSignals}`,
`#define NComponents ${this.header.NComponents}`,
`#define NOutputs ${this.header.NOutputs}`,
`#define NInputs ${this.header.NInputs}`,
`#define NVars ${this.header.NVars}`,
`#define __P__ "${this.header.P.toString()}"`,
""
);
}
_buildHashMaps(code) {
code.push("// Hash Maps ");
for (let hmName in this.hashMaps ) {
const hm = this.hashMaps[hmName];
let c = `Circom_HashEntry ${hmName}[256] = {`;
for (let i=0; i<256; i++) {
c += i>0 ? "," : "";
if (hm[i]) {
c += `{0x${hm[i][0]}LL, ${hm[i][1]}} /* ${hm[i][2]} */`;
} else {
c += "{0,0}";
}
}
c += "};";
code.push(c);
}
}
_buildComponentEntriesTables(code) {
code.push("// Component Entry tables");
for (let cetName in this.componentEntriesTables) {
const cet = this.componentEntriesTables[cetName];
code.push(`Circom_ComponentEntry ${cetName}[${cet.length}] = {`);
for (let j=0; j<cet.length; j++) {
const ty = cet[j].type == "S" ? "_typeSignal" : "_typeComponent";
code.push(` ${j>0?",":" "}{${cet[j].offset},${cet[j].sizeName}, ${ty}}`);
}
code.push("};");
}
}
_buildSizes(code) {
code.push("// Sizes");
for (let sName in this.sizes) {
const accSizes = this.sizes[sName];
let c = `Circom_Size ${sName}[${accSizes.length}] = {`;
for (let i=0; i<accSizes.length; i++) {
if (i>0) c += ",";
c += accSizes[i];
}
c += "};";
code.push(c);
}
}
_buildConstants(code) {
const self = this;
const n64 = Math.floor((self.header.P.bitLength() - 1) / 64)+1;
const R = bigInt.one.shiftLeft(n64*64);
code.push("// Constants");
code.push(`FrElement _constants[${self.constants.length}] = {`);
for (let i=0; i<self.constants.length; i++) {
code.push((i>0 ? "," : " ") + "{" + number2Code(self.constants[i]) + "}");
}
code.push("};");
function number2Code(n) {
if (n.lt(bigInt("80000000", 16)) ) {
return addShortMontgomeryPositive(n);
}
if (n.geq(self.header.P.minus(bigInt("80000000", 16))) ) {
return addShortMontgomeryNegative(n);
}
return addLongMontgomery(n);
function addShortMontgomeryPositive(a) {
return `${a.toString()}, 0x40000000, { ${getLongString(toMontgomery(a))} }`;
}
function addShortMontgomeryNegative(a) {
const b = a.minus(self.header.P);
return `${b.toString()}, 0x40000000, { ${getLongString(toMontgomery(a))} }`;
}
function addLongMontgomery(a) {
return `0, 0xC0000000, { ${getLongString(toMontgomery(a))} }`;
}
function getLongString(a) {
let r = bigInt(a);
let S = "";
let i = 0;
while (!r.isZero()) {
if (S!= "") S = S+",";
S += "0x" + r.and(bigInt("FFFFFFFFFFFFFFFF", 16)).toString(16) + "LL";
i++;
r = r.shiftRight(64);
}
while (i<n64) {
if (S!= "") S = S+",";
S += "0LL";
i++;
}
return S;
}
function toMontgomery(a) {
return a.times(R).mod(self.header.P);
}
}
}
_buildFunctions(code) {
for (let i=0; i<this.functions.length; i++) {
const cfb = this.functions[i];
cfb.build(code);
}
}
_buildComponents(code) {
code.push("// Components");
code.push(`Circom_Component _components[${this.components.length}] = {`);
for (let i=0; i<this.components.length; i++) {
const c = this.components[i];
const sep = i>0 ? " ," : " ";
code.push(`${sep}{${c.hashMapName}, ${c.entryTableName}, ${c.functionName}, ${c.nInSignals}, ${c.newThread}}`);
}
code.push("};");
}
_buildMapIsInput(code) {
code.push("// mapIsInput");
code.push(`u32 _mapIsInput[${this.mapIsInput.length}] = {`);
let line = "";
for (let i=0; i<this.mapIsInput.length; i++) {
line += i>0 ? ", " : " ";
line += toHex(this.mapIsInput[i]);
if (((i+1) % 64)==0) {
code.push(" "+line);
line = "";
}
}
if (line != "") code.push(" "+line);
code.push("};");
function toHex(number) {
if (number < 0) number = 0xFFFFFFFF + number + 1;
let S=number.toString(16).toUpperCase();
while (S.length<8) S = "0" + S;
return "0x"+S;
}
}
_buildWit2Sig(code) {
code.push("// Witness to Signal Table");
code.push(`int _wit2sig[${this.wit2sig.length}] = {`);
let line = "";
for (let i=0; i<this.wit2sig.length; i++) {
line += i>0 ? "," : " ";
line += this.wit2sig[i];
if (((i+1) % 64) == 0) {
code.push(" "+line);
line = "";
}
}
if (line != "") code.push(" "+line);
code.push("};");
}
_buildCircuitVar(code) {
code.push(
"// Circuit Variable",
"Circom_Circuit _circuit = {" ,
" NSignals,",
" NComponents,",
" NInputs,",
" NOutputs,",
" NVars,",
" _wit2sig,",
" _components,",
" _mapIsInput,",
" _constants,",
" __P__",
"};"
);
}
build() {
const code=[];
this._buildHeader(code);
this._buildSizes(code);
this._buildConstants(code);
this._buildHashMaps(code);
this._buildComponentEntriesTables(code);
this._buildFunctions(code);
this._buildComponents(code);
this._buildMapIsInput(code);
this._buildWit2Sig(code);
this._buildCircuitVar(code);
return streamFromMultiArray(code);
}
}
module.exports = BuilderC;

28
src/builder_wasm.js Normal file
View File

@@ -0,0 +1,28 @@
class BuilderWasm {
constructor() {
}
setHeader(header) {
this.header=header;
}
// ht is an array of 256 element that can be undefined or [Hash, Idx, KeyName] elements.
addHashMap(name, ht) {
this.hashTables[name] = ht;
}
addComponentEntriesTable(name, cet) {
this.componentEntriesTables[name] = cet;
}
addSizes(name, accSizes) {
this.sizes[name] = accSizes;
}
build() {
}
}
module.exports = BuilderWasm;

52
src/buildwasm.js Normal file
View File

@@ -0,0 +1,52 @@
const ModuleBuilder = require("wasmbuilder").ModuleBuilder;
const gen = require("./gencode");
module.exports = function buildWasm(ctx) {
const fDefined = {};
ctx.module = new ModuleBuilder();
for (let f in ctx.functions) {
ctx.f = ctx.module.addFunction(f);
ctx.c = ctx.f.getCodeBuilder();
ctx.scope = {};
for (let p in ctx.functions[f].params) {
const param = ctx.functions[f].params[p];
ctx.f.addParam(param.name, "i32");
ctx.scope[param.name] = {
type: "PARAM",
sels: param.sels,
getter: () => { return ctx.c.getLocal(param.name); },
setter: (v) => { return ctx.c.setLocal(param.name, v); }
};
}
gen(ctx, ctx.functions[f].block);
}
for (let i=0; i<ctx.components.length; i++) {
const h = hashComponentCall(ctx, i);
const fName = ctx.components[i].temlate+"_"+h;
if (!fDefined[fName]) {
ctx.f = ctx.module.addFunction(fName);
ctx.c = ctx.f.getCodeBuilder();
ctx.scope = {};
for (let p in ctx.components[i].params) {
ctx.scope[p] = createConstant(ctx, ctx.components[i].params[p]);
}
gen(ctx, ctx.templates[ctx.components[i].temlate].block);
}
ctx.components[i].f = fName;
}
};
function buildSetSignal(ctx) {
}

186
src/c_tester.js Normal file
View File

@@ -0,0 +1,186 @@
const chai = require("chai");
const assert = chai.assert;
const fs = require("fs");
var tmp = require("tmp-promise");
const path = require("path");
const compiler = require("./compiler");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const stringifyBigInts = require("./utils").stringifyBigInts;
const unstringifyBigInts = require("./utils").unstringifyBigInts;
const bigInt = require("big-integer");
const utils = require("./utils");
const loadR1cs = require("./r1csfile").loadR1cs;
const ZqField = require("fflib").ZqField;
module.exports = c_tester;
async function c_tester(circomFile, _options) {
tmp.setGracefulCleanup();
const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
// console.log(dir.path);
const baseName = path.basename(circomFile, ".circom");
const options = Object.assign({}, _options);
options.cSourceWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".cpp"));
options.symWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".sym"));
options.r1csFileName = path.join(dir.path, baseName + ".r1cs");
await compiler(circomFile, options);
const cdir = path.join(__dirname, "..", "c");
await exec("cp" +
` ${path.join(dir.path, baseName + ".cpp")}` +
" /tmp/circuit.cpp"
);
await exec("g++" +
` ${path.join(cdir, "main.cpp")}` +
` ${path.join(cdir, "calcwit.cpp")}` +
` ${path.join(cdir, "utils.cpp")}` +
` ${path.join(cdir, "fr.c")}` +
` ${path.join(cdir, "fr.o")}` +
` ${path.join(dir.path, baseName + ".cpp")} ` +
` -o ${path.join(dir.path, baseName)}` +
` -I ${cdir}` +
" -lgmp -std=c++11 -DSANITY_CHECK"
);
// console.log(dir.path);
return new CTester(dir, baseName);
}
class CTester {
constructor(dir, baseName) {
this.dir=dir;
this.baseName = baseName;
}
async release() {
await this.dir.cleanup();
}
async calculateWitness(input) {
await fs.promises.writeFile(
path.join(this.dir.path, "in.json"),
JSON.stringify(stringifyBigInts(input), null, 1)
);
await exec(`${path.join(this.dir.path, this.baseName)}` +
` ${path.join(this.dir.path, "in.json")}` +
` ${path.join(this.dir.path, "out.json")}`
);
const resStr = await fs.promises.readFile(
path.join(this.dir.path, "out.json")
);
const res = unstringifyBigInts(JSON.parse(resStr));
return res;
}
async loadSymbols() {
if (this.symbols) return;
this.symbols = {};
const symsStr = await fs.promises.readFile(
path.join(this.dir.path, this.baseName + ".sym"),
"utf8"
);
const lines = symsStr.split("\n");
for (let i=0; i<lines.length; i++) {
const arr = lines[i].split(",");
if (arr.length!=3) continue;
this.symbols[arr[2]] = {
idx: Number(arr[0]),
idxWit: Number(arr[1])
};
}
}
async loadConstraints() {
const self = this;
if (this.constraints) return;
const r1cs = await loadR1cs(path.join(this.dir.path, this.baseName + ".r1cs"),true, false);
self.field = new ZqField(r1cs.prime);
self.nWires = r1cs.nWires;
self.constraints = r1cs.constraints;
}
async assertOut(actualOut, expectedOut) {
const self = this;
if (!self.symbols) await self.loadSymbols();
checkObject("main", expectedOut);
function checkObject(prefix, eOut) {
if (Array.isArray(eOut)) {
for (let i=0; i<eOut.length; i++) {
checkObject(prefix + "["+i+"]", eOut[i]);
}
} else if ((typeof eOut == "object")&&(eOut.constructor.name == "Object")) {
for (let k in eOut) {
checkObject(prefix + "."+k, eOut[k]);
}
} else {
if (typeof self.symbols[prefix] == "undefined") {
assert(false, "Output variable not defined: "+ prefix);
}
const ba = bigInt(actualOut[self.symbols[prefix].idxWit]).toString();
const be = bigInt(eOut).toString();
assert.strictEqual(ba, be, prefix);
}
}
}
async getDecoratedOutput(witness) {
const self = this;
const lines = [];
if (!self.symbols) await self.loadSymbols();
for (let n in self.symbols) {
let v;
if (utils.isDefined(witness[self.symbols[n].idxWit])) {
v = witness[self.symbols[n].idxWit].toString();
} else {
v = "undefined";
}
lines.push(`${n} --> ${v}`);
}
return lines.join("\n");
}
async checkConstraints(witness) {
const self = this;
if (!self.constraints) await self.loadConstraints();
for (let i=0; i<self.constraints.length; i++) {
checkConstraint(self.constraints[i]);
}
function checkConstraint(constraint) {
const F = self.field;
const a = evalLC(constraint.a);
const b = evalLC(constraint.b);
const c = evalLC(constraint.c);
assert (F.sub(F.mul(a,b), c).isZero(), "Constraint doesn't match");
}
function evalLC(lc) {
const F = self.field;
let v = F.zero;
for (let w in lc) {
v = F.add(
v,
F.mul( lc[w], witness[w] )
);
}
return v;
}
}
}

View File

@@ -17,217 +17,241 @@
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
const fs = require("fs");
const path = require("path");
const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const __MASK__ = new bigInt(2).pow(253).minus(1);
const assert = require("assert");
const gen = require("./gencode");
const exec = require("./exec");
const lc = require("./lcalgebra");
const sONE = 0;
const build = require("./build");
const BuilderC = require("./builder_c");
const BuilderWasm = require("./builder_wasm");
const constructionPhase = require("./construction_phase");
const Ctx = require("./ctx");
const ZqField = require("fflib").ZqField;
const utils = require("./utils");
const buildR1cs = require("./r1csfile").buildR1cs;
const BigArray = require("./bigarray");
module.exports = compile;
const parser = require("../parser/jaz.js").parser;
const timeout = ms => new Promise(res => setTimeout(res, ms));
async function compile(srcFile, options) {
options.p = options.p || __P__;
if (!options) {
options = {};
}
if (typeof options.reduceConstraints === "undefined") {
options.reduceConstraints = true;
}
const fullFileName = srcFile;
const fullFilePath = path.dirname(fullFileName);
const ctx = new Ctx();
ctx.field = new ZqField(options.p);
ctx.verbose= options.verbose || false;
ctx.mainComponent = options.mainComponent || "main";
ctx.newThreadTemplates = options.newThreadTemplates;
const src = fs.readFileSync(fullFileName, "utf8");
const ast = parser.parse(src);
constructionPhase(ctx, srcFile);
assert(ast.type == "BLOCK");
console.log("NConstraints Before: "+ctx.constraints.length);
const ctx = {
scopes: [{}],
signals: {
one: {
fullName: "one",
value: bigInt(1),
equivalence: "",
direction: ""
if (ctx.error) {
throw(ctx.error);
}
},
currentComponent: "",
constraints: [],
components: {},
templates: {},
functions: {},
functionParams: {},
filePath: fullFilePath,
fileName: fullFileName
};
exec(ctx, ast);
if (!ctx.components["main"]) {
if (ctx.getComponentIdx(ctx.mainComponent)<0) {
throw new Error("A main component must be defined");
}
if (ctx.verbose) console.log("Classify Signals");
classifySignals(ctx);
if (options.reduceConstraints) {
if (ctx.verbose) console.log("Reduce Constants");
reduceConstants(ctx);
if (options.reduceConstraints) {
if (ctx.verbose) console.log("Reduce Constraints");
// Repeat while reductions are performed
let oldNConstrains = -1;
while (ctx.constraints.length != oldNConstrains) {
console.log("Reducing constraints: "+ctx.constraints.length);
oldNConstrains = ctx.constraints.length;
reduceConstrains(ctx);
}
}
console.log("NConstraints After: "+ctx.constraints.length);
generateWitnessNames(ctx);
if (ctx.error) {
throw(ctx.error);
}
ctx.scopes = [{}];
if (options.cSourceWriteStream) {
ctx.builder = new BuilderC();
build(ctx);
const rdStream = ctx.builder.build();
rdStream.pipe(options.cSourceWriteStream);
const mainCode = gen(ctx,ast);
// await new Promise(fulfill => options.cSourceWriteStream.on("finish", fulfill));
}
if (options.wasmWriteStream) {
ctx.builder = new BuilderWasm();
build(ctx);
const rdStream = ctx.builder.build();
rdStream.pipe(options.wasmWriteStream);
// await new Promise(fulfill => options.wasmWriteStream.on("finish", fulfill));
}
// const mainCode = gen(ctx,ast);
if (ctx.error) throw(ctx.error);
const def = buildCircuitDef(ctx, mainCode);
if (options.r1csFileName) {
await buildR1cs(ctx, options.r1csFileName);
}
if (options.symWriteStream) {
buildSyms(ctx, options.symWriteStream);
}
// const def = buildCircuitDef(ctx, mainCode);
return def;
}
function classifySignals(ctx) {
const ERROR = 0xFFFF;
function priorize(t1, t2) {
if ((t1 == "error") || (t2=="error")) return "error";
if (t1 == "internal") {
if ((t1 == ERROR) || (t2==ERROR)) return ERROR;
if (t1 == ctx.stINTERNAL) {
return t2;
} else if (t2=="internal") {
} else if (t2==ctx.stINTERNAL) {
return t1;
}
if ((t1 == "one") || (t2 == "one")) return "one";
if ((t1 == "constant") || (t2 == "constant")) return "constant";
if (t1!=t2) return "error";
if ((t1 == ctx.stONE) || (t2 == ctx.stONE)) return ctx.stONE;
if ((t1 == ctx.stOUTPUT) || (t2 == ctx.stOUTPUT)) return ctx.stOUTPUT;
if ((t1 == ctx.stCONSTANT) || (t2 == ctx.stCONSTANT)) return ctx.stCONSTANT;
if ((t1 == ctx.stDISCARDED) || (t2 == ctx.stDISCARDED)) return ctx.stDISCARDED;
if (t1!=t2) return ERROR;
return t1;
}
// First classify the signals
for (let s in ctx.signals) {
for (let s=0; s<ctx.signals.length; s++) {
const signal = ctx.signals[s];
let tAll = "internal";
let tAll = ctx.stINTERNAL;
let lSignal = signal;
let end = false;
while (!end) {
let t = lSignal.category || "internal";
if (s == "one") {
t = "one";
} else if (lSignal.value) {
t = "constant";
} else if (lSignal.component=="main") {
if (lSignal.direction == "IN") {
if (lSignal.private) {
t = "prvInput";
let t = lSignal.c || ctx.stINTERNAL;
if (s == 0) {
t = ctx.stONE;
} else if (lSignal.o & ctx.MAIN) {
if (lSignal.o & ctx.IN) {
if (lSignal.o & ctx.PRV) {
t = ctx.stPRVINPUT;
} else {
t = "pubInput";
t = ctx.stPUBINPUT;
}
} else if (lSignal.direction == "OUT") {
t = "output";
} else if (lSignal.o & ctx.OUT) {
t = ctx.stOUTPUT;
}
} else if (utils.isDefined(lSignal.v)) {
t = ctx.stCONSTANT;
}
tAll = priorize(t,tAll);
if (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
if (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
} else {
end=true;
}
}
if (tAll == "error") {
if (tAll == ERROR) {
throw new Error("Incompatible types in signal: " + s);
}
lSignal.category = tAll;
lSignal.c = tAll;
}
}
function generateWitnessNames(ctx) {
const totals = {
"output": 0,
"pubInput": 0,
"one": 0,
"prvInput": 0,
"internal": 0,
"constant": 0,
};
const totals = {};
totals[ctx.stONE] = 0;
totals[ctx.stOUTPUT] = 0;
totals[ctx.stPUBINPUT] = 0;
totals[ctx.stPRVINPUT] = 0;
totals[ctx.stINTERNAL] = 0;
totals[ctx.stDISCARDED] = 0;
totals[ctx.stCONSTANT] = 0;
const ids = {};
const counted = {};
// First classify the signals
for (let s in ctx.signals) {
for (let s=0; s<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("generate witness (counting): ", s);
const signal = ctx.signals[s];
let lSignal = signal;
while (lSignal.equivalence) lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) lSignal = ctx.signals[lSignal.e];
if (!counted[lSignal.fullName]) {
counted[lSignal.fullName] = true;
totals[lSignal.category] ++;
if (!( lSignal.o & ctx.COUNTED) ) {
lSignal.o |= ctx.COUNTED;
totals[lSignal.c] ++;
}
}
ids["one"] = 0;
ids["output"] = 1;
ids["pubInput"] = ids["output"] + totals["output"];
ids["prvInput"] = ids["pubInput"] + totals["pubInput"];
ids["internal"] = ids["prvInput"] + totals["prvInput"];
ids["constant"] = ids["internal"] + totals["internal"];
const nSignals = ids["constant"] + totals["constant"];
ids[ctx.stONE] = 0;
ids[ctx.stOUTPUT] = 1;
ids[ctx.stPUBINPUT] = ids[ctx.stOUTPUT] + totals[ctx.stOUTPUT];
ids[ctx.stPRVINPUT] = ids[ctx.stPUBINPUT] + totals[ctx.stPUBINPUT];
ids[ctx.stINTERNAL] = ids[ctx.stPRVINPUT] + totals[ctx.stPRVINPUT];
ids[ctx.stDISCARDED] = ids[ctx.stINTERNAL] + totals[ctx.stINTERNAL];
ids[ctx.stCONSTANT] = ids[ctx.stDISCARDED] + totals[ctx.stDISCARDED];
const nSignals = ids[ctx.stCONSTANT] + totals[ctx.stCONSTANT];
ctx.signalNames = new Array(nSignals);
for (let i=0; i< nSignals; i++) ctx.signalNames[i] = [];
ctx.signalName2Idx = {};
for (let s=0; s<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("seting id: ", s);
for (let s in ctx.signals) {
const signal = ctx.signals[s];
let lSignal = signal;
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if ( typeof(lSignal.id) === "undefined" ) {
lSignal.id = ids[lSignal.category] ++;
lSignal.id = ids[lSignal.c] ++;
}
signal.id = lSignal.id;
ctx.signalNames[signal.id].push(signal.fullName);
ctx.signalName2Idx[signal.fullName] = signal.id;
}
ctx.totals = totals;
}
function reduceConstants(ctx) {
const newConstraints = [];
const newConstraints = new BigArray();
for (let i=0; i<ctx.constraints.length; i++) {
const c = lc.canonize(ctx, ctx.constraints[i]);
if (!lc.isZero(c)) {
if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constants: ", i);
const c = ctx.lc.canonize(ctx, ctx.constraints[i]);
if (!ctx.lc.isZero(c)) {
newConstraints.push(c);
}
delete ctx.constraints[i];
}
ctx.constraints = newConstraints;
}
function reduceConstrains(ctx) {
const newConstraints = [];
for (let i=0; i<ctx.constraints.length; i++) {
indexVariables();
let possibleConstraints = ctx.constraints;
let ii=0;
while (possibleConstraints.length>0) {
let nextPossibleConstraints = new BigArray();
for (let i=0; i<possibleConstraints.length; i++) {
ii++;
if ((ctx.verbose)&&(ii%10000 == 0)) console.log("reducing constraints: ", i);
if (!ctx.constraints[i]) continue;
const c = ctx.constraints[i];
// Swap a and b if b has more variables.
@@ -239,72 +263,137 @@ function reduceConstrains(ctx) {
// Mov to C if possible.
if (isConstant(c.a)) {
const ct = {type: "NUMBER", value: c.a.values["one"]};
c.c = lc.add(lc.mul(c.b, ct), c.c);
c.a = { type: "LINEARCOMBINATION", values: {} };
c.b = { type: "LINEARCOMBINATION", values: {} };
const ct = {t: "N", v: c.a.coefs[sONE]};
c.c = ctx.lc.add(ctx.lc.mul(c.b, ct), c.c);
c.a = { t: "LC", coefs: {} };
c.b = { t: "LC", coefs: {} };
}
if (isConstant(c.b)) {
const ct = {type: "NUMBER", value: c.b.values["one"]};
c.c = lc.add(lc.mul(c.a, ct), c.c);
c.a = { type: "LINEARCOMBINATION", values: {} };
c.b = { type: "LINEARCOMBINATION", values: {} };
const ct = {t: "N", v: c.b.coefs[sONE]};
c.c = ctx.lc.add(ctx.lc.mul(c.a, ct), c.c);
c.a = { t: "LC", coefs: {} };
c.b = { t: "LC", coefs: {} };
}
if (lc.isZero(c.a) || lc.isZero(c.b)) {
if (ctx.lc.isZero(c.a) || ctx.lc.isZero(c.b)) {
const isolatedSignal = getFirstInternalSignal(ctx, c.c);
if (isolatedSignal) {
let lSignal = ctx.signals[isolatedSignal];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
const isolatedSignalEquivalence = {
type: "LINEARCOMBINATION",
values: {}
t: "LC",
coefs: {}
};
const invCoef = c.c.values[isolatedSignal].modInv(__P__);
for (const s in c.c.values) {
const invCoef = c.c.coefs[isolatedSignal].modInv(__P__);
for (const s in c.c.coefs) {
if (s != isolatedSignal) {
const v = __P__.minus(c.c.values[s]).times(invCoef).mod(__P__);
const v = __P__.minus(c.c.coefs[s]).times(invCoef).mod(__P__);
if (!v.isZero()) {
isolatedSignalEquivalence.values[s] = v;
isolatedSignalEquivalence.coefs[s] = v;
}
}
}
for (let j=0; j<newConstraints.length; j++) {
newConstraints[j] = lc.substitute(newConstraints[j], isolatedSignal, isolatedSignalEquivalence);
for (let j in lSignal.inConstraints) {
if ((j!=i)&&(ctx.constraints[j])) {
ctx.constraints[j] = ctx.lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence);
linkSignalsConstraint(j);
if (j<i) {
nextPossibleConstraints.push(j);
}
for (let j=i+1; j<ctx.constraints.length; j++ ) {
ctx.constraints[j] = lc.substitute(ctx.constraints[j], isolatedSignal, isolatedSignalEquivalence);
}
c.a={ type: "LINEARCOMBINATION", values: {} };
c.b={ type: "LINEARCOMBINATION", values: {} };
c.c={ type: "LINEARCOMBINATION", values: {} };
isolatedSignal.category = "constant";
}
}
if (!lc.isZero(c)) {
newConstraints.push(c);
ctx.constraints[i] = null;
lSignal.c = ctx.stDISCARDED;
} else {
if (ctx.lc.isZero(c.c)) ctx.constraints[i] = null;
}
}
ctx.constraints = newConstraints;
}
possibleConstraints = nextPossibleConstraints;
}
unindexVariables();
// Pack the constraints
let o = 0;
for (let i=0; i<ctx.constraints.length; i++) {
if (ctx.constraints[i]) {
if (o != i) {
ctx.constraints[o] = ctx.constraints[i];
}
o++;
}
}
ctx.constraints.length = o;
function indexVariables() {
for (let i=0; i<ctx.constraints.length; i++) linkSignalsConstraint(i);
}
function linkSignalsConstraint(cidx) {
const ct = ctx.constraints[cidx];
for (let k in ct.a.coefs) linkSignal(k, cidx);
for (let k in ct.b.coefs) linkSignal(k, cidx);
for (let k in ct.c.coefs) linkSignal(k, cidx);
}
function unindexVariables() {
for (let s=0; s<ctx.signals.length; s++) {
let lSignal = ctx.signals[s];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if (lSignal.inConstraints) delete lSignal.inConstraints;
}
}
/*
function unlinkSignal(signalName, cidx) {
let lSignal = ctx.signals[signalName];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if ((lSignal.inConstraints)&&(lSignal.inConstraints[cidx])) {
delete lSignal.inConstraints[cidx];
}
}
*/
function linkSignal(signalName, cidx) {
let lSignal = ctx.signals[signalName];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if (!lSignal.inConstraints) lSignal.inConstraints = {};
lSignal.inConstraints[cidx] = true;
}
function getFirstInternalSignal(ctx, l) {
for (let k in l.values) {
for (let k in l.coefs) {
const signal = ctx.signals[k];
if (signal.category == "internal") return k;
if (signal.c == ctx.stINTERNAL) return k;
}
return null;
}
function isConstant(l) {
for (let k in l.values) {
if ((k != "one") && (!l.values[k].isZero())) return false;
for (let k in l.coefs) {
if ((k != sONE) && (!l.coefs[k].isZero())) return false;
}
if (!l.values["one"] || l.values["one"].isZero()) return false;
if (!l.coefs[sONE] || l.coefs[sONE].isZero()) return false;
return true;
}
}
/*
function buildCircuitDef(ctx, mainCode) {
const res = {
@@ -363,6 +452,9 @@ function buildCircuitDef(ctx, mainCode) {
return res;
}
*/
/*
Build constraints
@@ -384,14 +476,14 @@ is converted to
A B C
*/
/*
function buildConstraints(ctx) {
const res = [];
function fillLC(dst, src) {
if (src.type != "LINEARCOMBINATION") throw new Error("Constraint is not a LINEARCOMBINATION");
for (let s in src.values) {
const v = src.values[s].toString();
if (src.t != "LC") throw new Error("Constraint is not a LINEARCOMBINATION");
for (let s in src.coefs) {
const v = src.coefs[s].toString();
const id = ctx.signalName2Idx[s];
dst[id] = v;
}
@@ -404,13 +496,52 @@ function buildConstraints(ctx) {
fillLC(A, ctx.constraints[i].a);
fillLC(B, ctx.constraints[i].b);
fillLC(C, lc.negate(ctx.constraints[i].c));
fillLC(C, ctx.lc.negate(ctx.constraints[i].c));
res.push([A,B,C]);
}
return res;
}
*/
function buildSyms(ctx, strm) {
let nSyms;
addSymbolsComponent(ctx.mainComponent + ".", ctx.getComponentIdx(ctx.mainComponent));
function addSymbolsComponent(prefix, idComponet) {
for (let n in ctx.components[idComponet].names.o) {
const entrie = ctx.components[idComponet].names.o[n];
addSymbolArray(prefix+n, entrie.type, entrie.sizes, entrie.offset);
}
}
function addSymbolArray(prefix, type, sizes, offset) {
if (sizes.length==0) {
if (type == "S") {
let s=offset;
while (ctx.signals[s].e >= 0) s = ctx.signals[s].e;
let wId = ctx.signals[s].id;
if (typeof(wId) == "undefined") wId=-1;
strm.write(`${offset},${wId},${prefix}\n`);
nSyms ++;
if ((ctx.verbose)&&(nSyms%10000 == 0)) console.log("Symbols saved: "+nSyms);
} else {
addSymbolsComponent(prefix+".", offset);
}
return 1;
} else {
let acc = 0;
for (let i=0; i<sizes[0]; i++) {
acc += addSymbolArray(`${prefix}[${i}]`, type, sizes.slice(1), offset + acc );
}
return acc;
}
}
}

1070
src/construction_phase.js Normal file

File diff suppressed because it is too large Load Diff

227
src/ctx.js Normal file
View File

@@ -0,0 +1,227 @@
const bigInt = require("big-integer");
const BigArray = require("./bigarray.js");
class TableName {
constructor (ctx) {
this.ctx = ctx;
this.o = {};
}
_allocElement(name, _sizes, type) {
const sizes = _sizes || [];
let l = 1;
for (let i=0; i<sizes.length; i++) {
l = l*sizes[i];
}
this.o[name] = {
sizes: sizes,
type: type
};
return l;
}
addSignal(name, sizes) {
const l = this._allocElement(name, sizes, "S");
const o = this.ctx.nSignals;
this.o[name].offset = o;
this.ctx.nSignals += l;
if (l>1) {
return [o, o+l];
} else {
return o;
}
}
addComponent(name, sizes) {
const l = this._allocElement(name, sizes, "C");
const o = this.ctx.nComponents;
this.o[name].offset = o;
this.ctx.nComponents += l;
if (l>1) {
return [o, o+l];
} else {
return o;
}
}
_getElement(name, _sels, type) {
const sels = _sels || [];
const s = this.o[name];
if (!s) return -1;
if (s.type != type) return -1;
if (sels.length > s.sizes.length) return -1;
let l=1;
for (let i = s.sizes.length-1; i>sels.length; i--) {
l = l*s.sizes[i];
}
let o =0;
let p=1;
for (let i=sels.length-1; i>=0; i--) {
if (sels[i] > s.sizes[i]) return -1; // Out of range
if (sels[i] < 0) return -1; // Out of range
o += p*sels[i];
p *= s.sizes[i];
}
if (l>1) {
return [s.offset + o, s.offset + o + l];
} else {
return s.offset + o;
}
}
getSignalIdx(name, sels) {
return this._getElement(name, sels, "S");
}
getComponentIdx(name, sels) {
return this._getElement(name, sels, "C");
}
getSizes(name) {
return this.o[name].sels;
}
}
module.exports = class Ctx {
constructor() {
this.stONE = 1;
this.stOUTPUT = 2;
this.stPUBINPUT = 3;
this.stPRVINPUT = 4;
this.stINTERNAL = 5;
this.stDISCARDED = 6;
this.stCONSTANT = 7;
this.IN = 0x01;
this.OUT = 0x02;
this.PRV = 0x04;
this.ONE = 0x08;
this.MAIN = 0x10;
this.COUNTED = 0x20;
this.scopes = [{}];
this.signals = new BigArray();
this.currentComponent= -1;
this.constraints= new BigArray();
this.components= new BigArray();
this.templates= {};
this.functions= {};
this.functionParams= {};
this.nSignals = 0;
this.nComponents =0;
this.names = new TableName(this);
this.main=false;
this.error = null;
this.warnings = [];
const oneIdx = this.addSignal("one");
this.signals[oneIdx] = {
v: bigInt(1),
o: this.ONE,
e: -1,
};
this.uniqueNames = {};
}
addSignal(name, sizes) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.addSignal(name, sizes);
} else {
return this.names.addSignal(name, sizes);
}
}
addComponent(name, sizes) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.addComponent(name, sizes);
} else {
return this.names.addComponent(name, sizes);
}
}
getSignalIdx(name, sels) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.getSignalIdx(name, sels);
} else {
return this.names.getSignalIdx(name, sels);
}
}
getComponentIdx(name, sels) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.getComponentIdx(name, sels);
} else {
return this.names.getComponentIdx(name, sels);
}
}
getSizes(name) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.getSizes(name);
} else {
return this.names.getSizes(name);
}
}
newTableName() {
return new TableName(this);
}
_buildErr(ast, errStr) {
if (typeof ast == "string") {
ast = null;
errStr = ast;
}
if (ast) {
return {
pos: {
first_line: ast.first_line,
first_column: ast.first_column,
last_line: ast.last_line,
last_column: ast.last_column
},
errStr: errStr,
ast: ast,
message: errStr,
errFile: this.fileName
};
} else {
return {
errStr: errStr,
message: errStr
};
}
}
throwError(ast, errStr) {
const err = this._buildErr(ast, errStr);
this.error = err;
}
logWarning(ast, errStr) {
const w = this._buildErr(ast, errStr);
this.warnings.push(w);
}
getUniqueName(suggestedName) {
if (!suggestedName) {
suggestedName = "_tmp";
}
if (typeof(this.uniqueNames[suggestedName]) == "undefined") {
this.uniqueNames[suggestedName] = 1;
return suggestedName;
} else {
const name = suggestedName + "_" + this.uniqueNames[suggestedName];
this.uniqueNames[suggestedName]++;
return name;
}
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,70 +0,0 @@
module.exports = genOpt;
function genOpt(ctx, ast) {
if (ast.type == "OP") {
if (ast.op == "=") {
return genOptVarAssignement(ctx, ast);
} else {
error(ctx, ast, "GENOPT -> Invalid operation: " + ast.op);
}
} else if (ast.type == "TEMPLATEDEF") {
return genOptTemplateDef(ctx, ast);
} else {
error(ctx, ast, "GENOPT -> Invalid AST node type: " + ast.type);
}
}
function error(ctx, ast, errStr) {
ctx.error = {
pos: {
first_line: ast.first_line,
first_column: ast.first_column,
last_line: ast.last_line,
last_column: ast.last_column
},
errStr: errStr,
errFile: ctx.fileName,
ast: ast
};
}
function genOptTemplateDef(ctx, ast) {
if (ctx.templates[ast.name]) {
return error(ctx, ast, "Template name already exists: "+ast.name);
}
ctx.templates[ast.name] = {
type: "TEMPLATE",
params: ast.params,
block: ast.block,
fileName: ctx.fileName,
filePath: ctx.filePath
};
}
function genOptVarAssignement(ctx, ast) {
let varName;
if (ast.values[0].type == "DECLARE") {
varName = genOptCode(ctx, ast.values[0]);
if (ctx.error) return;
} else {
varName = ast.values[0];
}
const varContent = getScope(ctx, varName.name, varName.selectors);
if (ctx.error) return;
if ((typeof(varContent) != "object")||(varContent == null)) return error(ctx, ast, "Variable not defined");
if (varContent.type == "COMPONENT") return genOptInstantiateComponet(ctx, varName, ast.values[1]);
if (varContent.type == "SIGNAL") return error(ctx, ast, "Cannot assig to a signal with `=` use <-- or <== ops");
const res = genOpt(ctx, ast.values[1]);
if (ctx.error) return;
setScope(ctx, varName.name, varName.selectors, res);
return v;
}

File diff suppressed because it is too large Load Diff

75
src/iterateast.js Normal file
View File

@@ -0,0 +1,75 @@
const assert = require("assert");
module.exports = iterateAST;
function iterateAST(ast, fn, _pfx) {
if (!ast) return;
const pfx = _pfx || "";
let itPfx = 0;
function getPfx() {
res = pfx+"."+itPfx;
itPfx ++;
return res;
}
let res = fn(ast, pfx);
if (res) return res;
function iterate(arr) {
if (arr) {
for (let i=0; i<arr.length; i++) {
res = iterateAST(arr[i], fn, getPfx());
if (res) return res;
}
}
}
if ((ast.type == "NUMBER")) {
//
} else if (ast.type == "VARIABLE") {
iterate(ast.selectors);
} else if (ast.type == "PIN") {
iterate(ast.component.selectors);
iterate(ast.pin.selectors);
} else if (ast.type == "OP") {
iterate(ast.values);
} else if (ast.type == "DECLARE") {
iterate(ast.name.selectors);
} else if (ast.type == "FUNCTIONCALL") {
iterate(ast.params);
} else if (ast.type == "BLOCK") {
iterate(ast.statements);
} else if (ast.type == "COMPUTE") {
iterateAST(ast.body, fn, getPfx());
} else if (ast.type == "FOR") {
iterateAST(ast.init, fn, getPfx());
iterateAST(ast.condition, fn, getPfx());
iterateAST(ast.step, fn, getPfx());
iterateAST(ast.body, fn, getPfx());
} else if (ast.type == "WHILE") {
iterateAST(ast.condition, fn, getPfx());
iterateAST(ast.body, fn, getPfx());
} else if (ast.type == "IF") {
iterateAST(ast.condition, fn, getPfx());
iterateAST(ast.then, fn, getPfx());
iterateAST(ast.else, fn, getPfx());
} else if (ast.type == "RETURN") {
iterateAST(ast.value, fn, getPfx());
} else if (ast.type == "ARRAY") {
iterate(ast.values);
} else if ((ast.type == "TEMPLATEDEF")) {
//
} else if ((ast.type == "FUNCTIONDEF")) {
//
} else if ((ast.type == "INCLUDE")) {
//
} else {
assert(false, "GEN -> Invalid AST iteration: " + ast.type);
}
}

View File

@@ -18,489 +18,555 @@
*/
/*
NUMBER: a
// Number
///////////////
N: a
{
type: "NUMBER",
value: bigInt(a)
t: "N",
v: bigInt(a)
}
LINEARCOMBINATION: c1*s1 + c2*s2 + c3*s3
// Signal
///////////////
{
type: "LINEARCOMBINATION",
values: {
t: "S",
sIdx: sIdx
}
// Linear Convination
//////////////////
LC: c1*s1 + c2*s2 + c3*s3
{
t: "LC",
coefs: {
s1: bigInt(c1),
s2: bigInt(c2),
s3: bigInt(c3)
}
}
QEQ: a*b + c WHERE a,b,c are LINEARCOMBINATION
// Quadratic Expression
//////////////////
QEX: a*b + c WHERE a,b,c are LC
{
type: "QEQ"
a: { type: LINEARCOMBINATION, values: {...} },
b: { type: LINEARCOMBINATION, values: {...} },
c: { type: LINEARCOMBINATION, values: {...} }
t: "QEX"
a: { t: "LC", coefs: {...} },
b: { t: "LC", coefs: {...} },
c: { t: "LC", coefs: {...} }
}
NQ: Non quadratic expression
{
t: "NQ"
}
*/
/*
+ NUM LC QEQ
NUM NUM LC QEQ
LC LC LC QEQ
QEQ QEQ QEQ ERR
+ N LC QEX NQ
N N LC QEX NQ
LC LC LC QEX NQ
QEX QEX QEX NQ NQ
NQ NQ NQ NQ NQ
* NUM LC QEQ
NUM NUM LC QEQ
LC LC QEQ ERR
QEQ QEQ ERR ERR
* N LC QEX NQ
N N LC QEX NQ
LC LC QEX NQ NQ
QEX QEX NQ NQ NQ
NQ NQ NQ NQ NQ
*/
const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const utils = require("./utils");
const sONE = 0;
exports.add = add;
exports.mul = mul;
exports.evaluate = evaluate;
exports.negate = negate;
exports.sub = sub;
exports.toQEQ = toQEQ;
exports.isZero = isZero;
exports.toString = toString;
exports.canonize = canonize;
exports.substitute = substitute;
class LCAlgebra {
constructor (aField) {
const self = this;
this.field= aField;
[
["lt",2],
["leq",2],
["eq",2],
["neq",2],
["geq",2],
["gt",2],
["idiv",2],
["mod",2],
["band",2],
["bor",2],
["bxor",2],
["bnot",2],
["land",2],
["lor",2],
["lnot",2],
["shl",2],
["shr",2],
].forEach( (op) => {
self._genNQOp(op[0], op[1]);
});
}
function signal2lc(a) {
let lc;
if (a.type == "SIGNAL") {
lc = {
type: "LINEARCOMBINATION",
values: {}
_genNQOp(op, nOps) {
const self=this;
self[op] = function() {
const operands = [];
for (let i=0; i<nOps; i++) {
if (typeof(arguments[i]) !== "object") throw new Error("Invalid operand type");
if (arguments[i].t !== "N") return {t: "NQ"};
operands.push(arguments[i].v);
}
return {
t: "N",
v: self.field[op](...operands)
};
lc.values[a.fullName] = bigInt(1);
};
}
_signal2lc(a) {
if (a.t == "S") {
const lc = {
t: "LC",
coefs: {}
};
lc.coefs[a.sIdx] = bigInt(1);
return lc;
} else {
return a;
}
}
}
function clone(a) {
_clone(a) {
const res = {};
res.type = a.type;
if (a.type == "NUMBER") {
res.value = bigInt(a.value);
} else if (a.type == "LINEARCOMBINATION") {
res.values = {};
for (let k in a.values) {
res.values[k] = bigInt(a.values[k]);
res.t = a.t;
if (a.t == "N") {
res.v = a.v;
} else if (a.t == "S") {
res.sIdx = a.sIdx;
} else if (a.t == "LC") {
res.coefs = {};
for (let k in a.coefs) {
res.coefs[k] = a.coefs[k];
}
} else if (a.type == "QEQ") {
res.a = clone(a.a);
res.b = clone(a.b);
res.c = clone(a.c);
} else if (a.type == "ERROR") {
res.errStr = a.errStr;
} else {
res.type = "ERROR";
res.errStr = "Invilid type when clonning: "+a.type;
} else if (a.t == "QEX") {
res.a = this._clone(a.a);
res.b = this._clone(a.b);
res.c = this._clone(a.c);
}
return res;
}
}
function add(_a, _b) {
const a = signal2lc(_a);
const b = signal2lc(_b);
if (a.type == "ERROR") return a;
if (b.type == "ERROR") return b;
if (a.type == "NUMBER") {
if (b.type == "NUMBER") {
return addNumNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return addLCNum(b,a);
} else if (b.type=="QEQ") {
return addQEQNum(b,a);
add(_a,_b) {
const self = this;
const a = self._signal2lc(_a);
const b = self._signal2lc(_b);
if (a.t == "NQ") return a;
if (b.t == "NQ") return b;
if (a.t == "N") {
if (b.t == "N") {
return add_N_N(a,b);
} else if (b.t=="LC") {
return add_LC_N(b,a);
} else if (b.t=="QEX") {
return add_QEX_N(b,a);
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 2: "+b.type };
return { type: "NQ" };
}
} else if (a.type=="LINEARCOMBINATION") {
if (b.type == "NUMBER") {
return addLCNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return addLCLC(a,b);
} else if (b.type=="QEQ") {
return addQEQLC(b,a);
} else if (a.t=="LC") {
if (b.t == "N") {
return add_LC_N(a,b);
} else if (b.t=="LC") {
return add_LC_LC(a,b);
} else if (b.t=="QEX") {
return add_QEX_LC(b,a);
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 2: "+b.type };
return { t: "NQ" };
}
} else if (a.type=="QEQ") {
if (b.type == "NUMBER") {
return addQEQNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return addQEQLC(a,b);
} else if (b.type=="QEQ") {
return { type: "ERROR", errStr: "QEQ + QEQ" };
} else if (a.t=="QEX") {
if (b.t == "N") {
return add_QEX_N(a,b);
} else if (b.t=="LC") {
return add_QEX_LC(a,b);
} else if (b.t=="QEX") {
return { t: "NQ" };
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 2: "+b.type };
return { t: "NQ" };
}
} else {
return { type: "ERROR", errStr: "LC Add Invalid Type 1: "+a.type };
return { t: "NQ" };
}
}
function addNumNum(a,b) {
if (!a.value || !b.value) return { type: "NUMBER" };
function add_N_N(a,b) {
return {
type: "NUMBER",
value: a.value.add(b.value).mod(__P__)
t: "N",
v: self.field.add(a.v, b.v)
};
}
function addLCNum(a,b) {
let res = clone(a);
if (!b.value) {
return { type: "ERROR", errStr: "LinearCombination + undefined" };
}
if (b.value.isZero()) return res;
if (!res.values["one"]) {
res.values["one"]=bigInt(b.value);
function add_LC_N(a,b) {
let res = self._clone(a);
if (b.v.isZero()) return res;
if (!utils.isDefined(res.coefs[sONE])) {
res.coefs[sONE]= b.v;
} else {
res.values["one"]= res.values["one"].add(b.value).mod(__P__);
res.coefs[sONE]= self.field.add(res.coefs[sONE], b.v);
}
return res;
}
}
function addLCLC(a,b) {
let res = clone(a);
for (let k in b.values) {
if (!res.values[k]) {
res.values[k]=bigInt(b.values[k]);
function add_LC_LC(a,b) {
let res = self._clone(a);
for (let k in b.coefs) {
if (!utils.isDefined(res.coefs[k])) {
res.coefs[k]=b.coefs[k];
} else {
res.values[k]= res.values[k].add(b.values[k]).mod(__P__);
res.coefs[k]= self.field.add(res.coefs[k], b.coefs[k]);
}
}
return res;
}
}
function addQEQNum(a,b) {
let res = clone(a);
res.c = addLCNum(res.c, b);
if (res.c.type == "ERROR") return res.c;
function add_QEX_N(a,b) {
let res = self._clone(a);
res.c = add_LC_N(res.c, b);
return res;
}
}
function addQEQLC(a,b) {
let res = clone(a);
res.c = addLCLC(res.c, b);
if (res.c.type == "ERROR") return res.c;
function add_QEX_LC(a,b) {
let res = self._clone(a);
res.c = add_LC_LC(res.c, b);
return res;
}
}
}
function mul(_a, _b) {
const a = signal2lc(_a);
const b = signal2lc(_b);
if (a.type == "ERROR") return a;
if (b.type == "ERROR") return b;
if (a.type == "NUMBER") {
if (b.type == "NUMBER") {
return mulNumNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return mulLCNum(b,a);
} else if (b.type=="QEQ") {
return mulQEQNum(b,a);
mul(_a,_b) {
const self = this;
const a = self._signal2lc(_a);
const b = self._signal2lc(_b);
if (a.t == "NQ") return a;
if (b.t == "NQ") return b;
if (a.t == "N") {
if (b.t == "N") {
return mul_N_N(a,b);
} else if (b.t=="LC") {
return mul_LC_N(b,a);
} else if (b.t=="QEX") {
return mul_QEX_N(b,a);
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 2: "+b.type };
return { t: "NQ"};
}
} else if (a.type=="LINEARCOMBINATION") {
if (b.type == "NUMBER") {
return mulLCNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return mulLCLC(a,b);
} else if (b.type=="QEQ") {
return { type: "ERROR", errStr: "LC * QEQ" };
} else if (a.t=="LC") {
if (b.t == "N") {
return mul_LC_N(a,b);
} else if (b.t=="LC") {
return mul_LC_LC(a,b);
} else if (b.t=="QEX") {
return { t: "NQ" };
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 2: "+b.type };
return { t: "NQ" };
}
} else if (a.type=="QEQ") {
if (b.type == "NUMBER") {
return mulQEQNum(a,b);
} else if (b.type=="LINEARCOMBINATION") {
return { type: "ERROR", errStr: "QEC * LC" };
} else if (b.type=="QEQ") {
return { type: "ERROR", errStr: "QEQ * QEQ" };
} else if (a.t=="QEX") {
if (b.t == "N") {
return mul_QEX_N(a,b);
} else if (b.t=="LC") {
return { t: "NQ" };
} else if (b.t=="QEX") {
return { t: "NQ" };
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 2: "+b.type };
return { t: "NQ" };
}
} else {
return { type: "ERROR", errStr: "LC Mul Invalid Type 1: "+a.type };
return { t: "NQ" };
}
}
function mulNumNum(a,b) {
if (!a.value || !b.value) return { type: "NUMBER" };
function mul_N_N(a,b) {
return {
type: "NUMBER",
value: a.value.times(b.value).mod(__P__)
t: "N",
v: self.field.mul(a.v, b.v)
};
}
function mulLCNum(a,b) {
let res = clone(a);
if (!b.value) {
return {type: "ERROR", errStr: "LinearCombination * undefined"};
}
for (let k in res.values) {
res.values[k] = res.values[k].times(b.value).mod(__P__);
function mul_LC_N(a,b) {
let res = self._clone(a);
for (let k in res.coefs) {
res.coefs[k] = self.field.mul(res.coefs[k], b.v);
}
return res;
}
}
function mulLCLC(a,b) {
function mul_LC_LC(a,b) {
return {
type: "QEQ",
a: clone(a),
b: clone(b),
c: { type: "LINEARCOMBINATION", values: {}}
t: "QEX",
a: self._clone(a),
b: self._clone(b),
c: { t: "LC", coefs: {}}
};
}
function mulQEQNum(a,b) {
let res = {
type: "QEQ",
a: mulLCNum(a.a, b),
b: clone(a.b),
c: mulLCNum(a.c, b)
};
if (res.a.type == "ERROR") return res.a;
if (res.c.type == "ERROR") return res.a;
return res;
}
function getSignalValue(ctx, signalName) {
const s = ctx.signals[signalName];
if (s.equivalence != "") {
return getSignalValue(ctx, s.equivalence);
} else {
const res = {
type: "NUMBER"
};
if (s.value) {
res.value = s.value;
}
return res;
}
}
function evaluate(ctx, n) {
if (n.type == "NUMBER") {
return n;
} else if (n.type == "SIGNAL") {
return getSignalValue(ctx, n.fullName);
} else if (n.type == "LINEARCOMBINATION") {
const v= {
type: "NUMBER",
value: bigInt(0)
};
for (let k in n.values) {
const s = getSignalValue(ctx, k);
if (s.type != "NUMBER") return {type: "ERROR", errStr: "Invalid signal in linear Combination: " + k};
if (!s.value) return { type: "NUMBER" };
v.value = v.value.add( n.values[k].times(s.value)).mod(__P__);
}
return v;
} else if (n.type == "QEQ") {
const a = evaluate(ctx, n.a);
if (a.type == "ERROR") return a;
if (!a.value) return { type: "NUMBER" };
const b = evaluate(ctx, n.b);
if (b.type == "ERROR") return b;
if (!b.value) return { type: "NUMBER" };
const c = evaluate(ctx, n.c);
if (c.type == "ERROR") return c;
if (!c.value) return { type: "NUMBER" };
function mul_QEX_N(a,b) {
return {
type: "NUMBER",
value: (a.value.times(b.value).add(c.value)).mod(__P__)
t: "QEX",
a: mul_LC_N(a.a, b),
b: self._clone(a.b),
c: mul_LC_N(a.c, b)
};
} else if (n.type == "ERROR") {
return n;
} else {
return {type: "ERROR", errStr: "Invalid type in evaluate: "+n.type};
}
}
}
function negate(_a) {
const a = signal2lc(_a);
let res = clone(a);
if (res.type == "NUMBER") {
res.value = __P__.minus(a.value).mod(__P__);
} else if (res.type == "LINEARCOMBINATION") {
for (let k in res.values) {
res.values[k] = __P__.minus(res.values[k]).mod(__P__);
neg(_a) {
const a = this._signal2lc(_a);
let res = this._clone(a);
if (res.t == "N") {
res.v = this.field.neg(a.v);
} else if (res.t == "LC") {
for (let k in res.coefs) {
res.coefs[k] = this.field.neg(res.coefs[k]);
}
} else if (res.type == "QEQ") {
res.a = negate(res.a);
res.c = negate(res.c);
} else if (res.type == "ERROR") {
return res;
} else if (res.t == "QEX") {
res.a = this.neg(res.a);
res.c = this.neg(res.c);
} else {
res = {type: "ERROR", errStr: "LC Negate invalid Type: "+res.type};
res = {t: "NQ"};
}
return res;
}
}
function sub(a, b) {
return add(a, negate(b));
}
sub(a, b) {
return this.add(a, this.neg(b));
}
function toQEQ(a) {
if (a.type == "NUMBER") {
div(a, b) {
if (b.t == "N") {
if (b.v.isZero()) throw new Error("Division by zero");
const inv = {
t: "N",
v: this.field.inv(b.v)
};
return this.mul(a, inv);
} else {
return {t: "NQ"};
}
}
pow(a, b) {
if (b.t == "N") {
if (b.v.isZero()) {
if (this.isZero(a)) {
throw new Error("Zero to the Zero");
}
return {
type: "QEQ",
a: {type: "LINEARCOMBINATION", values: {}},
b: {type: "LINEARCOMBINATION", values: {}},
c: {type: "LINEARCOMBINATION", values: {"one": bigInt(a.value)}}
t: "N",
v: this.field.one
};
} else if (a.type == "LINEARCOMBINATION") {
return {
type: "QEQ",
a: {type: "LINEARCOMBINATION", values: {}},
b: {type: "LINEARCOMBINATION", values: {}},
c: clone(a)
};
} else if (a.type == "QEQ") {
return clone(a);
} else if (a.type == "ERROR") {
return clone(a);
} else {
return {type: "ERROR", errStr: "toQEQ invalid Type: "+a.type};
}
}
function isZero(a) {
if (a.type == "NUMBER") {
return a.value.isZero();
} else if (a.type == "LINEARCOMBINATION") {
for (let k in a.values) {
if (!a.values[k].isZero()) return false;
}
return true;
} else if (a.type == "QEQ") {
return (isZero(a.a) || isZero(a.b)) && isZero(a.c);
} else if (a.type == "ERROR") {
return false;
} else {
return false;
}
}
function toString(a, ctx) {
if (a.type == "NUMBER") {
return a.value.toString();
} else if (a.type == "LINEARCOMBINATION") {
let S="";
for (let k in a.values) {
if (!a.values[k].isZero()) {
let c;
if (a.values[k].greater(__P__.divide(2))) {
S = S + "-";
c = __P__.minus(a.values[k]);
} else {
if (S!="") S=S+" + ";
c = a.values[k];
}
if (!c.equals(1)) {
S = S + c.toString() + "*";
}
let sigName = k;
if (ctx) {
while (ctx.signals[sigName].equivalence) sigName = ctx.signals[sigName].equivalence;
}
S = S + sigName;
}
}
if (S=="") return "0"; else return S;
} else if (a.type == "QEQ") {
return "( "+toString(a.a, ctx)+" ) * ( "+toString(a.b, ctx)+" ) + " + toString(a.c, ctx);
} else if (a.type == "ERROR") {
return "ERROR: "+a.errStr;
} else {
return "INVALID";
}
}
function canonize(ctx, a) {
if (a.type == "LINEARCOMBINATION") {
const res = clone(a);
for (let k in a.values) {
let s = k;
while (ctx.signals[s].equivalence) s= ctx.signals[s].equivalence;
if ((typeof(ctx.signals[s].value) != "undefined")&&(k != "one")) {
const v = res.values[k].times(ctx.signals[s].value).mod(__P__);
if (!res.values["one"]) {
res.values["one"]=v;
} else {
res.values["one"]= res.values["one"].add(v).mod(__P__);
}
delete res.values[k];
} else if (s != k) {
if (!res.values[s]) {
res.values[s]=bigInt(res.values[k]);
} else {
res.values[s]= res.values[s].add(res.values[k]).mod(__P__);
}
delete res.values[k];
}
}
for (let k in res.values) {
if (res.values[k].isZero()) delete res.values[k];
}
return res;
} else if (a.type == "QEQ") {
const res = {
type: "QEQ",
a: canonize(ctx, a.a),
b: canonize(ctx, a.b),
c: canonize(ctx, a.c)
};
return res;
} else {
} else if (b.v.eq(this.field.one)) {
return a;
}
}
function substitute(where, signal, equivalence) {
if (equivalence.type != "LINEARCOMBINATION") throw new Error("Equivalence must be a Linear Combination");
if (where.type == "LINEARCOMBINATION") {
if (!where.values[signal] || where.values[signal].isZero()) return where;
const res=clone(where);
const coef = res.values[signal];
for (let k in equivalence.values) {
if (k != signal) {
const v = coef.times(equivalence.values[k]).mod(__P__);
if (!res.values[k]) {
res.values[k]=v;
} else if (b.v.eq(bigInt(2))) {
return this.mul(a,a);
} else {
res.values[k]= res.values[k].add(v).mod(__P__);
}
if (res.values[k].isZero()) delete res.values[k];
if (a.t=="N") {
return {
t: "N",
v: this.field.pow(a.v, b.v)
};
} else {
return {t: "NQ"};
}
}
delete res.values[signal];
} else {
return {t: "NQ"};
}
}
substitute(where, signal, equivalence) {
if (equivalence.t != "LC") throw new Error("Equivalence must be a Linear Combination");
if (where.t == "LC") {
if (!utils.isDefined(where.coefs[signal]) || where.coefs[signal].isZero()) return where;
const res=this._clone(where);
const coef = res.coefs[signal];
for (let k in equivalence.coefs) {
if (k != signal) {
const v = this.field.mul( coef, equivalence.coefs[k] );
if (!utils.isDefined(res.coefs[k])) {
res.coefs[k]=v;
} else {
res.coefs[k]= this.field.add(res.coefs[k],v);
}
if (res.coefs[k].isZero()) delete res.coefs[k];
}
}
delete res.coefs[signal];
return res;
} else if (where.type == "QEQ") {
} else if (where.t == "QEX") {
const res = {
type: "QEQ",
a: substitute(where.a, signal, equivalence),
b: substitute(where.b, signal, equivalence),
c: substitute(where.c, signal, equivalence)
t: "QEX",
a: this.substitute(where.a, signal, equivalence),
b: this.substitute(where.b, signal, equivalence),
c: this.substitute(where.c, signal, equivalence)
};
return res;
} else {
return where;
}
}
toQEX(a) {
if (a.t == "N") {
const res = {
t: "QEX",
a: {t: "LC", coefs: {}},
b: {t: "LC", coefs: {}},
c: {t: "LC", coefs: {}}
};
res.c[sONE] = a.v;
return res;
} else if (a.t == "LC") {
return {
t: "QEX",
a: {t: "LC", coefs: {}},
b: {t: "LC", coefs: {}},
c: this._clone(a)
};
} else if (a.t == "QEX") {
return this._clone(a);
} else {
throw new Error(`Type ${a.t} can not be converted to QEX`);
}
}
isZero(a) {
if (a.t == "N") {
return a.v.isZero();
} else if (a.t == "LC") {
for (let k in a.coefs) {
if (!a.coefs[k].isZero()) return false;
}
return true;
} else if (a.t == "QEX") {
return (this.isZero(a.a) || this.isZero(a.b)) && this.isZero(a.c);
} else {
return false;
}
}
toString(a, ctx) {
if (a.t == "N") {
return a.v.toString();
} else if (a.t == "LC") {
let S="";
for (let k in a.coefs) {
if (!a.coefs[k].isZero()) {
let c;
if (a.coefs[k].greater(this.field.p.divide(2))) {
S = S + "-";
c = this.field.p.minus(a.coefs[k]);
} else {
if (S!="") S=S+" + ";
c = a.coefs[k];
}
if (!c.equals(bigInt.one)) {
S = S + c.toString() + "*";
}
let sIdx = k;
if (ctx) {
while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e;
}
S = S + "[" + sIdx + "]";
}
}
if (S=="") return "0"; else return S;
} else if (a.t == "QEX") {
return "( "+
this.toString(a.a, ctx)+" ) * ( "+
this.toString(a.b, ctx)+" ) + " +
this.toString(a.c, ctx);
} else {
return "NQ";
}
}
evaluate(ctx, n) {
if (n.t == "N") {
return n.v;
} else if (n.t == "SIGNAL") {
return getSignalValue(ctx, n.sIdx);
} else if (n.t == "LC") {
let v= this.field.zero;
for (let k in n.coefs) {
const s = getSignalValue(ctx, k);
if (s === null) return null;
v = this.field.add(v, this.field.mul( n.coefs[k], s));
}
return v;
} else if (n.type == "QEX") {
const a = this.evaluate(ctx, n.a);
if (a === null) return null;
const b = this.evaluate(ctx, n.b);
if (b === null) return null;
const c = this.evaluate(ctx, n.c);
if (c === null) return null;
return this.field.add(this.field.mul(a,b), c);
} else {
return null;
}
function getSignalValue(ctx, sIdx) {
let s = ctx.signals[sIdx];
while (s.e>=0) s = ctx.signals[s.e];
if (utils.isDefined(s.v)) return s.v;
return null;
}
}
canonize(ctx, a) {
if (a.t == "LC") {
const res = this._clone(a);
for (let k in a.coefs) {
let s = k;
while (ctx.signals[s].e>=0) s= ctx.signals[s].e;
if (utils.isDefined(ctx.signals[s].v)&&(k != sONE)) {
const v = this.field.mul(res.coefs[k], ctx.signals[s].v);
if (!utils.isDefined(res.coefs[sONE])) {
res.coefs[sONE]=v;
} else {
res.coefs[sONE]= this.field.add(res.coefs[sONE], v);
}
delete res.coefs[k];
} else if (s != k) {
if (!utils.isDefined(res.coefs[s])) {
res.coefs[s]=res.coefs[k];
} else {
res.coefs[s]= this.field.add(res.coefs[s], res.coefs[k]);
}
delete res.coefs[k];
}
}
for (let k in res.coefs) {
if (res.coefs[k].isZero()) delete res.coefs[k];
}
return res;
} else if (a.t == "QEX") {
const res = {
t: "QEX",
a: this.canonize(ctx, a.a),
b: this.canonize(ctx, a.b),
c: this.canonize(ctx, a.c)
};
return res;
} else {
return a;
}
}
}
module.exports = LCAlgebra;

288
src/r1csfile.js Normal file
View File

@@ -0,0 +1,288 @@
const fs = require("fs");
const assert = require("assert");
const bigInt = require("big-integer");
module.exports.buildR1cs = buildR1cs;
module.exports.loadR1cs = loadR1cs;
async function loadR1cs(fileName, loadConstraints, loadMap) {
const res = {};
const fd = await fs.promises.open(fileName, "r");
const b = Buffer.allocUnsafe(4);
await fd.read(b, 0, 4, 0);
if (b.toString() != "r1cs") assert(false, "Invalid File format");
let p=4;
let v = await readU32();
if (v>1) assert(false, "Version not supported");
const nSections = await readU32();
let pHeader;
let pConstraints;
let headerSize;
let constraintsSize;
let pMap;
let mapSize;
for (let i=0; i<nSections; i++) {
let ht = await readU32();
let hl = await readU32();
if (ht == 1) {
if (typeof pHeader != "undefined") assert(false, "File has two headder sections");
pHeader = p;
headerSize = hl;
} else if (ht==2) {
if (typeof pConstraints != "undefined") assert(false, "File has two constraints sections");
pConstraints = p;
constraintsSize = hl;
} else if (ht==3) {
pMap = p;
mapSize = hl;
}
p += hl;
}
if (typeof pHeader == "undefined") assert(false, "File has two header");
// Read Header
p = pHeader;
const fieldDefSize = await readU32();
const pFieldDef = p;
const defType = await readU32();
if (defType != 1) if (typeof pConstraints != "undefined") assert(false, "Field type not supported");
res.prime = await readBigInt();
if ( p != pFieldDef + fieldDefSize) assert("Invalid fieldDef size");
const bigIntFormat = await readU32();
if (bigIntFormat != 0) assert(false, "BigInt format not supported");
const idSize = await readU32();
if (idSize != 4) assert(false, "idSize not supported. Mus be 4");
res.nWires = await readU32();
res.nPubOuts = await readU32();
res.nPubIns = await readU32();
res.nPrvIns = await readU32();
res.nLabels = await readU32();
res.nConstraints = await readU32();
if (p != pHeader + headerSize) assert(false, "Invalid header section size");
if (loadConstraints) {
// Read Constraints
p = pConstraints;
res.constraints = [];
for (let i=0; i<res.nConstraints; i++) {
const c = await readConstraint();
res.constraints.push(c);
}
if (p != pConstraints + constraintsSize) assert(false, "Invalid constraints size");
}
// Read Labels
if (loadMap) {
p = pMap;
res.map = [];
for (let i=0; i<res.nLabels; i++) {
const idx = await readU32();
res.map.push(idx);
}
if (p != pMap + mapSize) assert(false, "Invalid Map size");
}
await fd.close();
return res;
async function readU32() {
const b = Buffer.allocUnsafe(4);
await fd.read(b, 0, 4, p);
p+=4;
return b.readInt32LE(0);
}
async function readBigInt() {
const bl = Buffer.allocUnsafe(1);
await fd.read(bl, 0, 1, p);
p++;
const l = bl[0];
const b = Buffer.allocUnsafe(l);
await fd.read(b, 0, l, p);
p += l;
const arr = Uint8Array.from(b);
const arrr = new Array(arr.length);
for (let i=0; i<arr.length; i++) {
arrr[i] = arr[arr.length-1-i];
}
const n = bigInt.fromArray(arrr, 256);
return n;
}
async function readConstraint() {
const c = {};
c.a = await readLC();
c.b = await readLC();
c.c = await readLC();
return c;
}
async function readLC() {
const lc= {};
const nIdx = await readU32();
for (let i=0; i<nIdx; i++) {
const idx = await readU32();
const val = await readBigInt();
lc[idx] = val;
}
return lc;
}
}
async function buildR1cs(ctx, fileName) {
const fd = await fs.promises.open(fileName, "w");
await fd.write("r1cs"); // Magic "r1cs"
let p = 4;
await writeU32(1); // Version
await writeU32(3); // Number of Sections
// Write the header
///////////
await writeU32(1); // Header type
const pHeaderSize = p;
await writeU32(0); // Temporally set to 0 length
// Field Def
const pFieldDefSize = p;
await writeU32(0); // Temporally set to 0 length
await writeU32(1);
await writeBigInt(ctx.field.p);
const fieldDefSize = p - pFieldDefSize - 4;
await writeU32(0); // Variable bigInt format
await writeU32(4); // Id Size
const NWires =
ctx.totals[ctx.stONE] +
ctx.totals[ctx.stOUTPUT] +
ctx.totals[ctx.stPUBINPUT] +
ctx.totals[ctx.stPRVINPUT] +
ctx.totals[ctx.stINTERNAL];
await writeU32(NWires);
await writeU32(ctx.totals[ctx.stOUTPUT]);
await writeU32(ctx.totals[ctx.stPUBINPUT]);
await writeU32(ctx.totals[ctx.stPRVINPUT]);
await writeU32(ctx.signals.length);
await writeU32(ctx.constraints.length);
const headerSize = p - pHeaderSize - 4;
// Write constraints
///////////
await writeU32(2); // Constraints type
const pConstraintsSize = p;
await writeU32(0); // Temporally set to 0 length
for (let i=0; i<ctx.constraints.length; i++) {
if ((ctx.verbose)&&(i%10000 == 0)) {
if (ctx.verbose) console.log("writing constraint: ", i);
await fd.datasync();
}
await writeConstraint(ctx.constraints[i]);
}
const constraintsSize = p - pConstraintsSize - 4;
// Write map
///////////
await writeU32(3); // wires2label type
const pMapSize = p;
await writeU32(0); // Temporally set to 0 length
const arr = new Array(NWires);
for (let i=0; i<ctx.signals.length; i++) {
const outIdx = ctx.signals[i].id;
if (ctx.signals[i].e>=0) continue; // If has an alias, continue..
assert(typeof outIdx != "undefined", `Signal ${i} does not have index`);
if (outIdx>=NWires) continue; // Is a constant or a discarded variable
if (typeof arr[ctx.signals[i].id] == "undefined") {
arr[outIdx] = i;
}
}
for (let i=0; i<arr.length; i++) {
await writeU32(arr[i]);
if ((ctx.verbose)&&(i%100000)) console.log("writing label2wire map: ", i);
}
const mapSize = p - pMapSize -4;
// Write sizes
await writeU32(headerSize, pHeaderSize);
await writeU32(fieldDefSize, pFieldDefSize);
await writeU32(constraintsSize, pConstraintsSize);
await writeU32(mapSize, pMapSize);
await fd.sync();
await fd.close();
async function writeU32(v, pos) {
const b = Buffer.allocUnsafe(4);
b.writeInt32LE(v);
await fd.write(b, 0, 4, pos);
if (typeof(pos) == "undefined") p += 4;
}
async function writeConstraint(c) {
await writeLC(c.a);
await writeLC(c.b);
await writeLC(ctx.lc.neg(c.c));
}
async function writeLC(lc) {
const idxs = Object.keys(lc.coefs);
await writeU32(idxs.length);
for (let s in lc.coefs) {
let lSignal = ctx.signals[s];
while (lSignal.e >=0 ) lSignal = ctx.signals[lSignal.e];
await writeU32(lSignal.id);
await writeBigInt(lc.coefs[s]);
}
}
async function writeBigInt(n) {
const bytes = bigInt(n).toArray(256).value.reverse();
await fd.write(Buffer.from([bytes.length, ...bytes ]));
p += bytes.length+1;
}
}

View File

@@ -0,0 +1,52 @@
const Readable = require("stream").Readable;
module.exports = function streamFromMultiarray(ma) {
const rs = Readable();
let curIndex = getFirstIdx(ma);
rs._read = function() {
let res;
res = objFromIdx(ma, curIndex);
curIndex = nextIdx(curIndex);
if (res!=null) {
rs.push(res + "\n");
} else {
rs.push(null);
}
};
return rs;
function getFirstIdx(ma) {
if (!Array.isArray(ma)) return [];
return [0, ...getFirstIdx(ma[0])];
}
function nextIdx(idx) {
if (idx == null) return null;
if (idx.length == 0) return null;
const parentIdx = idx.slice(0,-1);
const itObj = objFromIdx(ma, parentIdx);
const newLastIdx = idx[idx.length-1]+1;
if (newLastIdx < itObj.length) {
const resIdx = idx.slice();
resIdx[resIdx.length-1] = newLastIdx;
return [...resIdx, ...getFirstIdx(itObj[newLastIdx])];
} else {
return nextIdx(parentIdx);
}
}
function objFromIdx(ma, idx) {
if (idx == null) return null;
if (idx.length == 0) return ma;
if (ma.length == 0) return "";
return objFromIdx(ma[idx[0]], idx.slice(1));
}
};

134
src/utils.js Normal file
View File

@@ -0,0 +1,134 @@
const fnv = require("fnv-plus");
const bigInt = require("big-integer");
module.exports.ident =ident;
module.exports.extractSizes =extractSizes;
module.exports.flatArray = flatArray;
module.exports.csArr = csArr;
module.exports.accSizes = accSizes;
module.exports.fnvHash = fnvHash;
module.exports.stringifyBigInts = stringifyBigInts;
module.exports.unstringifyBigInts = unstringifyBigInts;
module.exports.sameSizes = sameSizes;
module.exports.isDefined = isDefined;
module.exports.accSizes2Str = accSizes2Str;
function ident(text) {
if (typeof text === "string") {
let lines = text.split("\n");
for (let i=0; i<lines.length; i++) {
if (lines[i]) lines[i] = " "+lines[i];
}
return lines.join("\n");
} else if (Array.isArray(text)) {
for (let i=0; i<text.length; i++ ) {
text[i] = ident(text[i]);
}
return text;
}
}
function extractSizes (o) {
if (! Array.isArray(o)) return [];
return [o.length, ...extractSizes(o[0])];
}
function flatArray(a) {
var res = [];
fillArray(res, a);
return res;
function fillArray(res, a) {
if (Array.isArray(a)) {
for (let i=0; i<a.length; i++) {
fillArray(res, a[i]);
}
} else {
res.push(bigInt(a));
}
}
}
// Input [1,2,3]
// Returns " ,1 ,2, 3"
function csArr(_arr) {
let S = "";
const arr = _arr || [];
for (let i=0; i<arr.length; i++) {
S = " ,"+arr[i];
}
return S;
}
function accSizes(_sizes) {
const sizes = _sizes || [];
const accSizes = [1, 0];
for (let i=sizes.length-1; i>=0; i--) {
accSizes.unshift(accSizes[0]*sizes[i]);
}
return accSizes;
}
function fnvHash(str) {
return fnv.hash(str, 64).hex();
}
function stringifyBigInts(o) {
if ((typeof(o) == "bigint") || o.isZero !== undefined) {
return o.toString(10);
} else if (Array.isArray(o)) {
return o.map(stringifyBigInts);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
res[k] = stringifyBigInts(o[k]);
}
return res;
} else {
return o;
}
}
function unstringifyBigInts(o) {
if ((typeof(o) == "string") && (/^[0-9]+$/.test(o) )) {
return bigInt(o);
} else if (Array.isArray(o)) {
return o.map(unstringifyBigInts);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
res[k] = unstringifyBigInts(o[k]);
}
return res;
} else {
return bigInt(o);
}
}
function sameSizes(s1, s2) {
if (!Array.isArray(s1)) return false;
if (!Array.isArray(s2)) return false;
if (s1.length != s2.length) return false;
for (let i=0; i<s1.length; i++) {
if (s1[i] != s2[i]) return false;
}
return true;
}
function isDefined(v) {
return ((typeof(v) != "undefined")&&(v != null));
}
function accSizes2Str(sizes) {
if (sizes.length == 2) return "";
return `[${sizes[0]/sizes[1]}]`+accSizes2Str(sizes.slice(1));
}

343
test/basiccases.js Normal file
View File

@@ -0,0 +1,343 @@
const path = require("path");
const bigInt = require("big-integer");
const c_tester = require("../index.js").c_tester;
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
function normalize(o) {
if ((typeof(o) == "bigint") || o.isZero !== undefined) {
const res = bigInt(o);
return norm(res);
} else if (Array.isArray(o)) {
return o.map(normalize);
} else if (typeof o == "object") {
const res = {};
for (let k in o) {
res[k] = normalize(o[k]);
}
return res;
} else {
const res = bigInt(o);
return norm(res);
}
function norm(n) {
let res = n.mod(__P__);
if (res.isNegative()) res = __P__.add(res);
return res;
}
}
async function doTest(circuit, testVectors) {
const cir = await c_tester(path.join(__dirname, "circuits", circuit));
for (let i=0; i<testVectors.length; i++) {
const w = await cir.calculateWitness(normalize(testVectors[i][0]));
await cir.assertOut(w, normalize(testVectors[i][1]) );
}
await cir.release();
}
describe("basic cases", function () {
this.timeout(100000);
it("inout", async () => {
await doTest(
"inout.circom",
[
[{in1: 1, in2: [2,3], in3:[[4,5], [6,7], [8,9]]}, {out1: 1, out2: [2,3], out3: [[4,5], [6,7],[8,9]]}],
]
);
});
it("add", async () => {
await doTest(
"add.circom",
[
[{in: [0,0]}, {out: 0}],
[{in: [0,1]}, {out: 1}],
[{in: [1,2]}, {out: 3}],
[{in: [__P__.minus(1),1]}, {out: 0}],
]
);
});
it("add constant", async () => {
await doTest(
"addconst1.circom",
[
[{in: 0}, {out: 15}],
[{in: 10}, {out: 25}],
[{in: __P__.minus(2)}, {out: 13}],
]
);
});
it("for unrolled", async () => {
await doTest(
"forunrolled.circom",
[
[{in: 0}, {out: [0,1,2]}],
[{in: 10}, {out: [10, 11, 12]}],
[{in: __P__.minus(2)}, {out: [__P__.minus(2), __P__.minus(1), 0]}],
]
);
});
it("for rolled", async () => {
await doTest(
"forrolled.circom",
[
[{in: 0}, {out: 0}],
[{in: 10}, {out: 10}],
]
);
});
it("while unrolled", async () => {
await doTest(
"whileunrolled.circom",
[
[{in: 0}, {out: [0,1,2]}],
[{in: 10}, {out: [10, 11, 12]}],
[{in: __P__.minus(2)}, {out: [__P__.minus(2), __P__.minus(1), 0]}],
]
);
});
it("while rolled", async () => {
await doTest(
"whilerolled.circom",
[
[{in: 0}, {out: 0}],
[{in: 10}, {out: 10}],
]
);
});
it("function1", async () => {
await doTest(
"function1.circom",
[
[{in: 0}, {out: 3}],
[{in: 10}, {out: 13}],
[{in: __P__.minus(2)}, {out: 1}],
]
);
});
it("function2", async () => {
await doTest(
"function2.circom",
[
[{in: 0}, {out: 3}],
[{in: 10}, {out: 13}],
[{in: __P__.minus(2)}, {out: 1}],
]
);
});
it("constants1", async () => {
await doTest(
"constants1.circom",
[
[{in: 0}, {out: 42}],
[{in: 10}, {out: 52}],
[{in: __P__.minus(2)}, {out: 40}],
]
);
});
it("arrays", async () => {
await doTest(
"arrays.circom",
[
[{in: 0}, {out: [1, 8, 51]}],
[{in: 10}, {out: [11, 28, 111]}],
[{in: __P__.minus(2)}, {out: [__P__.minus(1), 4, 39]}],
]
);
});
it("if unrolled", async () => {
await doTest(
"ifunrolled.circom",
[
[{in: 0}, {out: [1, 3, 6]}],
[{in: 10}, {out: [11, 13, 16]}],
[{in: __P__.minus(2)}, {out: [__P__.minus(1), 1, 4]}],
]
);
});
it("if rolled", async () => {
await doTest(
"ifrolled.circom",
[
[{in: 0}, {out: [1, 0, 0]}],
[{in: 1}, {out: [0, 1, 0]}],
[{in: 2}, {out: [0, 0, 1]}],
[{in: 3}, {out: [0, 0, 0]}],
[{in: __P__.minus(2)}, {out: [0,0,0]}],
]
);
});
it("inc", async () => {
await doTest(
"inc.circom",
[
[{in: 0}, {out: [5, 2]}],
[{in: 1}, {out: [6, 4]}],
[{in: 2}, {out: [7, 6]}],
[{in: 3}, {out: [8, 8]}],
[{in: __P__.minus(2)}, {out: [3,__P__.minus(2)]}],
]
);
});
it("dec", async () => {
await doTest(
"dec.circom",
[
[{in: 0}, {out: [1, __P__.minus(2)]}],
[{in: 1}, {out: [2, 0]}],
[{in: 2}, {out: [3, 2]}],
[{in: 3}, {out: [4, 4]}],
[{in: __P__.minus(2)}, {out: [__P__.minus(1),__P__.minus(6)]}],
]
);
});
it("ops", async () => {
await doTest(
"ops.circom",
[
[{in: [-2, 2]}, {add: 0, sub: -4, mul: -4}],
[{in: [-1, 1]}, {add: 0, sub: -2, mul: -1}],
[{in: [ 0, 0]}, {add: 0, sub: 0, mul: 0}],
[{in: [ 1,-1]}, {add: 0, sub: 2, mul: -1}],
[{in: [ 2,-2]}, {add: 0, sub: 4, mul: -4}],
[{in: [-2,-3]}, {add: -5, sub: 1, mul: 6}],
[{in: [ 2, 3]}, {add: 5, sub: -1, mul: 6}],
]
);
});
it("ops2", async () => {
await doTest(
"ops2.circom",
[
[{in: [-2, 2]}, {div: -1, idiv: bigInt("10944121435919637611123202872628637544274182200208017171849102093287904247807"), mod: 1}],
[{in: [-1, 1]}, {div: -1, idiv: -1, mod: 0}],
[{in: [ 1,-1]}, {div: -1, idiv: 0, mod: 1}],
]
);
});
it("ops3", async () => {
await doTest(
"ops3.circom",
[
[{in: [-2, 2]}, {neg1: 2,neg2: -2, pow: 4}],
[{in: [0, 1]}, {neg1: 0, neg2: -1, pow: 0}],
[{in: [ 1,-1]}, {neg1: -1, neg2: 1, pow: 1}],
]
);
});
it("Comparation ops", async () => {
await doTest(
"opscmp.circom",
[
[{in: [ 8, 9]}, {lt: 1, leq: 1, eq:0, neq:1, geq: 0, gt:0}],
[{in: [-2,-2]}, {lt: 0, leq: 1, eq:1, neq:0, geq: 1, gt:0}],
[{in: [-1,-2]}, {lt: 0, leq: 0, eq:0, neq:1, geq: 1, gt:1}],
[{in: [ 1,-1]}, {lt: 0, leq: 0, eq:0, neq:1, geq: 1, gt:1}], // In mod, negative values are higher than positive.
]
);
});
it("Bit ops", async () => {
const mask = bigInt("14474011154664524427946373126085988481658748083205070504932198000989141204991");
const m1m = bigInt("7414231717174750794300032619171286606889616317210963838766006185586667290624");
await doTest(
"opsbit.circom",
[
[{in: [ 5, 3]}, {and: 1, or: 7, xor:6, not1:mask.minus(5), shl: 40, shr:0}],
[{in: [ 0, 0]}, {and: 0, or: 0, xor:0, not1:mask, shl: 0, shr:0}],
[{in: [-1, 1]}, {and: 0, or: m1m.add(bigInt.one), xor:m1m.add(bigInt.one), not1:mask.minus(m1m), shl: m1m.shiftLeft(1).and(mask), shr:__P__.shiftRight(1).and(mask)}],
]
);
});
it("Logical ops", async () => {
await doTest(
"opslog.circom",
[
[{in: [ 5, 0]}, {and: 0, or: 1, not1:0}],
[{in: [ 0, 1]}, {and: 0, or: 1, not1:1}],
[{in: [-1, 9]}, {and: 1, or: 1, not1:0}],
[{in: [ 0, 0]}, {and: 0, or: 0, not1:1}],
]
);
});
it("Conditional Ternary operator", async () => {
await doTest(
"condternary.circom",
[
[{in: 0}, {out: 21}],
[{in: 1}, {out: 1}],
[{in: 2}, {out: 23}],
[{in:-1}, {out: 20}],
]
);
});
it("Compute block", async () => {
await doTest(
"compute.circom",
[
[{x: 1}, {y: 7}],
[{x: 2}, {y: 7}],
[{x: 3}, {y: 11}],
[{x:-1}, {y: -5}],
]
);
});
it("Component array ", async () => {
await doTest(
"componentarray.circom",
[
[{in: 1}, {out: 1}],
[{in: 2}, {out: 256}],
[{in: 3}, {out: 6561}],
[{in:-1}, {out: 1}],
]
);
});
it("Component array 2d", async () => {
await doTest(
"componentarray2.circom",
[
[{in: [1,2]}, {out: [1, 256]}],
[{in: [0,3]}, {out: [0, 6561]}],
]
);
});
it("Constant circuit", async () => {
await doTest(
"constantcircuit.circom",
[
// 0xbb67ae85
[{}, {out: [1,0,1,0, 0,0,0,1, 0,1,1,1, 0,1,0,1, 1,1,1,0, 0,1,1,0, 1,1,0,1, 1,1,0,1]}],
]
);
});
it("Constant internal circuit", async () => {
await doTest(
"constantinternalcircuit.circom",
[
[{in: 1}, {out: 5}],
[{in: 0}, {out: 4}],
[{in: -2}, {out: 2}],
[{in: 10}, {out: 14}]
]
);
});
it("include", async () => {
await doTest(
"include.circom",
[
[{in: 3}, {out: 6}],
[{in: 6}, {out: 15}],
]
);
});
});

View File

@@ -49,4 +49,19 @@ describe("Sum test", () => {
// await compiler(path.join(__dirname, "circuits", "assignsignal.circom"));
// }, /Cannot assign to a signal .*/);
// });
it("Should compile a code with compute", async () => {
const cirDef = await compiler(path.join(__dirname, "circuits", "compute.circom"));
const circuit = new snarkjs.Circuit(cirDef);
const witness = circuit.calculateWitness({ "x": 6});
assert(witness[0].equals(bigInt(1)));
assert(witness[1].equals(bigInt(37)));
assert(witness[2].equals(bigInt(6)));
});
it("Should compile a code with compute", async () => {
const cirDef = await compiler(path.join(__dirname, "circuits", "inout.circom"));
assert.equal(cirDef.constraints.length, 1);
});
});

9
test/circuits/add.circom Normal file
View File

@@ -0,0 +1,9 @@
template Add() {
signal input in[2];
signal output out;
out <== in[0] + in[1];
}
component main = Add();

View File

@@ -0,0 +1,16 @@
template AddConst(c) {
signal input in;
signal output out;
var a = 2;
var b = 3;
a=a+b;
a=a+4;
a=a+c;
out <== 5 + a + in;
}
// It should out <== in + 1+2+3+4+5 = in + 15
component main = AddConst(1);

View File

@@ -0,0 +1,40 @@
// arr1
function Add3(arr1, arr2, arr3) {
var res[3];
res[0] = arr1;
res[1] = 0;
for (var i=0; i<2; i += 1) {
res[1] = res[1] + arr2[i];
}
res[2] = 0;
for (var i=0; i<2; i++) {
for (var j=0; j<3; j += 1) {
res[2] = res[2] + arr3[i][j];
}
}
return res;
}
template Main() {
signal input in;
signal output out[3];
var c[3] = Add3(1, [2,3], [[4,5,6], [7,8,9]]); // [1, 5, 39];
var d[3] = Add3(in, [in+1, in+2], [[in+1, in+2, in+3], [in+1, in+2, in+3]]);
out[0] <-- d[0] + c[0];
out[0] === in+c[0];
out[1] <-- d[1]+c[1];
out[1] === 2*in+3+c[1];
out[2] <-- d[2]+c[2];
out[2] === 6*in+12+c[2];
}
component main = Main();

View File

@@ -0,0 +1,28 @@
template Square() {
signal input in;
signal output out;
out <== in*in;
}
template Main(n) {
signal input in;
signal output out;
component squares[n];
var i;
for (i=0; i<n; i++) {
squares[i] = Square();
if (i==0) {
squares[i].in <== in;
} else {
squares[i].in <== squares[i-1].out;
}
}
squares[n-1].out ==> out;
}
component main = Main(3);

View File

@@ -0,0 +1,27 @@
template Square() {
signal input in;
signal output out;
out <== in**2;
}
template Main(n, nrounds) {
signal input in[n];
signal output out[n];
component squares[n][nrounds];
for (var i=0; i<n; i++) {
for (var r=0; r<nrounds; r++) {
squares[i][r] = Square();
if (r==0) {
squares[i][r].in <== in[i];
} else {
squares[i][r].in <== squares[i][r-1].out;
}
}
squares[i][nrounds-1].out ==> out[i];
}
}
component main = Main(2, 3);

View File

@@ -0,0 +1,17 @@
template X() {
signal input x;
signal output y;
signal x2;
signal x3;
var a;
compute {
a = (x*x*x+6)/x;
y <-- a;
}
x2 <== x*x;
x3 <== x2*x;
x*y === x3+6;
}
component main = X();

View File

@@ -0,0 +1,15 @@
template CondTernary() {
signal input in;
signal output out;
var a = 3;
var b = a==3 ? 1 : 2; // b is 1
var c = a!=3 ? 10 : 20; // c is 20
var d = b+c; // d is 21
out <-- ((in & 1) != 1) ? in + d : in; // Add 21 if in is pair
}
component main = CondTernary()

View File

@@ -0,0 +1,17 @@
template H(x) {
signal output out[32];
var c[8] = [0x6a09e667,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19];
for (var i=0; i<32; i++) {
out[i] <== (c[x] >> i) & 1;
}
}
component main = H(1);

View File

@@ -0,0 +1,18 @@
template Const() {
signal output out[2];
out[0] <== 2;
out[1] <== 2;
}
template Main() {
signal input in;
signal output out;
component const = Const();
out <== const.out[0] + const.out[1] + in;
}
component main = Main();

View File

@@ -0,0 +1,39 @@
template Add(n) {
signal input in[n];
signal output out;
var lc = 0;
for (var i=0; i<n; i++) {
lc = lc + in[i];
}
out <== lc;
}
function FAdd(a,b) {
return a+b;
}
template Main() {
signal input in;
signal output out;
var o = FAdd(3,4);
o = o + FAdd(3,4);
o = o + FAdd(3,4); // o = 21
component A1 = Add(2);
A1.in[0] <== in;
A1.in[1] <== o;
component A2 = Add(2);
A2.in[0] <== A1.out;
A2.in[1] <== o;
out <== A2.out; // in + 42
}
component main = Main();

23
test/circuits/dec.circom Normal file
View File

@@ -0,0 +1,23 @@
template Main() {
signal input in;
signal output out[2];
// First play with variables;
var c = 3;
var d = c--; // d --> 3
var e = --c; // e --> 1
out[0] <== in + e; // in + 1
// Then play with signals
c = in;
d = c--; //d <-- in;
e = --c; // d <-- in-2
out[1] <== in + e; // 2*in -2
}
component main = Main();

View File

@@ -0,0 +1,14 @@
template ForRolled() {
signal input in;
signal output out;
var acc = 0;
for (var i=0; i<in; i = i+1) {
acc = acc + 1;
}
out <== acc;
}
component main = ForRolled();

View File

@@ -0,0 +1,10 @@
template ForUnrolled(n) {
signal input in;
signal output out[n];
for (var i=0; i<n; i = i+1) {
out[i] <== in + i;
}
}
component main = ForUnrolled(3);

View File

@@ -0,0 +1,12 @@
function func1(a,b) {
return a+b;
}
template Main() {
signal input in;
signal output out;
out <== func1(in, 3);
}
component main = Main();

View File

@@ -0,0 +1,13 @@
function fnConst(a,b) {
return a+b;
}
template Main() {
signal input in;
signal output out;
var a = fnConst(1,2);
out <== in +a;
}
component main = Main();

View File

@@ -0,0 +1,26 @@
template Main() {
signal input in;
signal output out[3];
if (in == 0) {
out[0] <-- 1; // TRUE
}
if (in != 0) {
out[0] <-- 0;
}
if (in == 1) {
out[1] <-- 1; // TRUE
} else {
out[1] <-- 0;
}
if (in == 2) {
out[2] <-- 1;
} else {
out[2] <-- 0; // TRUE
}
}
component main = Main();

View File

@@ -0,0 +1,31 @@
template Main() {
signal input in;
signal output out[3];
var c = 1;
if (c == 1) {
out[0] <== in +1; // TRUE
}
if (c == 0) {
out[0] <== in +2;
}
c = c +1;
if (c == 2) {
out[1] <== in + 3; // TRUE
} else {
out[1] <== in + 4;
}
c = c +1;
if (c == 2) {
out[2] <== in + 5;
} else {
out[2] <== in + 6; // TRUE
}
}
component main = Main();

1
test/circuits/in.bin Normal file
View File

@@ -0,0 +1 @@


1
test/circuits/in.json Normal file
View File

@@ -0,0 +1 @@
{"in1": 1, "in2": [2,3], "in3":[[4,5], [6,7], [8,9]]}

24
test/circuits/inc.circom Normal file
View File

@@ -0,0 +1,24 @@
template Main() {
signal input in;
signal output out[2];
// First play with variables;
var c = 3;
var d = c++; // d --> 3
var e = ++c; // e --> 5
out[0] <== in + e; // in + 5
// Then play with signals
c = in;
d = c++; //d <-- in;
e = ++c; // d <-- in+2
out[1] <== in + e; // 2*in +2
}
component main = Main();

1
test/circuits/inc.json Normal file
View File

@@ -0,0 +1 @@
{"x": "3"}

View File

@@ -0,0 +1,16 @@
include "included.circom";
include "included.circom"; // Include twice is fine. The second one is not included
template Main() {
signal input in;
signal output out;
component t1 = T1();
var a = F1(3);
in ==> t1.in;
t1.out + a ==> out; /// out <-- in**2/3+3
}
component main = Main();

View File

@@ -0,0 +1,10 @@
template T1() {
signal input in;
signal output out;
out <== in**2/3;
}
function F1(a) {
return a**2/3;
}

View File

@@ -0,0 +1,54 @@
template Internal() {
signal input in1;
signal input in2[2];
signal input in3[3][2];
signal output out1;
signal output out2[2];
signal output out3[3][2];
out1 <== in1;
out2[0] <== in2[0];
out2[1] <== in2[1];
out3[0][0] <== in3[0][0];
out3[0][1] <== in3[0][1];
out3[1][0] <== in3[1][0];
out3[1][1] <== in3[1][1];
out3[2][0] <== in3[2][0];
out3[2][1] <== in3[2][1];
}
template InOut() {
signal input in1;
signal input in2[2];
signal input in3[3][2];
signal output out1;
signal output out2[2];
signal output out3[3][2];
component internal = Internal();
internal.in1 <== in1;
internal.in2[0] <== in2[0];
internal.in2[1] <== in2[1];
internal.in3[0][0] <== in3[0][0];
internal.in3[0][1] <== in3[0][1];
internal.in3[1][0] <== in3[1][0];
internal.in3[1][1] <== in3[1][1];
internal.in3[2][0] <== in3[2][0];
internal.in3[2][1] <== in3[2][1];
internal.out1 ==> out1;
internal.out2[0] ==> out2[0];
internal.out2[1] ==> out2[1];
internal.out3[0][0] ==> out3[0][0];
internal.out3[0][1] ==> out3[0][1];
internal.out3[1][0] ==> out3[1][0];
internal.out3[1][1] ==> out3[1][1];
internal.out3[2][0] ==> out3[2][0];
internal.out3[2][1] ==> out3[2][1];
}
component main = InOut();

12
test/circuits/ops.circom Normal file
View File

@@ -0,0 +1,12 @@
template Ops() {
signal input in[2];
signal output add;
signal output sub;
signal output mul;
add <-- in[0] + in[1];
sub <-- in[0] - in[1];
mul <-- in[0] * in[1];
}
component main = Ops();

12
test/circuits/ops2.circom Normal file
View File

@@ -0,0 +1,12 @@
template Ops2() {
signal input in[2];
signal output div;
signal output idiv;
signal output mod;
div <-- in[0] / in[1];
idiv <-- in[0] \ in[1];
mod <-- in[0] % in[1];
}
component main = Ops2();

12
test/circuits/ops3.circom Normal file
View File

@@ -0,0 +1,12 @@
template Ops3() {
signal input in[2];
signal output neg1;
signal output neg2;
signal output pow;
neg1 <-- -in[0];
neg2 <-- -in[1];
pow <-- in[0] ** in[1];
}
component main = Ops3();

Some files were not shown because too many files have changed in this diff Show More