diff --git a/builds/target_1024b/aes_gctr_nivc_1024b.circom b/builds/target_1024b/aes_gctr_nivc_1024b.circom index a48234a..c3d1ae5 100644 --- a/builds/target_1024b/aes_gctr_nivc_1024b.circom +++ b/builds/target_1024b/aes_gctr_nivc_1024b.circom @@ -2,4 +2,4 @@ pragma circom 2.1.9; include "../../circuits/aes-gcm/nivc/aes-gctr-nivc.circom"; -component main { public [step_in] } = AESGCTRFOLD(1024); \ No newline at end of file +component main { public [step_in] } = AESGCTRFOLD(); \ No newline at end of file diff --git a/builds/target_512b/aes_gctr_nivc_512b.circom b/builds/target_512b/aes_gctr_nivc_512b.circom index f863572..c3d1ae5 100644 --- a/builds/target_512b/aes_gctr_nivc_512b.circom +++ b/builds/target_512b/aes_gctr_nivc_512b.circom @@ -2,4 +2,4 @@ pragma circom 2.1.9; include "../../circuits/aes-gcm/nivc/aes-gctr-nivc.circom"; -component main { public [step_in] } = AESGCTRFOLD(512); \ No newline at end of file +component main { public [step_in] } = AESGCTRFOLD(); \ No newline at end of file diff --git a/circuits.json b/circuits.json index 3ae4177..5bd122d 100644 --- a/circuits.json +++ b/circuits.json @@ -212,5 +212,52 @@ 12, 16 ] + }, + "nivc_aes": { + "file": "aes-gcm/nivc/aes-gctr-nivc", + "template": "AESGCTRFOLD" + }, + "nivc_start_line": { + "file": "http/nivc/parse_and_lock_start_line", + "template": "ParseAndLockStartLine", + "params": [ + 1024, + 50, + 200, + 50 + ] + }, + "nivc_lock_header": { + "file": "http/nivc/lock_header", + "template": "LockHeader", + "params": [ + 1024, + 50, + 100 + ] + }, + "nivc_body_mask": { + "file": "http/nivc/body_mask", + "template": "HTTPMaskBodyNIVC", + "params": [ + 1024 + ] + }, + "nivc_json_object": { + "file": "json/nivc/masker", + "template": "JsonMaskObjectNIVC", + "params": [ + 1024, + 10, + 10 + ] + }, + "nivc_json_array": { + "file": "json/nivc/masker", + "template": "JsonMaskArrayIndexNIVC", + "params": [ + 1024, + 10 + ] } } \ No newline at end of file diff --git a/circuits/aes-gcm/nivc/aes-gctr-nivc.circom b/circuits/aes-gcm/nivc/aes-gctr-nivc.circom index d0f97ca..75920ab 100644 --- a/circuits/aes-gcm/nivc/aes-gctr-nivc.circom +++ b/circuits/aes-gcm/nivc/aes-gctr-nivc.circom @@ -2,76 +2,32 @@ pragma circom 2.1.9; include "gctr-nivc.circom"; include "../../utils/array.circom"; - +include "../../utils/hash.circom"; // Compute AES-GCTR -template AESGCTRFOLD(DATA_BYTES) { - // ------------------------------------------------------------------------------------------------------------------ // - // ~~ Set sizes at compile time ~~ - assert(DATA_BYTES % 16 == 0); - // Value for accumulating both packed plaintext and ciphertext as well as counter - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; - // ------------------------------------------------------------------------------------------------------------------ // - - +template AESGCTRFOLD() { signal input key[16]; signal input iv[12]; signal input aad[16]; + signal input ctr[4]; signal input plainText[16]; - // step_in[0..DATA_BYTES] => accumulate plaintext blocks - // step_in[DATA_BYTES..DATA_BYTES*2] => accumulate ciphertext blocks - // step_in[DATA_BYTES_LEN*2..DATA_BYTES*2+4] => accumulate counter - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; + signal input cipherText[16]; + signal input step_in[1]; + signal output step_out[1]; - // We extract the number from the 4 byte word counter - component last_counter_bits = BytesToBits(4); - for(var i = 0; i < 4; i ++) { - last_counter_bits.in[i] <== step_in[DATA_BYTES + i]; - } - component last_counter_num = Bits2Num(32); - // pass in reverse order - for (var i = 0; i< 32; i++){ - last_counter_num.in[i] <== last_counter_bits.out[31 - i]; - } - signal index <== last_counter_num.out - 1; - - // folds one block - component aes = AESGCTRFOLDABLE(); - aes.key <== key; - aes.iv <== iv; - aes.aad <== aad; - aes.plainText <== plainText; + component aes = AESGCTRFOLDABLE(); + aes.key <== key; + aes.iv <== iv; + aes.aad <== aad; + aes.plainText <== plainText; + aes.lastCounter <== ctr; - for(var i = 0; i < 4; i++) { - aes.lastCounter[i] <== step_in[DATA_BYTES + i]; - } - - // Write out the plaintext and ciphertext to our accumulation arrays, both at once. - signal textToPack[16][2]; + aes.cipherText === cipherText; + var packedPlaintext = 0; for(var i = 0 ; i < 16 ; i++) { - textToPack[i][0] <== plainText[i]; - textToPack[i][1] <== aes.cipherText[i]; - } - signal nextPackedChunk[16] <== GenericBytePackArray(16,2)(textToPack); - - signal prevAccumulatedPackedText[DATA_BYTES]; - for(var i = 0 ; i < DATA_BYTES ; i++) { - prevAccumulatedPackedText[i] <== step_in[i]; - } - component nextAccumulatedPackedText = WriteToIndex(DATA_BYTES, 16); - nextAccumulatedPackedText.array_to_write_to <== prevAccumulatedPackedText; - nextAccumulatedPackedText.array_to_write_at_index <== nextPackedChunk; - nextAccumulatedPackedText.index <== index * 16; - - for(var i = 0 ; i < TOTAL_BYTES_ACROSS_NIVC ; i++) { - if(i < DATA_BYTES) { - step_out[i] <== nextAccumulatedPackedText.out[i]; - } else { - step_out[i] <== aes.counter[i - DATA_BYTES]; - } + packedPlaintext += plainText[i] * 2**(8*i); } + step_out[0] <== PoseidonChainer()([step_in[0],packedPlaintext]); } - diff --git a/circuits/http/nivc/body_mask.circom b/circuits/http/nivc/body_mask.circom index 1e53c34..d8956ed 100644 --- a/circuits/http/nivc/body_mask.circom +++ b/circuits/http/nivc/body_mask.circom @@ -1,24 +1,16 @@ pragma circom 2.1.9; include "../parser/machine.circom"; +include "../../utils/hash.circom"; template HTTPMaskBodyNIVC(DATA_BYTES) { - // ------------------------------------------------------------------------------------------------------------------ // - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; // aes ct/pt + ctr - // ------------------------------------------------------------------------------------------------------------------ // - - // ------------------------------------------------------------------------------------------------------------------ // - // ~ Unravel from previous NIVC step ~ - // Read in from previous NIVC step (HttpParseAndLockStartLine or HTTPLockHeader) - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; - - signal data[DATA_BYTES]; - // signal parsing_body[DATA_BYTES]; - for (var i = 0 ; i < DATA_BYTES ; i++) { - data[i] <== step_in[i]; - // parsing_body[i] <== step_in[DATA_BYTES + i * 5 + 4]; // `parsing_body` stored in every 5th slot of step_in/out - } + signal input step_in[1]; + signal output step_out[1]; + + // Authenticate the plaintext we are passing in + signal input data[DATA_BYTES]; + signal data_hash <== DataHasher(DATA_BYTES)(data); + data_hash === step_in[0]; // ------------------------------------------------------------------------------------------------------------------ // // PARSE @@ -46,13 +38,13 @@ template HTTPMaskBodyNIVC(DATA_BYTES) { // ------------------------------------------------------------------------------------------------------------------ // // ------------------------------------------------------------------------------------------------------------------ // - // ~ Write out to next NIVC step - for (var i = 0 ; i < TOTAL_BYTES_ACROSS_NIVC ; i++) { - if(i < DATA_BYTES) { - step_out[i] <== data[i] * State[i].next_parsing_body; - } else { - step_out[i] <== 0; - } + // Mask out just the JSON body + signal bodyMasked[DATA_BYTES]; + for (var i = 0 ; i < DATA_BYTES ; i++) { + bodyMasked[i] <== data[i] * State[i].next_parsing_body; } + + // Hash the new data so this can now be used in the chain later + step_out[0] <== DataHasher(DATA_BYTES)(bodyMasked); } diff --git a/circuits/http/nivc/lock_header.circom b/circuits/http/nivc/lock_header.circom index 29c31e5..2cb109e 100644 --- a/circuits/http/nivc/lock_header.circom +++ b/circuits/http/nivc/lock_header.circom @@ -4,22 +4,21 @@ include "../parser/machine.circom"; include "../interpreter.circom"; include "../../utils/array.circom"; include "circomlib/circuits/comparators.circom"; +include "@zk-email/circuits/utils/array.circom"; // TODO: should use a MAX_HEADER_NAME_LENGTH and a MAX_HEADER_VALUE_LENGTH template LockHeader(DATA_BYTES, MAX_HEADER_NAME_LENGTH, MAX_HEADER_VALUE_LENGTH) { - // ------------------------------------------------------------------------------------------------------------------ // - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; // aes pt/ct + ctr - // ------------------------------------------------------------------------------------------------------------------ // - // ------------------------------------------------------------------------------------------------------------------ // - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; + assert(DATA_BYTES >= MAX_HEADER_NAME_LENGTH + MAX_HEADER_VALUE_LENGTH); - // get the plaintext - signal data[DATA_BYTES]; - for (var i = 0 ; i < DATA_BYTES ; i++) { - data[i] <== step_in[i]; - } + signal input step_in[1]; + signal output step_out[1]; + + // Authenticate the plaintext we are passing in + signal input data[DATA_BYTES]; + signal data_hash <== DataHasher(DATA_BYTES)(data); + data_hash === step_in[0]; + step_out[0] <== step_in[0]; // ------------------------------------------------------------------------------------------------------------------ // // PARSE @@ -74,7 +73,11 @@ template LockHeader(DATA_BYTES, MAX_HEADER_NAME_LENGTH, MAX_HEADER_VALUE_LENGTH) // find header location signal headerNameLocation <== FirstStringMatch(DATA_BYTES, MAX_HEADER_NAME_LENGTH)(data, header); + // TODO (autoparallel): This could probably be optimized by selecting a subarray of length `MAX_HEADER_NAME_LENGTH + MAX_HEADER_VALUE_LENGTH` at `headerNameLocation` // This is the assertion that we have locked down the correct header + + // signal dataSubArray[MAX_HEADER_NAME_LENGTH + MAX_HEADER_VALUE_LENGTH] <== SelectSubArray(DATA_BYTES, MAX_HEADER_NAME_LENGTH + MAX_HEADER_VALUE_LENGTH)(data, headerNameLocation, MAX_HEADER_NAME_LENGTH + MAX_HEADER_VALUE_LENGTH); + // signal headerFieldNameValueMatch <== HeaderFieldNameValueMatchPadded(MAX_HEADER_NAME_LENGTH + MAX_HEADER_VALUE_LENGTH, MAX_HEADER_NAME_LENGTH, MAX_HEADER_VALUE_LENGTH)(dataSubArray, header, headerNameLength, value, headerValueLength, headerNameLocation); signal headerFieldNameValueMatch <== HeaderFieldNameValueMatchPadded(DATA_BYTES, MAX_HEADER_NAME_LENGTH, MAX_HEADER_VALUE_LENGTH)(data, header, headerNameLength, value, headerValueLength, headerNameLocation); headerFieldNameValueMatch === 1; @@ -82,18 +85,6 @@ template LockHeader(DATA_BYTES, MAX_HEADER_NAME_LENGTH, MAX_HEADER_VALUE_LENGTH) signal isParsingHeader <== IndexSelector(DATA_BYTES * 5)(httpParserState, headerNameLocation * 5 + 1); signal parsingHeader <== GreaterThan(10)([isParsingHeader, 0]); parsingHeader === 1; - - // ------------------------------------------------------------------------------------------------------------------ // - // write out the pt again - for (var i = 0 ; i < TOTAL_BYTES_ACROSS_NIVC ; i++) { - // add plaintext http input to step_out and ignore the ciphertext - if(i < DATA_BYTES) { - step_out[i] <== step_in[i]; - } else { - step_out[i] <== 0; - } - } - } // TODO: Handrolled template that I haven't tested YOLO. diff --git a/circuits/http/nivc/parse_and_lock_start_line.circom b/circuits/http/nivc/parse_and_lock_start_line.circom index 5b00255..32cc4ec 100644 --- a/circuits/http/nivc/parse_and_lock_start_line.circom +++ b/circuits/http/nivc/parse_and_lock_start_line.circom @@ -4,26 +4,23 @@ include "../parser/machine.circom"; include "../interpreter.circom"; include "../../utils/bytes.circom"; -// TODO: Note that TOTAL_BYTES will match what we have for AESGCMFOLD step_out -// I have not gone through to double check the sizes of everything yet. template ParseAndLockStartLine(DATA_BYTES, MAX_BEGINNING_LENGTH, MAX_MIDDLE_LENGTH, MAX_FINAL_LENGTH) { - // ------------------------------------------------------------------------------------------------------------------ // - // ~~ Set sizes at compile time ~~ - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; // AES ct/pt + ctr - // ------------------------------------------------------------------------------------------------------------------ // - - // ------------------------------------------------------------------------------------------------------------------ // - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; - - // Get the plaintext - signal packedData[DATA_BYTES]; - for (var i = 0 ; i < DATA_BYTES ; i++) { - packedData[i] <== step_in[i]; + var MINIMUM_PARSE_LENGTH = MAX_BEGINNING_LENGTH + MAX_MIDDLE_LENGTH + MAX_FINAL_LENGTH; + assert(DATA_BYTES >= MINIMUM_PARSE_LENGTH); + + signal input step_in[1]; + signal output step_out[1]; + + // Authenticate the plaintext we are passing in + signal input data[DATA_BYTES]; + signal data_hash <== DataHasher(DATA_BYTES)(data); + data_hash === step_in[0]; + step_out[0] <== step_in[0]; + + signal dataToParse[MINIMUM_PARSE_LENGTH]; + for(var i = 0 ; i < MINIMUM_PARSE_LENGTH ; i++) { + dataToParse[i] <== data[i]; } - component unpackData = UnpackDoubleByteArray(DATA_BYTES); - unpackData.in <== packedData; - signal data[DATA_BYTES] <== unpackData.lower; signal input beginning[MAX_BEGINNING_LENGTH]; signal input beginning_length; @@ -32,10 +29,11 @@ template ParseAndLockStartLine(DATA_BYTES, MAX_BEGINNING_LENGTH, MAX_MIDDLE_LENG signal input final[MAX_FINAL_LENGTH]; signal input final_length; - // Initialze the parser - component State[DATA_BYTES]; + // Initialze the parser, note that we only need to parse as much as the `MINIMUM_PARSE_LENGTH` + // since the start line could not possibly go past this point, or else this would fail anyway + component State[MINIMUM_PARSE_LENGTH]; State[0] = HttpStateUpdate(); - State[0].byte <== data[0]; + State[0].byte <== dataToParse[0]; State[0].parsing_start <== 1; State[0].parsing_header <== 0; State[0].parsing_field_name <== 0; @@ -50,9 +48,9 @@ template ParseAndLockStartLine(DATA_BYTES, MAX_BEGINNING_LENGTH, MAX_MIDDLE_LENG */ // Setup to check middle bytes - signal startLineMask[DATA_BYTES]; - signal middleMask[DATA_BYTES]; - signal finalMask[DATA_BYTES]; + signal startLineMask[MINIMUM_PARSE_LENGTH]; + signal middleMask[MINIMUM_PARSE_LENGTH]; + signal finalMask[MINIMUM_PARSE_LENGTH]; startLineMask[0] <== inStartLine()(State[0].parsing_start); middleMask[0] <== inStartMiddle()(State[0].parsing_start); finalMask[0] <== inStartEnd()(State[0].parsing_start); @@ -61,9 +59,9 @@ template ParseAndLockStartLine(DATA_BYTES, MAX_BEGINNING_LENGTH, MAX_MIDDLE_LENG var middle_start_counter = 1; var middle_end_counter = 1; var final_end_counter = 1; - for(var data_idx = 1; data_idx < DATA_BYTES; data_idx++) { + for(var data_idx = 1; data_idx < MINIMUM_PARSE_LENGTH; data_idx++) { State[data_idx] = HttpStateUpdate(); - State[data_idx].byte <== data[data_idx]; + State[data_idx].byte <== dataToParse[data_idx]; State[data_idx].parsing_start <== State[data_idx - 1].next_parsing_start; State[data_idx].parsing_header <== State[data_idx - 1].next_parsing_header; State[data_idx].parsing_field_name <== State[data_idx - 1].next_parsing_field_name; @@ -85,27 +83,17 @@ template ParseAndLockStartLine(DATA_BYTES, MAX_BEGINNING_LENGTH, MAX_MIDDLE_LENG // Additionally verify beginning had correct length beginning_length === middle_start_counter - 1; - signal beginningMatch <== SubstringMatchWithIndexPadded(DATA_BYTES, MAX_BEGINNING_LENGTH)(data, beginning, beginning_length, 0); + signal beginningMatch <== SubstringMatchWithIndexPadded(MINIMUM_PARSE_LENGTH, MAX_BEGINNING_LENGTH)(dataToParse, beginning, beginning_length, 0); // Check middle is correct by substring match and length check - signal middleMatch <== SubstringMatchWithIndexPadded(DATA_BYTES, MAX_MIDDLE_LENGTH)(data, middle, middle_length, middle_start_counter); + signal middleMatch <== SubstringMatchWithIndexPadded(MINIMUM_PARSE_LENGTH, MAX_MIDDLE_LENGTH)(dataToParse, middle, middle_length, middle_start_counter); middleMatch === 1; middle_length === middle_end_counter - middle_start_counter - 1; // Check final is correct by substring match and length check - signal finalMatch <== SubstringMatchWithIndexPadded(DATA_BYTES, MAX_FINAL_LENGTH)(data, final, final_length, middle_end_counter); + signal finalMatch <== SubstringMatchWithIndexPadded(MINIMUM_PARSE_LENGTH, MAX_FINAL_LENGTH)(dataToParse, final, final_length, middle_end_counter); finalMatch === 1; // -2 here for the CRLF final_length === final_end_counter - middle_end_counter - 2; - - // ------------------------------------------------------------------------------------------------------------------ // - // write out the pt again - for (var i = 0 ; i < TOTAL_BYTES_ACROSS_NIVC ; i++) { - // add plaintext http input to step_out and ignore the ciphertext - if(i < DATA_BYTES) { - step_out[i] <== data[i]; // PASS OUT JUST THE PLAINTEXT DATA - } else { - step_out[i] <== 0; - } - } } + diff --git a/circuits/json/nivc/extractor.circom b/circuits/json/nivc/extractor.circom index b5ba6b3..f2de60f 100644 --- a/circuits/json/nivc/extractor.circom +++ b/circuits/json/nivc/extractor.circom @@ -2,40 +2,31 @@ pragma circom 2.1.9; include "circomlib/circuits/gates.circom"; include "@zk-email/circuits/utils/array.circom"; +include "../../utils/hash.circom"; template MaskExtractFinal(DATA_BYTES, MAX_VALUE_LENGTH) { - // ------------------------------------------------------------------------------------------------------------------ // - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; // aes pt/ct + ctr - // ------------------------------------------------------------------------------------------------------------------ // - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; + signal input step_in[1]; + signal input data[DATA_BYTES]; + + signal output step_out[1]; signal is_zero_mask[DATA_BYTES]; signal is_prev_starting_index[DATA_BYTES]; signal value_starting_index[DATA_BYTES]; - signal data[DATA_BYTES]; - for (var i = 0 ; i < DATA_BYTES ; i++) { - data[i] <== step_in[i]; - } + signal data_hash <== DataHasher(DATA_BYTES)(data); + data_hash === step_in[0]; value_starting_index[0] <== 0; is_prev_starting_index[0] <== 0; - is_zero_mask[0] <== IsZero()(step_in[0]); + is_zero_mask[0] <== IsZero()(data[0]); for (var i=1 ; i < DATA_BYTES ; i++) { - is_zero_mask[i] <== IsZero()(step_in[i]); + is_zero_mask[i] <== IsZero()(data[i]); is_prev_starting_index[i] <== IsZero()(value_starting_index[i-1]); value_starting_index[i] <== value_starting_index[i-1] + i * (1-is_zero_mask[i]) * is_prev_starting_index[i]; } - // TODO: Clear step out? + signal value[MAX_VALUE_LENGTH] <== SelectSubArray(DATA_BYTES, MAX_VALUE_LENGTH)(data, value_starting_index[DATA_BYTES-1], MAX_VALUE_LENGTH); - for (var i = 0 ; i < MAX_VALUE_LENGTH ; i++) { - // log(i, value[i]); - step_out[i] <== value[i]; - } - for (var i = MAX_VALUE_LENGTH ; i < TOTAL_BYTES_ACROSS_NIVC ; i++) { - step_out[i] <== 0; - } - // TODO: Do anything with last depth? - // step_out[TOTAL_BYTES_ACROSS_NIVC - 1] <== 0; + + step_out[0] <== DataHasher(MAX_VALUE_LENGTH)(value); } \ No newline at end of file diff --git a/circuits/json/nivc/masker.circom b/circuits/json/nivc/masker.circom index 76d4c08..2ffbf27 100644 --- a/circuits/json/nivc/masker.circom +++ b/circuits/json/nivc/masker.circom @@ -3,21 +3,18 @@ pragma circom 2.1.9; include "../interpreter.circom"; template JsonMaskObjectNIVC(DATA_BYTES, MAX_STACK_HEIGHT, MAX_KEY_LENGTH) { - // ------------------------------------------------------------------------------------------------------------------ // assert(MAX_STACK_HEIGHT >= 2); // TODO (autoparallel): idk if we need this now - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; // aes ct/pt + ctr - // ------------------------------------------------------------------------------------------------------------------ // - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; - - // Declaration of signals. - signal data[DATA_BYTES]; + + signal input step_in[1]; signal input key[MAX_KEY_LENGTH]; signal input keyLen; - for(var i = 0 ; i < DATA_BYTES ; i++) { - data[i] <== step_in[i]; - } + signal output step_out[1]; + + // Authenticate the (potentially further masked) plaintext we are passing in + signal input data[DATA_BYTES]; + signal data_hash <== DataHasher(DATA_BYTES)(data); + data_hash === step_in[0]; // flag determining whether this byte is matched value signal is_value_match[DATA_BYTES - MAX_KEY_LENGTH]; @@ -50,63 +47,67 @@ template JsonMaskObjectNIVC(DATA_BYTES, MAX_STACK_HEIGHT, MAX_KEY_LENGTH) { is_key_match_for_value[1] <== Mux1()([is_key_match_for_value[0] * (1-is_next_pair_at_depth[0]), is_key_match[0] * (1-is_next_pair_at_depth[0])], is_key_match[0]); is_value_match[0] <== parsing_value[0] * is_key_match_for_value[1]; - step_out[0] <== data[0] * is_value_match[0]; + signal masked[DATA_BYTES]; + masked[0] <== data[0] * is_value_match[0]; // TODO (autoparallel): it might be dumb to do this with the max key length but fuck it - for(var data_idx = 1; data_idx < DATA_BYTES - MAX_KEY_LENGTH; 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; - - // - parsing key - // - parsing value (different for string/numbers and array) - // - key match (key 1, key 2) - // - is next pair - // - is key match for value - // - value_mask - // - mask - - // check if inside key or not - parsing_key[data_idx] <== InsideKey()(State[data_idx].next_stack[0], State[data_idx].next_parsing_string, State[data_idx].next_parsing_number); - // check if inside value - parsing_value[data_idx] <== InsideValueObject()(State[data_idx].next_stack[0], State[data_idx].next_stack[1], State[data_idx].next_parsing_string, State[data_idx].next_parsing_number); - - // to get correct value, check: - // - key matches at current index and depth of key is as specified - // - whether next KV pair starts - // - whether key matched for a value (propogate key match until new KV pair of lower depth starts) - is_key_match[data_idx] <== KeyMatchAtIndex(DATA_BYTES, MAX_KEY_LENGTH, data_idx)(data, key, keyLen, parsing_key[data_idx]); - is_next_pair_at_depth[data_idx] <== NextKVPairAtDepth(MAX_STACK_HEIGHT)(State[data_idx].next_stack, data[data_idx], 0); - is_key_match_for_value[data_idx+1] <== Mux1()([is_key_match_for_value[data_idx] * (1-is_next_pair_at_depth[data_idx]), is_key_match[data_idx] * (1-is_next_pair_at_depth[data_idx])], is_key_match[data_idx]); - is_value_match[data_idx] <== is_key_match_for_value[data_idx+1] * parsing_value[data_idx]; - - or[data_idx - 1] <== OR()(is_value_match[data_idx], is_value_match[data_idx - 1]); - - // mask = currently parsing value and all subsequent keys matched - step_out[data_idx] <== data[data_idx] * or[data_idx - 1]; - } - for(var i = DATA_BYTES - MAX_KEY_LENGTH; i < DATA_BYTES + 4; i ++) { - step_out[i] <== 0; + for(var data_idx = 1; data_idx < DATA_BYTES; data_idx++) { + if(data_idx < DATA_BYTES - MAX_KEY_LENGTH) { + 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; + + // - parsing key + // - parsing value (different for string/numbers and array) + // - key match (key 1, key 2) + // - is next pair + // - is key match for value + // - value_mask + // - mask + + // check if inside key or not + parsing_key[data_idx] <== InsideKey()(State[data_idx].next_stack[0], State[data_idx].next_parsing_string, State[data_idx].next_parsing_number); + // check if inside value + parsing_value[data_idx] <== InsideValueObject()(State[data_idx].next_stack[0], State[data_idx].next_stack[1], State[data_idx].next_parsing_string, State[data_idx].next_parsing_number); + + // to get correct value, check: + // - key matches at current index and depth of key is as specified + // - whether next KV pair starts + // - whether key matched for a value (propogate key match until new KV pair of lower depth starts) + + // TODO (autoparallel): this can be optimized i'm sure of it, running without it saves 110k constraints on 1024b (553k with it) + is_key_match[data_idx] <== KeyMatchAtIndex(DATA_BYTES, MAX_KEY_LENGTH, data_idx)(data, key, keyLen, parsing_key[data_idx]); + + // TODO (autoparallel): this could also likely be optimized, costs like 140k constraints itself + is_next_pair_at_depth[data_idx] <== NextKVPairAtDepth(MAX_STACK_HEIGHT)(State[data_idx].next_stack, data[data_idx], 0); + is_key_match_for_value[data_idx+1] <== Mux1()([is_key_match_for_value[data_idx] * (1-is_next_pair_at_depth[data_idx]), is_key_match[data_idx] * (1-is_next_pair_at_depth[data_idx])], is_key_match[data_idx]); + is_value_match[data_idx] <== is_key_match_for_value[data_idx+1] * parsing_value[data_idx]; + + or[data_idx - 1] <== OR()(is_value_match[data_idx], is_value_match[data_idx - 1]); + + // mask = currently parsing value and all subsequent keys matched + masked[data_idx] <== data[data_idx] * or[data_idx - 1]; // TODO here + } else { + masked[data_idx] <== 0; + } } + step_out[0] <== DataHasher(DATA_BYTES)(masked); } template JsonMaskArrayIndexNIVC(DATA_BYTES, MAX_STACK_HEIGHT) { - // ------------------------------------------------------------------------------------------------------------------ // assert(MAX_STACK_HEIGHT >= 2); // TODO (autoparallel): idk if we need this now - var TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; // aes ct/pt + ctr - // ------------------------------------------------------------------------------------------------------------------ // - signal input step_in[TOTAL_BYTES_ACROSS_NIVC]; - signal output step_out[TOTAL_BYTES_ACROSS_NIVC]; - - // Declaration of signals. - signal data[DATA_BYTES]; + + signal input step_in[1]; signal input index; - for(var i = 0 ; i < DATA_BYTES ; i++) { - data[i] <== step_in[i]; - } + signal output step_out[1]; + + // Authenticate the (potentially further masked) plaintext we are passing in + signal input data[DATA_BYTES]; + signal data_hash <== DataHasher(DATA_BYTES)(data); + data_hash === step_in[0]; component State[DATA_BYTES]; State[0] = StateUpdate(MAX_STACK_HEIGHT); @@ -121,9 +122,8 @@ template JsonMaskArrayIndexNIVC(DATA_BYTES, MAX_STACK_HEIGHT) { signal or[DATA_BYTES - 1]; parsing_array[0] <== InsideArrayIndexObject()(State[0].next_stack[0], State[0].next_stack[1], State[0].next_parsing_string, State[0].next_parsing_number, index); - step_out[0] <== data[0] * parsing_array[0]; // TODO (autoparallel): is this totally correcot or do we need an or, i think it's right - - + signal masked[DATA_BYTES]; + masked[0] <== data[0] * parsing_array[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]; @@ -134,9 +134,7 @@ template JsonMaskArrayIndexNIVC(DATA_BYTES, MAX_STACK_HEIGHT) { parsing_array[data_idx] <== InsideArrayIndexObject()(State[data_idx].next_stack[0], State[data_idx].next_stack[1], State[data_idx].next_parsing_string, State[data_idx].next_parsing_number, index); or[data_idx - 1] <== OR()(parsing_array[data_idx], parsing_array[data_idx - 1]); - step_out[data_idx] <== data[data_idx] * or[data_idx - 1]; - } - for(var i = DATA_BYTES ; i < TOTAL_BYTES_ACROSS_NIVC; i++) { - step_out[i] <== 0; + masked[data_idx] <== data[data_idx] * or[data_idx - 1]; } + step_out[0] <== DataHasher(DATA_BYTES)(masked); } diff --git a/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts b/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts index c80fae5..84139bd 100644 --- a/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts +++ b/circuits/test/aes-gcm/nivc/aes-gctr-nivc.test.ts @@ -1,19 +1,26 @@ import { assert } from "chai"; import { WitnessTester } from "circomkit"; import { circomkit } from "../../common"; +import { PoseidonModular } from "../../common/poseidon"; -describe("aes-gctr-nivc", () => { - let circuit_one_block: WitnessTester<["key", "iv", "plainText", "aad", "step_in"], ["step_out"]>; +function bytesToBigInt(bytes: number[] | Uint8Array): bigint { + let result = BigInt(0); + + for (let i = 0; i < 16; i++) { + result += BigInt(bytes[i]) * BigInt(2 ** (8 * i)); + } + return result; +} + +describe("aes-gctr-nivc", () => { + let circuit_one_block: WitnessTester<["key", "iv", "plainText", "aad", "ctr", "cipherText", "step_in"], ["step_out"]>; - const DATA_BYTES_0 = 16; - const TOTAL_BYTES_ACROSS_NIVC_0 = DATA_BYTES_0 + 4; it("all correct for self generated single zero pt block case", async () => { circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { file: "aes-gcm/nivc/aes-gctr-nivc", template: "AESGCTRFOLD", - params: [DATA_BYTES_0], // input len is 16 bytes }); let key = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; @@ -22,24 +29,17 @@ describe("aes-gctr-nivc", () => { let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; let ct = [0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78]; - const counter = [0x00, 0x00, 0x00, 0x01]; - const step_in = new Array(TOTAL_BYTES_ACROSS_NIVC_0).fill(0x00); - counter.forEach((value, index) => { - step_in[DATA_BYTES_0 + index] = value; - }); - - const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText, aad: aad, step_in: step_in }, ["step_out"]) + const ctr = [0x00, 0x00, 0x00, 0x01]; + const step_in = 0; - let packed = plainText.map((x, i) => x + (ct[i] * 256)); - let expected = [...packed, 0x00, 0x00, 0x00, 0x02]; - assert.deepEqual(witness.step_out, expected.map(BigInt)); + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText, aad: aad, ctr: ctr, cipherText: ct, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, PoseidonModular([step_in, bytesToBigInt(plainText)])); }); it("all correct for self generated single non zero pt block", async () => { circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { file: "aes-gcm/nivc/aes-gctr-nivc", template: "AESGCTRFOLD", - params: [DATA_BYTES_0], // input len is 16 bytes }); let key = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; @@ -48,24 +48,13 @@ describe("aes-gctr-nivc", () => { let aad = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; let ct = [0x29, 0x29, 0xd2, 0xbb, 0x1a, 0xe9, 0x48, 0x04, 0x40, 0x2b, 0x8e, 0x77, 0x6e, 0x0d, 0x33, 0x56]; - const counter = [0x00, 0x00, 0x00, 0x01]; - const step_in = new Array(TOTAL_BYTES_ACROSS_NIVC_0).fill(0x00); - counter.forEach((value, index) => { - step_in[DATA_BYTES_0 + index] = value; - }); - - const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText, aad: aad, step_in: step_in }, ["step_out"]) + const ctr = [0x00, 0x00, 0x00, 0x01]; + const step_in = 0; - let packed = plainText.map((x, i) => x + (ct[i] * 256)); - let expected = [...packed, 0x00, 0x00, 0x00, 0x02]; - assert.deepEqual(witness.step_out, expected.map(BigInt)); + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText, aad: aad, ctr: ctr, cipherText: ct, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, PoseidonModular([step_in, bytesToBigInt(plainText)])); }); - const DATA_BYTES_1 = 32; - const TOTAL_BYTES_ACROSS_NIVC_1 = DATA_BYTES_1 + 4; - - - let zero_block = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; let key = [0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31]; let plainText1 = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; let plainText2 = [0x74, 0x65, 0x73, 0x74, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; @@ -78,38 +67,27 @@ describe("aes-gctr-nivc", () => { circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { file: "aes-gcm/nivc/aes-gctr-nivc", template: "AESGCTRFOLD", - params: [DATA_BYTES_1], // input len is 32 bytes }); - const counter = [0x00, 0x00, 0x00, 0x01]; - const step_in = new Array(TOTAL_BYTES_ACROSS_NIVC_1).fill(0x00); - counter.forEach((value, index) => { - step_in[DATA_BYTES_1 + index] = value; - }); + const ctr = [0x00, 0x00, 0x00, 0x01]; + const step_in = 0; - const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText1, aad: aad, step_in: step_in }, ["step_out"]) - - let packed1 = plainText1.map((x, i) => x + (ct_part1[i] * 256)); - let expected = packed1.concat(zero_block).concat([0x00, 0x00, 0x00, 0x02]); - assert.deepEqual(witness.step_out, expected.map(BigInt)); + const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText1, aad: aad, ctr: ctr, cipherText: ct_part1, step_in: step_in }, ["step_out"]) + assert.deepEqual(witness.step_out, PoseidonModular([step_in, bytesToBigInt(plainText1)])); }); it("all correct for self generated two block case second fold", async () => { circuit_one_block = await circomkit.WitnessTester("aes-gcm-fold", { file: "aes-gcm/nivc/aes-gctr-nivc", template: "AESGCTRFOLD", - params: [DATA_BYTES_1], // input len is 32 bytes }); - let packed1 = plainText1.map((x, i) => x + (ct_part1[i] * 256)); - let packed2 = plainText2.map((x, i) => x + (ct_part2[i] * 256)); - let step_in = packed1.concat(zero_block).concat([0x00, 0x00, 0x00, 0x02]); - step_in = step_in.concat(new Array(TOTAL_BYTES_ACROSS_NIVC_1 - step_in.length).fill(0)); - - - let expected = packed1.concat(packed2).concat([0x00, 0x00, 0x00, 0x03]); + const ctr_0 = [0x00, 0x00, 0x00, 0x01]; + const ctr_1 = [0x00, 0x00, 0x00, 0x02]; + const step_in_0 = 0; - const witness = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText2, aad: aad, step_in: step_in }, ["step_out"]) - assert.deepEqual(witness.step_out, expected.map(BigInt)); + const witness_0 = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText1, aad: aad, ctr: ctr_0, cipherText: ct_part1, step_in: step_in_0 }, ["step_out"]) + const witness_1 = await circuit_one_block.compute({ key: key, iv: iv, plainText: plainText2, aad: aad, ctr: ctr_1, cipherText: ct_part2, step_in: witness_0.step_out }, ["step_out"]) + assert.deepEqual(witness_1.step_out, PoseidonModular([BigInt(witness_0.step_out.toString()), bytesToBigInt(plainText2)])); }); }); \ No newline at end of file diff --git a/circuits/test/common/poseidon.ts b/circuits/test/common/poseidon.ts index b6a06b5..62509cc 100644 --- a/circuits/test/common/poseidon.ts +++ b/circuits/test/common/poseidon.ts @@ -71,4 +71,27 @@ export function PoseidonModular(input: Array): bigint } return result; +} + +export function DataHasher(input: number[]): bigint { + if (input.length % 16 !== 0) { + throw new Error("DATA_BYTES must be divisible by 16"); + } + + let hashes: bigint[] = [BigInt(0)]; // Initialize first hash as 0 + + for (let i = 0; i < Math.floor(input.length / 16); i++) { + let packedInput = BigInt(0); + + // Pack 16 bytes into a single number + for (let j = 0; j < 16; j++) { + packedInput += BigInt(input[16 * i + j]) * BigInt(2 ** (8 * j)); + } + + // Compute next hash using previous hash and packed input + hashes.push(PoseidonModular([hashes[i], packedInput])); + } + + // Return the last hash + return hashes[Math.floor(input.length / 16)]; } \ No newline at end of file diff --git a/circuits/test/full/full.test.ts b/circuits/test/full/full.test.ts index d657774..1b5089b 100644 --- a/circuits/test/full/full.test.ts +++ b/circuits/test/full/full.test.ts @@ -1,5 +1,8 @@ -import { CircuitSignals } from "circomkit"; +import { assert } from "chai"; import { circomkit, WitnessTester, toByte } from "../common"; +import { DataHasher } from "../common/poseidon"; + + // HTTP/1.1 200 OK // content-type: application/json; charset=utf-8 @@ -20,7 +23,7 @@ import { circomkit, WitnessTester, toByte } from "../common"; // } // 320 bytes in the HTTP response -let http_response_plaintext = [ +const http_response_plaintext = [ 72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 13, 10, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 32, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, 61, 117, 116, 102, 45, 56, 13, 10, 99, @@ -37,19 +40,145 @@ let http_response_plaintext = [ 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 93, 13, 10, 32, 32, 32, 125, 13, 10, 125]; +const http_response_ciphertext = [ + 75, 220, 142, 158, 79, 135, 141, 163, 211, 26, 242, 137, 81, 253, 181, 117, + 253, 246, 197, 197, 61, 46, 55, 87, 218, 137, 240, 143, 241, 177, 225, 129, + 80, 114, 125, 72, 45, 18, 224, 179, 79, 231, 153, 198, 163, 252, 197, 219, + 233, 46, 202, 120, 99, 253, 76, 9, 70, 11, 200, 218, 228, 251, 133, 248, + 233, 177, 19, 241, 205, 128, 65, 76, 10, 31, 71, 198, 177, 78, 108, 246, + 175, 152, 42, 97, 255, 182, 157, 245, 123, 95, 130, 101, 129, 138, 236, 146, + 47, 22, 22, 13, 125, 1, 109, 158, 189, 131, 44, 43, 203, 118, 79, 181, + 86, 33, 235, 186, 75, 20, 7, 147, 102, 75, 90, 222, 255, 140, 94, 52, + 191, 145, 192, 71, 239, 245, 247, 175, 117, 136, 173, 235, 250, 189, 74, 155, + 103, 25, 164, 187, 22, 26, 39, 37, 113, 248, 170, 146, 73, 75, 45, 208, + 125, 49, 101, 11, 120, 215, 93, 160, 14, 147, 129, 181, 150, 59, 167, 197, + 230, 122, 77, 245, 247, 215, 136, 98, 1, 180, 213, 30, 214, 88, 83, 42, + 33, 112, 61, 4, 197, 75, 134, 149, 22, 228, 24, 95, 131, 35, 44, 181, + 135, 31, 173, 36, 23, 192, 177, 127, 156, 199, 167, 212, 66, 235, 194, 102, + 61, 144, 121, 59, 187, 179, 212, 34, 117, 47, 96, 3, 169, 73, 204, 88, + 36, 48, 158, 220, 237, 198, 180, 105, 7, 188, 109, 24, 201, 217, 186, 191, + 232, 63, 93, 153, 118, 214, 157, 167, 15, 216, 191, 152, 41, 106, 24, 127, + 8, 144, 78, 218, 133, 125, 89, 97, 10, 246, 8, 244, 112, 169, 190, 206, + 14, 217, 109, 147, 130, 61, 214, 237, 143, 77, 14, 14, 70, 56, 94, 97, + 207, 214, 106, 249, 37, 7, 186, 95, 174, 146, 203, 148, 173, 172, 13, 113 +]; +const http_body = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 123, 13, 10, 32, 32, 32, 34, + 100, 97, 116, 97, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 34, 105, 116, 101, 109, + 115, 34, 58, 32, 91, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 13, 10, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, + 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 34, 112, 114, 111, 102, 105, 108, 101, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, + 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, + 32, 32, 93, 13, 10, 32, 32, 32, 125, 13, 10, 125, +]; +const lengthDiff = http_response_plaintext.length - http_body.length; + +// Create an array of zeros with the length difference +const padding = new Array(lengthDiff).fill(0); + +// Concatenate the padding with http_body +const padded_http_body = [...padding, ...http_body]; + +const http_response_hash = DataHasher(http_response_plaintext); +const http_body_mask_hash = DataHasher(padded_http_body); + + +const json_key0_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 34, 105, 116, 101, 109, 115, 34, 58, 32, 91, + 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, 116, 105, 115, 116, + 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, + 102, 105, 108, 101, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 34, 110, 97, 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, + 102, 116, 34, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 93, 13, 0, + 0, 0, 0, 0, 0, 0, 0, +]; +const json_key0_mask_hash = DataHasher(json_key0_mask); + +const json_key1_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 13, 10, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 102, 105, 108, 101, 34, + 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, + 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; +const json_key1_mask_hash = DataHasher(json_key1_mask); + +const json_arr_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, + 97, 116, 97, 34, 58, 32, 34, 65, 114, 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 102, 105, 108, 101, 34, 58, 32, 123, 13, + 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, 101, 34, + 58, 32, 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; +const json_arr_mask_hash = DataHasher(json_arr_mask); + +const json_key2_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, + 97, 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; +const json_key2_mask_hash = DataHasher(json_key2_mask); + +const json_key3_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, + 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, +]; +const json_key3_mask_hash = DataHasher(json_key3_mask); + describe("NIVC_FULL", async () => { - let aesCircuit: WitnessTester<["key", "iv", "plainText", "aad", "step_in"], ["step_out"]>; - let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; - let lockHeaderCircuit: WitnessTester<["step_in", "header", "headerNameLength", "value", "headerValueLength"], ["step_out"]>; - let bodyMaskCircuit: WitnessTester<["step_in"], ["step_out"]>; - let parse_circuit: WitnessTester<["step_in"], ["step_out"]>; - let json_mask_object_circuit: WitnessTester<["step_in", "key", "keyLen"], ["step_out"]>; - let json_mask_arr_circuit: WitnessTester<["step_in", "index"], ["step_out"]>; - let extract_value_circuit: WitnessTester<["step_in"], ["step_out"]>; + let aesCircuit: WitnessTester<["key", "iv", "aad", "ctr", "plainText", "cipherText", "step_in"], ["step_out"]>; + let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "data", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; + let lockHeaderCircuit: WitnessTester<["step_in", "data", "header", "headerNameLength", "value", "headerValueLength"], ["step_out"]>; + let bodyMaskCircuit: WitnessTester<["step_in", "data"], ["step_out"]>; + let json_mask_object_circuit: WitnessTester<["step_in", "data", "key", "keyLen"], ["step_out"]>; + let json_mask_arr_circuit: WitnessTester<["step_in", "data", "index"], ["step_out"]>; + let extract_value_circuit: WitnessTester<["step_in", "data"], ["step_out"]>; const DATA_BYTES = 320; const MAX_STACK_HEIGHT = 5; - const TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; const MAX_HEADER_NAME_LENGTH = 20; const MAX_HEADER_VALUE_LENGTH = 35; @@ -62,15 +191,15 @@ describe("NIVC_FULL", async () => { const final = [79, 75]; // OK const MAX_KEY_LENGTH = 8; - const MAX_VALUE_LENGTH = 35; + const MAX_VALUE_LENGTH = 32; before(async () => { aesCircuit = await circomkit.WitnessTester("AESGCTRFOLD", { file: "aes-gcm/nivc/aes-gctr-nivc", template: "AESGCTRFOLD", - params: [DATA_BYTES], }); console.log("#constraints (AES-GCTR):", await aesCircuit.getConstraintCount()); + httpParseAndLockStartLineCircuit = await circomkit.WitnessTester(`ParseAndLockStartLine`, { file: "http/nivc/parse_and_lock_start_line", template: "ParseAndLockStartLine", @@ -92,13 +221,6 @@ describe("NIVC_FULL", async () => { }); console.log("#constraints (HTTP-BODY-MASK):", await bodyMaskCircuit.getConstraintCount()); - json_mask_arr_circuit = await circomkit.WitnessTester(`JsonMaskArrayIndexNIVC`, { - file: "json/nivc/masker", - template: "JsonMaskArrayIndexNIVC", - params: [DATA_BYTES, MAX_STACK_HEIGHT], - }); - console.log("#constraints (JSON-MASK-ARRAY-INDEX):", await json_mask_arr_circuit.getConstraintCount()); - json_mask_object_circuit = await circomkit.WitnessTester(`JsonMaskObjectNIVC`, { file: "json/nivc/masker", template: "JsonMaskObjectNIVC", @@ -106,6 +228,13 @@ describe("NIVC_FULL", async () => { }); console.log("#constraints (JSON-MASK-OBJECT):", await json_mask_object_circuit.getConstraintCount()); + json_mask_arr_circuit = await circomkit.WitnessTester(`JsonMaskArrayIndexNIVC`, { + file: "json/nivc/masker", + template: "JsonMaskArrayIndexNIVC", + params: [DATA_BYTES, MAX_STACK_HEIGHT], + }); + console.log("#constraints (JSON-MASK-ARRAY-INDEX):", await json_mask_arr_circuit.getConstraintCount()); + extract_value_circuit = await circomkit.WitnessTester(`JsonMaskExtractFinal`, { file: "json/nivc/extractor", template: "MaskExtractFinal", @@ -124,35 +253,37 @@ describe("NIVC_FULL", async () => { let middlePadded = middle.concat(Array(MAX_MIDDLE_LENGTH - middle.length).fill(0)); let finalPadded = final.concat(Array(MAX_FINAL_LENGTH - final.length).fill(0)); it("NIVC_CHAIN", async () => { - // fold 16 bytes at a time - let aes_gcm: CircuitSignals = { step_out: [] }; - // console.log("DATA_BYTES", DATA_BYTES); - - // Run the 0th chunk of plaintext - const counter = [0x00, 0x00, 0x00, 0x01]; - const init_nivc_input = new Array(TOTAL_BYTES_ACROSS_NIVC).fill(0x00); - counter.forEach((value, index) => { - init_nivc_input[DATA_BYTES + index] = value; - }); + // Run AES chain + let ctr = [0x00, 0x00, 0x00, 0x01]; + const init_nivc_input = 0; + let pt = http_response_plaintext.slice(0, 16); - aes_gcm = await aesCircuit.compute({ key: Array(16).fill(0), iv: Array(12).fill(0), plainText: pt, aad: Array(16).fill(0), step_in: init_nivc_input }, ["step_out"]); - for (let i = 1; i < (DATA_BYTES / 16); i++) { + let ct = http_response_ciphertext.slice(0, 16); + let aes_gcm = await aesCircuit.compute({ key: Array(16).fill(0), iv: Array(12).fill(0), ctr: ctr, plainText: pt, aad: Array(16).fill(0), cipherText: ct, step_in: init_nivc_input }, ["step_out"]); + let i = 0; + console.log("AES `step_out[", i, "]`: ", aes_gcm.step_out); + for (i = 1; i < (DATA_BYTES / 16); i++) { + ctr[3] += 1; // This will work since we don't run a test that overlows a byte let pt = http_response_plaintext.slice(i * 16, i * 16 + 16); - aes_gcm = await aesCircuit.compute({ key: Array(16).fill(0), iv: Array(12).fill(0), plainText: pt, aad: Array(16).fill(0), step_in: aes_gcm.step_out }, ["step_out"]); + let ct = http_response_ciphertext.slice(i * 16, i * 16 + 16); + aes_gcm = await aesCircuit.compute({ key: Array(16).fill(0), iv: Array(12).fill(0), ctr: ctr, plainText: pt, aad: Array(16).fill(0), cipherText: ct, step_in: aes_gcm.step_out }, ["step_out"]); + console.log("AES `step_out[", i, "]`: ", aes_gcm.step_out); } - let out = aes_gcm.step_out as number[]; - let extendedJsonInput = out.slice(0, DATA_BYTES).concat(Array(Math.max(0, TOTAL_BYTES_ACROSS_NIVC - http_response_plaintext.length)).fill(0)); - let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: extendedJsonInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); - - let lockHeader = await lockHeaderCircuit.compute({ step_in: parseAndLockStartLine.step_out, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }, ["step_out"]); + assert.deepEqual(http_response_hash, aes_gcm.step_out); - let bodyMask = await bodyMaskCircuit.compute({ step_in: lockHeader.step_out }, ["step_out"]); + // Lock the start line + let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: aes_gcm.step_out, data: http_response_plaintext, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); + console.log("Start Line `step_out`: ", parseAndLockStartLine.step_out); - let bodyMaskOut = bodyMask.step_out as number[]; - let idx = bodyMaskOut.indexOf('{'.charCodeAt(0)); + // Lock a header + let lockHeader = await lockHeaderCircuit.compute({ step_in: parseAndLockStartLine.step_out, data: http_response_plaintext, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }, ["step_out"]); + console.log("Lock Header `step_out`: ", lockHeader.step_out); - let maskedInput = extendedJsonInput.fill(0, 0, idx); - maskedInput = maskedInput.fill(0, 320); + // Mask the body + // let bodyMask = await bodyMaskCircuit.compute({ step_in: lockHeader.step_out, data: http_response_plaintext }, ["step_out"]); + let bodyMask = await bodyMaskCircuit.compute({ step_in: http_response_hash, data: http_response_plaintext }, ["step_out"]); + console.log("Body Mask `step_out`: ", bodyMask.step_out); + assert.deepEqual(bodyMask.step_out, http_body_mask_hash); let key0 = [100, 97, 116, 97, 0, 0, 0, 0]; // "data" let key0Len = 4; @@ -163,20 +294,34 @@ describe("NIVC_FULL", async () => { let key3 = [110, 97, 109, 101, 0, 0, 0, 0]; // "name" let key3Len = 4; - let value = toByte("\"Taylor Swift\""); + // let value = toByte("\"Taylor Swift\""); - let json_extract_key0 = await json_mask_object_circuit.compute({ step_in: bodyMaskOut, key: key0, keyLen: key0Len }, ["step_out"]); + let json_extract_key0 = await json_mask_object_circuit.compute({ step_in: bodyMask.step_out, data: http_body, key: key0, keyLen: key0Len }, ["step_out"]); + console.log("JSON Extract key0 `step_out`:", json_extract_key0.step_out); + assert.deepEqual(json_extract_key0.step_out, json_key0_mask_hash); - let json_num = json_extract_key0.step_out as number[]; - console.log("json_extract_key0", json_num); - let json_extract_key1 = await json_mask_object_circuit.compute({ step_in: json_extract_key0.step_out, key: key1, keyLen: key1Len }, ["step_out"]); + let json_extract_key1 = await json_mask_object_circuit.compute({ step_in: json_extract_key0.step_out, data: json_key0_mask, key: key1, keyLen: key1Len }, ["step_out"]); + assert.deepEqual(json_extract_key1.step_out, json_key1_mask_hash); + console.log("JSON Extract key1 `step_out`:", json_extract_key1.step_out); - let json_extract_arr = await json_mask_arr_circuit.compute({ step_in: json_extract_key1.step_out, index: 0 }, ["step_out"]); + let json_extract_arr = await json_mask_arr_circuit.compute({ step_in: json_extract_key1.step_out, data: json_key1_mask, index: 0 }, ["step_out"]); + assert.deepEqual(json_extract_arr.step_out, json_arr_mask_hash); + console.log("JSON Extract arr `step_out`:", json_extract_arr.step_out); - let json_extract_key2 = await json_mask_object_circuit.compute({ step_in: json_extract_arr.step_out, key: key2, keyLen: key2Len }, ["step_out"]); + let json_extract_key2 = await json_mask_object_circuit.compute({ step_in: json_extract_arr.step_out, data: json_arr_mask, key: key2, keyLen: key2Len }, ["step_out"]); + assert.deepEqual(json_extract_key2.step_out, json_key2_mask_hash); + console.log("JSON Extract key2 `step_out`:", json_extract_key2.step_out); - let json_extract_key3 = await json_mask_object_circuit.compute({ step_in: json_extract_key2.step_out, key: key3, keyLen: key3Len }, ["step_out"]); + let json_extract_key3 = await json_mask_object_circuit.compute({ step_in: json_extract_key2.step_out, data: json_key2_mask, key: key3, keyLen: key3Len }, ["step_out"]); + assert.deepEqual(json_extract_key3.step_out, json_key3_mask_hash); + console.log("JSON Extract key3 `step_out`:", json_extract_key3.step_out); - await extract_value_circuit.expectPass({ step_in: json_extract_key3.step_out }, { step_out: value }); + // TODO (autoparallel): we need to rethink extraction here. + let finalOutput = toByte("\"Taylor Swift\""); + let finalOutputPadded = finalOutput.concat(Array(Math.max(0, MAX_VALUE_LENGTH - finalOutput.length)).fill(0)); + let final_value_hash = DataHasher(finalOutputPadded); + let extractValue = await extract_value_circuit.compute({ step_in: json_extract_key3.step_out, data: json_key3_mask }, ["step_out"]); + console.log("finalValue", extractValue.step_out); + assert.deepEqual(extractValue.step_out, final_value_hash); }); }); \ No newline at end of file diff --git a/circuits/test/http/nivc/body_mask.test.ts b/circuits/test/http/nivc/body_mask.test.ts index 1e45ed4..04067cb 100644 --- a/circuits/test/http/nivc/body_mask.test.ts +++ b/circuits/test/http/nivc/body_mask.test.ts @@ -1,4 +1,6 @@ import { circomkit, WitnessTester, toByte } from "../../common"; +import { assert } from "chai"; +import { DataHasher } from "../../common/poseidon"; // HTTP/1.1 200 OK // content-type: application/json; charset=utf-8 @@ -36,13 +38,40 @@ let http_response_plaintext = [ 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 93, 13, 10, 32, 32, 32, 125, 13, 10, 125]; +const http_body = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 123, 13, 10, 32, 32, 32, 34, + 100, 97, 116, 97, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 34, 105, 116, 101, 109, + 115, 34, 58, 32, 91, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 13, 10, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, + 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 34, 112, 114, 111, 102, 105, 108, 101, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, + 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, + 32, 32, 93, 13, 10, 32, 32, 32, 125, 13, 10, 125, +]; + +const lengthDiff = http_response_plaintext.length - http_body.length; + +// Create an array of zeros with the length difference +const padding = new Array(lengthDiff).fill(0); + +// Concatenate the padding with http_body +const padded_http_body = [...padding, ...http_body]; + +const http_response_hash = DataHasher(http_response_plaintext); +const http_body_mask_hash = DataHasher(padded_http_body); + describe("NIVC_HTTP", async () => { - let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; - let lockHeaderCircuit: WitnessTester<["step_in", "header", "headerNameLength", "value", "headerValueLength"], ["step_out"]>; - let bodyMaskCircuit: WitnessTester<["step_in"], ["step_out"]>; + let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "data", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; + let lockHeaderCircuit: WitnessTester<["step_in", "data", "header", "headerNameLength", "value", "headerValueLength"], ["step_out"]>; + let bodyMaskCircuit: WitnessTester<["step_in", "data"], ["step_out"]>; const DATA_BYTES = 320; - const TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; + const TOTAL_BYTES_ACROSS_NIVC = 1; const MAX_HEADER_NAME_LENGTH = 20; const MAX_HEADER_VALUE_LENGTH = 35; @@ -88,18 +117,11 @@ describe("NIVC_HTTP", async () => { let middlePadded = middle.concat(Array(MAX_MIDDLE_LENGTH - middle.length).fill(0)); let finalPadded = final.concat(Array(MAX_FINAL_LENGTH - final.length).fill(0)); it("HTTPParseAndExtract", async () => { - let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: extendedJsonInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); - - let lockHeader = await lockHeaderCircuit.compute({ step_in: parseAndLockStartLine.step_out, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }, ["step_out"]); - - let bodyMask = await bodyMaskCircuit.compute({ step_in: lockHeader.step_out }, ["step_out"]); - - let bodyMaskOut = bodyMask.step_out as number[]; - let idx = bodyMaskOut.indexOf('{'.charCodeAt(0)); - - let maskedInput = extendedJsonInput.fill(0, 0, idx); - maskedInput = maskedInput.fill(0, 320); + let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: http_response_hash, data: http_response_plaintext, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); + let lockHeader = await lockHeaderCircuit.compute({ step_in: parseAndLockStartLine.step_out, data: http_response_plaintext, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }, ["step_out"]); + let bodyMask = await bodyMaskCircuit.compute({ step_in: lockHeader.step_out, data: http_response_plaintext }, ["step_out"]); + console.log("GOT TRHOUGH THIRD CIRCUIT"); - bodyMaskOut === maskedInput; + assert.deepEqual(bodyMask.step_out, http_body_mask_hash); }); }); \ No newline at end of file diff --git a/circuits/test/http/nivc/lock_header.test.ts b/circuits/test/http/nivc/lock_header.test.ts index 81d3dd6..14cc914 100644 --- a/circuits/test/http/nivc/lock_header.test.ts +++ b/circuits/test/http/nivc/lock_header.test.ts @@ -1,12 +1,12 @@ import { circomkit, WitnessTester, toByte } from "../../common"; import { readHTTPInputFile } from "../../common/http"; +import { DataHasher } from "../../common/poseidon"; describe("HTTPLockHeader", async () => { - let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; - let lockHeaderCircuit: WitnessTester<["step_in", "header", "headerNameLength", "value", "headerValueLength"], ["step_out"]>; + let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "data", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; + let lockHeaderCircuit: WitnessTester<["step_in", "data", "header", "headerNameLength", "value", "headerValueLength"], ["step_out"]>; - const DATA_BYTES = 320; - const TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; + const DATA_BYTES = 336; const MAX_BEGINNING_LENGTH = 10; const MAX_MIDDLE_LENGTH = 50; @@ -32,7 +32,8 @@ describe("HTTPLockHeader", async () => { function generatePassCase(input: number[], beginning: number[], middle: number[], final: number[], headerName: number[], headerValue: number[], desc: string) { it(`should pass: \"${headerName}: ${headerValue}\", ${desc}`, async () => { - let extendedInput = input.concat(Array(Math.max(0, TOTAL_BYTES_ACROSS_NIVC - input.length)).fill(0)); + let extendedInput = input.concat(Array(Math.max(0, DATA_BYTES - input.length)).fill(0)); + const http_response_hash = DataHasher(extendedInput); let beginningPadded = beginning.concat(Array(MAX_BEGINNING_LENGTH - beginning.length).fill(0)); let middlePadded = middle.concat(Array(MAX_MIDDLE_LENGTH - middle.length).fill(0)); @@ -41,15 +42,16 @@ describe("HTTPLockHeader", async () => { let headerNamePadded = headerName.concat(Array(MAX_HEADER_NAME_LENGTH - headerName.length).fill(0)); let headerValuePadded = headerValue.concat(Array(MAX_HEADER_VALUE_LENGTH - headerValue.length).fill(0)); - let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); + let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: http_response_hash, data: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); - await lockHeaderCircuit.expectPass({ step_in: parseAndLockStartLine.step_out, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }); + await lockHeaderCircuit.expectPass({ step_in: parseAndLockStartLine.step_out, data: extendedInput, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }); }); } function generateFailCase(input: number[], beginning: number[], middle: number[], final: number[], headerName: number[], headerValue: number[], desc: string) { it(`should fail: ${desc}`, async () => { - let extendedInput = input.concat(Array(Math.max(0, TOTAL_BYTES_ACROSS_NIVC - input.length)).fill(0)); + let extendedInput = input.concat(Array(Math.max(0, DATA_BYTES - input.length)).fill(0)); + const http_response_hash = DataHasher(extendedInput); let beginningPadded = beginning.concat(Array(MAX_BEGINNING_LENGTH - beginning.length).fill(0)); let middlePadded = middle.concat(Array(MAX_MIDDLE_LENGTH - middle.length).fill(0)); @@ -58,9 +60,9 @@ describe("HTTPLockHeader", async () => { let headerNamePadded = headerName.concat(Array(MAX_HEADER_NAME_LENGTH - headerName.length).fill(0)); let headerValuePadded = headerValue.concat(Array(MAX_HEADER_VALUE_LENGTH - headerValue.length).fill(0)); - let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); + let parseAndLockStartLine = await httpParseAndLockStartLineCircuit.compute({ step_in: http_response_hash, data: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }, ["step_out"]); - await lockHeaderCircuit.expectFail({ step_in: parseAndLockStartLine.step_out, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }); + await lockHeaderCircuit.expectFail({ step_in: parseAndLockStartLine.step_out, data: extendedInput, header: headerNamePadded, headerNameLength: headerName.length, value: headerValuePadded, headerValueLength: headerValue.length }); }); } diff --git a/circuits/test/http/nivc/parse_and_lock_start_line.test.ts b/circuits/test/http/nivc/parse_and_lock_start_line.test.ts index ff0c29b..202650c 100644 --- a/circuits/test/http/nivc/parse_and_lock_start_line.test.ts +++ b/circuits/test/http/nivc/parse_and_lock_start_line.test.ts @@ -1,11 +1,11 @@ import { circomkit, WitnessTester, toByte } from "../../common"; import { readHTTPInputFile } from "../../common/http"; +import { DataHasher } from "../../common/poseidon"; describe("HTTPParseAndLockStartLine", async () => { - let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; + let httpParseAndLockStartLineCircuit: WitnessTester<["step_in", "data", "beginning", "beginning_length", "middle", "middle_length", "final", "final_length"], ["step_out"]>; - const DATA_BYTES = 320; - const TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; + const DATA_BYTES = 336; const MAX_BEGINNING_LENGTH = 10; const MAX_MIDDLE_LENGTH = 50; @@ -22,25 +22,28 @@ describe("HTTPParseAndLockStartLine", async () => { function generatePassCase(input: number[], beginning: number[], middle: number[], final: number[], desc: string) { it(`(valid) witness: ${desc}`, async () => { - let extendedInput = input.concat(Array(Math.max(0, TOTAL_BYTES_ACROSS_NIVC - input.length)).fill(0)); + let extendedInput = input.concat(Array(Math.max(0, DATA_BYTES - input.length)).fill(0)); + const http_response_hash = DataHasher(extendedInput); let beginningPadded = beginning.concat(Array(MAX_BEGINNING_LENGTH - beginning.length).fill(0)); let middlePadded = middle.concat(Array(MAX_MIDDLE_LENGTH - middle.length).fill(0)); let finalPadded = final.concat(Array(MAX_FINAL_LENGTH - final.length).fill(0)); - await httpParseAndLockStartLineCircuit.expectPass({ step_in: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }); + await httpParseAndLockStartLineCircuit.expectPass({ step_in: [http_response_hash], data: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }); }); } function generateFailCase(input: number[], beginning: number[], middle: number[], final: number[], desc: string) { it(`(valid) witness: ${desc}`, async () => { - let extendedInput = input.concat(Array(Math.max(0, TOTAL_BYTES_ACROSS_NIVC - input.length)).fill(0)); + let extendedInput = input.concat(Array(Math.max(0, DATA_BYTES - input.length)).fill(0)); + const http_response_hash = DataHasher(extendedInput); + let beginningPadded = beginning.concat(Array(MAX_BEGINNING_LENGTH - beginning.length).fill(0)); let middlePadded = middle.concat(Array(MAX_MIDDLE_LENGTH - middle.length).fill(0)); let finalPadded = final.concat(Array(MAX_FINAL_LENGTH - final.length).fill(0)); - await httpParseAndLockStartLineCircuit.expectFail({ step_in: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }); + await httpParseAndLockStartLineCircuit.expectFail({ step_in: [http_response_hash], data: extendedInput, beginning: beginningPadded, beginning_length: beginning.length, middle: middlePadded, middle_length: middle.length, final: finalPadded, final_length: final.length }); }); } diff --git a/circuits/test/json/nivc/masker_nivc.test.ts b/circuits/test/json/nivc/masker_nivc.test.ts index 455dd89..0d9a265 100644 --- a/circuits/test/json/nivc/masker_nivc.test.ts +++ b/circuits/test/json/nivc/masker_nivc.test.ts @@ -1,5 +1,6 @@ import { circomkit, WitnessTester, generateDescription, readJsonFile, toByte } from "../../common"; -import { join } from "path"; +import { DataHasher } from "../../common/poseidon"; +import { assert } from "chai"; // HTTP/1.1 200 OK // content-type: application/json; charset=utf-8 @@ -20,7 +21,8 @@ import { join } from "path"; // } // 202 bytes in the JSON -let json_input = [123, 13, 10, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, +let json_input = [ + 123, 13, 10, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 34, 105, 116, 101, 109, 115, 34, 58, 32, 91, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, @@ -30,16 +32,74 @@ let json_input = [123, 13, 10, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 123 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 93, 13, 10, 32, 32, 32, 125, 13, 10, 125]; +const json_key0_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 13, 10, 32, 32, 32, 32, 32, 32, + 32, 34, 105, 116, 101, 109, 115, 34, 58, 32, 91, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, + 97, 34, 58, 32, 34, 65, 114, 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 102, 105, 108, 101, 34, 58, 32, 123, 13, 10, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, 101, 34, 58, 32, + 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, + 13, 10, 32, 32, 32, 32, 32, 32, 32, 93, 13, 10, 32, 32, 32, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +const json_key0_mask_hash = DataHasher(json_key0_mask); + +const json_key1_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 91, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 123, 13, 10, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, + 116, 105, 115, 116, 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 34, 112, 114, 111, 102, 105, 108, 101, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, + 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, + 32, 32, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +const json_key1_mask_hash = DataHasher(json_key1_mask); + +const json_arr_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 13, 10, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 65, 114, 116, 105, 115, 116, + 34, 44, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 112, 114, 111, 102, 105, + 108, 101, 34, 58, 32, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, + 97, 109, 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; +const json_arr_mask_hash = DataHasher(json_arr_mask); + +const json_key2_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 123, 13, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 34, 110, 97, 109, + 101, 34, 58, 32, 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 13, 10, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +const json_key2_mask_hash = DataHasher(json_key2_mask); + +const json_key3_mask = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 34, 84, 97, 121, 108, 111, 114, 32, 83, 119, 105, 102, 116, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; +const json_key3_mask_hash = DataHasher(json_key3_mask); + describe("NIVC Extract", async () => { - let json_mask_object_circuit: WitnessTester<["step_in", "key", "keyLen"], ["step_out"]>; - let json_mask_arr_circuit: WitnessTester<["step_in", "index"], ["step_out"]>; - let extract_value_circuit: WitnessTester<["step_in"], ["step_out"]>; + let json_mask_object_circuit: WitnessTester<["step_in", "data", "key", "keyLen"], ["step_out"]>; + let json_mask_arr_circuit: WitnessTester<["step_in", "data", "index"], ["step_out"]>; + let extract_value_circuit: WitnessTester<["step_in", "data"], ["step_out"]>; - const DATA_BYTES = 202; + const DATA_BYTES = 208; const MAX_STACK_HEIGHT = 5; const MAX_KEY_LENGTH = 8; - const MAX_VALUE_LENGTH = 35; - const TOTAL_BYTES_ACROSS_NIVC = DATA_BYTES + 4; + const MAX_VALUE_LENGTH = 32; before(async () => { json_mask_arr_circuit = await circomkit.WitnessTester(`JsonMaskArrayIndexNIVC`, { @@ -64,7 +124,6 @@ describe("NIVC Extract", async () => { console.log("#constraints:", await extract_value_circuit.getConstraintCount()); }); - let extended_json_input = json_input.concat(Array(Math.max(0, TOTAL_BYTES_ACROSS_NIVC - json_input.length)).fill(0)); let key0 = [100, 97, 116, 97, 0, 0, 0, 0]; // "data" let key0Len = 4; @@ -79,17 +138,34 @@ describe("NIVC Extract", async () => { it("parse and mask", async () => { - let json_extract_key0 = await json_mask_object_circuit.compute({ step_in: extended_json_input, key: key0, keyLen: key0Len }, ["step_out"]); + console.log(json_input.length); + let extended_json_input = json_input.concat(Array(Math.max(0, DATA_BYTES - json_input.length)).fill(0)); + + let jsonInputHash = DataHasher(extended_json_input); + let json_extract_key0 = await json_mask_object_circuit.compute({ step_in: jsonInputHash, data: extended_json_input, key: key0, keyLen: key0Len }, ["step_out"]); + console.log("JSON Extract key0 `step_out`:", json_extract_key0.step_out); + assert.deepEqual(json_extract_key0.step_out, json_key0_mask_hash); - let json_extract_key1 = await json_mask_object_circuit.compute({ step_in: json_extract_key0.step_out, key: key1, keyLen: key1Len }, ["step_out"]); + let json_extract_key1 = await json_mask_object_circuit.compute({ step_in: json_extract_key0.step_out, data: json_key0_mask, key: key1, keyLen: key1Len }, ["step_out"]); + assert.deepEqual(json_extract_key1.step_out, json_key1_mask_hash); + console.log("JSON Extract key1 `step_out`:", json_extract_key1.step_out); - let json_extract_arr = await json_mask_arr_circuit.compute({ step_in: json_extract_key1.step_out, index: 0 }, ["step_out"]); + let json_extract_arr = await json_mask_arr_circuit.compute({ step_in: json_extract_key1.step_out, index: 0, data: json_key1_mask }, ["step_out"]); + assert.deepEqual(json_extract_arr.step_out, json_arr_mask_hash); + console.log("JSON Extract arr `step_out`:", json_extract_arr.step_out); - let json_extract_key2 = await json_mask_object_circuit.compute({ step_in: json_extract_arr.step_out, key: key2, keyLen: key2Len }, ["step_out"]); + let json_extract_key2 = await json_mask_object_circuit.compute({ step_in: json_extract_arr.step_out, data: json_arr_mask, key: key2, keyLen: key2Len }, ["step_out"]); + assert.deepEqual(json_extract_key2.step_out, json_key2_mask_hash); + console.log("JSON Extract key2 `step_out`:", json_extract_key2.step_out); - let json_extract_key3 = await json_mask_object_circuit.compute({ step_in: json_extract_key2.step_out, key: key3, keyLen: key3Len }, ["step_out"]); + let json_extract_key3 = await json_mask_object_circuit.compute({ step_in: json_extract_key2.step_out, data: json_key2_mask, key: key3, keyLen: key3Len }, ["step_out"]); + assert.deepEqual(json_extract_key3.step_out, json_key3_mask_hash); + console.log("JSON Extract key3 `step_out`:", json_extract_key3.step_out); value = value.concat(Array(MAX_VALUE_LENGTH - value.length).fill(0)); - await extract_value_circuit.expectPass({ step_in: json_extract_key3.step_out }, { step_out: value }); + let final_value_hash = DataHasher(value); + let extractValue = await extract_value_circuit.compute({ step_in: json_extract_key3.step_out, data: json_key3_mask }, ["step_out"]); + console.log("JSON Extract finalValue `step_out`:", extractValue.step_out); + assert.deepEqual(extractValue.step_out, final_value_hash); }); }); \ No newline at end of file diff --git a/circuits/test/utils/hash.test.ts b/circuits/test/utils/hash.test.ts index 1661367..95e2d80 100644 --- a/circuits/test/utils/hash.test.ts +++ b/circuits/test/utils/hash.test.ts @@ -47,4 +47,68 @@ describe("hash", () => { ); }); }); + + describe("PoseidonChainer", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`PoseidonChainer`, { + file: "utils/hash", + template: "PoseidonChainer", + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: in = [69,420]", async () => { + const input = [69, 420]; + const hash = PoseidonModular(input); + await circuit.expectPass( + { in: input }, + { out: hash } + ); + }); + }); + + describe("DataHasher", () => { + let circuit: WitnessTester<["in"], ["out"]>; + + before(async () => { + circuit = await circomkit.WitnessTester(`DataHasher`, { + file: "utils/hash", + template: "DataHasher", + params: [16], + }); + console.log("#constraints:", await circuit.getConstraintCount()); + }); + + it("witness: in = [0,...x16]", async () => { + const input = Array(16).fill(0); + const hash = PoseidonModular([0, 0]); + await circuit.expectPass( + { in: input }, + { out: hash } + ); + }); + + it("witness: in = [1,0,...x15]", async () => { + let input = Array(16).fill(0); + input[0] = 1; + const hash = PoseidonModular([0, 1]); + await circuit.expectPass( + { in: input }, + { out: hash } + ); + }); + + it("witness: in = [0,0,...x15,1]", async () => { + let input = Array(16).fill(0); + input[15] = 1; + const hash = PoseidonModular([0, "1329227995784915872903807060280344576"]); + await circuit.expectPass( + { in: input }, + { out: hash } + ); + }); + + }); }); \ No newline at end of file diff --git a/circuits/utils/hash.circom b/circuits/utils/hash.circom index e2e1823..8ff969d 100644 --- a/circuits/utils/hash.circom +++ b/circuits/utils/hash.circom @@ -57,4 +57,30 @@ template PoseidonModular(numElements) { } out <== _out; +} + +template PoseidonChainer() { + signal input in[2]; + signal output out; + + out <== Poseidon(2)(in); +} + +template DataHasher(DATA_BYTES) { + assert(DATA_BYTES % 16 == 0); + signal input in[DATA_BYTES]; + signal output out; + + signal hashes[DATA_BYTES \ 16 + 1]; + hashes[0] <== 0; + + for(var i = 0 ; i < DATA_BYTES \ 16 ; i++) { + var packedInput = 0; + for(var j = 0 ; j < 16 ; j++) { + packedInput += in[16 * i + j] * 2**(8*j); + } + hashes[i+1] <== PoseidonChainer()([hashes[i],packedInput]); + } + + out <== hashes[DATA_BYTES \ 16]; } \ No newline at end of file diff --git a/package.json b/package.json index b993580..f37dbe5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "web-prover-circuits", "description": "ZK Circuits for WebProofs", - "version": "0.3.1", + "version": "0.4.0", "license": "Apache-2.0", "repository": { "type": "git",