pragma circom 2.1.6; include "circomlib/circuits/comparators.circom"; /// @title Base64Decode /// @notice Decodes a Base64 encoded string to array of bytes. /// @notice Only support inputs with length = `byteLength` (no 0 padding). /// @notice It is known that padding char '=' can be replaed with `A` to produce the same output /// as Base64Lookup returns `0` for both, but a pracical attack from this is unlikely. /// @param byteLength Byte length of the encoded value - length of the output array. /// @input in Base64 encoded string; assumes elements to be valid Base64 characters. /// @output out Decoded array of bytes. template Base64Decode(byteLength) { var charLength = 4 * ((byteLength + 2) \ 3); // 4 chars encode 3 bytes signal input in[charLength]; signal output out[byteLength]; component bitsIn[charLength\4][4]; component bitsOut[charLength\4][3]; component translate[charLength\4][4]; var idx = 0; for (var i = 0; i < charLength; i += 4) { for (var j = 0; j < 3; j++) { bitsOut[i\4][j] = Bits2Num(8); } for (var j = 0; j < 4; j++) { bitsIn[i\4][j] = Num2Bits(6); translate[i\4][j] = Base64Lookup(); translate[i\4][j].in <== in[i+j]; translate[i\4][j].out ==> bitsIn[i\4][j].in; } // Do the re-packing from four 6-bit words to three 8-bit words. for (var j = 0; j < 6; j++) { bitsOut[i\4][0].in[j+2] <== bitsIn[i\4][0].out[j]; } bitsOut[i\4][0].in[0] <== bitsIn[i\4][1].out[4]; bitsOut[i\4][0].in[1] <== bitsIn[i\4][1].out[5]; for (var j = 0; j < 4; j++) { bitsOut[i\4][1].in[j+4] <== bitsIn[i\4][1].out[j]; } for (var j = 0; j < 4; j++) { bitsOut[i\4][1].in[j] <== bitsIn[i\4][2].out[j+2]; } bitsOut[i\4][2].in[6] <== bitsIn[i\4][2].out[0]; bitsOut[i\4][2].in[7] <== bitsIn[i\4][2].out[1]; for (var j = 0; j < 6; j++) { bitsOut[i\4][2].in[j] <== bitsIn[i\4][3].out[j]; } for (var j = 0; j < 3; j++) { if (idx+j < byteLength) { out[idx+j] <== bitsOut[i\4][j].out; } } idx += 3; } } /// @title Base64Lookup /// @notice http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-base /// @input in input character; assumes input to be valid Base64 character (though constrained implicitly). /// @output out output bit value. template Base64Lookup() { signal input in; signal output out; // ['A', 'Z'] component le_Z = LessThan(8); le_Z.in[0] <== in; le_Z.in[1] <== 90+1; component ge_A = GreaterThan(8); ge_A.in[0] <== in; ge_A.in[1] <== 65-1; signal range_AZ <== ge_A.out * le_Z.out; signal sum_AZ <== range_AZ * (in - 65); // ['a', 'z'] component le_z = LessThan(8); le_z.in[0] <== in; le_z.in[1] <== 122+1; component ge_a = GreaterThan(8); ge_a.in[0] <== in; ge_a.in[1] <== 97-1; signal range_az <== ge_a.out * le_z.out; signal sum_az <== sum_AZ + range_az * (in - 71); // ['0', '9'] component le_9 = LessThan(8); le_9.in[0] <== in; le_9.in[1] <== 57+1; component ge_0 = GreaterThan(8); ge_0.in[0] <== in; ge_0.in[1] <== 48-1; signal range_09 <== ge_0.out * le_9.out; signal sum_09 <== sum_az + range_09 * (in + 4); // '+' component equal_plus = IsZero(); equal_plus.in <== in - 43; signal sum_plus <== sum_09 + equal_plus.out * (in + 19); // '/' component equal_slash = IsZero(); equal_slash.in <== in - 47; signal sum_slash <== sum_plus + equal_slash.out * (in + 16); out <== sum_slash; // '=' component equal_eqsign = IsZero(); equal_eqsign.in <== in - 61; 1 === range_AZ + range_az + range_09 + equal_plus.out + equal_slash.out + equal_eqsign.out; }