Skip to content

Commit 115cf25

Browse files
committed
signer: add poseidon hash tests
1 parent 5e7519a commit 115cf25

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { expect } from 'expect';
2+
import { Poseidon, hashWithPrefix } from './poseidon-bigint.js';
3+
import { callForestHashGeneric, CallForest } from './sign-zkapp-command.js';
4+
import { prefixes } from '../../bindings/crypto/constants.js';
5+
import { NetworkId } from './types.js';
6+
7+
type Leaf = bigint;
8+
9+
async function testCallForestPaddingCollision() {
10+
const net: NetworkId = 'testnet';
11+
const hashLeaf = (leaf: Leaf) => Poseidon.hash([leaf]);
12+
const hash = (leaf: Leaf, _networkId: NetworkId) => hashLeaf(leaf);
13+
14+
const forestBase: CallForest<Leaf> = [{ accountUpdate: 1n, children: [] }];
15+
const forestPadded: CallForest<Leaf> = [
16+
{ accountUpdate: 1n, children: [] },
17+
{ accountUpdate: 0n, children: [] }, // extra trailing zero leaf
18+
];
19+
20+
const baseDigest = callForestHashGeneric(
21+
forestBase,
22+
hash,
23+
hashWithPrefix,
24+
0n,
25+
net
26+
);
27+
const paddedDigest = callForestHashGeneric(
28+
forestPadded,
29+
hash,
30+
hashWithPrefix,
31+
0n,
32+
net
33+
);
34+
35+
expect(baseDigest).not.toEqual(paddedDigest);
36+
37+
// Show the intermediate node hash differs when padding is added, by revealing
38+
// the cons hash structure explicitly.
39+
const nodeHashBase = hashWithPrefix(prefixes.accountUpdateNode, [
40+
hashLeaf(1n),
41+
0n,
42+
]);
43+
const nodeHashPaddedFirst = nodeHashBase;
44+
const nodeHashPaddedSecond = hashWithPrefix(prefixes.accountUpdateNode, [
45+
hashLeaf(0n),
46+
0n,
47+
]);
48+
const recomposedPadded = hashWithPrefix(prefixes.accountUpdateCons, [
49+
nodeHashPaddedSecond,
50+
hashWithPrefix(prefixes.accountUpdateCons, [nodeHashPaddedFirst, 0n]),
51+
]);
52+
53+
expect(paddedDigest).not.toEqual(recomposedPadded);
54+
}
55+
56+
await testCallForestPaddingCollision();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { expect } from 'expect';
2+
import { createNullifier } from './nullifier.js';
3+
4+
async function testNullifierPaddingDifference() {
5+
const sk = 5n;
6+
7+
const messageShort = [1n];
8+
const messagePadded = [1n, 0n];
9+
10+
const nullifierShort = createNullifier(messageShort, sk);
11+
const nullifierPadded = createNullifier(messagePadded, sk);
12+
13+
// The nullifier should change when padding changes the message
14+
expect(nullifierShort.public.nullifier).not.toEqual(nullifierPadded.public.nullifier);
15+
expect(nullifierShort.public.s).not.toEqual(nullifierPadded.public.s);
16+
}
17+
18+
await testNullifierPaddingDifference();
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect } from 'expect';
2+
import { HashInput, packToFields, hashWithPrefix } from './poseidon-bigint.js';
3+
import { Field } from './field-bigint.js';
4+
import { Group } from './curve-bigint.js';
5+
import { signaturePrefix } from './signature.js';
6+
import { NetworkId } from './types.js';
7+
8+
/**
9+
* Demonstrates that adding zero padding inside a packed field chunk produces
10+
* a different packed field array and Poseidon digest.
11+
*/
12+
async function testPoseidonPaddingCollision() {
13+
const shortMessage: HashInput = { packed: [[Field(1n), 1]] };
14+
const paddedMessage: HashInput = { packed: [[Field(1n), 1], [Field(0n), 1]] };
15+
16+
const packedShort = packToFields(shortMessage);
17+
const packedPadded = packToFields(paddedMessage);
18+
19+
const dummyPubKey: Group = { x: Field(3n), y: Field(5n) };
20+
const r = Field(7n);
21+
const net: NetworkId = 'testnet';
22+
23+
const hashMessageCompat = (msg: HashInput) => {
24+
const input = HashInput.append(msg, { fields: [dummyPubKey.x, dummyPubKey.y, r] });
25+
return hashWithPrefix(signaturePrefix(net), packToFields(input));
26+
};
27+
28+
const hShort = hashMessageCompat(shortMessage);
29+
const hPadded = hashMessageCompat(paddedMessage);
30+
31+
// With a non-zero payload bit, padding changes the packed field and the hash.
32+
expect(packedShort).not.toEqual(packedPadded);
33+
expect(hShort).not.toEqual(hPadded);
34+
35+
// packing zero bits does produce the same result however
36+
const zeroPacked: HashInput = { packed: [[Field(0n), 1]] };
37+
const zeroPaddedPacked: HashInput = { packed: [[Field(0n), 1], [Field(0n), 1]] };
38+
39+
expect(packToFields(zeroPacked)).toEqual(packToFields(zeroPaddedPacked));
40+
}
41+
42+
await testPoseidonPaddingCollision();

0 commit comments

Comments
 (0)