mina-attestations
Version:
Private Attestations on Mina
160 lines • 5.34 kB
JavaScript
/**
* Misc gadgets for attestation contracts.
*/
import { Field, Gadgets, Provable, TupleN, UInt64, UInt8, } from 'o1js';
import { assert } from "../util.js";
export { pack, unpack, packBytes, unpackBytes, uint64FromBytesBE, uint64ToBytesBE, lessThan16, assertInRange16, assertLessThan16, rangeCheck, };
/**
* Pack a list of fields of bit size `chunkSize` each into a single field.
* Uses little-endian encoding.
*
* **Warning**: Assumes, but doesn't prove, that each chunk fits in the chunk size.
*/
function pack(chunks, chunkSize) {
let p = chunks.length * chunkSize;
assert(chunks.length <= 1 || p < Field.sizeInBits, () => `pack(): too many chunks, got ${chunks.length} * ${chunkSize} = ${p}`);
let sum = Field(0);
chunks.forEach((chunk, i) => {
sum = sum.add(chunk.mul(1n << BigInt(i * chunkSize)));
});
return sum.seal();
}
/**
* Unpack a field into a list of fields of bit size `chunkSize` each.
* Uses little-endian encoding.
*
* Proves that the output fields have at most `chunkSize` bits,
* and that the input has at most `chunkSize * numChunks` bits.
*/
function unpack(word, chunkSize, numChunks) {
function computeChunks() {
let x = Field.from(word).toBigInt();
let mask = (1n << BigInt(chunkSize)) - 1n;
return TupleN.fromArray(numChunks, Array.from({ length: numChunks }, (_, i) => Field((x >> BigInt(i * chunkSize)) & mask)));
}
let chunks = Field.from(word).isConstant()
? computeChunks()
: Provable.witnessFields(numChunks, computeChunks);
// range check fields, so decomposition is unique and outputs are in range
chunks.forEach((chunk) => rangeCheck(chunk, chunkSize));
// check decomposition
// this asserts that the composition doesn't overflow
pack(chunks, chunkSize).assertEquals(word);
return chunks;
}
function packBytes(bytes) {
let fields = bytes.map((x) => x.value);
return pack(fields, 8);
}
function unpackBytes(word, numBytes) {
let fields = unpack(word, 8, numBytes);
return fields.map((x) => UInt8.Unsafe.fromField(x));
}
function uint64FromBytesBE(bytes) {
let field = packBytes(bytes.toReversed());
return UInt64.Unsafe.fromField(field);
}
function uint64ToBytesBE(x) {
return unpackBytes(x.value, 8).toReversed();
}
function rangeCheck(x, bits) {
switch (bits) {
case 1:
x.assertBool();
break;
case 4:
rangeCheckLessThan16(4, x);
case 8:
Gadgets.rangeCheck8(x);
break;
case 16:
Gadgets.rangeCheck16(x);
break;
case 32:
Gadgets.rangeCheck32(x);
break;
case 64:
UInt64.check(UInt64.Unsafe.fromField(x));
break;
}
}
/**
* Slightly more efficient version of Provable.if() which produces garbage if both t is a non-dummy and b is true.
*
* t + b*s
*
* Cost: 2*|T|, or |T| if t is all zeros
*/
function unsafeIf(b, type, t, s) {
let fields = add(type.toFields(t), mul(type.toFields(s), b));
let aux = type.toAuxiliary(t);
Provable.asProver(() => {
if (b.toBoolean())
aux = type.toAuxiliary(s);
});
return type.fromFields(fields, aux);
}
function seal(type, t) {
let fields = type.toFields(t);
let aux = type.toAuxiliary(t);
fields = fields.map((x) => x.seal());
return type.fromFields(fields, aux);
}
function mul(fields, mask) {
return fields.map((x) => x.mul(mask.toField()));
}
function add(t, s) {
return t.map((t, i) => t.add(s[i]));
}
/**
* Asserts that 0 <= i <= x without other assumptions on i,
* assuming that 0 <= x < 2^16.
*/
function assertInRange16(i, x) {
Gadgets.rangeCheck16(i);
Gadgets.rangeCheck16(Field(x).sub(i).seal());
}
/**
* Asserts that i < x, assuming that i in [0,2^32) and x in [0,2^16).
*
* Cost: 1.5
*/
function assertLessThan16(i, x) {
if (i.isConstant() && Field(x).isConstant()) {
assert(i.toBigint() < Field(x).toBigInt(), 'assertLessThan16');
}
// assumptions on i, x imply that x - 1 - i is in [0, 2^16) - 1 - [0, 2^32) = [-1-2^32, 2^16-1) = (p-2^32, p) u [0, 2^16-1)
// checking 0 <= x - 1 - i < 2^16 excludes the negative part of the range
Gadgets.rangeCheck16(Field(x).sub(1).sub(i.value).seal());
}
/**
* Returns i <? x for i, x < 2^16.
*
* Note: This is also sound for i < 2^32, just not complete in that case
*
* Cost: 2.5
*/
function lessThan16(i, x) {
let b = Provable.witness(Field, () => BigInt(i.toBigInt() < Field(x).toBigInt()));
let isLessThan = b.assertBool();
Gadgets.rangeCheck16(b
.mul(1n << 16n)
.add(i)
.sub(x));
return isLessThan;
}
// copied from o1js
// https://github.com/o1-labs/o1js/blob/main/src/lib/provable/gadgets/range-check.ts
function rangeCheckLessThan16(bits, x) {
assert(bits < 16, `bits must be less than 16, got ${bits}`);
if (x.isConstant()) {
assert(x.toBigInt() < 1n << BigInt(bits), `rangeCheckLessThan16: expected field to fit in ${bits} bits, got ${x}`);
return;
}
// check that x fits in 16 bits
Gadgets.rangeCheck16(x);
// check that 2^(16 - bits)*x < 2^16, i.e. x < 2^bits
let xM = x.mul(1 << (16 - bits)).seal();
Gadgets.rangeCheck16(xM);
}
//# sourceMappingURL=gadgets.js.map