diff --git a/.gitignore b/.gitignore index 40d4c9c..bbed9c8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ node_modules/* +# Circomkit generated build/* ptau/* circuits/test/*.circom circuits/main/* + +# Rust generated inputs/**/*.json \ No newline at end of file diff --git a/circuits.json b/circuits.json index 43017d8..c931162 100644 --- a/circuits.json +++ b/circuits.json @@ -1,50 +1,66 @@ { - "test_extract": { + "extract": { "file": "extract", "template": "Extract", "params": [ - 4, - 21 + 157, + 13 ] }, - "test_extract_two_key": { + "value_string": { "file": "extract", "template": "Extract", "params": [ - 4, - 40 + 12, + 1 ] }, - "test_extract_depth": { + "value_number": { "file": "extract", "template": "Extract", "params": [ - 4, - 64 + 12, + 2 ] }, - "test_extract_sambhav": { + "value_array": { "file": "extract", "template": "Extract", "params": [ - 4, - 105 + 18, + 2 ] }, - "test_extract_hard": { + "value_array_nested": { "file": "extract", "template": "Extract", "params": [ - 4, - 48 + 24, + 4 ] }, - "extract": { + "value_array_object": { + "file": "extract", + "template": "Extract", + "params": [ + 25, + 4 + ] + }, + "value_array_object_array": { + "file": "extract", + "template": "Extract", + "params": [ + 31, + 5 + ] + }, + "value_object": { "file": "extract", "template": "Extract", "params": [ - 10, - 787 + 21, + 3 ] } } \ No newline at end of file diff --git a/circuits/bytes.circom b/circuits/bytes.circom deleted file mode 100644 index 6b5c38b..0000000 --- a/circuits/bytes.circom +++ /dev/null @@ -1,58 +0,0 @@ -pragma circom 2.1.9; - -/* -All tests for this file are located in: `./test/bytes.test.ts` - -Some of the functions here were based off the circomlib: -https://github.com/iden3/circomlib/blob/cff5ab6288b55ef23602221694a6a38a0239dcc0/circuits/bitify.circom -*/ - -/* -This function reads in a unsigned 8-bit integer and converts it to an array of bits. - -# Inputs: -- `in`: a number -- `array[n]`: the array we want to search through -- `out`: either `0` or `1` - - `1` if `in` is found inside `array` - - `0` otherwise - -# Constraints: -- `in`: must be between `0` and `2**8 - 1` -*/ -template U8ToBits() { - signal input in; - signal byte[8]; - var lc1 = 0; - - // log("input to u8ToByte: ", in); - - var e2 = 1; - for (var i = 0; i < 8; i++) { - byte[i] <-- (in >> i) & 1; - byte[i] * (byte[i] - 1) === 0; - lc1 += byte[i] * e2; - e2 = e2 + e2; - } - lc1 === in; -} - -/* -This function reads in an array of unsigned numbers that will be constrained to be valid unsigned 8-bit integers. - -# Inputs: -- `n`: the length of the ASCII string (as integers) to verify -- `in[n]`: a list of numbers - -# Constraints: -- `in[n]`: each element of this array must be between `0` and `2**8-1` -*/ -template ASCII(n) { - signal input in[n]; - - component Byte[n]; - for(var i = 0; i < n; i++) { - Byte[i] = U8ToBits(); - Byte[i].in <== in[i]; - } -} \ No newline at end of file diff --git a/circuits/extract.circom b/circuits/extract.circom index 76ebcae..cffeeee 100644 --- a/circuits/extract.circom +++ b/circuits/extract.circom @@ -1,54 +1,53 @@ pragma circom 2.1.9; -include "bytes.circom"; -include "operators.circom"; +include "utils.circom"; include "parser.circom"; -template Extract(KEY_BYTES, DATA_BYTES) { - signal input key[KEY_BYTES]; +template Extract(DATA_BYTES, MAX_STACK_HEIGHT) { signal input data[DATA_BYTES]; - signal output KeyMatches[DATA_BYTES - KEY_BYTES]; // TODO: Add assertions on the inputs here! //--------------------------------------------------------------------------------------------// //-CONSTRAINTS--------------------------------------------------------------------------------// - //--------------------------------------------------------------------------------------------// - // Working with a single key for now to do substring matching - component keyASCII = ASCII(KEY_BYTES); - keyASCII.in <== key; - + //--------------------------------------------------------------------------------------------// component dataASCII = ASCII(DATA_BYTES); dataASCII.in <== data; //--------------------------------------------------------------------------------------------// // Initialze the parser component State[DATA_BYTES]; - State[0] = StateUpdate(); - State[0].byte <== data[0]; - State[0].tree_depth <== 0; - State[0].parsing_key <== 0; - State[0].inside_key <== 0; - State[0].parsing_value <== 0; - State[0].inside_value <== 0; - - for(var data_pointer = 1; data_pointer < DATA_BYTES; data_pointer++) { - State[data_pointer] = StateUpdate(); - State[data_pointer].byte <== data[data_pointer]; - State[data_pointer].tree_depth <== State[data_pointer - 1].next_tree_depth; - State[data_pointer].parsing_key <== State[data_pointer - 1].next_parsing_key; - State[data_pointer].inside_key <== State[data_pointer - 1].next_inside_key; - State[data_pointer].parsing_value <== State[data_pointer - 1].next_parsing_value; - State[data_pointer].inside_value <== State[data_pointer - 1].next_inside_value; + State[0] = StateUpdate(MAX_STACK_HEIGHT); + State[0].byte <== data[0]; + for(var i = 0; i < MAX_STACK_HEIGHT; i++) { + State[0].stack[i] <== [0,0]; + } + State[0].parsing_string <== 0; + State[0].parsing_number <== 0; + + for(var data_idx = 1; data_idx < DATA_BYTES; data_idx++) { + State[data_idx] = StateUpdate(MAX_STACK_HEIGHT); + State[data_idx].byte <== data[data_idx]; + State[data_idx].stack <== State[data_idx - 1].next_stack; + State[data_idx].parsing_string <== State[data_idx - 1].next_parsing_string; + State[data_idx].parsing_number <== State[data_idx - 1].next_parsing_number; // Debugging - log("State[", data_pointer, "].tree_depth", "= ", State[data_pointer].tree_depth); - log("State[", data_pointer, "].parsing_key", "= ", State[data_pointer].parsing_key); - log("State[", data_pointer, "].inside_key", "= ", State[data_pointer].inside_key); - log("State[", data_pointer, "].parsing_value", "= ", State[data_pointer].parsing_value); - log("State[", data_pointer, "].inside_value", "= ", State[data_pointer].inside_value); - log("---"); + for(var i = 0; i isz.in; - - isz.out ==> out; -} - -/* -This function is an indicator for two equal array inputs. - -# Inputs: -- `n`: the length of arrays to compare -- `in[2][n]`: two arrays of `n` numbers -- `out`: either `0` or `1` - - `1` if `in[0]` is equal to `in[1]` as arrays (i.e., component by component) - - `0` otherwise -*/ -template IsEqualArray(n) { - signal input in[2][n]; - signal output out; - - var accum = 0; - component equalComponent[n]; - - for(var i = 0; i < n; i++) { - equalComponent[i] = IsEqual(); - equalComponent[i].in[0] <== in[0][i]; - equalComponent[i].in[1] <== in[1][i]; - accum += equalComponent[i].out; - } - - component totalEqual = IsEqual(); - totalEqual.in[0] <== n; - totalEqual.in[1] <== accum; - out <== totalEqual.out; -} - - -// TODO: There should be a way to have the below assertion come from the field itself. -/* -This function is an indicator for if an array contains an element. - -# Inputs: -- `n`: the size of the array to search through -- `in`: a number -- `array[n]`: the array we want to search through -- `out`: either `0` or `1` - - `1` if `in` is found inside `array` - - `0` otherwise -*/ -template Contains(n) { - assert(n > 0); - /* - If `n = p` for this large `p`, then it could be that this function - returns the wrong value if every element in `array` was equal to `in`. - This is EXTREMELY unlikely and iterating this high is impossible anyway. - But it is better to check than miss something, so we bound it by `2**254` for now. - */ - assert(n < 2**254); - signal input in; - signal input array[n]; - signal output out; - - var accum = 0; - component equalComponent[n]; - for(var i = 0; i < n; i++) { - equalComponent[i] = IsEqual(); - equalComponent[i].in[0] <== in; - equalComponent[i].in[1] <== array[i]; - accum = accum + equalComponent[i].out; - } - - component someEqual = IsZero(); - someEqual.in <== accum; - - // Apply `not` to this by 1-x - out <== 1 - someEqual.out; -} - -template ArrayAdd(n) { - signal input lhs[n]; - signal input rhs[n]; - signal output out[n]; - - for(var i = 0; i < n; i++) { - out[i] <== lhs[i] + rhs[i]; - } -} - -template ArrayMul(n) { - signal input lhs[n]; - signal input rhs[n]; - signal output out[n]; - - for(var i = 0; i < n; i++) { - out[i] <== lhs[i] * rhs[i]; - } -} \ No newline at end of file diff --git a/circuits/parser.circom b/circuits/parser.circom index e4ec021..068cb46 100644 --- a/circuits/parser.circom +++ b/circuits/parser.circom @@ -1,219 +1,330 @@ -pragma circom 2.1.9; - -include "operators.circom"; /* -Notes: for `test.json` -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - POINTER | Read In: | STATE -------------------------------------------------- -State[1] | { | PARSING TO KEY -------------------------------------------------- -State[7] | " | INSIDE KEY -------------------------------------------------- -State[12]| " | NOT INSIDE KEY -------------------------------------------------- -State[13]| : | PARSING TO VALUE -------------------------------------------------- -State[15]| " | INSIDE VALUE -------------------------------------------------- -State[19]| " | NOT INSIDE VALUE -------------------------------------------------- -State[20]| " | COMPLETE WITH KV PARSING -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -State[20].next_tree_depth == 0 | VALID JSON -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# `parser` +This module consists of the core parsing components for generating proofs of selective disclosure in JSON. + +## Layout +The key ingredients of `parser` are: + - `StateUpdate`: has as input a current state of a stack-machine parser. + Also takes in a `byte` as input which combines with the current state + to produce the `next_*` states. + - `StateToMask`: Reads the current state to decide whether accept instruction tokens + or ignore them for the current task (e.g., ignore `[` if `parsing_string == 1`). + - `GetTopOfStack`: Helper function that yields the topmost allocated stack value + and a pointer (index) to that value. + - `RewriteStack`: Combines all the above data and produces the `next_stack`. + +`parser` brings in many functions from the `utils` module and `language`. +The inclusion of `langauge` allows for this file to (eventually) be generic over +a grammar for different applications (e.g., HTTP, YAML, TOML, etc.). + +## Testing +Tests for this module are located in the files: `circuits/test/parser/*.test.ts */ +pragma circom 2.1.9; + +include "utils.circom"; +include "language.circom"; + /* -TODO +This template is for updating the state of the parser from a current state to a next state. + +# Params: + - `MAX_STACK_HEIGHT`: the maximum stack height that can be used before triggering overflow. + +# Inputs: + - `byte` : the byte value of ASCII that was read by the parser. + - `stack[MAX_STACK_HEIGHT][2]`: the stack machine's current stack. + - `parsing_number` : a bool flag that indicates whether the parser is currently parsing a string or not. + - `parsing_number` : a bool flag that indicates whether the parser is currently parsing a number or not. + +# Outputs: + - `next_stack[MAX_STACK_HEIGHT][2]`: the stack machine's stack after reading `byte`. + - `next_parsing_number` : a bool flag that indicates whether the parser is currently parsing a string or not after reading `byte`. + - `next_parsing_number` : a bool flag that indicates whether the parser is currently parsing a number or not after reading `byte`. */ -template StateUpdate() { - signal input byte; - - signal input tree_depth; // STATUS_INDICATOR -- how deep in a JSON branch we are, e.g., `user.balance.value` key should be at depth `3`. - // constrainted to be greater than or equal to `0`. - signal input parsing_key; // BIT_FLAG -- whether we are currently parsing bytes until we find the next key (mutally exclusive with `inside_key` and both `*_value flags). - signal input inside_key; // BIT_FLAG -- whether we are currently inside a key (mutually exclusive with `parsing_key` and both `*_value` flags). - signal input parsing_value; // BIT_FLAG -- whether we are currently parsing bytes until we find the next value (mutually exclusive with `inside_value` and both `*_key` flags). - signal input inside_value; // BIT_FLAG -- whether we are currently inside a value (mutually exclusive with `parsing_value` and both `*_key` flags). - - signal output next_tree_depth; // STATUS_INDICATOR -- next state for `tree_depth`. - signal output next_parsing_key; // BIT_FLAG -- next state for `parsing_key`. - signal output next_inside_key; // BIT_FLAG -- next state for `inside_key`. - signal output next_parsing_value; // BIT_FLAG -- next state for `parsing_value`. - signal output next_inside_value; // BIT_FLAG -- next state for `inside_value`. - - // TODO: Add this in! - // signal input escaping; // BIT_FLAG -- whether we have hit an escape ASCII symbol inside of a key or value. - // signal output escaping; +template StateUpdate(MAX_STACK_HEIGHT) { + signal input byte; // TODO: Does this need to be constrained within here? - //--------------------------------------------------------------------------------------------// - //-Delimeters---------------------------------------------------------------------------------// - // - ASCII char: `{` - var start_brace = 123; - // - ASCII char: `}` - var end_brace = 125; - // - ASCII char `[` - var start_bracket = 91; - // - ASCII char `]` - var end_bracket = 93; - // - ASCII char `"` - var quote = 34; - // - ASCII char `:` - var colon = 58; - // - ASCII char `,` - var comma = 44; - //--------------------------------------------------------------------------------------------// - // White space - // - ASCII char: `\n` - var newline = 10; - // - ASCII char: ` ` - var space = 32; - //--------------------------------------------------------------------------------------------// - // Escape - // - ASCII char: `\` - var escape = 92; - //--------------------------------------------------------------------------------------------// + signal input stack[MAX_STACK_HEIGHT][2]; + signal input parsing_string; + signal input parsing_number; + + signal output next_stack[MAX_STACK_HEIGHT][2]; + signal output next_parsing_string; + signal output next_parsing_number; + + component Syntax = Syntax(); + component Command = Command(); - // TODO: ADD CASE FOR `is_number` for in range 48-57 https://www.ascii-code.com since a value may just be a number //--------------------------------------------------------------------------------------------// - //-Instructions for ASCII---------------------------------------------------------------------// - var state[5] = [tree_depth, parsing_key, inside_key, parsing_value, inside_value]; - var do_nothing[5] = [ 0, 0, 0, 0, 0 ]; // Command returned by switch if we want to do nothing, e.g. read a whitespace char while looking for a key - var hit_start_brace[5] = [ 1, 1, 0, -1, 0 ]; // Command returned by switch if we hit a start brace `{` - var hit_end_brace[5] = [-1, 0, 0, 0, 0 ]; // Command returned by switch if we hit a end brace `}` - var hit_quote[5] = [ 0, 0, 1, 0, 1 ]; // Command returned by switch if we hit a quote `"` - var hit_colon[5] = [ 0, -1, 0, 1, 0 ]; // Command returned by switch if we hit a colon `:` - var hit_comma[5] = [ 0, 1, 0, -1, 0 ]; // Command returned by switch if we hit a comma `,` + // Break down what was read + // * read in a start brace `{` * + component readStartBrace = IsEqual(); + readStartBrace.in <== [byte, Syntax.START_BRACE]; + // * read in an end brace `}` * + component readEndBrace = IsEqual(); + readEndBrace.in <== [byte, Syntax.END_BRACE]; + // * read in a start bracket `[` * + component readStartBracket = IsEqual(); + readStartBracket.in <== [byte, Syntax.START_BRACKET]; + // * read in an end bracket `]` * + component readEndBracket = IsEqual(); + readEndBracket.in <== [byte, Syntax.END_BRACKET]; + // * read in a colon `:` * + component readColon = IsEqual(); + readColon.in <== [byte, Syntax.COLON]; + // * read in a comma `,` * + component readComma = IsEqual(); + readComma.in <== [byte, Syntax.COMMA]; + // * read in some delimeter * + signal readDelimeter <== readStartBrace.out + readEndBrace.out + readStartBracket.out + readEndBracket.out + + readColon.out + readComma.out; + // * read in some number * + component readNumber = InRange(8); + readNumber.in <== byte; + readNumber.range <== [48, 57]; // This is the range where ASCII digits are + // * read in a quote `"` * + component readQuote = IsEqual(); + readQuote.in <== [byte, Syntax.QUOTE]; //--------------------------------------------------------------------------------------------// - + // Yield instruction based on what byte we read * + component readStartBraceInstruction = ScalarArrayMul(3); + readStartBraceInstruction.scalar <== readStartBrace.out; + readStartBraceInstruction.array <== Command.START_BRACE; + component readEndBraceInstruction = ScalarArrayMul(3); + readEndBraceInstruction.scalar <== readEndBrace.out; + readEndBraceInstruction.array <== Command.END_BRACE; + component readStartBracketInstruction = ScalarArrayMul(3); + readStartBracketInstruction.scalar <== readStartBracket.out; + readStartBracketInstruction.array <== Command.START_BRACKET; + component readEndBracketInstruction = ScalarArrayMul(3); + readEndBracketInstruction.scalar <== readEndBracket.out; + readEndBracketInstruction.array <== Command.END_BRACKET; + component readColonInstruction = ScalarArrayMul(3); + readColonInstruction.scalar <== readColon.out; + readColonInstruction.array <== Command.COLON; + component readCommaInstruction = ScalarArrayMul(3); + readCommaInstruction.scalar <== readComma.out; + readCommaInstruction.array <== Command.COMMA; + component readNumberInstruction = ScalarArrayMul(3); + readNumberInstruction.scalar <== readNumber.out; + readNumberInstruction.array <== Command.NUMBER; + component readQuoteInstruction = ScalarArrayMul(3); + readQuoteInstruction.scalar <== readQuote.out; + readQuoteInstruction.array <== Command.QUOTE; + + component Instruction = GenericArrayAdd(3,8); + Instruction.arrays <== [readStartBraceInstruction.out, readEndBraceInstruction.out, + readStartBracketInstruction.out, readEndBracketInstruction.out, + readColonInstruction.out, readCommaInstruction.out, + readNumberInstruction.out, readQuoteInstruction.out]; //--------------------------------------------------------------------------------------------// - //-State machine updating---------------------------------------------------------------------// - // * yield instruction based on what byte we read * - component matcher = Switch(5, 5); - matcher.branches <== [start_brace, end_brace, quote, colon, comma ]; - matcher.vals <== [hit_start_brace, hit_end_brace, hit_quote, hit_colon, hit_comma]; - matcher.case <== byte; + // Apply state changing data // * get the instruction mask based on current state * - component mask = StateToMask(); - mask.state <== state; + component mask = StateToMask(MAX_STACK_HEIGHT); + mask.readDelimeter <== readDelimeter; + mask.readNumber <== readNumber.out; + mask.parsing_string <== parsing_string; + mask.parsing_number <== parsing_number; // * multiply the mask array elementwise with the instruction array * - component mulMaskAndOut = ArrayMul(5); - mulMaskAndOut.lhs <== mask.mask; - mulMaskAndOut.rhs <== matcher.out; - // * add the masked instruction to the state to get new state * - component addToState = ArrayAdd(5); - addToState.lhs <== state; - addToState.rhs <== mulMaskAndOut.out; - // * set the new state * - next_tree_depth <== addToState.out[0]; - next_parsing_key <== addToState.out[1]; - next_inside_key <== addToState.out[2]; - next_parsing_value <== addToState.out[3]; - next_inside_value <== addToState.out[4]; + component mulMaskAndOut = ArrayMul(3); + mulMaskAndOut.lhs <== mask.out; + mulMaskAndOut.rhs <== Instruction.out; + // * compute the new stack * + component newStack = RewriteStack(MAX_STACK_HEIGHT); + newStack.stack <== stack; + newStack.read_write_value <== mulMaskAndOut.out[0]; + newStack.readStartBrace <== readStartBrace.out; + newStack.readStartBracket <== readStartBracket.out; + newStack.readEndBrace <== readEndBrace.out; + newStack.readEndBracket <== readEndBracket.out; + newStack.readColon <== readColon.out; + newStack.readComma <== readComma.out; + // * set all the next state of the parser * + next_stack <== newStack.next_stack; + next_parsing_string <== parsing_string + mulMaskAndOut.out[1]; + next_parsing_number <== parsing_number + mulMaskAndOut.out[2]; //--------------------------------------------------------------------------------------------// +} + +/* +This template is for updating the state of the parser from a current state to a next state. + +# Params: + - `n`: tunable parameter for the number of `parsing_states` needed (TODO: could be removed). + +# Inputs: + - `readDelimeter` : a bool flag that indicates whether the byte value read was a delimeter. + - `readNumber` : a bool flag that indicates whether the byte value read was a number. + - `parsing_number`: a bool flag that indicates whether the parser is currently parsing a string or not. + - `parsing_number`: a bool flag that indicates whether the parser is currently parsing a number or not. + +# Outputs: + - `out[3]`: an array of values fed to update the stack and the parsing state flags. + - 0: mask for `read_write_value` + - 1: mask for `parsing_string` + - 2: mask for `parsing_number` +*/ +template StateToMask(n) { + // TODO: Probably need to assert things are bits where necessary. + signal input readDelimeter; + signal input readNumber; + signal input parsing_string; + signal input parsing_number; + signal output out[3]; + + + // `read_write_value`can change: IF NOT `parsing_string` + out[0] <== (1 - parsing_string); + + // `parsing_string` can change: + out[1] <== 1 - 2 * parsing_string; - //--------------------------------------------------------------------------------------------// - // // DEBUGGING: internal state - // for(var i = 0; i<5; i++) { - // log("-----------------------"); - // log("mask[",i,"]: ", mask.mask[i]); - // log("mulMaskAndOut[",i,"]:", mulMaskAndOut.out[i]); - // log("state[",i,"]: ", state[i]); - // log("next_state[",i,"]: ", addToState.out[i]); - // } - //--------------------------------------------------------------------------------------------// //--------------------------------------------------------------------------------------------// - //-Constraints--------------------------------------------------------------------------------// - // * constrain bit flags * - next_parsing_key * (1 - next_parsing_key) === 0; // - constrain that `next_parsing_key` remain a bit flag - next_inside_key * (1 - next_inside_key) === 0; // - constrain that `next_inside_key` remain a bit flag - next_parsing_value * (1 - next_parsing_value) === 0; // - constrain that `next_parsing_value` remain a bit flag - next_inside_value * (1 - next_inside_value) === 0; // - constrain that `next_inside_value` remain a bit flag - // * constrain `tree_depth` to never hit -1 (TODO: should always moves in 1 bit increments?) - component isMinusOne = IsEqual(); - isMinusOne.in[0] <== -1; - isMinusOne.in[1] <== next_tree_depth; - isMinusOne.out === 0; - //--------------------------------------------------------------------------------------------// + // `parsing_number` is more complicated to deal with + /* We have the possible relevant states below: + [isParsingString, isParsingNumber, readNumber, readDelimeter]; + 1 2 4 8 + Above is the binary value for each if is individually enabled + This is a total of 2^4 states + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + [0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]; + and the above is what we want to set `next_parsing_number` to given those + possible. + Below is an optimized version that could instead be done with a `Switch` + */ + signal parsingNumberReadDelimeter <== parsing_number * (readDelimeter); + signal readNumberNotParsingNumber <== (1 - parsing_number) * readNumber; + signal notParsingStringAndParsingNumberReadDelimeterOrReadNumberNotParsingNumber <== (1 - parsing_string) * (parsingNumberReadDelimeter + readNumberNotParsingNumber); + // 10 above ^^^^^^^^^^^^^^^^^ 4 above ^^^^^^^^^^^^^^^^^^ + signal parsingNumberNotReadNumber <== parsing_number * (1 - readNumber) ; + signal parsingNumberNotReadNumberNotReadDelimeter <== parsingNumberNotReadNumber * (1-readDelimeter); + out[2] <== notParsingStringAndParsingNumberReadDelimeterOrReadNumberNotParsingNumber + parsingNumberNotReadNumberNotReadDelimeter; + // Sorry about the long names, but they hopefully read clearly! } +// TODO: Check if underconstrained /* -This function is creates an exhaustive switch statement from `0` up to `n`. +This template is for getting the values at the top of the stack as well as the pointer to the top. + +# Params: + - `n`: tunable parameter for the stack height. # Inputs: -- `m`: the number of switch cases -- `n`: the output array length -- `case`: which case of the switch to select -- `branches[m]`: the values that enable taking different branches in the switch - (e.g., if `branch[i] == 10` then if `case == 10` we set `out == `vals[i]`) -- `vals[m][n]`: the value that is emitted for a given switch case - (e.g., `val[i]` array is emitted on `case == `branch[i]`) - -# Outputs -- `match`: is set to `0` if `case` does not match on any of `branches` -- `out[n]`: the selected output value if one of `branches` is selected (will be `[0,0,...]` otherwise) + - `stack[n][2]` : the stack to get the values and pointer of. + +# Outputs: + - `value[2]`: the value at the top of the stack + - `pointer` : the pointer for the top of stack index */ -template Switch(m, n) { - assert(m > 0); - assert(n > 0); - signal input case; - signal input branches[m]; - signal input vals[m][n]; - signal output match; - signal output out[n]; - - - // Verify that the `case` is in the possible set of branches - component indicator[m]; - component matchChecker = Contains(m); - signal component_out[m][n]; - var sum[n]; - for(var i = 0; i < m; i++) { - indicator[i] = IsZero(); - indicator[i].in <== case - branches[i]; - matchChecker.array[i] <== 1 - indicator[i].out; - for(var j = 0; j < n; j++) { - component_out[i][j] <== indicator[i].out * vals[i][j]; - sum[j] += component_out[i][j]; - } - } - matchChecker.in <== 0; - match <== matchChecker.out; +template GetTopOfStack(n) { + signal input stack[n][2]; + signal output value[2]; + signal output pointer; - out <== sum; + component isUnallocated[n]; + component atTop = SwitchArray(n,2); + var selector = 0; + for(var i = 0; i < n; i++) { + isUnallocated[i] = IsEqualArray(2); + isUnallocated[i].in[0] <== [0,0]; + isUnallocated[i].in[1] <== stack[i]; + selector += (1 - isUnallocated[i].out); + atTop.branches[i] <== i + 1; + atTop.vals[i] <== stack[i]; + } + atTop.case <== selector; + value <== atTop.out; + pointer <== selector; } -// TODO: Note at the moment mask 2 and 4 are the same, so this can be removed if it maintains. -template StateToMask() { - signal input state[5]; - signal output mask[5]; - - var tree_depth = state[0]; - var parsing_key = state[1]; - var inside_key = state[2]; - var parsing_value = state[3]; - var inside_value = state[4]; +// TODO: IMPORTANT NOTE, THE STACK IS CONSTRAINED TO 2**8 so the InRange work (could be changed) +/* +This template is for updating the stack given the current stack and the byte we read in `StateUpdate`. + +# Params: + - `n`: tunable parameter for the number of bits needed to represent the `MAX_STACK_HEIGHT`. - signal NOT_INSIDE_KEY_AND_NOT_INSIDE_VALUE <== (1 - inside_key) * (1 - inside_value); - signal NOT_PARSING_VALUE_NOT_INSIDE_VALUE <== (1 - parsing_value) * (1 - inside_value); +# Inputs: + - `read_write_value` : what value should be pushed to or popped from the stack. + - `readStartBrace` : a bool flag that indicates whether the byte value read was a start brace `{`. + - `readEndBrace` : a bool flag that indicates whether the byte value read was a end brace `}`. + - `readStartBracket` : a bool flag that indicates whether the byte value read was a start bracket `[`. + - `readEndBracket` : a bool flag that indicates whether the byte value read was a end bracket `]`. + - `readColon` : a bool flag that indicates whether the byte value read was a colon `:`. + - `readComma` : a bool flag that indicates whether the byte value read was a comma `,`. - component init_tree = IsZero(); - init_tree.in <== tree_depth; +# Outputs: + - `next_stack[n][2]`: the next stack of the parser. +*/ +template RewriteStack(n) { + assert(n < 2**8); + signal input stack[n][2]; + signal input read_write_value; + signal input readStartBrace; + signal input readStartBracket; + signal input readEndBrace; + signal input readEndBracket; + signal input readColon; + signal input readComma; - // `tree_depth` can change: `IF (parsing_key XOR parsing_value XOR end_of_kv)` - mask[0] <== init_tree.out + parsing_key + parsing_value; // TODO: Make sure these are never both 1! + signal output next_stack[n][2]; - // `parsing_key` can change: `IF ((NOT inside_key) AND (NOT inside_value) AND (NOT parsing_value))` - mask[1] <== NOT_INSIDE_KEY_AND_NOT_INSIDE_VALUE; + //--------------------------------------------------------------------------------------------// + // * scan value on top of stack * + component topOfStack = GetTopOfStack(n); + topOfStack.stack <== stack; + signal pointer <== topOfStack.pointer; + signal current_value[2] <== topOfStack.value; + // * check if we are currently in a value of an object * + component inObjectValue = IsEqualArray(2); + inObjectValue.in[0] <== current_value; + inObjectValue.in[1] <== [1,1]; + // * check if value indicates currently in an array * + component inArray = IsEqual(); + inArray.in[0] <== current_value[0]; + inArray.in[1] <== 2; + //--------------------------------------------------------------------------------------------// - // `inside_key` can change: `IF ((NOT parsing_value) AND (NOT inside_value) AND inside_key) THEN mask <== -1 ELSEIF (NOT parsing_value) AND (NOT inside_value) THEN mask <== 1` - mask[2] <== NOT_PARSING_VALUE_NOT_INSIDE_VALUE - 2 * inside_key; + //--------------------------------------------------------------------------------------------// + // * composite signals * + signal readCommaInArray <== readComma * inArray.out; + signal readCommaNotInArray <== readComma * (1 - inArray.out); + //--------------------------------------------------------------------------------------------// + + //--------------------------------------------------------------------------------------------// + // * determine whether we are pushing or popping from the stack * + component isPush = IsEqual(); + isPush.in <== [readStartBrace + readStartBracket, 1]; + component isPop = IsEqual(); + isPop.in <== [readEndBrace + readEndBracket, 1]; + // * set an indicator array for where we are pushing to or popping from* + component indicator[n]; + for(var i = 0; i < n; i++) { + // Points + indicator[i] = IsZero(); + indicator[i].in <== pointer - isPop.out - readColon - readComma - i; // Note, pointer points to unallocated region! + } + //--------------------------------------------------------------------------------------------// - // `parsing_value` can change: `IF ((NOT inside_key) AND (NOT inside_value) AND (tree_depth != 0))` - mask[3] <== NOT_INSIDE_KEY_AND_NOT_INSIDE_VALUE * (1 - init_tree.out); + //--------------------------------------------------------------------------------------------// + // * loop to modify the stack by rebuilding it * + signal stack_change_value[2] <== [(isPush.out + isPop.out) * read_write_value, readColon + readCommaInArray - readCommaNotInArray]; + signal second_index_clear[n]; + for(var i = 0; i < n; i++) { + next_stack[i][0] <== stack[i][0] + indicator[i].out * stack_change_value[0]; + second_index_clear[i] <== stack[i][1] * (readEndBrace + readEndBracket); // Checking if we read some end char + next_stack[i][1] <== stack[i][1] + indicator[i].out * (stack_change_value[1] - second_index_clear[i]); + } + //--------------------------------------------------------------------------------------------// - // `inside_value` can change: `IF (parsing_value AND (NOT inside_value)) THEN mask <== 1 ELSEIF (inside_value) mask <== -1` - mask[4] <== parsing_value - 2 * inside_value; + //--------------------------------------------------------------------------------------------// + // * check for under or overflow + component isUnderflowOrOverflow = InRange(8); + isUnderflowOrOverflow.in <== pointer - isPop.out + isPush.out; + isUnderflowOrOverflow.range <== [0,n]; + isUnderflowOrOverflow.out === 1; + //--------------------------------------------------------------------------------------------// } \ No newline at end of file diff --git a/circuits/test/bytes.test.ts b/circuits/test/bytes.test.ts deleted file mode 100644 index 2d26bde..0000000 --- a/circuits/test/bytes.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { circomkit, WitnessTester } from "./common"; - -describe("bytes", () => { - let circuit: WitnessTester<["in"], ["out"]>; - - describe("U8ToBits", () => { - before(async () => { - circuit = await circomkit.WitnessTester(`U8ToBits`, { - file: "circuits/bytes", - template: "U8ToBits", - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("(valid) witness: in = 0", async () => { - await circuit.expectPass({ in: 0 }); - }); - - it("(valid) witness: in = 15", async () => { - await circuit.expectPass({ in: 15 }); - }); - - it("(valid) witness: in = 255", async () => { - await circuit.expectPass({ in: 255 }); - }); - - it("(invalid) witness: in = 256", async () => { - await circuit.expectFail({ in: 256 }); - }); - - it("(invalid) witness: in = 42069", async () => { - await circuit.expectFail({ in: 42069 }); - }); - }); - - describe("ASCII", () => { - before(async () => { - circuit = await circomkit.WitnessTester(`ASCII`, { - file: "circuits/bytes", - template: "ASCII", - params: [13], - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("(valid) witness: in = b\"Hello, world!\"", async () => { - await circuit.expectPass( - { in: [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] }, - ); - }); - - it("(invalid) witness: in = [256, ...]", async () => { - await circuit.expectFail( - { in: [256, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] } - ); - }); - }); -}); diff --git a/circuits/test/common/index.ts b/circuits/test/common/index.ts index d187e36..f78729b 100644 --- a/circuits/test/common/index.ts +++ b/circuits/test/common/index.ts @@ -5,4 +5,20 @@ export const circomkit = new Circomkit({ verbose: false, }); -export { WitnessTester }; \ No newline at end of file +export { WitnessTester }; + +function stringifyValue(value: any): string { + if (Array.isArray(value)) { + return `[${value.map(stringifyValue).join(', ')}]`; + } + if (typeof value === 'object' && value !== null) { + return `{${Object.entries(value).map(([k, v]) => `${k}: ${stringifyValue(v)}`).join(', ')}}`; + } + return String(value); +} + +export function generateDescription(input: any): string { + return Object.entries(input) + .map(([key, value]) => `${key} = ${stringifyValue(value)}`) + .join(", "); +} \ No newline at end of file diff --git a/circuits/test/operators.test.ts b/circuits/test/operators.test.ts deleted file mode 100644 index 50baec2..0000000 --- a/circuits/test/operators.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { circomkit, WitnessTester } from "./common"; - -describe("operators", () => { - describe("IsZero", () => { - let circuit: WitnessTester<["in"], ["out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`IsZero`, { - file: "circuits/operators", - template: "IsZero", - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: 0", async () => { - await circuit.expectPass( - { in: 0 }, - { out: 1 }); - }); - - it("witness: 1", async () => { - await circuit.expectPass( - { in: 1 }, - { out: 0 } - ); - }); - - it("witness: 42069", async () => { - await circuit.expectPass( - { in: 42069 }, - { out: 0 } - ); - }); - }); - - describe("IsEqual", () => { - let circuit: WitnessTester<["in"], ["out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`IsEqual`, { - file: "circuits/operators", - template: "IsEqual", - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: [0,0]", async () => { - await circuit.expectPass( - { in: [0, 0] }, - { out: 1 } - ); - }); - - it("witness: [42069, 42069]", async () => { - await circuit.expectPass( - { in: [42069, 42069] }, - { out: 1 }, - ); - }); - - it("witness: [42069, 0]", async () => { - await circuit.expectPass( - { in: [42069, 0] }, - { out: 0 }, - ); - }); - }); - - describe("IsEqualArray", () => { - let circuit: WitnessTester<["in"], ["out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`IsEqualArray`, { - file: "circuits/operators", - template: "IsEqualArray", - params: [3], - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: [[0,0,0],[0,0,0]]", async () => { - await circuit.expectPass( - { in: [[0, 0, 0], [0, 0, 0]] }, - { out: 1 } - ); - }); - - it("witness: [[1,420,69],[1,420,69]]", async () => { - await circuit.expectPass( - { in: [[1, 420, 69], [1, 420, 69]] }, - { out: 1 }, - ); - }); - - it("witness: [[0,0,0],[1,420,69]]", async () => { - await circuit.expectPass( - { in: [[0, 0, 0], [1, 420, 69]] }, - { out: 0 }, - ); - }); - - it("witness: [[1,420,0],[1,420,69]]", async () => { - await circuit.expectPass( - { in: [[1, 420, 0], [1, 420, 69]] }, - { out: 0 }, - ); - }); - - it("witness: [[1,0,69],[1,420,69]]", async () => { - await circuit.expectPass( - { in: [[1, 0, 69], [1, 420, 69]] }, - { out: 0 }, - ); - }); - - it("witness: [[0,420,69],[1,420,69]]", async () => { - await circuit.expectPass( - { in: [[0, 420, 69], [1, 420, 69]] }, - { out: 0 }, - ); - }); - }); - - describe("Contains", () => { - let circuit: WitnessTester<["in", "array"], ["out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`Contains`, { - file: "circuits/operators", - template: "Contains", - params: [3], - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: in = 0, array = [0,1,2]", async () => { - await circuit.expectPass( - { in: 0, array: [0, 1, 2] }, - { out: 1 } - ); - }); - - it("witness: in = 1, array = [0,1,2]", async () => { - await circuit.expectPass( - { in: 1, array: [0, 1, 2] }, - { out: 1 } - ); - }); - - it("witness: in = 2, array = [0,1,2]", async () => { - await circuit.expectPass( - { in: 2, array: [0, 1, 2] }, - { out: 1 } - ); - }); - - it("witness: in = 42069, array = [0,1,2]", async () => { - await circuit.expectPass( - { in: 42069, array: [0, 1, 2] }, - { out: 0 } - ); - }); - - }); - - describe("ArrayAdd", () => { - let circuit: WitnessTester<["lhs", "rhs"], ["out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`ArrayAdd`, { - file: "circuits/operators", - template: "ArrayAdd", - params: [3], - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: lhs = [0,1,2], rhs = [3,5,7]", async () => { - await circuit.expectPass( - { lhs: [0, 1, 2], rhs: [3, 5, 7] }, - { out: [3, 6, 9] } - ); - }); - - }); - - describe("ArrayMul", () => { - let circuit: WitnessTester<["lhs", "rhs"], ["out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`ArrayMul`, { - file: "circuits/operators", - template: "ArrayMul", - params: [3], - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: lhs = [0,1,2], rhs = [3,5,7]", async () => { - await circuit.expectPass( - { lhs: [0, 1, 2], rhs: [3, 5, 7] }, - { out: [0, 5, 14] } - ); - }); - - }); -}); diff --git a/circuits/test/parser.test.ts b/circuits/test/parser.test.ts deleted file mode 100644 index 3e6fbf2..0000000 --- a/circuits/test/parser.test.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { start } from "repl"; -import { circomkit, WitnessTester } from "./common"; - -describe("parser", () => { - describe("Switch", () => { - let circuit: WitnessTester<["case", "branches", "vals"], ["match", "out"]>; - before(async () => { - circuit = await circomkit.WitnessTester(`Switch`, { - file: "circuits/parser", - template: "Switch", - params: [3, 2], - }); - console.log("#constraints:", await circuit.getConstraintCount()); - }); - - it("witness: case = 0, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { - await circuit.expectPass( - { case: 0, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, - { match: 1, out: [69, 0] }, - ); - }); - - it("witness: case = 1, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { - await circuit.expectPass( - { case: 1, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, - { match: 1, out: [420, 1] }, - ); - }); - - it("witness: case = 2, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { - await circuit.expectPass( - { case: 2, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, - { match: 1, out: [1337, 2] }, - ); - }); - - it("witness: case = 3, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { - await circuit.expectPass( - { case: 3, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, - { match: 0, out: [0, 0] } - ); - }); - - it("witness: case = 420, branches = [69, 420, 1337], vals = [[10,3], [20,5], [30,7]]", async () => { - await circuit.expectPass( - { case: 420, branches: [69, 420, 1337], vals: [[10, 3], [20, 5], [30, 7]] }, - { match: 1, out: [20, 5] } - ); - }); - - it("witness: case = 0, branches = [69, 420, 1337], vals = [[10,3], [20,5], [30,7]]", async () => { - await circuit.expectPass( - { case: 0, branches: [69, 420, 1337], vals: [[10, 3], [20, 5], [30, 7]] }, - { match: 0, out: [0, 0] } - ); - }); - - }); - - //--------------------------------------------------------------------------------------------// - //-Delimeters---------------------------------------------------------------------------------// - // - ASCII char: `{` - const start_brace = 123; - // - ASCII char: `}` - const end_brace = 125; - // - ASCII char `[` - const start_bracket = 91; - // - ASCII char `]` - const end_bracket = 93; - // - ASCII char `"` - const quote = 34; - // - ASCII char `:` - const colon = 58; - // - ASCII char `,` - const comma = 44; - //--------------------------------------------------------------------------------------------// - // White space - // - ASCII char: `\n` - const newline = 10; - // - ASCII char: ` ` - const space = 32; - //--------------------------------------------------------------------------------------------// - // Escape - // - ASCII char: `\` - const escape = 92; - //--------------------------------------------------------------------------------------------// - - describe("StateUpdate", () => { - let circuit: WitnessTester< - ["byte", "tree_depth", "parsing_key", "inside_key", "parsing_value", "inside_value", "end_of_kv"], - ["next_tree_depth", "next_parsing_key", "next_inside_key", "next_parsing_value", "next_inside_value", "next_end_of_kv"] - >; - - function generatePassCase(input: any, expected: any, desc: string) { - const description = Object.entries(input) - .map(([key, value]) => `${key} = ${value}`) - .join(", "); - - it(`(valid) witness: ${description}\n${desc}`, async () => { - await circuit.expectPass(input, expected); - }); - } - - function generateFailCase(input: any, desc: string) { - const description = Object.entries(input) - .map(([key, value]) => `${key} = ${value}`) - .join(", "); - - it(`(invalid) witness: ${description}\n${desc}`, async () => { - await circuit.expectFail(input); - }); - } - - before(async () => { - circuit = await circomkit.WitnessTester(`StateUpdate`, { - file: "circuits/parser", - template: "StateUpdate", - }); - console.log("#constraints:", await circuit.getConstraintCount()); - - }); - - let init = { - byte: 0, - tree_depth: 0, - parsing_key: 0, - inside_key: 0, - parsing_value: 0, - inside_value: 0, - }; - let out = { - next_tree_depth: init.tree_depth, - next_parsing_key: init.parsing_key, - next_inside_key: init.inside_key, - next_parsing_value: init.parsing_value, - next_inside_value: init.inside_value, - }; - - // Test 1: init setup -> `do_nothing` byte - generatePassCase(init, out, ">>>> `NUL` read"); - - // Test 2: init setup -> `{` is read - let read_start_brace = { ...init }; - read_start_brace.byte = start_brace; - let read_start_brace_out = { ...out }; - read_start_brace_out.next_tree_depth = 1; - read_start_brace_out.next_parsing_key = 1; - generatePassCase(read_start_brace, read_start_brace_out, ">>>> `{` read"); - - // Test 3: init setup -> `}` is read (should be INVALID) - let read_end_brace = { ...init }; - read_end_brace.byte = end_brace; - generateFailCase(read_end_brace, ">>>> `}` read --> (FAIL! NEGATIVE TREE DEPTH!)"); - - // Test 4: `tree_depth == 1` and `parsing_key == 1` setup -> `"` is read - let in_tree_find_key = { ...init }; - in_tree_find_key.tree_depth = 1; - in_tree_find_key.parsing_key = 1; - in_tree_find_key.byte = quote; - let in_tree_find_key_out = { ...out }; - in_tree_find_key_out.next_parsing_key = 1; - in_tree_find_key_out.next_inside_key = 1; - in_tree_find_key_out.next_tree_depth = 1; - generatePassCase(in_tree_find_key, in_tree_find_key_out, ">>>> `\"` read"); - - // Test 5: `tree_depth == 1` AND `inside_key ==1` setup -> ` ` is read - let in_key = { ...init }; - in_key.tree_depth = 1; - in_key.inside_key = 1; - in_key.byte = space; - let in_key_out = { ...out }; - in_key_out.next_inside_key = 1; - in_key_out.next_tree_depth = 1; - generatePassCase(in_key, in_key_out, ">>>> ` ` read"); - - // Test 6: `tree_depth == 1` AND `inside_key == 1 AND `parsing_key == 0` setup -> `"` is read - let in_key_to_exit = { ...init }; - in_key_to_exit.tree_depth = 1; - in_key_to_exit.inside_key = 1; - in_key_to_exit.byte = quote; - let in_key_to_exit_out = { ...out }; - in_key_to_exit_out.next_tree_depth = 1; - generatePassCase(in_key_to_exit, in_key_to_exit_out, "`\"` read"); - - // Test 7: `tree_depth == 1` AND parsed through key` setup -> `:` is read - let parsed_key_wait_to_parse_value = { ...init }; - parsed_key_wait_to_parse_value.tree_depth = 1; - parsed_key_wait_to_parse_value.parsing_key = 1; - parsed_key_wait_to_parse_value.byte = colon; - let parsed_key_wait_to_parse_value_out = { ...out }; - parsed_key_wait_to_parse_value_out.next_tree_depth = 1; - parsed_key_wait_to_parse_value_out.next_parsing_value = 1; - generatePassCase(parsed_key_wait_to_parse_value, parsed_key_wait_to_parse_value_out, ">>>> `:` read"); - - // Test 8: `tree_depth == 1` AND parsing_value == 1` setup -> `"` is read - let in_tree_find_value = { ...init }; - in_tree_find_value.tree_depth = 1; - in_tree_find_value.parsing_value = 1; - in_tree_find_value.byte = quote; - let in_tree_find_value_out = { ...out }; - in_tree_find_value_out.next_tree_depth = 1; - in_tree_find_value_out.next_inside_value = 1; - in_tree_find_value_out.next_parsing_value = 1; - generatePassCase(in_tree_find_value, in_tree_find_value_out, ">>>> `\"` read"); - - // Test 9: `tree_depth == 1` AND inside_value` setup -> ` ` is read - let in_value = { ...init }; - in_value.tree_depth = 1; - in_value.inside_value = 1; - in_value.byte = space; - let in_value_out = { ...out }; - in_value_out.next_tree_depth = 1; - in_value_out.next_inside_value = 1; - generatePassCase(in_value, in_value_out, ">>>> ` ` is read"); - - // Test 10: `tree_depth == 1` AND inside_value` setup -> `"` is read - let in_value_to_exit = { ...init }; - in_value_to_exit.tree_depth = 1; - in_value_to_exit.parsing_value = 1; - in_value_to_exit.inside_value = 1; - in_value_to_exit.byte = quote; - let in_value_to_exit_out = { ...out }; - in_value_to_exit_out.next_tree_depth = 1; - // in_value_to_exit_out.next_end_of_kv = 1; - in_value_to_exit_out.next_parsing_value = 1; - generatePassCase(in_value_to_exit, in_value_to_exit_out, ">>>> `\"` is read"); - - // Test 11: `tree_depth == 1` AND end_of_kv` setup -> ` ` is read - let in_end_of_kv = { ...init }; - in_end_of_kv.tree_depth = 1; - in_end_of_kv.byte = space; - let in_end_of_kv_out = { ...out }; - in_end_of_kv_out.next_tree_depth = 1; - generatePassCase(in_end_of_kv, in_end_of_kv_out, ">>>> ` ` is read"); - - // Test 12: `tree_depth == 1` AND end_of_kv` setup -> `,` is read - let end_of_kv_to_parse_to_key = { ...init }; - end_of_kv_to_parse_to_key.tree_depth = 1; - end_of_kv_to_parse_to_key.parsing_value = 1; - // end_of_kv_to_parse_to_key.end_of_kv = 1; - end_of_kv_to_parse_to_key.byte = comma; - let end_of_kv_to_parse_to_key_out = { ...out }; - end_of_kv_to_parse_to_key_out.next_tree_depth = 1; - end_of_kv_to_parse_to_key_out.next_parsing_key = 1; - generatePassCase(end_of_kv_to_parse_to_key, end_of_kv_to_parse_to_key_out, ">>>> ` ` is read"); - - // Test 13: `tree_depth == 1` AND end_of_kv` setup -> `}` is read - let end_of_kv_to_exit_json = { ...init }; - end_of_kv_to_exit_json.tree_depth = 1; - end_of_kv_to_exit_json.parsing_value = 1; - end_of_kv_to_exit_json.byte = end_brace; - let end_of_kv_to_exit_json_out = { ...out }; - end_of_kv_to_exit_json_out.next_parsing_value = 1; - generatePassCase(end_of_kv_to_exit_json, end_of_kv_to_exit_json_out, ">>>> `}` is read"); - - // NOTE: At this point, we can parse JSON that has 2 keys at depth 1! - - // Test 14: `tree_depth == 1` AND parsing_value` setup -> `{` is read - let end_of_key_to_inner_object = { ...init }; - end_of_key_to_inner_object.tree_depth = 1; - end_of_key_to_inner_object.parsing_value = 1; - end_of_key_to_inner_object.byte = start_brace; - let end_of_key_to_inner_object_out = { ...out }; - end_of_key_to_inner_object_out.next_tree_depth = 2; - end_of_key_to_inner_object_out.next_parsing_key = 1; - generatePassCase(end_of_key_to_inner_object, end_of_key_to_inner_object_out, ">>>> `{` is read"); - }); - -}); - - diff --git a/circuits/test/parser/index.ts b/circuits/test/parser/index.ts new file mode 100644 index 0000000..3bde941 --- /dev/null +++ b/circuits/test/parser/index.ts @@ -0,0 +1,56 @@ +// constants.ts + +export const Delimiters = { + // ASCII char: `{` + START_BRACE: 123, + // ASCII char: `}` + END_BRACE: 125, + // ASCII char `[` + START_BRACKET: 91, + // ASCII char `]` + END_BRACKET: 93, + // ASCII char `"` + QUOTE: 34, + // ASCII char `:` + COLON: 58, + // ASCII char `,` + COMMA: 44, +}; + +export const WhiteSpace = { + // ASCII char: `\n` + NEWLINE: 10, + // ASCII char: ` ` + SPACE: 32, +}; + +export const Numbers = { + ZERO: 48, + ONE: 49, + TWO: 50, + THREE: 51, + FOUR: 52, + FIVE: 53, + SIX: 54, + SEVEN: 55, + EIGHT: 56, + NINE: 57 +} + +export const Escape = { + // ASCII char: `\` + BACKSLASH: 92, +}; + +export const INITIAL_IN = { + byte: 0, + stack: [[0, 0], [0, 0], [0, 0], [0, 0]], + parsing_string: 0, + parsing_number: 0, +}; + +export const INITIAL_OUT = { + next_stack: INITIAL_IN.stack, + next_parsing_string: INITIAL_IN.parsing_string, + next_parsing_number: INITIAL_IN.parsing_number, +}; \ No newline at end of file diff --git a/circuits/test/parser/parser.test.ts b/circuits/test/parser/parser.test.ts new file mode 100644 index 0000000..11e1bea --- /dev/null +++ b/circuits/test/parser/parser.test.ts @@ -0,0 +1,118 @@ +import { circomkit, WitnessTester, generateDescription } from "../common"; +import { Delimiters, WhiteSpace, Numbers, Escape, INITIAL_IN, INITIAL_OUT } from '.'; + + + +describe("StateUpdate", () => { + let circuit: WitnessTester< + ["byte", "stack", "parsing_string", "parsing_number"], + ["next_stack", "next_parsing_string", "next_parsing_number"] + >; + + function generatePassCase(input: any, expected: any, desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description}\n${desc}`, async () => { + await circuit.expectPass(input, expected); + }); + } + + function generateFailCase(input: any, desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description}\n${desc}`, async () => { + await circuit.expectFail(input); + }); + } + + before(async () => { + circuit = await circomkit.WitnessTester(`StateUpdate`, { + file: "circuits/parser", + template: "StateUpdate", + params: [4], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + + }); + + //-TEST_1----------------------------------------------------------// + // init: ZEROS then read `do_nothing` byte + // expect: ZEROS + generatePassCase(INITIAL_IN, INITIAL_OUT, ">>>> `NUL` read"); + + + //-TEST_2----------------------------------------------------------// + // state: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // read: `"` + // expect: parsing_string --> 1 + let in_object_find_key = { ...INITIAL_IN }; + in_object_find_key.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_object_find_key.byte = Delimiters.QUOTE; + let in_object_find_key_out = { ...INITIAL_OUT }; + in_object_find_key_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_object_find_key_out.next_parsing_string = 1; + generatePassCase(in_object_find_key, + in_object_find_key_out, + ">>>> `\"` read" + ); + + //-TEST_3----------------------------------------------------------// + // state: stack = [[1, 0], [0, 0], [0, 0], [0, 0]], parsing_string == 1 + // read: ` ` + // expect: NIL + let in_key = { ...INITIAL_IN }; + in_key.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_key.parsing_string = 1; + in_key.byte = WhiteSpace.SPACE; + let in_key_out = { ...INITIAL_OUT }; + in_key_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_key_out.next_parsing_string = 1; + generatePassCase(in_key, in_key_out, ">>>> ` ` read"); + + //-TEST_4----------------------------------------------------------// + // init: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // read: `"` + // expect: parsing_string --> 0 + // + let in_key_to_exit = { ...INITIAL_IN }; + in_key_to_exit.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_key_to_exit.parsing_string = 1 + in_key_to_exit.byte = Delimiters.QUOTE; + let in_key_to_exit_out = { ...INITIAL_OUT }; + in_key_to_exit_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(in_key_to_exit, in_key_to_exit_out, "`\"` read"); + + //-TEST_5----------------------------------------------------------// + // state: stack == [[1, 1], [0, 0], [0, 0], [0, 0]] + // read: `"` + // expect: parsing_string --> 1 + let in_tree_find_value = { ...INITIAL_IN }; + in_tree_find_value.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + in_tree_find_value.byte = Delimiters.QUOTE; + let in_tree_find_value_out = { ...INITIAL_OUT }; + in_tree_find_value_out.next_stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + in_tree_find_value_out.next_parsing_string = 1; + generatePassCase(in_tree_find_value, + in_tree_find_value_out, + ">>>> `\"` read" + ); + + //-TEST_6----------------------------------------------------------// + // state: stack == [[1, 1], [0, 0], [0, 0], [0, 0]];, parsing_string == 1 + // read: `"` + // expect: parsing_string == 0, + let in_value_to_exit = { ...INITIAL_IN }; + in_value_to_exit.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + in_value_to_exit.parsing_string = 1; + in_value_to_exit.byte = Delimiters.QUOTE; + let in_value_to_exit_out = { ...INITIAL_OUT }; + in_value_to_exit_out.next_stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + generatePassCase(in_value_to_exit, + in_value_to_exit_out, + ">>>> `\"` is read" + ); + +}); + + + diff --git a/circuits/test/parser/stack.test.ts b/circuits/test/parser/stack.test.ts new file mode 100644 index 0000000..69e0b1a --- /dev/null +++ b/circuits/test/parser/stack.test.ts @@ -0,0 +1,235 @@ +import { circomkit, WitnessTester, generateDescription } from "../common"; +import { Delimiters, WhiteSpace, Numbers, Escape, INITIAL_IN, INITIAL_OUT } from '.'; + +describe("GetTopOfStack", () => { + let circuit: WitnessTester<["stack"], ["value", "pointer"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`GetTopOfStack`, { + file: "circuits/parser", + template: "GetTopOfStack", + params: [4], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + function generatePassCase(input: any, expected: any) { + const description = generateDescription(input); + + it(`(valid) witness: ${description}`, async () => { + await circuit.expectPass(input, expected); + }); + } + + let input = { stack: [[1, 0], [2, 0], [3, 1], [4, 2]] }; + let output = { value: [4, 2], pointer: 3 }; + generatePassCase(input, output); + + input.stack[2] = [0, 0]; + input.stack[3] = [0, 0]; + output.value = [2, 0] + output.pointer = 1; + generatePassCase(input, output); + + input.stack[0] = [0, 0]; + input.stack[1] = [0, 0]; + output.value = [0, 0] + output.pointer = 0; + generatePassCase(input, output); +}); + +describe("StateUpdate :: RewriteStack", () => { + let circuit: WitnessTester< + ["byte", "stack", "parsing_string", "parsing_number"], + ["next_stack", "next_parsing_string", "next_parsing_number"] + >; + before(async () => { + circuit = await circomkit.WitnessTester(`GetTopOfStack`, { + file: "circuits/parser", + template: "StateUpdate", + params: [4], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + function generatePassCase(input: any, expected: any, desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description}\n${desc}`, async () => { + await circuit.expectPass(input, expected); + }); + } + + function generateFailCase(input: any, desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description}\n${desc}`, async () => { + await circuit.expectFail(input); + }); + } + + //-TEST_1----------------------------------------------------------// + // init: stack == [[0, 0], [0, 0], [0, 0], [0, 0]] + // read: `{` + // expect: stack --> [[1, 0], [0, 0], [0, 0], [0, 0]] + let read_start_brace = { ...INITIAL_IN }; + read_start_brace.byte = Delimiters.START_BRACE; + let read_start_brace_out = { ...INITIAL_OUT }; + read_start_brace_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(read_start_brace, + read_start_brace_out, + ">>>> `{` read" + ); + + //-TEST_2----------------------------------------------------------// + // state: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // read: `{` + // expect: stack --> [[1, 0], [1, 0], [0, 0], [0, 0]] + let in_object = { ...INITIAL_IN }; + in_object.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_object.byte = Delimiters.START_BRACE; + let in_object_out = { ...INITIAL_OUT }; + in_object_out.next_stack = [[1, 0], [1, 0], [0, 0], [0, 0]]; + generatePassCase(in_object, in_object_out, ">>>> `{` read"); + + //-TEST_3----------------------------------------------------------// + // state: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // read: `}` + // expect: stack --> [[0, 0], [0, 0], [0, 0], [0, 0]] + let in_object_to_leave = { ...INITIAL_IN }; + in_object_to_leave.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + in_object_to_leave.byte = Delimiters.END_BRACE; + let in_object_to_leave_out = { ...INITIAL_OUT }; + generatePassCase(in_object_to_leave, + in_object_to_leave_out, + ">>>> `}` read" + ); + + //-TEST_4----------------------------------------------------------// + // init: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // read: `[` + // expect: stack --> [[1, 0], [2, 0], [0, 0], [0, 0]] + let in_object_to_read_start_bracket = { ...INITIAL_IN }; + in_object_to_read_start_bracket.byte = Delimiters.START_BRACKET; + in_object_to_read_start_bracket.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + let in_object_to_read_start_bracket_out = { ...INITIAL_OUT }; + in_object_to_read_start_bracket_out.next_stack = [[1, 0], [2, 0], [0, 0], [0, 0]]; + generatePassCase(in_object_to_read_start_bracket, + in_object_to_read_start_bracket_out, + ">>>> `[` read" + ); + + //-TEST_5----------------------------------------------------------// + // init: stack == [[1, 0], [2, 0], [0, 0], [0, 0]] + // read: `]` + // expect: stack --> [[1, 0], [0, 0], [0, 0], [0, 0]] + let in_object_and_array = { ...INITIAL_IN }; + in_object_and_array.byte = Delimiters.END_BRACKET; + in_object_and_array.stack = [[1, 0], [2, 0], [0, 0], [0, 0]]; + let in_object_and_array_out = { ...INITIAL_OUT }; + in_object_and_array_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(in_object_and_array, + in_object_and_array_out, + ">>>> `]` read" + ); + + //-TEST_6-----------------------------------------------------------// + // state: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // read: `:` + // expect: stack --> [[1, 1], [0, 0], [0, 0], [0, 0]] + let parsed_key_wait_to_parse_value = { ...INITIAL_IN }; + parsed_key_wait_to_parse_value.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + parsed_key_wait_to_parse_value.byte = Delimiters.COLON; + let parsed_key_wait_to_parse_value_out = { ...INITIAL_OUT }; + parsed_key_wait_to_parse_value_out.next_stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + generatePassCase(parsed_key_wait_to_parse_value, + parsed_key_wait_to_parse_value_out, + ">>>> `:` read" + ); + + //-TEST_7----------------------------------------------------------// + // init: stack == [[1, 0], [0, 0], [0, 0], [0, 0]] + // expect: stack --> [[1, 0], [0, 0], [0, 0], [0, 0]] + let in_object_and_value = { ...INITIAL_IN }; + in_object_and_value.byte = Delimiters.COMMA; + in_object_and_value.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + let in_object_and_value_out = { ...INITIAL_OUT }; + in_object_and_value_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(in_object_and_value, + in_object_and_value_out, + ">>>> `,` read" + ); + + //-TEST_8----------------------------------------------------------// + // init: stack == [[1, 1], [0, 0], [0, 0], [0, 0]] + // read: `}` + // expect: stack --> [[0, 0], [0, 0], [0, 0], [0, 0]] + let in_object_and_value_to_leave_object = { ...INITIAL_IN }; + in_object_and_value_to_leave_object.byte = Delimiters.END_BRACE; + in_object_and_value_to_leave_object.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + let in_object_and_value_to_leave_object_out = { ...INITIAL_OUT }; + in_object_and_value_to_leave_object_out.next_stack = [[0, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(in_object_and_value_to_leave_object, + in_object_and_value_to_leave_object_out, + ">>>> `}` read" + ); + + //-TEST_9----------------------------------------------------------// + // idea: Inside a number value after a key in an object. + // state: stack == [[1, 1], [0, 0], [0, 0], [0, 0]], parsing_number == 1 + // read: `,` + // expect: pointer --> 2 + // stack --> [[1, 0], [0, 0], [0, 0], [0, 0]] + // parsing_number --> 0 + let inside_number = { ...INITIAL_IN }; + inside_number.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + inside_number.parsing_number = 1; + inside_number.byte = Delimiters.COMMA; + let inside_number_out = { ...INITIAL_OUT }; + inside_number_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(inside_number, inside_number_out, ">>>> `,` read"); + + + // TODO: FAIL CASES, ADD STACK UNDERFLOW CASES TOO and RENUMBER + //-TEST_3----------------------------------------------------------// + // state: INIT + // read: `}` + // expect: FAIL (stack underflow) + let read_end_brace = { ...INITIAL_IN }; + read_end_brace.byte = Delimiters.END_BRACE; + generateFailCase(read_end_brace, + ">>>> `}` read --> (stack underflow)" + ); + + //-TEST_9----------------------------------------------------------// + // init: stack == [[1, 0], [1, 0], [1, 0], [1, 0]] + // expect: FAIL, STACK OVERFLOW + let in_max_stack = { ...INITIAL_IN }; + in_max_stack.byte = Delimiters.START_BRACE; + in_max_stack.stack = [[1, 0], [1, 0], [1, 0], [1, 0]]; + generateFailCase(in_max_stack, ">>>> `{` read --> (stack overflow)"); + + //-TEST_10----------------------------------------------------------// + // init: stack == [[1, 0], [1, 0], [1, 0], [1, 0]] + // expect: FAIL, STACK OVERFLOW + let in_max_stack_2 = { ...INITIAL_IN }; + in_max_stack_2.byte = Delimiters.START_BRACKET; + in_max_stack_2.stack = [[1, 0], [1, 0], [1, 0], [1, 0]]; + generateFailCase(in_max_stack, ">>>> `[` read --> (stack overflow)"); + + // TODO: This requires a more careful check of the stack that popping clears the current value. Use an IsZero + // //-TEST_3----------------------------------------------------------// + // // init: stack == [1,0,0,0] + // // read: `]` + // // expect: FAIL, INVALID CHAR + // let in_object_to_read_start_bracket = { ...INITIAL_IN }; + // in_object_to_read_start_bracket.byte = Delimiters.START_BRACKET; + // in_object_to_read_start_bracket.pointer = 1; + // in_object_to_read_start_bracket.stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + // let in_object_to_read_start_bracket_out = { ...INITIAL_OUT }; + // in_object_to_read_start_bracket_out.next_pointer = 2; + // in_object_to_read_start_bracket_out.next_stack = [[1, 0], [2, 0], [0, 0], [0, 0]]; + // generatePassCase(in_object_to_read_start_bracket, + // in_object_to_read_start_bracket_out, + // ">>>> `[` read" + // ); +}); \ No newline at end of file diff --git a/circuits/test/parser/values.test.ts b/circuits/test/parser/values.test.ts new file mode 100644 index 0000000..eb1c1f3 --- /dev/null +++ b/circuits/test/parser/values.test.ts @@ -0,0 +1,97 @@ +import { circomkit, WitnessTester, generateDescription } from "../common"; +import { Delimiters, WhiteSpace, Numbers, Escape, INITIAL_IN, INITIAL_OUT } from '.'; + +describe("StateUpdate :: Values", () => { + let circuit: WitnessTester< + ["byte", "pointer", "stack", "parsing_string", "parsing_number"], + ["next_pointer", "next_stack", "next_parsing_string", "next_parsing_number"] + >; + before(async () => { + circuit = await circomkit.WitnessTester(`GetTopOfStack`, { + file: "circuits/parser", + template: "StateUpdate", + params: [4], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + function generatePassCase(input: any, expected: any, desc: string) { + const description = generateDescription(input); + + it(`(valid) witness: ${description}\n${desc}`, async () => { + await circuit.expectPass(input, expected); + }); + } + + //-TEST_1----------------------------------------------------------// + // idea: Read a number value after a key in an object. + // state: stack == [[1, 1], [0, 0], [0, 0], [0, 0]] + // read: `0` + // expect: stack --> [[1, 1], [0, 0], [0, 0], [0, 0]] + // parsing_number --> 1 + let read_number = { ...INITIAL_IN }; + read_number.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + read_number.byte = Numbers.ZERO; + let read_number_out = { ...INITIAL_OUT }; + read_number_out.next_stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + read_number_out.next_parsing_number = 1; + generatePassCase(read_number, read_number_out, ">>>> `0` read"); + + // // TODO: Note that reading a space while reading a number will not throw an error! + + //-TEST_2----------------------------------------------------------// + // idea: Inside a number value after a key in an object. + // state: pointer == 2, stack == [1,3,0,0], parsing_number == 1 + // read: `1` + // expect: pointer --> 2 + // stack --> [1,3,0,0] + // parsing_number --> 0 + let inside_number_continue = { ...INITIAL_IN }; + inside_number_continue.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + inside_number_continue.parsing_number = 1; + inside_number_continue.byte = Numbers.ONE; + let inside_number_continue_out = { ...INITIAL_OUT }; + inside_number_continue_out.next_stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + inside_number_continue_out.next_parsing_number = 1; + generatePassCase(inside_number_continue, inside_number_continue_out, ">>>> `1` read"); + + //-TEST_3----------------------------------------------------------// + // idea: Inside a string key inside an object + // state: stack == [[1, 0], [0, 0], [0, 0], [0, 0]], parsing_string == 1 + // read: `,` + // expect: stack --> [[1, 0], [0, 0], [0, 0], [0, 0]] + // parsing_string --> 0 + let inside_number = { ...INITIAL_IN }; + inside_number.stack = [[1, 1], [0, 0], [0, 0], [0, 0]]; + inside_number.parsing_string = 1; + inside_number.byte = Delimiters.COMMA; + let inside_number_out = { ...INITIAL_OUT }; + inside_number_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + inside_number_out.next_parsing_string = 1; + generatePassCase(inside_number, inside_number_out, ">>>> `,` read"); + + describe("StateUpdate :: Values :: Array", () => { + // Internal array parsing -----------------------------------------// + + //-TEST_10----------------------------------------------------------// + // init: stack == [[1, 0], [2, 0], [0, 0], [0, 0]] + // read: `,` + // expext: stack --> [[1, 0], [2, 1], [0, 0], [0, 0]] + let in_arr = { ...INITIAL_IN }; + in_arr.stack = [[1, 0], [2, 0], [0, 0], [0, 0]]; + in_arr.byte = Delimiters.COMMA; + let in_arr_out = { ...INITIAL_OUT }; + in_arr_out.next_stack = [[1, 0], [2, 1], [0, 0], [0, 0]]; + generatePassCase(in_arr, in_arr_out, ">>>> `,` read"); + + //-TEST_10----------------------------------------------------------// + // init: stack == [[1, 0], [2, 1], [0, 0], [0, 0]] + // read: `]` + // expect: stack --> [[1, 0], [0, 0], [0, 0], [0, 0]] + let in_arr_idx_to_leave = { ...INITIAL_IN }; + in_arr_idx_to_leave.stack = [[1, 0], [2, 1], [0, 0], [0, 0]]; + in_arr_idx_to_leave.byte = Delimiters.END_BRACKET; + let in_arr_idx_to_leave_out = { ...INITIAL_OUT }; + in_arr_idx_to_leave_out.next_stack = [[1, 0], [0, 0], [0, 0], [0, 0]]; + generatePassCase(in_arr_idx_to_leave, in_arr_idx_to_leave_out, ">>>> `]` read"); + }); +}); \ No newline at end of file diff --git a/circuits/test/utils/utils.test.ts b/circuits/test/utils/utils.test.ts new file mode 100644 index 0000000..3721566 --- /dev/null +++ b/circuits/test/utils/utils.test.ts @@ -0,0 +1,311 @@ +import { circomkit, WitnessTester } from "../common"; + +describe("ASCII", () => { + let circuit: WitnessTester<["in"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`ASCII`, { + file: "circuits/utils", + template: "ASCII", + params: [13], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("(valid) witness: in = b\"Hello, world!\"", async () => { + await circuit.expectPass( + { in: [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] }, + ); + }); + + it("(invalid) witness: in = [256, ...]", async () => { + await circuit.expectFail( + { in: [256, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33] } + ); + }); +}); + +describe("IsEqualArray", () => { + let circuit: WitnessTester<["in"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`IsEqualArray`, { + file: "circuits/utils", + template: "IsEqualArray", + params: [3], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: [[0,0,0],[0,0,0]]", async () => { + await circuit.expectPass( + { in: [[0, 0, 0], [0, 0, 0]] }, + { out: 1 } + ); + }); + + it("witness: [[1,420,69],[1,420,69]]", async () => { + await circuit.expectPass( + { in: [[1, 420, 69], [1, 420, 69]] }, + { out: 1 }, + ); + }); + + it("witness: [[0,0,0],[1,420,69]]", async () => { + await circuit.expectPass( + { in: [[0, 0, 0], [1, 420, 69]] }, + { out: 0 }, + ); + }); + + it("witness: [[1,420,0],[1,420,69]]", async () => { + await circuit.expectPass( + { in: [[1, 420, 0], [1, 420, 69]] }, + { out: 0 }, + ); + }); + + it("witness: [[1,0,69],[1,420,69]]", async () => { + await circuit.expectPass( + { in: [[1, 0, 69], [1, 420, 69]] }, + { out: 0 }, + ); + }); + + it("witness: [[0,420,69],[1,420,69]]", async () => { + await circuit.expectPass( + { in: [[0, 420, 69], [1, 420, 69]] }, + { out: 0 }, + ); + }); +}); + +describe("Contains", () => { + let circuit: WitnessTester<["in", "array"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`Contains`, { + file: "circuits/utils", + template: "Contains", + params: [3], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: in = 0, array = [0,1,2]", async () => { + await circuit.expectPass( + { in: 0, array: [0, 1, 2] }, + { out: 1 } + ); + }); + + it("witness: in = 1, array = [0,1,2]", async () => { + await circuit.expectPass( + { in: 1, array: [0, 1, 2] }, + { out: 1 } + ); + }); + + it("witness: in = 2, array = [0,1,2]", async () => { + await circuit.expectPass( + { in: 2, array: [0, 1, 2] }, + { out: 1 } + ); + }); + + it("witness: in = 42069, array = [0,1,2]", async () => { + await circuit.expectPass( + { in: 42069, array: [0, 1, 2] }, + { out: 0 } + ); + }); + +}); + +describe("ArrayAdd", () => { + let circuit: WitnessTester<["lhs", "rhs"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`ArrayAdd`, { + file: "circuits/utils", + template: "ArrayAdd", + params: [3], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: lhs = [0,1,2], rhs = [3,5,7]", async () => { + await circuit.expectPass( + { lhs: [0, 1, 2], rhs: [3, 5, 7] }, + { out: [3, 6, 9] } + ); + }); + +}); + +describe("ArrayMul", () => { + let circuit: WitnessTester<["lhs", "rhs"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`ArrayMul`, { + file: "circuits/utils", + template: "ArrayMul", + params: [3], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: lhs = [0,1,2], rhs = [3,5,7]", async () => { + await circuit.expectPass( + { lhs: [0, 1, 2], rhs: [3, 5, 7] }, + { out: [0, 5, 14] } + ); + }); + +}); + +describe("GenericArrayAdd", () => { + let circuit: WitnessTester<["arrays"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`ArrayAdd`, { + file: "circuits/utils", + template: "GenericArrayAdd", + params: [3, 2], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: arrays = [[0,1,2],[3,5,7]]", async () => { + await circuit.expectPass( + { arrays: [[0, 1, 2], [3, 5, 7]] }, + { out: [3, 6, 9] } + ); + }); + +}); + +describe("InRange", () => { + let circuit: WitnessTester<["in", "range"], ["out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`InRange`, { + file: "circuits/utils", + template: "InRange", + params: [8], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: in = 1, range = [0,2]", async () => { + await circuit.expectPass( + { in: 1, range: [0, 2] }, + { out: 1 } + ); + }); + + it("witness: in = 69, range = [128,255]", async () => { + await circuit.expectPass( + { in: 69, range: [128, 255] }, + { out: 0 } + ); + }); + + it("witness: in = 200, range = [128,255]", async () => { + await circuit.expectPass( + { in: 1, range: [0, 2] }, + { out: 1 } + ); + }); +}); + +describe("Switch", () => { + let circuit: WitnessTester<["case", "branches", "vals"], ["match", "out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`Switch`, { + file: "circuits/utils", + template: "Switch", + params: [3], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: case = 0, branches = [0, 1, 2], vals = [69, 420, 1337]", async () => { + await circuit.expectPass( + { case: 0, branches: [0, 1, 2], vals: [69, 420, 1337] }, + { match: 1, out: 69 }, + ); + }); + + it("witness: case = 1, branches = [0, 1, 2], vals = [69, 420, 1337]", async () => { + await circuit.expectPass( + { case: 1, branches: [0, 1, 2], vals: [69, 420, 1337] }, + { match: 1, out: 420 }, + ); + }); + + it("witness: case = 2, branches = [0, 1, 2], vals = [69, 420, 1337]", async () => { + await circuit.expectPass( + { case: 2, branches: [0, 1, 2], vals: [69, 420, 1337] }, + { match: 1, out: 1337 }, + ); + }); + + it("witness: case = 3, branches = [0, 1, 2], vals = [69, 420, 1337]", async () => { + await circuit.expectPass( + { case: 3, branches: [0, 1, 2], vals: [69, 420, 1337] }, + { match: 0, out: 0 }, + ); + }); + + +}); + +describe("SwitchArray", () => { + let circuit: WitnessTester<["case", "branches", "vals"], ["match", "out"]>; + before(async () => { + circuit = await circomkit.WitnessTester(`SwitchArray`, { + file: "circuits/utils", + template: "SwitchArray", + params: [3, 2], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: case = 0, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { + await circuit.expectPass( + { case: 0, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, + { match: 1, out: [69, 0] }, + ); + }); + + it("witness: case = 1, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { + await circuit.expectPass( + { case: 1, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, + { match: 1, out: [420, 1] }, + ); + }); + + it("witness: case = 2, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { + await circuit.expectPass( + { case: 2, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, + { match: 1, out: [1337, 2] }, + ); + }); + + it("witness: case = 3, branches = [0, 1, 2], vals = [[69,0], [420,1], [1337,2]]", async () => { + await circuit.expectPass( + { case: 3, branches: [0, 1, 2], vals: [[69, 0], [420, 1], [1337, 2]] }, + { match: 0, out: [0, 0] } + ); + }); + + it("witness: case = 420, branches = [69, 420, 1337], vals = [[10,3], [20,5], [30,7]]", async () => { + await circuit.expectPass( + { case: 420, branches: [69, 420, 1337], vals: [[10, 3], [20, 5], [30, 7]] }, + { match: 1, out: [20, 5] } + ); + }); + + it("witness: case = 0, branches = [69, 420, 1337], vals = [[10,3], [20,5], [30,7]]", async () => { + await circuit.expectPass( + { case: 0, branches: [69, 420, 1337], vals: [[10, 3], [20, 5], [30, 7]] }, + { match: 0, out: [0, 0] } + ); + }); + +}); + diff --git a/circuits/utils.circom b/circuits/utils.circom new file mode 100644 index 0000000..ed0f485 --- /dev/null +++ b/circuits/utils.circom @@ -0,0 +1,342 @@ +/* +# `utils` +This module consists of helper templates for convencience. +It mostly extends the `bitify` and `comparators` modules from Circomlib. + +## Layout +The key ingredients of `utils` are: + - `ASCII`: Verify if a an input array contains valid ASCII values (e.g., u8 vals). + - `IsEqualArray`: Check if two arrays are equal component by component. + - `Contains`: Check if an element is contained in a given array. + - `ArrayAdd`: Add two arrays together component by component. + - `ArrayMul`: Multiply two arrays together component by component. + - `GenericArrayAdd`: Add together an arbitrary amount of arrays. + - `ScalarArrayMul`: Multiply each array element by a scalar value. + - `InRange`: Check if a given number is in a given range. + - `Switch`: Return a scalar value given a specific case. + - `SwitchArray`: Return an array given a specific case. + + +## Testing +Tests for this module are located in the file: `./test/utils/utils.test.ts` +*/ + +pragma circom 2.1.9; + +include "circomlib/circuits/bitify.circom"; +include "circomlib/circuits/comparators.circom"; + + + +/* +This template passes if a given array contains only valid ASCII values (e.g., u8 vals). + +# Params: + - `n`: the length of the array + +# Inputs: + - `in[n]`: array to check +*/ +template ASCII(n) { + signal input in[n]; + + component Byte[n]; + for(var i = 0; i < n; i++) { + Byte[i] = Num2Bits(8); + Byte[i].in <== in[i]; + } +} + +/* +This template is an indicator for two equal array inputs. + +# Params: + - `n`: the length of arrays to compare + +# Inputs: + - `in[2][n]`: two arrays of `n` numbers + +# Outputs: + - `out`: either `0` or `1` + - `1` if `in[0]` is equal to `in[1]` as arrays (i.e., component by component) + - `0` otherwise +*/ +template IsEqualArray(n) { + signal input in[2][n]; + signal output out; + + var accum = 0; + component equalComponent[n]; + + for(var i = 0; i < n; i++) { + equalComponent[i] = IsEqual(); + equalComponent[i].in[0] <== in[0][i]; + equalComponent[i].in[1] <== in[1][i]; + accum += equalComponent[i].out; + } + + component totalEqual = IsEqual(); + totalEqual.in[0] <== n; + totalEqual.in[1] <== accum; + out <== totalEqual.out; +} + + +// TODO: There should be a way to have the below assertion come from the field itself. +/* +This template is an indicator for if an array contains an element. + +# Params: + - `n`: the size of the array to search through + +# Inputs: + - `in`: a number + - `array[n]`: the array we want to search through + +# Outputs: + - `out`: either `0` or `1` + - `1` if `in` is found inside `array` + - `0` otherwise +*/ +template Contains(n) { + assert(n > 0); + /* + If `n = p` for this large `p`, then it could be that this template + returns the wrong value if every element in `array` was equal to `in`. + This is EXTREMELY unlikely and iterating this high is impossible anyway. + But it is better to check than miss something, so we bound it by `2**254` for now. + */ + assert(n < 2**254); + signal input in; + signal input array[n]; + signal output out; + + var accum = 0; + component equalComponent[n]; + for(var i = 0; i < n; i++) { + equalComponent[i] = IsEqual(); + equalComponent[i].in[0] <== in; + equalComponent[i].in[1] <== array[i]; + accum = accum + equalComponent[i].out; + } + + component someEqual = IsZero(); + someEqual.in <== accum; + + // Apply `not` to this by 1-x + out <== 1 - someEqual.out; +} + +/* +This template adds two arrays component by component. + +# Params: + - `n`: the length of arrays to compare + +# Inputs: + - `in[2][n]`: two arrays of `n` numbers + +# Outputs: + - `out[n]`: the array sum value +*/ +template ArrayAdd(n) { + signal input lhs[n]; + signal input rhs[n]; + signal output out[n]; + + for(var i = 0; i < n; i++) { + out[i] <== lhs[i] + rhs[i]; + } +} + +/* +This template multiplies two arrays component by component. + +# Params: + - `n`: the length of arrays to compare + +# Inputs: + - `in[2][n]`: two arrays of `n` numbers + +# Outputs: + - `out[n]`: the array multiplication value +*/ +template ArrayMul(n) { + signal input lhs[n]; + signal input rhs[n]; + signal output out[n]; + + for(var i = 0; i < n; i++) { + out[i] <== lhs[i] * rhs[i]; + } +} + +/* +This template multiplies two arrays component by component. + +# Params: + - `m`: the length of the arrays to add + - `n`: the number of arrays to add + +# Inputs: + - `arrays[m][n]`: `n` arrays of `m` numbers + +# Outputs: + - `out[m]`: the sum of all the arrays +*/ +template GenericArrayAdd(m,n) { + signal input arrays[n][m]; + signal output out[m]; + + var accum[m]; + for(var i = 0; i < m; i++) { + for(var j = 0; j < n; j++) { + accum[i] += arrays[j][i]; + } + } + out <== accum; +} + +/* +This template multiplies each component of an array by a scalar value. + +# Params: + - `n`: the length of the array + +# Inputs: + - `array[n]`: an array of `n` numbers + +# Outputs: + - `out[n]`: the scalar multiplied array +*/ +template ScalarArrayMul(n) { + signal input array[n]; + signal input scalar; + signal output out[n]; + + for(var i = 0; i < n; i++) { + out[i] <== scalar * array[i]; + } +} + +/* +This template checks if a given `n`-bit value is contained in a range of `n`-bit values + +# Params: + - `n`: the number of bits to use + +# Inputs: + - `range[2]`: the lower and upper bound of the array, respectively + +# Outputs: + - `out`: either `0` or `1` + - `1` if `in` is within the range + - `0` otherwise +*/ +template InRange(n) { + signal input in; + signal input range[2]; + signal output out; + + component gte = GreaterEqThan(n); + gte.in <== [in, range[0]]; + + component lte = LessEqThan(n); + lte.in <== [in, range[1]]; + + out <== gte.out * lte.out; +} + +/* +This template is creates an exhaustive switch statement from a list of branch values. +# Params: + - `n`: the number of switch cases + +# Inputs: + - `case`: which case of the switch to select + - `branches[n]`: the values that enable taking different branches in the switch + (e.g., if `branch[i] == 10` then if `case == 10` we set `out == `vals[i]`) + - `vals[n]`: the value that is emitted for a given switch case + (e.g., `val[i]` array is emitted on `case == `branch[i]`) + +# Outputs + - `match`: is set to `0` if `case` does not match on any of `branches` + - `out[n]`: the selected output value if one of `branches` is selected (will be `0` otherwise) + ^^^^^^ BEWARE OF THIS FACT ABOVE! +*/ +template Switch(n) { + assert(n > 0); + signal input case; + signal input branches[n]; + signal input vals[n]; + signal output match; + signal output out; + + + // Verify that the `case` is in the possible set of branches + component indicator[n]; + component matchChecker = Contains(n); + signal temp_val[n]; + var sum; + for(var i = 0; i < n; i++) { + indicator[i] = IsZero(); + indicator[i].in <== case - branches[i]; + matchChecker.array[i] <== 1 - indicator[i].out; + temp_val[i] <== indicator[i].out * vals[i]; + sum += temp_val[i]; + } + matchChecker.in <== 0; + match <== matchChecker.out; + + out <== sum; +} + +/* +This template is creates an exhaustive switch statement from a list of branch values. +# Params: + - `m`: the number of switch cases + - `n`: the output array length + +# Inputs: + + - `case`: which case of the switch to select + - `branches[m]`: the values that enable taking different branches in the switch + (e.g., if `branch[i] == 10` then if `case == 10` we set `out == `vals[i]`) + - `vals[m][n]`: the value that is emitted for a given switch case + (e.g., `val[i]` array is emitted on `case == `branch[i]`) + +# Outputs + - `match`: is set to `0` if `case` does not match on any of `branches` + - `out[n]`: the selected output value if one of `branches` is selected (will be `[0,0,...]` otherwise) + ^^^^^^ BEWARE OF THIS FACT ABOVE! +*/ +template SwitchArray(m, n) { + assert(m > 0); + assert(n > 0); + signal input case; + signal input branches[m]; + signal input vals[m][n]; + signal output match; + signal output out[n]; + + + // Verify that the `case` is in the possible set of branches + component indicator[m]; + component matchChecker = Contains(m); + signal component_out[m][n]; + var sum[n]; + for(var i = 0; i < m; i++) { + indicator[i] = IsZero(); + indicator[i].in <== case - branches[i]; + matchChecker.array[i] <== 1 - indicator[i].out; + for(var j = 0; j < n; j++) { + component_out[i][j] <== indicator[i].out * vals[i][j]; + sum[j] += component_out[i][j]; + } + } + matchChecker.in <== 0; + match <== matchChecker.out; + + out <== sum; +} + diff --git a/inputs/test_extract_two_key/input.json b/inputs/test_extract_two_key/input.json deleted file mode 100644 index 619812a..0000000 --- a/inputs/test_extract_two_key/input.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "key": [ - 107, - 101, - 121, - 49 - ], - "data": [ - 123, - 10, - 32, - 32, - 32, - 32, - 34, - 107, - 101, - 121, - 49, - 34, - 58, - 32, - 34, - 97, - 98, - 99, - 34, - 44, - 10, - 32, - 32, - 32, - 32, - 34, - 107, - 101, - 121, - 50, - 34, - 58, - 32, - 34, - 100, - 101, - 102, - 34, - 10, - 125 - ] -} \ No newline at end of file diff --git a/json_examples/example.json b/json_examples/example.json deleted file mode 100644 index c8eef94..0000000 --- a/json_examples/example.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "glossary": { - "title": "example glossary", - "GlossDiv": { - "title": "S", - "GlossList": { - "GlossEntry": { - "ID": "SGML", - "SortAs": "SGML", - "GlossTerm": "Standard Generalized Markup Language", - "Acronym": "SGML", - "Abbrev": "ISO 8879:1986", - "GlossDef": { - "para": "A meta-markup language, used to create markup languages such as DocBook.", - "GlossSeeAlso": [ - "GML", - "XML" - ] - }, - "GlossSee": "markup" - } - } - } - } -} \ No newline at end of file diff --git a/json_examples/response/reddit.json b/json_examples/response/reddit.json new file mode 100644 index 0000000..163e6b4 --- /dev/null +++ b/json_examples/response/reddit.json @@ -0,0 +1,14 @@ +{ + "data": { + "redditorInfoByName": { + "id": "t2_bepsb", + "karma": { + "fromAwardsGiven": 0, + "fromAwardsReceived": 470, + "fromComments": 9583, + "fromPosts": 13228, + "total": 23281 + } + } + } +} \ No newline at end of file diff --git a/json_examples/spotify_response.json b/json_examples/response/spotify.json similarity index 100% rename from json_examples/spotify_response.json rename to json_examples/response/spotify.json diff --git a/json_examples/venmo_response.json b/json_examples/response/venmo.json similarity index 100% rename from json_examples/venmo_response.json rename to json_examples/response/venmo.json diff --git a/json_examples/test.json b/json_examples/test.json deleted file mode 100644 index 31a17d9..0000000 --- a/json_examples/test.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "key1": "abc" -} \ No newline at end of file diff --git a/json_examples/test/example.json b/json_examples/test/example.json new file mode 100644 index 0000000..8664af9 --- /dev/null +++ b/json_examples/test/example.json @@ -0,0 +1,24 @@ +{ "a": +{ "b": "c", +"d": { +"e": "f", +"g": { +"h": { +"i": "j", +"k": "l", +"m": "n", +"o": "p", +"q": "r", +"s": { +"t": "u", +"v": [ +"w", +"x" +] +}, +"y": "z" +} +} +} +} +} \ No newline at end of file diff --git a/json_examples/test/example_json.md b/json_examples/test/example_json.md new file mode 100644 index 0000000..a398ff8 --- /dev/null +++ b/json_examples/test/example_json.md @@ -0,0 +1,25 @@ +{ "a": // 7 +{ "b": "c", // 19 +"d": { // 25 +"e": "f", // 35 +"g": { // 42 +"h": { // 48 +"i": "j", +"k": "l", +"m": "n", +"o": "p", +"q": "r", +"s": { +"t": "u", +"v": [ +"w", +"x" +] +}, // 138 +"y": "z" // 147 +} // 149 +} // 151 +} // 153 +} // 155 +} // 157 + diff --git a/json_examples/test/string_escape.json b/json_examples/test/string_escape.json new file mode 100644 index 0000000..8765251 --- /dev/null +++ b/json_examples/test/string_escape.json @@ -0,0 +1 @@ +{ "a": "\"b\"" } \ No newline at end of file diff --git a/json_examples/test/two_keys.json b/json_examples/test/two_keys.json new file mode 100644 index 0000000..437a6c5 --- /dev/null +++ b/json_examples/test/two_keys.json @@ -0,0 +1,4 @@ +{ + "key1": "abc", + "key2": "def" +} \ No newline at end of file diff --git a/json_examples/test/value_array.json b/json_examples/test/value_array.json new file mode 100644 index 0000000..865f425 --- /dev/null +++ b/json_examples/test/value_array.json @@ -0,0 +1 @@ +{ "k" : [420,69] } \ No newline at end of file diff --git a/json_examples/test/value_array_nested.json b/json_examples/test/value_array_nested.json new file mode 100644 index 0000000..13907e1 --- /dev/null +++ b/json_examples/test/value_array_nested.json @@ -0,0 +1 @@ +{ "a": [[1,0],[0,1,3]] } \ No newline at end of file diff --git a/json_examples/test/value_array_object.json b/json_examples/test/value_array_object.json new file mode 100644 index 0000000..2bbaec9 --- /dev/null +++ b/json_examples/test/value_array_object.json @@ -0,0 +1 @@ +{"a":[{"b":5},{"c":"b"}]} \ No newline at end of file diff --git a/json_examples/test/value_array_object_array.json b/json_examples/test/value_array_object_array.json new file mode 100644 index 0000000..885e4ed --- /dev/null +++ b/json_examples/test/value_array_object_array.json @@ -0,0 +1 @@ +{"a":[{"b":5},{"c":[0,1,"a"]}]} \ No newline at end of file diff --git a/json_examples/test/value_number.json b/json_examples/test/value_number.json new file mode 100644 index 0000000..70ddfbb --- /dev/null +++ b/json_examples/test/value_number.json @@ -0,0 +1 @@ +{ "k" : 69 } \ No newline at end of file diff --git a/json_examples/test/value_object.json b/json_examples/test/value_object.json new file mode 100644 index 0000000..dfaaba1 --- /dev/null +++ b/json_examples/test/value_object.json @@ -0,0 +1 @@ +{ "a": { "b": "c" } } \ No newline at end of file diff --git a/json_examples/test/value_string.json b/json_examples/test/value_string.json new file mode 100644 index 0000000..7f1fc65 --- /dev/null +++ b/json_examples/test/value_string.json @@ -0,0 +1 @@ +{ "k": "v" } \ No newline at end of file diff --git a/json_examples/test_hard.json b/json_examples/test_hard.json deleted file mode 100644 index 7904a51..0000000 --- a/json_examples/test_hard.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "key1": "{}a,bc}", - "key2": "\"abc\"" -} \ No newline at end of file diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..ba1c261 --- /dev/null +++ b/notes.md @@ -0,0 +1,51 @@ +# Notes + +## TODOs +### JSON Types +- [x] Object +- [x] String +- [x] Array +- [x] Number +- [ ] Boolean +- [ ] Null + +Parsing null and bool need to do some kind of look ahead parsing. To handle numbers properly we also probably need that actually since we need to look ahead to where we get white space. +Need to look ahead for `true` and `false` for example to ensure we get a full match, or we fail or something. Lookaehad might be overkill, but yeah. + +#### Numbers +Numbers can have `e` and decimal `.` in them. Riperoni. + +### string escape +shouldn't be too hard, just add one more state variable `escaping` that is only enabled when parsing a string and can only be toggled -- next state will always have to set back to 0. + +This could also allow for parsing unicode + +### Other thoughts + - Pointer may not actually be necessary, because it just points to the first unallocated position in the stac + - How do we know how tall to make the stack? Open braces `{`, open brackets `[` and colons `:` all push onto the stack. + - We might not actually need to push the stack higher with a colon, instead we could push state into the second slot of the stack like we do with commas inside of arrays. + +## Expected Output +> This is old at this point, but we should update it. +``` +Notes: for `test.json` +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + POINTER | Read In: | STATE +------------------------------------------------- +State[1] | { | PARSING TO KEY +------------------------------------------------- +State[7] | " | INSIDE KEY +------------------------------------------------- +State[12]| " | NOT INSIDE KEY +------------------------------------------------- +State[13]| : | PARSING TO VALUE +------------------------------------------------- +State[15]| " | INSIDE VALUE +------------------------------------------------- +State[19]| " | NOT INSIDE VALUE +------------------------------------------------- +State[20]| " | COMPLETE WITH KV PARSING +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +State[20].next_tree_depth == 0 | VALID JSON +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cf94f9d..8f16d6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,14 +53,43 @@ pub fn main() -> Result<(), Box> { // Create a witness file as `input.json` let witness = Witness { keys: keys_map, - data, + data: data.clone(), }; + if !args.output_dir.exists() { + std::fs::create_dir_all(&args.output_dir)?; + } + let output_file = args.output_dir.join(args.filename); let mut file = std::fs::File::create(output_file)?; file.write_all(serde_json::to_string_pretty(&witness)?.as_bytes())?; - println!("Input file created successfully."); + // Prepare lines to print + let mut lines = Vec::new(); + lines.push(String::from("Key lengths:")); + for (index, key) in args.keys.iter().enumerate() { + lines.push(format!("key{} length: {}", index + 1, key.len())); + } + lines.push(format!("Data length: {}", data.len())); + + // Print the output inside a nicely formatted box + print_boxed_output(lines); Ok(()) } + +fn print_boxed_output(lines: Vec) { + // Determine the maximum length of the lines + let max_length = lines.iter().map(|line| line.len()).max().unwrap_or(0); + + // Characters for the box + let top_border = format!("┌{}┐", "─".repeat(max_length + 2)); + let bottom_border = format!("└{}┘", "─".repeat(max_length + 2)); + + // Print the box with content + println!("{}", top_border); + for line in lines { + println!("│ {: