Skip to content

Commit

Permalink
make hash to group return canonical result directly
Browse files Browse the repository at this point in the history
  • Loading branch information
mitschabaude committed Apr 9, 2024
1 parent 9e9ca06 commit ae0b671
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/bindings
17 changes: 3 additions & 14 deletions src/lib/provable/crypto/nullifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,7 @@ class Nullifier extends Struct({
let pk_fields = Group.toFields(publicKey);

// x and y of hash(msg, pk), it doesn't return a Group because y is split into x0 and x1, both two roots of a field element
let {
x,
y: { x0 },
} = Poseidon.hashToGroup([...message, ...pk_fields]);

// check to prevent the prover from using the second square root and forging a non-unique nullifier
x0.isEven().assertTrue();

let h_m_pk = Group.fromFields([x, x0]);
let h_m_pk = Poseidon.hashToGroup([...message, ...pk_fields]);

// pk^c
let pk_c = this.publicKey.scale(c);
Expand All @@ -83,8 +75,7 @@ class Nullifier extends Struct({
Poseidon.hash([
...Group.toFields(G),
...pk_fields,
x,
x0,
...Group.toFields(h_m_pk),
...Group.toFields(nullifier),
...Group.toFields(g_r),
...Group.toFields(h_m_pk_s_div_nullifier_s),
Expand Down Expand Up @@ -191,9 +182,7 @@ class Nullifier extends Struct({

const r = Scalar.random();

const gm = Hash([...message, ...Group.toFields(pk)]);

const h_m_pk = Group({ x: gm.x, y: gm.y.x0 });
const h_m_pk = Hash([...message, ...Group.toFields(pk)]);

const nullifier = h_m_pk.scale(sk.toBigInt());
const h_m_pk_r = h_m_pk.scale(r.toBigInt());
Expand Down
49 changes: 22 additions & 27 deletions src/lib/provable/crypto/poseidon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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';

// external API
export { Poseidon, TokenSymbol };
Expand Down Expand Up @@ -78,38 +79,32 @@ const Poseidon = {
return [Field(0), Field(0), Field(0)];
},

hashToGroup(input: Field[]) {
if (isConstant(input)) {
let result = PoseidonBigint.hashToGroup(toBigints(input));
assert(result !== undefined, 'hashToGroup works on all inputs');
let { x, y } = result;
return {
x: Field(x),
y: { x0: Field(y.x0), x1: Field(y.x1) },
};
}

// y = sqrt(y^2)
let [, xv, yv] = Snarky.poseidon.hashToGroup(MlFieldArray.to(input));

let x = Field(xv);
let y = Field(yv);
Unsafe: {
hashToGroup(input: Field[]) {
if (isConstant(input)) {
let result = PoseidonBigint.hashToGroup(toBigints(input));
assert(result !== undefined, 'hashToGroup works on all inputs');
let { x, y } = result;
return new Group({ x, y });
}

let x0 = Provable.witness(Field, () => {
// the even root of y^2 will become x0, so the APIs are uniform
let isEven = y.toBigInt() % 2n === 0n;
// y = sqrt(y^2)
let [, x, y] = Snarky.poseidon.hashToGroup(MlFieldArray.to(input));
return new Group({ x, y });
},
},

// we just change the order so the even root is x0
// y.mul(-1); is the second root of sqrt(y^2)
return isEven ? y : y.mul(-1);
});
hashToGroup(input: Field[]): Group {
if (isConstant(input)) return Poseidon.Unsafe.hashToGroup(input);

let x1 = x0.mul(-1);
let { x, y } = Poseidon.Unsafe.hashToGroup(input);

// we check that either x0 or x1 match the original root y
y.equals(x0).or(y.equals(x1)).assertTrue();
// 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 { x, y: { x0, x1 } };
return new Group({ x, y });
},

/**
Expand Down
20 changes: 3 additions & 17 deletions src/lib/provable/test/group.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ import { Field } from '../field.js';
console.log('group consistency tests');

test(Random.field, Random.scalar, Random.field, (a, s0, x0, assert) => {
const {
x: x1,
y: { x0: y1 },
} = Poseidon.hashToGroup([a])!;
const g = Group.from(x1, y1);
const g = Group(Poseidon.hashToGroup([a])!);

// scale by a scalar
const s = Scalar.from(s0);
Expand All @@ -26,19 +22,9 @@ test(Random.field, Random.scalar, Random.field, (a, s0, x0, assert) => {

// tests consistency between in- and out-circuit implementations
test(Random.field, Random.field, (a, b, assert) => {
const {
x: x1,
y: { x0: y1 },
} = Poseidon.hashToGroup([a])!;

const {
x: x2,
y: { x0: y2 },
} = Poseidon.hashToGroup([b])!;

const zero = Group.zero;
const g1 = Group.from(x1, y1);
const g2 = Group.from(x2, y2);
const g1 = Group(Poseidon.hashToGroup([a])!);
const g2 = Group(Poseidon.hashToGroup([b])!);

run(g1, g2, (x, y) => x.add(y), assert);
run(g1.neg(), g2.neg(), (x, y) => x.add(y), assert);
Expand Down
5 changes: 2 additions & 3 deletions src/mina-signer/src/nullifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ function createNullifier(message: Field[], sk: PrivateKey): Nullifier {

const r = Scalar.random();

const gm = Hash([...message, ...Group.toFields(pk)]);
if (!gm) throw Error('hashToGroup: Point is undefined');
const h_m_pk = { x: gm.x, y: gm.y.x0 };
const h_m_pk = Hash([...message, ...Group.toFields(pk)]);
if (!h_m_pk) throw Error('hashToGroup: Point is undefined');

const nullifier = Group.scale(h_m_pk, sk);
const h_m_pk_r = Group.scale(h_m_pk, r);
Expand Down

0 comments on commit ae0b671

Please sign in to comment.