Skip to content

Commit

Permalink
Changed ZKP for working with chunked veiled balance
Browse files Browse the repository at this point in the history
  • Loading branch information
KlausKidman committed Nov 27, 2024
1 parent 8d2ae37 commit 267d316
Show file tree
Hide file tree
Showing 13 changed files with 1,479 additions and 693 deletions.
89 changes: 51 additions & 38 deletions examples/typescript/veiled_operations_zkp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import {
verifyProofVeiledWithdraw,
genProofVeiledTransfer,
verifyProofVeiledTransfer,
genSigmaProofVeiledKeyRotation,
verifySigmaProofVeiledKeyRotation,
RistrettoPoint,
amountToChunks,
genProofVeiledKeyRotation,
verifyProofVeiledKeyRotation,
genProofVeiledNormalization,
verifyProofVeiledNormalization,
chunksToAmount
} from "@aptos-labs/ts-sdk";
import { bytesToHex } from "@noble/hashes/utils";

Expand All @@ -34,35 +37,30 @@ const example = async () => {
console.log(`Public key: ${privateKeyBob.publicKey().toString()}\n\n`);

const twistedElGamalAliceInstance = new TwistedElGamal(privateKeyAlice);
const ciphertextAlice = twistedElGamalAliceInstance.encrypt(BALANCE);
const ciphertextAlice = amountToChunks(BALANCE, 4).map(chunk => twistedElGamalAliceInstance.encrypt(chunk));
console.log("=== Ciphertext points ===");
console.log("This ciphertext represents Alice's encrypted balance");
console.log(`Point C: ${ciphertextAlice.C.toString()}`);
console.log(`Point D: ${ciphertextAlice.D.toString()}`);
console.log("Points of ciphertext:");
console.log(ciphertextAlice.map(({ C, D }) => ({ C: C.toString(), D: D.toString()})));

// Start withdraw prof

console.log("\n\n=== Veiled Withdraw Proof ===");
const withdrawProof = await genProofVeiledWithdraw({
const withdrawOutputs = await genProofVeiledWithdraw({
privateKey: privateKeyAlice,
encryptedBalance: ciphertextAlice,
amount: AMOUNT,
changedBalance: BALANCE - AMOUNT,
});
console.log("Generated withdraw proof");
console.log("Sigma Proof:");
console.log(bytesToHex(withdrawProof.sigma), "\n");
console.log("Range Proof:");
console.log(bytesToHex(withdrawProof.range), "\n");

const rangeProofCommitment = ciphertextAlice.C.subtract(RistrettoPoint.BASE.multiply(AMOUNT));
console.log(bytesToHex(withdrawOutputs.proof.sigma), "\n");

const isWithdrawProofValid = await verifyProofVeiledWithdraw({
publicKey: privateKeyAlice.publicKey(),
encryptedBalance: ciphertextAlice,
oldEncryptedBalance: ciphertextAlice,
newEncryptedBalance: withdrawOutputs.newEncryptedBalance,
amount: AMOUNT,
proof: withdrawProof,
rangeProofCommitment: rangeProofCommitment.toRawBytes(),
proof: withdrawOutputs.proof,
});
console.log("Is veiled withdraw proof valid:", isWithdrawProofValid);

Expand All @@ -74,24 +72,20 @@ const example = async () => {
const transferProofOutput = await genProofVeiledTransfer({
senderPrivateKey: privateKeyAlice,
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
encryptedBalance: ciphertextAlice,
amount: AMOUNT,
changedSenderBalance: BALANCE - AMOUNT,
changedBalance: BALANCE - AMOUNT,
});
console.log("=== Generated transfer proof ===");
console.log("Sigma Proof:");
console.log(bytesToHex(transferProofOutput.proof.sigma), "\n");
console.log("Range Proof for amount:");
console.log(bytesToHex(transferProofOutput.proof.rangeAmount), "\n");
console.log("Range Proof for new balance:");
console.log(bytesToHex(transferProofOutput.proof.rangeNewBalance), "\n");

const isTransferProofValid = await verifyProofVeiledTransfer({
senderPublicKey: privateKeyAlice.publicKey(),
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
encryptedAmountBySender: transferProofOutput.encryptedAmountBySender,
maskedRecipientPublicKey: transferProofOutput.maskedRecipientPublicKey,
oldEncryptedBalance: ciphertextAlice,
newEncryptedBalance: transferProofOutput.newEncryptedBalance,
encryptedAmountByRecipient: transferProofOutput.encryptedAmountByRecipient,
proof: transferProofOutput.proof,
});

Expand All @@ -105,26 +99,22 @@ const example = async () => {
const transferProofWithAuditorsOutputs = await genProofVeiledTransfer({
senderPrivateKey: privateKeyAlice,
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
encryptedBalance: ciphertextAlice,
amount: AMOUNT,
changedSenderBalance: BALANCE - AMOUNT,
changedBalance: BALANCE - AMOUNT,
auditorPublicKeys,
});
console.log("\n=== Generated transfer proof with auditors ===");
console.log("Sigma Proof:");
console.log(bytesToHex(transferProofWithAuditorsOutputs.proof.sigma), "\n");
console.log("Range Proof for amount:");
console.log(bytesToHex(transferProofWithAuditorsOutputs.proof.rangeAmount), "\n");
console.log("Range Proof for new balance:");
console.log(bytesToHex(transferProofWithAuditorsOutputs.proof.rangeNewBalance), "\n");

try {
const isTransferProofWithAuditorsValid = await verifyProofVeiledTransfer({
senderPublicKey: privateKeyAlice.publicKey(),
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
encryptedAmountBySender: transferProofWithAuditorsOutputs.encryptedAmountBySender,
maskedRecipientPublicKey: transferProofWithAuditorsOutputs.maskedRecipientPublicKey,
oldEncryptedBalance: ciphertextAlice,
newEncryptedBalance: transferProofWithAuditorsOutputs.newEncryptedBalance,
encryptedAmountByRecipient: transferProofWithAuditorsOutputs.encryptedAmountByRecipient,
proof: transferProofWithAuditorsOutputs.proof,
auditors: {
publicKeys: auditorPublicKeys,
Expand All @@ -146,26 +136,49 @@ const example = async () => {
console.log(`New Private key: ${privateKeyAlice.toString()}`);
console.log(`New Public key: ${privateKeyAlice.publicKey().toString()}\n`);

const keyRotationProofOutputs = genSigmaProofVeiledKeyRotation({
const keyRotationProofOutputs = await genProofVeiledKeyRotation({
oldPrivateKey: privateKeyAlice,
newPrivateKey: newPrivateKeyAlice,
balance: BALANCE,
oldEncryptedBalance: ciphertextAlice,
});
console.log("Generated key rotation proof");
console.log(bytesToHex(keyRotationProofOutputs.proof), "\n");
console.log(bytesToHex(keyRotationProofOutputs.proof.sigma), "\n");

const isKeyRotatingProofValid = verifySigmaProofVeiledKeyRotation({
const isKeyRotatingProofValid = await verifyProofVeiledKeyRotation({
oldPublicKey: privateKeyAlice.publicKey(),
newPublicKey: newPrivateKeyAlice.publicKey(),
oldEncryptedBalance: ciphertextAlice,
newEncryptedBalance: keyRotationProofOutputs.encryptedBalanceByNewPublicKey,
newEncryptedBalance: keyRotationProofOutputs.newEncryptedBalance,
proof: keyRotationProofOutputs.proof,
});

console.log("Is key rotation proof valid:", isKeyRotatingProofValid);

// End key rotation proof
// Start normalization proof

const unnormalizedBalanceChunks = [2n**32n + 100n, 2n**32n + 200n, 2n**32n + 300n, 0n]
const unnormalizedEncryptedBalanceAlice = unnormalizedBalanceChunks.map(chunk => twistedElGamalAliceInstance.encrypt(chunk));
console.log("\n\n=== Veiled normalization proof ===");
const normalizationProofOutputs = await genProofVeiledNormalization({
privateKey: privateKeyAlice,
encryptedBalance: unnormalizedEncryptedBalanceAlice,
balance: chunksToAmount(unnormalizedBalanceChunks),
});
console.log("Generated normalization proof");
console.log(bytesToHex(normalizationProofOutputs.proof.sigma), "\n");

const isNormalizationProofValid = await verifyProofVeiledNormalization({
publicKey: privateKeyAlice.publicKey(),
encryptedBalance: unnormalizedEncryptedBalanceAlice,
normalizedEncryptedBalance: normalizationProofOutputs.normalizedEncryptedBalance,
proof: normalizationProofOutputs.proof,
});

console.log("Is normalization proof valid:", isNormalizationProofValid);

// End normalization proof
};

example();
8 changes: 8 additions & 0 deletions src/core/crypto/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ed25519 } from "@noble/curves/ed25519";
import { randomBytes } from "@noble/hashes/utils";
import { Hex } from "../hex";
import { HexInput } from "../../types";
import { VEILED_BALANCE_CHUNK_SIZE } from "./veiledOperationZKP/consts";

/**
* Helper function to convert a message to sign or to verify to a valid message input
Expand Down Expand Up @@ -52,3 +53,10 @@ export function ed25519GenRandom(): bigint {

return rand;
}

/*
* Generate list of random number less then order of curve ed25519
*/
export function ed25519GenListOfRandom(len = VEILED_BALANCE_CHUNK_SIZE) {
return new Array(len).fill(0n).map(() => ed25519GenRandom())
}
11 changes: 11 additions & 0 deletions src/core/crypto/veiledOperationZKP/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Number of chunks for veiled balance
*/
export const VEILED_BALANCE_CHUNK_SIZE = 4;


/**
* Max bits of amount in a chunk for normalized veiled balance
*/
export const CHUNK_BITS = 32;
export const CHUNK_BITS_BI = BigInt(CHUNK_BITS);
56 changes: 56 additions & 0 deletions src/core/crypto/veiledOperationZKP/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

import { sha512 } from "@noble/hashes/sha512";
import { bytesToNumberLE, concatBytes } from "@noble/curves/abstract/utils";
import { TwistedEd25519PrivateKey, TwistedEd25519PublicKey } from "../twistedEd25519";
import { HexInput } from "../../../types";
import { ed25519modN } from "../utils";
import { CHUNK_BITS_BI } from "./consts";

/*
* Transform public keys to Uint8Array
Expand All @@ -18,3 +22,55 @@ export function publicKeyToU8(pk: TwistedEd25519PublicKey | HexInput): Uint8Arra
export function toTwistedEd25519PrivateKey(pk: TwistedEd25519PrivateKey | HexInput): TwistedEd25519PrivateKey {
return ArrayBuffer.isView(pk) || typeof pk === "string" ? new TwistedEd25519PrivateKey(pk) : pk;
}

/*
* Generate Fiat-Shamir challenge
*/
export function genFiatShamirChallenge(...arrays: Uint8Array[]): bigint {
const hash = sha512(concatBytes(...arrays));
return ed25519modN(bytesToNumberLE(hash));
}

/**
* Returns the original amount from the provided chunks,
* where each chunk is represented as a 32 bit number
*
* amount = a0 + a1 * (2 ** 32) + a2 * (2 ** 64) ... a_i * (2 ** 32 * i)
*/
export function chunksToAmount (chunks: bigint[]): bigint {
return chunks.reduce((acc, chunk, i) => acc + chunk * (2n ** (CHUNK_BITS_BI * BigInt(i))), 0n)
}

/**
* Returns a list of chunks of the given length from the amount, where each chunk is represented as a 32-bit number
*
* @example
* const amount = 10n + 20n * (2n ** 32n) + 30n (2n ** 64n) + 40n * (2n ** 96n )
* const chunkedAmount = amountToChunks(a, 4)
* // an example of the returned data
* ```
* chunkedAmount = [10n, 20n, 30n, 40n]
* ```
*/
export function amountToChunks(amount: bigint, chunksCount: number): bigint[] {
const chunksCountBI = BigInt(chunksCount)
if (amount > (2n ** (chunksCountBI * CHUNK_BITS_BI) - 1n)) {
throw new Error (`Amount must be less than 2n**${CHUNK_BITS_BI * chunksCountBI}`)
}

const chunks = [];
let a = amount
for (let i = chunksCount - 1; i >= 0; i -= 1) {
if (i === 0) {
chunks[i] = a
} else {
const bits = 2n ** (CHUNK_BITS_BI * BigInt(i))
const aMod = a % bits;
const chunk = a === aMod ? 0n : (a - aMod) / bits;
chunks[i] = chunk;
a = aMod
}
}

return chunks
}
1 change: 1 addition & 0 deletions src/core/crypto/veiledOperationZKP/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./rangeProof";
export * from "./sigmaProofs";
export * from "./sigmaProofsSerializers";
export * from "./veiledOperationProofs";
export { amountToChunks, chunksToAmount } from "./helpers";
Loading

0 comments on commit 267d316

Please sign in to comment.