diff --git a/CHANGELOG.md b/CHANGELOG.md index 6148465cd0..3b77842dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm As a replacement, `UInt64.Unsafe.fromField()` was introduced - This prevents you from accidentally creating a `UInt64` without proving that it fits in 64 bits - Equivalent changes were made to `UInt32` +- Fixed vulnerability in `Field.to/fromBits()` outlined in [#1023](https://github.com/o1-labs/o1js/issues/1023) by imposing a limit of 254 bits https://github.com/o1-labs/o1js/pull/1461 ### Added diff --git a/src/lib/field.ts b/src/lib/field.ts index 0e643cb2a5..a4cd6adf28 100644 --- a/src/lib/field.ts +++ b/src/lib/field.ts @@ -1,10 +1,11 @@ -import { Snarky, Provable } from '../snarky.js'; +import { Snarky } from '../snarky.js'; import { Field as Fp } from '../provable/field-bigint.js'; import { defineBinable } from '../bindings/lib/binable.js'; import type { NonNegativeInteger } from '../bindings/crypto/non-negative.js'; import { asProver, inCheckedComputation } from './provable-context.js'; import { Bool } from './bool.js'; import { assert } from './errors.js'; +import { Provable } from './provable.js'; // external API export { Field }; @@ -908,34 +909,41 @@ class Field { * Returns an array of {@link Bool} elements representing [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of this {@link Field} element. * * If you use the optional `length` argument, proves that the field element fits in `length` bits. - * The `length` has to be between 0 and 255 and the method throws if it isn't. + * The `length` has to be between 0 and 254 and the method throws if it isn't. * * **Warning**: The cost of this operation in a zk proof depends on the `length` you specify, - * which by default is 255 bits. Prefer to pass a smaller `length` if possible. + * which by default is 254 bits. Prefer to pass a smaller `length` if possible. * * @param length - the number of bits to fit the element. If the element does not fit in `length` bits, the functions throws an error. * * @return An array of {@link Bool} element representing little endian binary representation of this {@link Field}. */ - toBits(length?: number) { - if (length !== undefined) checkBitLength('Field.toBits()', length); + toBits(length: number = 254) { + checkBitLength('Field.toBits()', length, 254); if (this.isConstant()) { let bits = Fp.toBits(this.toBigInt()); - if (length !== undefined) { - if (bits.slice(length).some((bit) => bit)) - throw Error(`Field.toBits(): ${this} does not fit in ${length} bits`); - return bits.slice(0, length).map((b) => new Bool(b)); - } - return bits.map((b) => new Bool(b)); + if (bits.slice(length).some((bit) => bit)) + throw Error(`Field.toBits(): ${this} does not fit in ${length} bits`); + return bits.slice(0, length).map((b) => new Bool(b)); } - let [, ...bits] = Snarky.field.toBits(length ?? Fp.sizeInBits, this.value); - return bits.map((b) => new Bool(b)); + let bits = Provable.witness(Provable.Array(Bool, length), () => { + let f = this.toBigInt(); + return Array.from( + { length }, + (_, k) => new Bool(!!((f >> BigInt(k)) & 0x1n)) + ); + }); + Field.fromBits(bits).assertEquals( + this, + `Field.toBits(): Input does not fit in ${length} bits` + ); + return bits; } /** * Convert a bit array into a {@link Field} element using [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) * - * The method throws if the given bits do not fit in a single Field element. A Field element can be at most 255 bits. + * The method throws if the given bits do not fit in a single Field element. In this case, no more than 254 bits are allowed because some 255 bit integers do not fit into a single Field element. * * **Important**: If the given `bytes` array is an array of `booleans` or {@link Bool} elements that all are `constant`, the resulting {@link Field} element will be a constant as well. Or else, if the given array is a mixture of constants and variables of {@link Bool} type, the resulting {@link Field} will be a variable as well. * @@ -944,20 +952,20 @@ class Field { * @return A {@link Field} element matching the [little endian binary representation](https://en.wikipedia.org/wiki/Endianness) of the given `bytes` array. */ static fromBits(bits: (Bool | boolean)[]) { - let length = bits.length; - checkBitLength('Field.fromBits()', length); + const length = bits.length; + checkBitLength('Field.fromBits()', length, 254); if (bits.every((b) => typeof b === 'boolean' || b.toField().isConstant())) { let bits_ = bits .map((b) => (typeof b === 'boolean' ? b : b.toBoolean())) .concat(Array(Fp.sizeInBits - length).fill(false)); return new Field(Fp.fromBits(bits_)); } - let bitsVars = bits.map((b): FieldVar => { - if (typeof b === 'boolean') return b ? FieldVar[1] : FieldVar[0]; - return b.toField().value; - }); - let x = Snarky.field.fromBits([0, ...bitsVars]); - return new Field(x); + return bits + .map((b) => new Bool(b)) + .reduce((acc, bit, idx) => { + const shift = 1n << BigInt(idx); + return acc.add(bit.toField().mul(shift)); + }, Field.from(0)).seal(); } /** diff --git a/src/lib/field.unit-test.ts b/src/lib/field.unit-test.ts index 71607acb14..2c99a3c737 100644 --- a/src/lib/field.unit-test.ts +++ b/src/lib/field.unit-test.ts @@ -196,7 +196,9 @@ test(Random.field, Random.field, (x0, y0, assert) => { Provable.asProver(() => assert(z.toBigInt() === Fp.mul(x0, y0))); // toBits / fromBits - let bits = Fp.toBits(x0); + // Fp.toBits() returns 255 bits, but our new to/from impl only accepts <=254 + // https://github.com/o1-labs/o1js/pull/1461 + let bits = Fp.toBits(x0).slice(0, -1); let x1 = Provable.witness(Field, () => Field.fromBits(bits)); let bitsVars = x1.toBits(); Provable.asProver(() => diff --git a/src/lib/provable.ts b/src/lib/provable.ts index f2b918ff7f..ceff63ba68 100644 --- a/src/lib/provable.ts +++ b/src/lib/provable.ts @@ -3,7 +3,8 @@ * - a namespace with tools for writing provable code * - the main interface for types that can be used in provable code */ -import { Field, Bool } from './core.js'; +import { Bool } from './bool.js'; +import { Field } from './field.js'; import { Provable as Provable_, Snarky } from '../snarky.js'; import type { FlexibleProvable, ProvableExtended } from './circuit-value.js'; import { Context } from './global-context.js'; @@ -254,7 +255,7 @@ function witness = FlexibleProvable>( // } return [0, ...fieldConstants]; }); - fields = fieldVars.map(Field); + fields = fieldVars.map((x) => new Field(x)); } finally { snarkContext.leave(id); } @@ -291,7 +292,7 @@ async function witnessAsync< let fields = type.toFields(proverValue); return fields.map((x) => x.toBigInt()); }); - fields = fieldVars.map(Field); + fields = fieldVars.map((x) => new Field(x)); } finally { snarkContext.leave(id); } @@ -440,7 +441,7 @@ function switch_>( if (mask.every((b) => b.toField().isConstant())) checkMask(); else Provable.asProver(checkMask); let size = type.sizeInFields(); - let fields = Array(size).fill(Field(0)); + let fields = Array(size).fill(new Field(0)); for (let i = 0; i < nValues; i++) { let valueFields = type.toFields(values[i]); let maskField = mask[i].toField(); diff --git a/src/lib/scalar.ts b/src/lib/scalar.ts index 7439f3713a..055a7bbe0d 100644 --- a/src/lib/scalar.ts +++ b/src/lib/scalar.ts @@ -99,7 +99,10 @@ class Scalar { * **Warning**: The bits are interpreted as the bits of 2s + 1 + 2^255, where s is the Scalar. */ static fromBits(bits: Bool[]) { - return Scalar.fromFields(bits.map((b) => b.toField())); + return Scalar.fromFields([ + ...bits.map((b) => b.toField()), + ...Array(Fq.sizeInBits - bits.length).fill(new Bool(false)), + ]); } /** diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index b9070412fa..45eee51c92 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -50,16 +50,16 @@ } }, "HelloWorld": { - "digest": "11c21044c0ab297f29b386d1d04d0318cff4effa75191371dd576282882c2a8d", + "digest": "103e8bd9af45de70a1b214377e71bd05ae464e932dbe48aeada70362fa38b05", "methods": { "update": { - "rows": 826, - "digest": "5215ab9c7474b26f3a9073d821aef59b" + "rows": 825, + "digest": "226ff56f432f39d8056bf7f1813669f5" } }, "verificationKey": { - "data": "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", - "hash": "12073693408844675717690269959590209616934536940765116494766700428390466839" + "data": "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", + "hash": "21868179330353774930881761197110482602042218337984430556708224781269131363222" } }, "TokenContract": { @@ -251,7 +251,7 @@ } }, "diverse": { - "digest": "3822c67734f73c594779eab98899d2e4cc254b7b9d5261b2aada529f781c1234", + "digest": "384e7eb44f1ad3b899aae057c48a9eab8ad72c8120ef13a1e906c91b93028828", "methods": { "ecdsa": { "rows": 28186, @@ -262,8 +262,8 @@ "digest": "1496e5b8cc253c9909551a25c190ab85" }, "pallas": { - "rows": 1616, - "digest": "8a9ee7267139d6280971f9750a610713" + "rows": 1610, + "digest": "0ff5537aae21b4d47b8c901aa5e71007" }, "poseidon": { "rows": 947, @@ -279,8 +279,8 @@ } }, "verificationKey": { - "data": "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", - "hash": "16059383130523018273678724949684090892300714372032191113254352030711948582951" + "data": "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", + "hash": "25099125262785569464008872277824954042674175732318382315709475361977342159172" } } } \ No newline at end of file