Files
zk-email-verify/packages/circuits/utils/bytes.circom
2025-02-07 21:53:01 +05:30

210 lines
6.5 KiB
Plaintext

pragma circom 2.1.6;
include "circomlib/circuits/bitify.circom";
include "circomlib/circuits/comparators.circom";
include "./array.circom";
include "./constants.circom";
include "./functions.circom";
function computeIntChunkLength(byteLength) {
var packSize = MAX_BYTES_IN_FIELD();
var remain = byteLength % packSize;
var numChunks = (byteLength - remain) / packSize;
if (remain > 0) {
numChunks += 1;
}
return numChunks;
}
/// @title PackBytes
/// @notice Pack an array of bytes to numbers that fit in the field
/// @param maxBytes the maximum number of bytes in the input array
/// @input in the input byte array; assumes elements to be bytes
/// @output out the output integer array
template PackBytes(maxBytes) {
var packSize = MAX_BYTES_IN_FIELD();
var maxInts = computeIntChunkLength(maxBytes);
signal input in[maxBytes];
signal output out[maxInts];
signal intSums[maxInts][packSize];
for (var i = 0; i < maxInts; i++) {
for(var j=0; j < packSize; j++) {
var idx = packSize * i + j;
// Copy the previous value if we are out of bounds - we take last item as final result
if(idx >= maxBytes) {
intSums[i][j] <== intSums[i][j-1];
}
// First item of each chunk is the byte itself
else if (j == 0){
intSums[i][j] <== in[idx];
}
// Every other item is 256^j * byte
else {
intSums[i][j] <== intSums[i][j-1] + (1 << (8*j)) * in[idx];
}
}
}
// Last item of each chunk is the final sum
for (var i = 0; i < maxInts; i++) {
out[i] <== intSums[i][packSize-1];
}
}
/// @title PackByteSubArray
/// @notice Select sub array from the input array and pack it to numbers that fit in the field
/// @notice This is not used in ZK-Email circuits anywhere
/// @param maxArrayLen the maximum number of elements in the input array
/// @param maxSubArrayLen the maximum number of elements in the sub array
/// @input in the input byte array; assumes elements to be bytes
/// @input startIndex the start index of the sub array; assumes to be a valid index
/// @input length the length of the sub array; assumes to fit in `ceil(log2(maxSubArrayLen))` bits
/// @output out the output integer array
template PackByteSubArray(maxArrayLen, maxSubArrayLen) {
assert(maxSubArrayLen < maxArrayLen);
var chunkLength = computeIntChunkLength(maxSubArrayLen);
signal input in[maxArrayLen];
signal input startIndex;
signal input length;
signal output out[chunkLength];
component SelectSubArray = SelectSubArray(maxArrayLen, maxSubArrayLen);
SelectSubArray.in <== in;
SelectSubArray.startIndex <== startIndex;
SelectSubArray.length <== length;
component packer = PackBytes(maxSubArrayLen);
packer.in <== SelectSubArray.out;
out <== packer.out;
}
/// @title DigitBytesToInt
/// @notice Converts a byte array representing digits to an integer
/// @notice Assumes the output number fits in the field
/// @param n The number of bytes in the input array
/// @input in The input byte array; assumes elements are between 48 and 57 (ASCII numbers)
/// @output out The output integer; assumes to fit in the field
template DigitBytesToInt(n) {
signal input in[n];
signal output out;
signal sums[n+1];
sums[0] <== 0;
for(var i = 0; i < n; i++) {
sums[i + 1] <== 10 * sums[i] + (in[i] - 48);
}
out <== sums[n];
}
// NOTE: this circuit is unaudited and should not be used in production
/// @title SplitBytesToWords
/// @notice split an array of bytes into an array of words
/// @notice useful for casting a message or modulus before RSA verification
/// @param l: number of bytes in the input array
/// @param n: number of bits in a word
/// @param k: number of words
/// @input in: array of bytes
/// @output out: array of words
template SplitBytesToWords (l,n,k) {
signal input in[l];
signal output out[k];
component num2bits[l];
for (var i = 0 ; i < l ; i++){
num2bits[i] = Num2Bits(8);
num2bits[i].in <== in[i];
}
component bits2num[k];
for (var i = 0 ; i < k ; i++){
bits2num[i] = Bits2Num(n);
for(var j = 0 ; j < n ; j++){
if(i*n + j >= 8 * l){
bits2num[i].in[j] <== 0;
}
else{
bits2num[i].in[j] <== num2bits[l - (( i * n + j) \ 8) - 1].out[ ((i * n + j) % 8)];
}
}
}
for( var i = 0 ; i< k ; i++){
out[i] <== bits2num[i].out;
}
}
// Asserts that a given input is binary.
//
// Inputs:
// - in: an input signal, expected to be 0 or 1.
template AssertBit() {
signal input in;
in * (in - 1) === 0;
}
// The ByteMask template masks an input array using a binary mask array.
// Each element in the input array is multiplied by the corresponding element in the mask array.
// The mask array is validated to ensure all elements are binary (0 or 1).
//
// Parameters:
// - maxLength: The maximum length of the input and mask arrays.
//
// Inputs:
// - body: An array of signals representing the body to be masked.
// - mask: An array of signals representing the binary mask.
//
// Outputs:
// - out: An array of signals representing the masked input.
template ByteMask(maxLength) {
signal input in[maxLength];
signal input mask[maxLength];
signal output out[maxLength];
component bit_check[maxLength];
for (var i = 0; i < maxLength; i++) {
bit_check[i] = AssertBit();
bit_check[i].in <== mask[i];
out[i] <== in[i] * mask[i];
}
}
/// @title PackBits
/// @notice Packs an array of bits into field elements of specified bit size
/// @dev Assumes input bits are in big-endian order and maintains BE in output
/// @param numBits Total number of input bits
/// @param bitsPerElement Number of bits to pack into each element
/// @input in Array of bits in big-endian order
/// @output out Array of packed field elements in big-endian order
template PackBits(numBits, bitsPerElement) {
signal input in[numBits];
var numElements = (numBits + bitsPerElement - 1) \ bitsPerElement;
signal output out[numElements];
for (var i = 0; i < numElements; i++) {
var sum = 0;
for (var j = 0; j < bitsPerElement; j++) {
var idx = i * bitsPerElement + j;
if (idx < numBits) {
// Maintain BE order by using (bitsPerElement - 1 - j) for bit position
sum += in[idx] * (1 << (bitsPerElement - 1 - j));
}
}
out[i] <== sum;
}
}