UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

239 lines 8.87 kB
var _Sponge_sponge; import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib"; import { HashInput, Struct } from '../types/struct.js'; import { Snarky } from '../../../bindings.js'; import { Field } from '../wrapped.js'; import { createHashHelpers } from './hash-generic.js'; import { Provable } from '../provable.js'; import { MlFieldArray } from '../../ml/fields.js'; import { Poseidon as PoseidonBigint } from '../../../bindings/crypto/poseidon.js'; import { assert } from '../../util/errors.js'; import { rangeCheckN } from '../gadgets/range-check.js'; import { TupleN } from '../../util/types.js'; import { Group } from '../group.js'; import { ProvableType } from '../types/provable-intf.js'; import { stringLengthInBytes } from '../../../bindings/lib/binable.js'; // external API export { Poseidon, TokenSymbol }; // internal API export { HashInput, HashHelpers, emptyHashWithPrefix, hashWithPrefix, salt, packToFields, emptyReceiptChainHash, hashConstant, isHashable, }; class Sponge { // TODO: implement constant version in TS. currently, you need to call `initializeBindings()` before successfully calling this constructor() { _Sponge_sponge.set(this, void 0); let isChecked = Provable.inCheckedComputation(); assert(Snarky !== undefined, 'Poseidon.Sponge(): bindings are not initialized, try calling `await initializeBindings()` first.'); __classPrivateFieldSet(this, _Sponge_sponge, Snarky.poseidon.sponge.create(isChecked), "f"); } absorb(x) { Snarky.poseidon.sponge.absorb(__classPrivateFieldGet(this, _Sponge_sponge, "f"), x.value); } squeeze() { return Field(Snarky.poseidon.sponge.squeeze(__classPrivateFieldGet(this, _Sponge_sponge, "f"))); } } _Sponge_sponge = new WeakMap(); const Poseidon = { hash(input) { if (isConstant(input)) { return Field(PoseidonBigint.hash(toBigints(input))); } return Poseidon.update(Poseidon.initialState(), input)[0]; }, update(state, input) { if (isConstant(state) && isConstant(input)) { let newState = PoseidonBigint.update(toBigints(state), toBigints(input)); return TupleN.fromArray(3, newState.map(Field)); } let newState = Snarky.poseidon.update(MlFieldArray.to(state), MlFieldArray.to(input)); return MlFieldArray.from(newState); }, hashWithPrefix(prefix, input) { let init = Poseidon.update(Poseidon.initialState(), [prefixToField(prefix)]); return Poseidon.update(init, input)[0]; }, initialState() { return [Field(0), Field(0), Field(0)]; }, Unsafe: { /** * Low-level version of `Poseidon.hashToGroup()`. * * **Warning**: This function is marked unsafe because its output is not deterministic. * It returns the square root of a value without constraining which of the two possible * square roots is chosen. This allows the prover to choose between two different hashes, * which can be a vulnerability if consuming code treats the output as unique. */ hashToGroup(input) { if (isConstant(input)) { let result = PoseidonBigint.hashToGroup(toBigints(input)); assert(result !== undefined, 'hashToGroup works on all inputs'); return new Group(result); } // y = sqrt(y^2) let [, x, y] = Snarky.poseidon.hashToGroup(MlFieldArray.to(input)); return new Group({ x, y }); }, }, /** * Hashes a list of field elements to a point on the Pallas curve. * * The output point is deterministic and its discrete log is not efficiently computable. */ hashToGroup(input) { if (isConstant(input)) return Poseidon.Unsafe.hashToGroup(input); let { x, y } = Poseidon.Unsafe.hashToGroup(input); // the y coordinate is calculated using a square root, so it has two possible values // to make the output deterministic, we negate y if it is odd let sign = Field.from(1n).sub(y.isOdd().toField().mul(2n)); // -1 is y is odd, 1 else y = y.mul(sign); return new Group({ x, y }); }, /** * Hashes a provable type efficiently. * * ```ts * let skHash = Poseidon.hashPacked(PrivateKey, secretKey); * ``` * * Note: Instead of just doing `Poseidon.hash(value.toFields())`, this * uses the `toInput()` method on the provable type to pack the input into as few * field elements as possible. This saves constraints because packing has a much * lower per-field element cost than hashing. */ hashPacked(type, value) { let input = ProvableType.get(type).toInput(value); let packed = packToFields(input); return Poseidon.hash(packed); }, Sponge, }; function hashConstant(input) { return Field(PoseidonBigint.hash(toBigints(input))); } const HashHelpers = createHashHelpers(Field, Poseidon); let { salt, emptyHashWithPrefix, hashWithPrefix } = HashHelpers; // same as Random_oracle.prefix_to_field in OCaml function prefixToField(prefix) { if (prefix.length * 8 >= 255) throw Error('prefix too long'); let bits = [...prefix] .map((char) => { // convert char to 8 bits let bits = []; for (let j = 0, c = char.charCodeAt(0); j < 8; j++, c >>= 1) { if (j === 7) assert(c === 0, `Invalid character ${char}, only ASCII characters are supported.`); bits.push(!!(c & 1)); } return bits; }) .flat(); return Field.fromBits(bits); } /** * Convert the {fields, packed} hash input representation to a list of field elements * Random_oracle_input.Chunked.pack_to_fields */ function packToFields({ fields = [], packed = [] }) { if (packed.length === 0) return fields; let packedBits = []; let currentPackedField = Field(0); let currentSize = 0; for (let [field, size] of packed) { currentSize += size; if (currentSize < 255) { currentPackedField = currentPackedField.mul(Field(1n << BigInt(size))).add(field); } else { packedBits.push(currentPackedField); currentSize = size; currentPackedField = field; } } packedBits.push(currentPackedField); return fields.concat(packedBits); } function isHashable(obj) { if (!obj) { return false; } const hasToInput = 'toInput' in obj && typeof obj.toInput === 'function'; const hasEmpty = 'empty' in obj && typeof obj.empty === 'function'; return hasToInput && hasEmpty; } const TokenSymbolPure = { toFields({ field }) { return [field]; }, toAuxiliary(value) { return [value?.symbol ?? '']; }, fromFields([field], [symbol]) { return { symbol, field }; }, sizeInFields() { return 1; }, check({ field }) { rangeCheckN(48, field); }, toValue({ symbol }) { return symbol; }, fromValue(symbol) { if (typeof symbol !== 'string') return symbol; let bytesLength = new TextEncoder().encode(symbol).length; if (bytesLength > MAX_TOKEN_SYMBOL_LENGTH) throw Error(`Token symbol ${symbol} should be a maximum of ${MAX_TOKEN_SYMBOL_LENGTH} bytes, but is ${bytesLength}`); let field = prefixToField(symbol); return { symbol, field }; }, toJSON({ symbol }) { return symbol; }, fromJSON(symbol) { let field = prefixToField(symbol); return { symbol, field }; }, toInput({ field }) { return { packed: [[field, 48]] }; }, empty() { return { symbol: '', field: Field(0n) }; }, }; const MAX_TOKEN_SYMBOL_LENGTH = 6; class TokenSymbol extends Struct(TokenSymbolPure) { constructor(symbol) { if (typeof symbol === 'object') { super({ symbol: symbol.symbol, field: symbol.field }); } else { let bytesLength = stringLengthInBytes(symbol); if (bytesLength > MAX_TOKEN_SYMBOL_LENGTH) { throw Error(`Token symbol ${symbol} should be a maximum of ${MAX_TOKEN_SYMBOL_LENGTH} bytes, but is ${bytesLength}`); } super({ symbol: symbol, field: prefixToField(symbol) }); } } static from(value) { return TokenSymbol.fromValue(value); } static empty() { return new TokenSymbol(''); } } function emptyReceiptChainHash() { return emptyHashWithPrefix('CodaReceiptEmpty'); } function isConstant(fields) { return fields.every((x) => x.isConstant()); } function toBigints(fields) { return fields.map((x) => x.toBigInt()); } //# sourceMappingURL=poseidon.js.map