UNPKG

mina-attestations

Version:
94 lines 3.63 kB
/** * Gadgets to convert any field element to/from digits in some base. */ import { Field, Provable, UInt8 } from 'o1js'; import { DynamicArray } from "./dynamic-array.js"; import { assertInRange16 } from "./gadgets.js"; import { assert } from "../util.js"; import { DynamicString } from "./dynamic-string.js"; export { toDecimalString, toBaseBE, fromBaseBE }; /** * Computes the unique decimal string representation of `value`. * * You need to pass in the maximum supported number of digits `maxDigits`. * * The method costs about `10 * maxDigits` constraints. */ function toDecimalString(value, maxDigits) { let digits = toBaseBE(value, 10, maxDigits); // map the digits to ASCII characters let asciiDigits = digits.map(UInt8, (digit) => UInt8.Unsafe.fromField(digit.add(48n).seal())); return DynamicString.from(asciiDigits); } /** * Computes a variable-length digit representation of `value` in base `base`. * * Returns a `DynamicArray` that starts with the **most significant digit**. * * The method guarantees that: * - The most significant digit is non-zero (unless the value is zero) * - The dynamic length of the array equals the minimum number of digits needed to represent the value. * - The output digits are in the range `[0, base)`. * * You need to pass in the maximum supported number of digits `maxDigits`, and the cost * in terms of constraints linearly depends on this value. */ function toBaseBE(value, base, maxDigits) { let digits = Provable.witness(DynamicArray(Field, { maxLength: maxDigits }), () => { let basen = BigInt(base); let remaining = value.toBigInt(); let digitsLE = []; while (remaining > 0) { digitsLE.push(remaining % basen); remaining /= basen; } return digitsLE.reverse(); }); // all digits are in range digits.array.forEach((d) => assertDigit(d, base)); // the digits correctly represent the value fromBaseBE(digits, base).assertEquals(value); // the most significant digit is not 0, except if the value is 0 if (maxDigits > 0) { digits.array[0].equals(0) .implies(value.equals(0)) .assertTrue('most significant digit must not be 0'); } return digits; } /** * Recomputes the value from a variable-length digit representation in base `base`. * * Expects a `DynamicArray` that contains digits in big-endian order. * * Digits are _assumed_ (not proved) to be in the range `[0, base)` or a similarly * "small" range that guarantees that the digit sum doesn't wrap around the field. */ function fromBaseBE(digits, base) { // this gadget is only sound if the digit sum can't overflow assert(BigInt(base) ** BigInt(digits.maxLength) < Field.ORDER, 'base^maxLength must not overflow the field'); // compute powers to multiply digits with // base^3, base^2, base^1, base^0, 0, 0, ..., 0 // ^ length let power = Field(1); let powers = Array(digits.maxLength); digits.forEachReverse((_, isPadding, i) => { powers[i] = Provable.if(isPadding.not(), power, Field(0)); if (i > 0) power = Provable.if(isPadding.not(), power.mul(base), power); }); let sum = Field(0); digits.forEach((digit, _, i) => { sum = sum.add(digit.mul(powers[i])); }); return sum.seal(); } function assertDigit(digit, base) { if (base === 2) { digit.assertBool(); return; } assert(base < 2 ** 16, 'base must be < 2^16'); assertInRange16(digit, base - 1); } //# sourceMappingURL=gadgets-digits.js.map