feat(sdk): support AccountCommitment type in proveWithdrawal (#78)

This commit is contained in:
nigiri
2025-04-25 13:43:07 -03:00
committed by GitHub
parent 22251acf8e
commit 0c280d5943
3 changed files with 90 additions and 15 deletions

View File

@@ -5,6 +5,7 @@ import { Commitment, CommitmentProof } from "../types/commitment.js";
import { WithdrawalProof, WithdrawalProofInput } from "../types/withdrawal.js";
import { ContractInteractionsService } from "./contracts.service.js";
import { Hex, Address, Chain } from "viem";
import { AccountCommitment } from "../types/account.js";
/**
* Main SDK class providing access to all privacy pool functionality.
@@ -72,7 +73,7 @@ export class PrivacyPoolSDK {
* @param withdrawal - Withdrawal details
*/
public async proveWithdrawal(
commitment: Commitment,
commitment: Commitment | AccountCommitment ,
input: WithdrawalProofInput,
): Promise<WithdrawalProof> {
return await this.withdrawalService.proveWithdrawal(commitment, input);

View File

@@ -4,8 +4,8 @@ import {
CircuitName,
CircuitsInterface,
} from "../interfaces/circuits.interface.js";
import { Commitment } from "../types/commitment.js";
import { WithdrawalProof, WithdrawalProofInput } from "../types/withdrawal.js";
import { AccountCommitment, Commitment } from "../index.js";
/**
* Service responsible for handling withdrawal-related operations.
@@ -23,8 +23,8 @@ export class WithdrawalService {
* @throws {ProofError} If proof generation fails
*/
public async proveWithdrawal(
commitment: Commitment,
input: WithdrawalProofInput,
commitment: Commitment | AccountCommitment,
input: WithdrawalProofInput
): Promise<WithdrawalProof> {
try {
const inputSignals = this.prepareInputSignals(commitment, input);
@@ -80,9 +80,25 @@ export class WithdrawalService {
* Prepares input signals for the withdrawal circuit.
*/
private prepareInputSignals(
commitment: Commitment,
input: WithdrawalProofInput,
commitment: Commitment | AccountCommitment,
input: WithdrawalProofInput
): Record<string, bigint | bigint[] | string> {
let existingValue: bigint;
let existingNullifier: bigint;
let existingSecret: bigint;
let label: bigint;
if ("preimage" in commitment) {
existingValue = commitment.preimage.value;
existingNullifier = commitment.preimage.precommitment.nullifier;
existingSecret = commitment.preimage.precommitment.secret;
label = commitment.preimage.label;
} else {
existingValue = commitment.value;
existingNullifier = commitment.nullifier;
existingSecret = commitment.secret;
label = commitment.label;
}
return {
// Public signals
withdrawnValue: input.withdrawalAmount,
@@ -93,10 +109,10 @@ export class WithdrawalService {
context: input.context,
// Private signals
label: commitment.preimage.label,
existingValue: commitment.preimage.value,
existingNullifier: commitment.preimage.precommitment.nullifier,
existingSecret: commitment.preimage.precommitment.secret,
label,
existingValue,
existingNullifier,
existingSecret,
newNullifier: input.newNullifier,
newSecret: input.newSecret,

View File

@@ -5,6 +5,7 @@ import * as snarkjs from "snarkjs";
import { Commitment, Hash, Secret } from "../../src/types/commitment.js";
import { LeanIMTMerkleProof } from "@zk-kit/lean-imt";
import { ProofError } from "../../src/errors/base.error.js";
import { AccountCommitment } from "../../src/types/account.js";
vi.mock("snarkjs");
vi.mock("viem", async (importOriginal) => {
@@ -55,7 +56,7 @@ describe("PrivacyPoolSDK", () => {
BigInt(1),
BigInt(2),
BigInt(3) as Secret,
BigInt(4) as Secret,
BigInt(4) as Secret
);
expect(result).toStrictEqual({
proof: "PROOF",
@@ -65,7 +66,7 @@ describe("PrivacyPoolSDK", () => {
expect(snarkjs.groth16.fullProve).toHaveBeenCalledWith(
inputSignals,
binariesMock.commitment.wasm,
binariesMock.commitment.zkey,
binariesMock.commitment.zkey
);
});
@@ -81,7 +82,7 @@ describe("PrivacyPoolSDK", () => {
sdk.verifyCommitment({
proof: {} as snarkjs.Groth16Proof,
publicSignals: [],
}),
})
).rejects.toThrowError(ProofError);
});
@@ -158,6 +159,63 @@ describe("PrivacyPoolSDK", () => {
expect(downloadArtifactsSpy).toHaveBeenCalledOnce();
});
it("can prove withdrawal with account commitment", async () => {
const mockAccountCommitment: AccountCommitment = {
hash: BigInt(1) as Hash,
value: BigInt(1000),
label: BigInt(3) as Hash,
nullifier: BigInt(2) as Secret,
secret: BigInt(4) as Secret,
blockNumber: BigInt(5),
txHash: "0x1234",
};
snarkjs.groth16.fullProve = vi.fn().mockResolvedValue({
proof: "mockProof",
publicSignals: "mockPublicSignals",
});
const stateMerkleProof: LeanIMTMerkleProof<bigint> = {
root: BigInt(5),
leaf: mockCommitment.hash,
index: 1,
siblings: [BigInt(6), BigInt(7)],
};
const aspMerkleProof: LeanIMTMerkleProof<bigint> = {
root: BigInt(8),
leaf: BigInt(3),
index: 2,
siblings: [BigInt(9), BigInt(10)],
};
const withdrawalInput = {
withdrawalAmount: BigInt(500),
stateMerkleProof,
aspMerkleProof,
stateRoot: BigInt(5) as Hash,
aspRoot: BigInt(8) as Hash,
newNullifier: BigInt(12) as Secret,
newSecret: BigInt(13) as Secret,
context: BigInt(1),
stateTreeDepth: BigInt(32),
aspTreeDepth: BigInt(32),
};
const downloadArtifactsSpy = vi
.spyOn(circuits, "downloadArtifacts")
.mockResolvedValue(binariesMock);
const result = await sdk.proveWithdrawal(
mockAccountCommitment,
withdrawalInput
);
expect(result).toHaveProperty("proof", "mockProof");
expect(result).toHaveProperty("publicSignals", "mockPublicSignals");
expect(downloadArtifactsSpy).toHaveBeenCalledOnce();
});
it("should throw error on proof generation failure", async () => {
snarkjs.groth16.fullProve = vi
.fn()
@@ -191,7 +249,7 @@ describe("PrivacyPoolSDK", () => {
};
await expect(
sdk.proveWithdrawal(mockCommitment, withdrawalInput),
sdk.proveWithdrawal(mockCommitment, withdrawalInput)
).rejects.toThrow(ProofError);
});
@@ -207,7 +265,7 @@ describe("PrivacyPoolSDK", () => {
sdk.verifyWithdrawal({
proof: {} as snarkjs.Groth16Proof,
publicSignals: [],
}),
})
).rejects.toThrow(ProofError);
});