Skip to content

Commit

Permalink
fixed range proof approach, normalization and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lukachi committed Dec 13, 2024
1 parent ab84351 commit 0b50c78
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

export * from "./aptos";
export * from "./aptosConfig";
export * from "./veiledCoin";
8 changes: 5 additions & 3 deletions src/api/veiledCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const VEILED_COIN_MODULE_ADDRESS = "0xcbd21318a3fe6eb6c01f3c371d9aca238a6cd7201d
export class VeiledCoin {
constructor(readonly config: AptosConfig) {}

static VEILED_COIN_MODULE_ADDRESS = VEILED_COIN_MODULE_ADDRESS;

async getBalance(args: {
accountAddress: AccountAddress;
tokenAddress: string;
Expand Down Expand Up @@ -197,18 +199,18 @@ export class VeiledCoin {
});

if (!isNormalized) {
const aliceBalances = await this.getBalance({
const accountBalance = await this.getBalance({
accountAddress: AccountAddress.from(args.sender),
tokenAddress: args.tokenAddress,
});

const aliceVB = await VeiledAmount.fromEncrypted(aliceBalances.actual, args.decryptionKey);
const aliceVB = await VeiledAmount.fromEncrypted(accountBalance.actual, args.decryptionKey);

const normalizationTx = await VeiledCoin.buildNormalizationTxPayload({
decryptionKey: args.decryptionKey,
sender: args.sender,
tokenAddress: args.tokenAddress,
unnormilizedEncryptedBalance: aliceBalances.pending,
unnormilizedEncryptedBalance: accountBalance.actual,
balanceAmount: aliceVB.amount,
});
txList.push(normalizationTx);
Expand Down
1 change: 1 addition & 0 deletions src/core/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from "./singleKey";
export * from "./twistedEd25519";
export * from "./twistedElGamal";
export * from "./veiled";
export * from "./rangeProof";
50 changes: 50 additions & 0 deletions src/core/crypto/rangeProof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

export interface RangeProofInputs {
v: bigint;
r: Uint8Array;
valBase: Uint8Array;
randBase: Uint8Array;
bits?: number;
}

export interface VerifyRangeProofInputs {
proof: Uint8Array;
commitment: Uint8Array;
valBase: Uint8Array;
randBase: Uint8Array;
bits?: number;
}

export class RangeProofExecutor {
/**
* Generate range Zero Knowledge Proof
*
* @param opts.v The value to create the range proof for
* @param opts.r A vector of bytes representing the blinding scalar used to hide the value.
* @param opts.valBase A vector of bytes representing the generator point for the value.
* @param opts.randBase A vector of bytes representing the generator point for the randomness.
* @param opts.bits Bits size of value to create the range proof
*/
static generateRangeZKP: (opts: RangeProofInputs) => Promise<{ proof: Uint8Array; commitment: Uint8Array }>;

/**
* Verify range Zero Knowledge Proof
*
* @param opts.proof A vector of bytes representing the serialized range proof to be verified.
* @param opts.commitment A vector of bytes representing the Pedersen commitment the range proof is generated for.
* @param opts.valBase A vector of bytes representing the generator point for the value.
* @param opts.randBase A vector of bytes representing the generator point for the randomness.
* @param opts.bits Bits size of the value for range proof
*/
static verifyRangeZKP: (opts: VerifyRangeProofInputs) => Promise<boolean>;

static setGenerateRangeZKP(func: (opts: RangeProofInputs) => Promise<{ proof: Uint8Array; commitment: Uint8Array }>) {
this.generateRangeZKP = func;
}

static setVerifyRangeZKP(func: (opts: VerifyRangeProofInputs) => Promise<boolean>) {
this.verifyRangeZKP = func;
}
}
15 changes: 13 additions & 2 deletions src/core/crypto/twistedEd25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

import { ed25519, RistrettoPoint } from "@noble/curves/ed25519";
import { bytesToNumberLE } from "@noble/curves/abstract/utils";
import { bytesToNumberLE, numberToBytesLE } 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";
import { ed25519InvertN, ed25519modN } from "./utils";
import { Ed25519Signature } from "./ed25519";

export { RistrettoPoint } from "@noble/curves/ed25519";
export type RistPoint = InstanceType<typeof RistrettoPoint>;
Expand Down Expand Up @@ -158,6 +159,16 @@ export class TwistedEd25519PrivateKey extends Serializable {
return TwistedEd25519PrivateKey.fromDerivationPathInner(path, mnemonicToSeed(mnemonics));
}

static decryptionKeyDerivationMessage = "Sign this message to derive decryption key from your private key";

static fromSignature(signature: Ed25519Signature): TwistedEd25519PrivateKey {
const scalarLE = bytesToNumberLE(signature.toUint8Array());
const invertModScalarLE = ed25519modN(scalarLE);
const key = numberToBytesLE(invertModScalarLE, 32);

return new TwistedEd25519PrivateKey(key);
}

/**
* A private inner function so we can separate from the main fromDerivationPath() method
* to add tests to verify we create the keys correctly.
Expand Down
1 change: 1 addition & 0 deletions src/core/crypto/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { invert, mod } from "@noble/curves/abstract/modular";
import { bytesToNumberBE } from "@noble/curves/abstract/utils";
import { ed25519 } from "@noble/curves/ed25519";
import { randomBytes } from "@noble/hashes/utils";
import { Buffer } from "buffer";
import { Hex } from "../hex";
import { HexInput } from "../../types";

Expand Down
2 changes: 1 addition & 1 deletion src/core/crypto/veiled/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

export * from "./rangeProof";
export * from "./veiledKeyRotation";
export * from "./veiledNormalization";
export * from "./veiledTransfer";
export * from "./veiledWithdraw";
export * from "./veiledAmount";
6 changes: 3 additions & 3 deletions src/core/crypto/veiled/veiledKeyRotation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { bytesToNumberLE, concatBytes, numberToBytesLE } from "@noble/curves/abstract/utils";
import { utf8ToBytes } from "@noble/hashes/utils";
import { PROOF_CHUNK_SIZE, SIGMA_PROOF_KEY_ROTATION_SIZE } from "./consts";
import { generateRangeZKP, verifyRangeZKP } from "./rangeProof";
import { RangeProofExecutor } from "../rangeProof";
import { H_RISTRETTO, RistrettoPoint, TwistedEd25519PrivateKey, TwistedEd25519PublicKey } from "../twistedEd25519";
import { TwistedElGamalCiphertext } from "../twistedElGamal";
import { genFiatShamirChallenge } from "./helpers";
Expand Down Expand Up @@ -282,7 +282,7 @@ export class VeiledKeyRotation {
async genRangeProof(): Promise<Uint8Array[]> {
const rangeProof = await Promise.all(
this.currVeiledAmount.amountChunks.map((chunk, i) =>
generateRangeZKP({
RangeProofExecutor.generateRangeZKP({
v: chunk,
r: this.newDecryptionKey.toUint8Array(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down Expand Up @@ -319,7 +319,7 @@ export class VeiledKeyRotation {
static async verifyRangeProof(opts: { rangeProof: Uint8Array[]; newEncryptedBalance: TwistedElGamalCiphertext[] }) {
const rangeProofValidations = await Promise.all(
opts.rangeProof.map((proof, i) =>
verifyRangeZKP({
RangeProofExecutor.verifyRangeZKP({
proof,
commitment: opts.newEncryptedBalance[i].C.toRawBytes(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down
6 changes: 3 additions & 3 deletions src/core/crypto/veiled/veiledNormalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { genFiatShamirChallenge, publicKeyToU8 } from "./helpers";
import { ed25519GenListOfRandom, ed25519GenRandom, ed25519InvertN, ed25519modN } from "../utils";
import { H_RISTRETTO, TwistedEd25519PrivateKey, TwistedEd25519PublicKey } from "../twistedEd25519";
import { TwistedElGamalCiphertext } from "../twistedElGamal";
import { generateRangeZKP, verifyRangeZKP } from "./rangeProof";
import { RangeProofExecutor } from "../rangeProof";
import { VeiledAmount } from "./veiledAmount";

export type VeiledNormalizationSigmaProof = {
Expand Down Expand Up @@ -252,7 +252,7 @@ export class VeiledNormalization {
async genRangeProof(): Promise<Uint8Array[]> {
const rangeProof = await Promise.all(
this.normalizedVeiledAmount.amountChunks.map((chunk, i) =>
generateRangeZKP({
RangeProofExecutor.generateRangeZKP({
v: chunk,
r: this.decryptionKey.toUint8Array(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand All @@ -270,7 +270,7 @@ export class VeiledNormalization {
}): Promise<boolean> {
const isRangeProofValidations = await Promise.all(
opts.rangeProof.map((proof, i) =>
verifyRangeZKP({
RangeProofExecutor.verifyRangeZKP({
proof,
commitment: opts.normalizedEncryptedBalance[i].C.toRawBytes(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down
10 changes: 5 additions & 5 deletions src/core/crypto/veiled/veiledTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ed25519GenListOfRandom, ed25519GenRandom, ed25519InvertN, ed25519modN }
import { PROOF_CHUNK_SIZE, SIGMA_PROOF_TRANSFER_SIZE } from "./consts";
import { genFiatShamirChallenge, publicKeyToU8 } from "./helpers";
import { HexInput } from "../../../types";
import { generateRangeZKP, verifyRangeZKP } from "./rangeProof";
import { RangeProofExecutor } from "../rangeProof";
import { VeiledAmount } from "./veiledAmount";

export type VeiledTransferSigmaProof = {
Expand Down Expand Up @@ -444,7 +444,7 @@ export class VeiledTransfer {
async genRangeProof(): Promise<VeiledTransferRangeProof> {
const rangeProofAmountPromise = Promise.all(
this.veiledAmountToTransfer.amountChunks.map((chunk, i) =>
generateRangeZKP({
RangeProofExecutor.generateRangeZKP({
v: chunk,
r: numberToBytesLE(this.randomness[i], 32),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand All @@ -455,7 +455,7 @@ export class VeiledTransfer {

const rangeProofNewBalancePromise = Promise.all(
this.veiledAmountAfterTransfer.amountChunks.map((chunk, i) =>
generateRangeZKP({
RangeProofExecutor.generateRangeZKP({
v: chunk,
r: this.senderDecryptionKey.toUint8Array(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down Expand Up @@ -509,15 +509,15 @@ export class VeiledTransfer {
}) {
const rangeProofsValidations = await Promise.all([
...opts.rangeProofAmount.map((proof, i) =>
verifyRangeZKP({
RangeProofExecutor.verifyRangeZKP({
proof,
commitment: opts.encryptedAmountByRecipient[i].C.toRawBytes(),
valBase: RistrettoPoint.BASE.toRawBytes(),
randBase: H_RISTRETTO.toRawBytes(),
}),
),
...opts.rangeProofNewBalance.map((proof, i) =>
verifyRangeZKP({
RangeProofExecutor.verifyRangeZKP({
proof,
commitment: opts.encryptedActualBalanceAfterTransfer[i].C.toRawBytes(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down
6 changes: 3 additions & 3 deletions src/core/crypto/veiled/veiledWithdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { H_RISTRETTO, TwistedEd25519PrivateKey, TwistedEd25519PublicKey } from "
import { TwistedElGamalCiphertext } from "../twistedElGamal";
import { PROOF_CHUNK_SIZE, SIGMA_PROOF_WITHDRAW_SIZE } from "./consts";
import { ed25519GenListOfRandom, ed25519GenRandom, ed25519InvertN, ed25519modN } from "../utils";
import { generateRangeZKP, verifyRangeZKP } from "./rangeProof";
import { RangeProofExecutor } from "../rangeProof";
import { VeiledAmount } from "./veiledAmount";

export type VeiledWithdrawSigmaProof = {
Expand Down Expand Up @@ -274,7 +274,7 @@ export class VeiledWithdraw {
async genRangeProof() {
const rangeProof = await Promise.all(
this.veiledAmountAfterWithdraw.amountChunks.map((chunk, i) =>
generateRangeZKP({
RangeProofExecutor.generateRangeZKP({
v: chunk,
r: this.decryptionKey.toUint8Array(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down Expand Up @@ -307,7 +307,7 @@ export class VeiledWithdraw {
}) {
const rangeProofVerificationResults = await Promise.all(
opts.rangeProof.map((proof, i) =>
verifyRangeZKP({
RangeProofExecutor.verifyRangeZKP({
proof,
commitment: opts.encryptedActualBalanceAfterWithdraw[i].C.toRawBytes(),
valBase: RistrettoPoint.BASE.toRawBytes(),
Expand Down
8 changes: 7 additions & 1 deletion tests/e2e/api/veiledCoin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
import { VeiledAmount } from "../../../src/core/crypto/veiled/veiledAmount";
import { longTestTimeout } from "../../unit/helper";
import { VeiledBalance, VeiledCoin } from "../../../src/api/veiledCoin";
import { RangeProofExecutor } from "../../../src/core/crypto/rangeProof";
import { generateRangeZKP, verifyRangeZKP } from "../../unit/veiled/wasmRangeProof";

describe("Veiled balance api", () => {
const APTOS_NETWORK: Network = NetworkToNetworkName[Network.TESTNET];
Expand Down Expand Up @@ -102,6 +104,10 @@ describe("Veiled balance api", () => {
privateKey: new Ed25519PrivateKey(TESTNET_PK!),
});

/** !important: for testing purposes */
RangeProofExecutor.setGenerateRangeZKP(generateRangeZKP);
RangeProofExecutor.setVerifyRangeZKP(verifyRangeZKP);

test(
"it should fund Alice aptos accounts balances",
async () => {
Expand Down Expand Up @@ -366,7 +372,7 @@ describe("Veiled balance api", () => {
tokenAddress: TOKEN_ADDRESS,
});

unnormalizedAliceEncryptedBalance = unnormalizedAliceBalances.pending;
unnormalizedAliceEncryptedBalance = unnormalizedAliceBalances.actual;
}

expect(isAliceBalanceNormalized).toBeTruthy();
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/veiled/api/normalize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ describe("Normalize", () => {
const normalizeTx = await aptos.veiledCoin.normalizeUserBalance({
tokenAddress: TOKEN_ADDRESS,
decryptionKey: aliceVeiled,
unnormilizedEncryptedBalance: balances.pending.amountEncrypted!,
balanceAmount: balances.pending.amount,
unnormilizedEncryptedBalance: balances.actual.amountEncrypted!,
balanceAmount: balances.actual.amount,

sender: alice.accountAddress,
});
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/veiled/dkDerivation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Account, Ed25519PrivateKey, TwistedEd25519PrivateKey } from "../../../src";

describe("Decryption key derivation from Private key", () => {
it("Should derive decryption key from private key", () => {
const alice = Account.fromPrivateKey({
privateKey: new Ed25519PrivateKey("0x9d7669b01809f7486e1710c2050b2aa48fc05f68159e9654277be7490470eb16"),
});

const signature = alice.sign(TwistedEd25519PrivateKey.decryptionKeyDerivationMessage);

const aliceDecryptionKey = TwistedEd25519PrivateKey.fromSignature(signature);

expect(aliceDecryptionKey.toString()).toEqual("0x9d4c06efdf5a4ea056cc7c9c17137c3f89bb868f9c3ec5b0d3de0bc1d963cf0a");
});
});
8 changes: 7 additions & 1 deletion tests/unit/veiled/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
NetworkToNetworkName,
TransactionWorkerEventsEnum,
TwistedEd25519PrivateKey,
VeiledAmount,
} from "../../../src";
import { VeiledAmount } from "../../../src/core/crypto/veiled/veiledAmount";
import { RangeProofExecutor } from "../../../src/core/crypto/rangeProof";
import { generateRangeZKP, verifyRangeZKP } from "./wasmRangeProof";

/**
* Address of the mocked fungible token on the testnet
Expand Down Expand Up @@ -102,3 +104,7 @@ export const getTestVeiledAccount = () => {

return TwistedEd25519PrivateKey.generate();
};

/** !important: for testing purposes */
RangeProofExecutor.setGenerateRangeZKP(generateRangeZKP);
RangeProofExecutor.setVerifyRangeZKP(verifyRangeZKP);
14 changes: 14 additions & 0 deletions tests/unit/veiled/veiledProofs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ import {
VeiledTransfer,
VeiledKeyRotation,
VeiledNormalization,
RangeProofExecutor,
} from "../../../src";
import { toTwistedEd25519PrivateKey } from "../../../src/core/crypto/veiled/helpers";
import { VeiledAmount } from "../../../src/core/crypto/veiled/veiledAmount";
import { generateRangeZKP, verifyRangeZKP } from "./wasmRangeProof";

/** !important: for testing purposes */
RangeProofExecutor.setGenerateRangeZKP(generateRangeZKP);
RangeProofExecutor.setVerifyRangeZKP(verifyRangeZKP);

describe("Generate 'veiled coin' proofs", () => {
const ALICE_BALANCE = 70n;
Expand All @@ -31,7 +37,11 @@ describe("Generate 'veiled coin' proofs", () => {
amountToWithdraw: WITHDRAW_AMOUNT,
});

const sigmaProofStartTime = performance.now();
veiledWithdrawSigmaProof = await veiledWithdraw.genSigmaProof();
const sigmaProofStartTimeEnd = performance.now();

console.log("sigmaProofStartTime", sigmaProofStartTimeEnd - sigmaProofStartTime);

expect(veiledWithdrawSigmaProof).toBeDefined();
});
Expand All @@ -50,7 +60,11 @@ describe("Generate 'veiled coin' proofs", () => {

let veiledWithdrawRangeProof: Uint8Array[];
test("Generate withdraw range proof", async () => {
const rangeProofStartTime = performance.now();
veiledWithdrawRangeProof = await veiledWithdraw.genRangeProof();
const rangeProofStartTimeEnd = performance.now();

console.log("rangeProofStartTime", rangeProofStartTimeEnd - rangeProofStartTime);
});
test("Verify withdraw range proof", async () => {
const isValid = VeiledWithdraw.verifyRangeProof({
Expand Down
File renamed without changes.

0 comments on commit 0b50c78

Please sign in to comment.