Skip to content

Commit

Permalink
Merge pull request #1715 from o1-labs/feat/fix-nullifier
Browse files Browse the repository at this point in the history
Refactor Nullifier methods to use safer MerkleMapWitness operations
  • Loading branch information
MartinMinkov authored Jul 3, 2024
2 parents 2cff02d + fb1e4fb commit e8f64bb
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- `ForeignField`-based representation of scalars via `ScalarField` https://github.com/o1-labs/o1js/pull/1705
- Introduced new V2 methods for nullifier operations: `isUnusedV2()`, `assertUnusedV2()`, and `setUsedV2()` https://github.com/o1-labs/o1js/pull/1715

### Deprecated

- Deprecated `Nullifier.isUnused()`, `Nullifier.assertUnused()`, and `Nullifier.setUsed()` methods https://github.com/o1-labs/o1js/pull/1715

## [1.4.0](https://github.com/o1-labs/o1js/compare/40c597775...ed198f305) - 2024-06-25

Expand Down
4 changes: 2 additions & 2 deletions src/examples/nullifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ class PayoutOnlyOnce extends SmartContract {
);

// we compute the current root and make sure the entry is set to 0 (= unused)
nullifier.assertUnused(nullifierWitness, nullifierRoot);
nullifier.assertUnusedV2(nullifierWitness, nullifierRoot);

// we set the nullifier to 1 (= used) and calculate the new root
let newRoot = nullifier.setUsed(nullifierWitness);
let newRoot = nullifier.setUsedV2(nullifierWitness);

// we update the on-chain root
this.nullifierRoot.set(newRoot);
Expand Down
50 changes: 41 additions & 9 deletions src/lib/provable/crypto/nullifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,31 @@ class Nullifier extends Struct({
return Poseidon.hash(Group.toFields(this.public.nullifier));
}

/**
* @deprecated This method uses the deprecated {@link MerkleMapWitness.computeRootAndKey} which may be vulnerable to hash collisions in key indices. Use {@link isUnusedV2} instead, which utilizes the safer {@link MerkleMapWitness.computeRootAndKeyV2} method.
*/
isUnused(witness: MerkleMapWitness, root: Field) {
let [newRoot, key] = witness.computeRootAndKey(Field(0));
key.assertEquals(this.key());
let isUnused = newRoot.equals(root);

let isUsed = witness.computeRootAndKey(Field(1))[0].equals(root);
// prove that our Merkle witness is correct
isUsed.or(isUnused).assertTrue();
return isUnused; // if this is false, `isUsed` is true because of the check before
}

/**
* Returns the state of the Nullifier.
*
* @example
* ```ts
* // returns a Bool based on whether or not the nullifier has been used before
* let isUnused = nullifier.isUnused();
* let isUnused = nullifier.isUnusedV2();
* ```
*/
isUnused(witness: MerkleMapWitness, root: Field) {
let [newRoot, key] = witness.computeRootAndKey(Field(0));
isUnusedV2(witness: MerkleMapWitness, root: Field) {
let [newRoot, key] = witness.computeRootAndKeyV2(Field(0));
key.assertEquals(this.key());
let isUnused = newRoot.equals(root);

Expand All @@ -116,32 +130,50 @@ class Nullifier extends Struct({
return isUnused; // if this is false, `isUsed` is true because of the check before
}

/**
* @deprecated This method uses the deprecated {@link MerkleMapWitness.computeRootAndKey} which may be vulnerable to hash collisions in key indices. Use {@link assertUnusedV2} instead, which utilizes the safer {@link MerkleMapWitness.computeRootAndKeyV2} method.
*/
assertUnused(witness: MerkleMapWitness, root: Field) {
let [impliedRoot, key] = witness.computeRootAndKey(Field(0));
this.key().assertEquals(key);
impliedRoot.assertEquals(root);
}

/**
* Checks if the Nullifier has been used before.
*
* @example
* ```ts
* // asserts that the nullifier has not been used before, throws an error otherwise
* nullifier.assertUnused();
* nullifier.assertUnusedV2();
* ```
*/
assertUnused(witness: MerkleMapWitness, root: Field) {
let [impliedRoot, key] = witness.computeRootAndKey(Field(0));
assertUnusedV2(witness: MerkleMapWitness, root: Field) {
let [impliedRoot, key] = witness.computeRootAndKeyV2(Field(0));
this.key().assertEquals(key);
impliedRoot.assertEquals(root);
}

/**
* @deprecated This method uses the deprecated {@link MerkleMapWitness.computeRootAndKey} which may be vulnerable to hash collisions in key indices. Use {@link setUsedV2} instead, which utilizes the safer {@link MerkleMapWitness.computeRootAndKeyV2} method.
*/
setUsed(witness: MerkleMapWitness) {
let [newRoot, key] = witness.computeRootAndKey(Field(1));
key.assertEquals(this.key());
return newRoot;
}

/**
* Sets the Nullifier, returns the new Merkle root.
*
* @example
* ```ts
* // calculates the new root of the Merkle tree in which the nullifier is set to used
* let newRoot = nullifier.setUsed(witness);
* let newRoot = nullifier.setUsedV2(witness);
* ```
*/
setUsed(witness: MerkleMapWitness) {
let [newRoot, key] = witness.computeRootAndKey(Field(1));
setUsedV2(witness: MerkleMapWitness) {
let [newRoot, key] = witness.computeRootAndKeyV2(Field(1));
key.assertEquals(this.key());
return newRoot;
}
Expand Down

0 comments on commit e8f64bb

Please sign in to comment.