Skip to content

Commit

Permalink
Merge pull request #5 from distributed-lab/feature/range-and-audit-pr…
Browse files Browse the repository at this point in the history
…oofs

Range and audit proofs
  • Loading branch information
KlausKidman authored Oct 24, 2024
2 parents 7ac9c75 + 3fdf965 commit d53211e
Show file tree
Hide file tree
Showing 13 changed files with 1,154 additions and 576 deletions.
170 changes: 80 additions & 90 deletions examples/typescript/veiled_operations_zkp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@
import {
TwistedElGamal,
TwistedEd25519PrivateKey,
generateVeiledWithdrawProof,
verifyVeiledWithdrawProof,
generateVeiledTransferProof,
verifyVeiledTransferProof,
generateVeiledKeyRotationProof,
verifyVeiledKeyRotationProof,
genProofVeiledWithdraw,
verifyProofVeiledWithdraw,
genProofVeiledTransfer,
verifyProofVeiledTransfer,
genSigmaProofVeiledKeyRotation,
verifySigmaProofVeiledKeyRotation,
RistrettoPoint,
} from "@aptos-labs/ts-sdk";
import { mod } from "@noble/curves/abstract/modular";
import { bytesToNumberLE } from "@noble/curves/abstract/utils";
import { ed25519, RistrettoPoint } from "@noble/curves/ed25519";
import { bytesToHex, randomBytes } from "@noble/hashes/utils";
import { bytesToHex } from "@noble/hashes/utils";

const BALANCE = BigInt(70);
const AMOUNT = BigInt(50);
Expand All @@ -36,7 +34,7 @@ const example = async () => {
console.log(`Public key: ${privateKeyBob.publicKey().toString()}\n\n`);

const twistedElGamalAliceInstance = new TwistedElGamal(privateKeyAlice);
const ciphertextAlice = await twistedElGamalAliceInstance.encrypt(BALANCE);
const ciphertextAlice = twistedElGamalAliceInstance.encrypt(BALANCE);
console.log("=== Ciphertext points ===");
console.log("This ciphertext represents Alice's encrypted balance");
console.log(`Point C: ${ciphertextAlice.C.toString()}`);
Expand All @@ -45,86 +43,99 @@ const example = async () => {
// Start withdraw prof

console.log("\n\n=== Veiled Withdraw Proof ===");
const withdrawProof = generateVeiledWithdrawProof({
const withdrawProof = await genProofVeiledWithdraw({
privateKey: privateKeyAlice,
encryptedBalance: ciphertextAlice,
amount: AMOUNT,
changedBalance: BALANCE - AMOUNT,
});
console.log("Generated withdraw proof");
console.log(
{
X1: bytesToHex(withdrawProof.X1),
X2: bytesToHex(withdrawProof.X2),
alpha1: bytesToHex(withdrawProof.alpha1),
alpha2: bytesToHex(withdrawProof.alpha2),
alpha3: bytesToHex(withdrawProof.alpha3),
},
"\n",
);

const isWithdrawProfValid = verifyVeiledWithdrawProof({
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));

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

// End withdraw prof
// Start transfer prof

console.log("\n\n=== Veiled transfer proof ===");
const random = randomBytes(32);
console.log(`Randomness to encrypt the amount: ${bytesToHex(random)}\n`);

const amountCiphertext = twistedElGamalAliceInstance.encrypt(AMOUNT, random);
console.log("The amount encrypted by Alice");
console.log(`Point C: ${amountCiphertext.C.toString()}`);
console.log(`Point D: ${amountCiphertext.D.toString()}\n`);

const transferProof = generateVeiledTransferProof({
const transferProofOutput = await genProofVeiledTransfer({
senderPrivateKey: privateKeyAlice,
receiverPublicKey: privateKeyBob.publicKey(),
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
amount: AMOUNT,
changedSenderBalance: BALANCE - AMOUNT,
random,
});
console.log("Generated transfer proof");
console.log(
{
X1: bytesToHex(transferProof.X1),
X2: bytesToHex(transferProof.X2),
X3: bytesToHex(transferProof.X3),
X4: bytesToHex(transferProof.X4),
X5: bytesToHex(transferProof.X5),
alpha1: bytesToHex(transferProof.alpha1),
alpha2: bytesToHex(transferProof.alpha2),
alpha3: bytesToHex(transferProof.alpha3),
alpha4: bytesToHex(transferProof.alpha4),
alpha5: bytesToHex(transferProof.alpha5),
},
"\n",
);

const rAmount = mod(bytesToNumberLE(random), ed25519.CURVE.n);
const receiverPKRistretto = RistrettoPoint.fromHex(privateKeyBob.publicKey().toUint8Array());
const receiverDa = receiverPKRistretto.multiply(rAmount).toRawBytes();
console.log("The recipient's public key multiplied by the randomness used to encrypt the amount being sent:");
console.log(bytesToHex(receiverDa), "\n");

const isTransferProofValid = verifyVeiledTransferProof({
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(),
receiverPublicKey: privateKeyBob.publicKey(),
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
encryptedAmountBySender: amountCiphertext,
receiverDa,
proof: transferProof,
encryptedAmountBySender: transferProofOutput.encryptedAmountBySender,
maskedRecipientPublicKey: transferProofOutput.maskedRecipientPublicKey,
proof: transferProofOutput.proof,
});

console.log("Is veiled transfer proof valid:", isTransferProofValid);

const privateKeyAuditor1 = TwistedEd25519PrivateKey.generate();
const privateKeyAuditor2 = TwistedEd25519PrivateKey.generate();

const auditorPublicKeys = [privateKeyAuditor1.publicKey(), privateKeyAuditor2.publicKey()];

const transferProofWithAuditorsOutputs = await genProofVeiledTransfer({
senderPrivateKey: privateKeyAlice,
recipientPublicKey: privateKeyBob.publicKey(),
encryptedSenderBalance: ciphertextAlice,
amount: AMOUNT,
changedSenderBalance: 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,
proof: transferProofWithAuditorsOutputs.proof,
auditors: {
publicKeys: auditorPublicKeys,
decryptionKeys: transferProofWithAuditorsOutputs.maskedAuditorsPublicKeys,
},
});
console.log("Is veiled transfer proof with auditors valid:", isTransferProofWithAuditorsValid);
} catch (e) {
console.log(e);
}

// End transfer prof
// Start key rotation proof

Expand All @@ -135,42 +146,21 @@ const example = async () => {
console.log(`New Private key: ${privateKeyAlice.toString()}`);
console.log(`New Public key: ${privateKeyAlice.publicKey().toString()}\n`);

const random2 = randomBytes(32);
console.log(`Randomness to encrypt the balance: ${bytesToHex(random)}\n`);
const newEncryptedBalance = TwistedElGamal.encryptWithPK(BALANCE, newPrivateKeyAlice.publicKey(), random2);
console.log("Balance encrypted with new public key (ciphertext)");
console.log(`Point C: ${newEncryptedBalance.C.toString()}`);
console.log(`Point D: ${newEncryptedBalance.D.toString()}\n`);

const keyRotationProof = generateVeiledKeyRotationProof({
const keyRotationProofOutputs = genSigmaProofVeiledKeyRotation({
oldPrivateKey: privateKeyAlice,
newPrivateKey: newPrivateKeyAlice,
balance: BALANCE,
encryptedBalance: ciphertextAlice,
random: random2,
});
console.log("Generated key rotation proof");
console.log(
{
X1: bytesToHex(keyRotationProof.X1),
X2: bytesToHex(keyRotationProof.X2),
X3: bytesToHex(keyRotationProof.X3),
X4: bytesToHex(keyRotationProof.X4),
alpha1: bytesToHex(keyRotationProof.alpha1),
alpha2: bytesToHex(keyRotationProof.alpha2),
alpha3: bytesToHex(keyRotationProof.alpha3),
alpha4: bytesToHex(keyRotationProof.alpha4),
alpha5: bytesToHex(keyRotationProof.alpha5),
},
"\n",
);

const isKeyRotatingProofValid = verifyVeiledKeyRotationProof({
console.log(bytesToHex(keyRotationProofOutputs.proof), "\n");

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

console.log("Is key rotation proof valid:", isKeyRotatingProofValid);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"dependencies": {
"@aptos-labs/aptos-cli": "^0.2.0",
"@aptos-labs/aptos-client": "^0.1.1",
"@distributedlab/aptos-wasm-bindings": "^0.2.4",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"@scure/bip32": "^1.4.0",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 21 additions & 3 deletions src/core/crypto/twistedEd25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// SPDX-License-Identifier: Apache-2.0

import { ed25519, RistrettoPoint } from "@noble/curves/ed25519";
import { invert } from "@noble/curves/abstract/modular";
import { bytesToNumberLE } from "@noble/curves/abstract/utils";
import { Deserializer } from "../../bcs/deserializer";
import { Serializable, Serializer } from "../../bcs/serializer";
import { Hex } from "../hex";
import { HexInput } from "../../types";
import { CKDPriv, deriveKey, HARDENED_OFFSET, isValidHardenedPath, mnemonicToSeed, splitPath } from "./hdKey";
import { ed25519InvertN } from "./utils";

export { RistrettoPoint } from "@noble/curves/ed25519";
export type RistPoint = InstanceType<typeof RistrettoPoint>;

/**
* The hash of the basepoint of the Ristretto255 group using SHA3_512
*/
Expand Down Expand Up @@ -67,6 +67,15 @@ export class TwistedEd25519PublicKey {
return this.key.toString();
}

/**
* Get the public key as a hex string without the 0x prefix.
*
* @returns string representation of the public key
*/
toStringWithoutPrefix(): string {
return this.key.toStringWithoutPrefix();
}

// region Serializable

serialize(serializer: Serializer): void {
Expand Down Expand Up @@ -186,7 +195,7 @@ export class TwistedEd25519PrivateKey extends Serializable {
*/
publicKey(): TwistedEd25519PublicKey {
const scalarLE = bytesToNumberLE(this.key.toUint8Array());
const invertModScalarLE = invert(scalarLE, ed25519.CURVE.n);
const invertModScalarLE = ed25519InvertN(scalarLE);
const key = H_RISTRETTO.multiply(invertModScalarLE).toRawBytes();

return new TwistedEd25519PublicKey(key);
Expand All @@ -210,6 +219,15 @@ export class TwistedEd25519PrivateKey extends Serializable {
return this.key.toString();
}

/**
* Get the private key as a hex string without the 0x prefix.
*
* @returns string representation of the private key
*/
toStringWithoutPrefix(): string {
return this.key.toStringWithoutPrefix();
}

// endregion

// region Serializable
Expand Down
Loading

0 comments on commit d53211e

Please sign in to comment.