diff --git a/src/api/index.ts b/src/api/index.ts index 8e835a66..10782596 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,3 +3,4 @@ export * from "./aptos"; export * from "./aptosConfig"; +export * from "./veiledCoin"; diff --git a/src/api/veiledCoin.ts b/src/api/veiledCoin.ts index 1584682e..77716796 100644 --- a/src/api/veiledCoin.ts +++ b/src/api/veiledCoin.ts @@ -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; @@ -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); diff --git a/src/core/crypto/index.ts b/src/core/crypto/index.ts index 901d8b33..f824e8e9 100644 --- a/src/core/crypto/index.ts +++ b/src/core/crypto/index.ts @@ -15,3 +15,4 @@ export * from "./singleKey"; export * from "./twistedEd25519"; export * from "./twistedElGamal"; export * from "./veiled"; +export * from "./rangeProof"; diff --git a/src/core/crypto/rangeProof.ts b/src/core/crypto/rangeProof.ts new file mode 100644 index 00000000..48029794 --- /dev/null +++ b/src/core/crypto/rangeProof.ts @@ -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; + + static setGenerateRangeZKP(func: (opts: RangeProofInputs) => Promise<{ proof: Uint8Array; commitment: Uint8Array }>) { + this.generateRangeZKP = func; + } + + static setVerifyRangeZKP(func: (opts: VerifyRangeProofInputs) => Promise) { + this.verifyRangeZKP = func; + } +} diff --git a/src/core/crypto/twistedEd25519.ts b/src/core/crypto/twistedEd25519.ts index a500caef..0ff199fd 100644 --- a/src/core/crypto/twistedEd25519.ts +++ b/src/core/crypto/twistedEd25519.ts @@ -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; @@ -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. diff --git a/src/core/crypto/utils.ts b/src/core/crypto/utils.ts index 69074fc1..776b50cc 100644 --- a/src/core/crypto/utils.ts +++ b/src/core/crypto/utils.ts @@ -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"; diff --git a/src/core/crypto/veiled/index.ts b/src/core/crypto/veiled/index.ts index 7bf20f03..6fe1584e 100644 --- a/src/core/crypto/veiled/index.ts +++ b/src/core/crypto/veiled/index.ts @@ -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"; diff --git a/src/core/crypto/veiled/veiledKeyRotation.ts b/src/core/crypto/veiled/veiledKeyRotation.ts index 96606244..b1daa0f1 100644 --- a/src/core/crypto/veiled/veiledKeyRotation.ts +++ b/src/core/crypto/veiled/veiledKeyRotation.ts @@ -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"; @@ -282,7 +282,7 @@ export class VeiledKeyRotation { async genRangeProof(): Promise { const rangeProof = await Promise.all( this.currVeiledAmount.amountChunks.map((chunk, i) => - generateRangeZKP({ + RangeProofExecutor.generateRangeZKP({ v: chunk, r: this.newDecryptionKey.toUint8Array(), valBase: RistrettoPoint.BASE.toRawBytes(), @@ -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(), diff --git a/src/core/crypto/veiled/veiledNormalization.ts b/src/core/crypto/veiled/veiledNormalization.ts index 617d05a9..688a0b6f 100644 --- a/src/core/crypto/veiled/veiledNormalization.ts +++ b/src/core/crypto/veiled/veiledNormalization.ts @@ -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 = { @@ -252,7 +252,7 @@ export class VeiledNormalization { async genRangeProof(): Promise { const rangeProof = await Promise.all( this.normalizedVeiledAmount.amountChunks.map((chunk, i) => - generateRangeZKP({ + RangeProofExecutor.generateRangeZKP({ v: chunk, r: this.decryptionKey.toUint8Array(), valBase: RistrettoPoint.BASE.toRawBytes(), @@ -270,7 +270,7 @@ export class VeiledNormalization { }): Promise { const isRangeProofValidations = await Promise.all( opts.rangeProof.map((proof, i) => - verifyRangeZKP({ + RangeProofExecutor.verifyRangeZKP({ proof, commitment: opts.normalizedEncryptedBalance[i].C.toRawBytes(), valBase: RistrettoPoint.BASE.toRawBytes(), diff --git a/src/core/crypto/veiled/veiledTransfer.ts b/src/core/crypto/veiled/veiledTransfer.ts index e70d13dd..00668207 100644 --- a/src/core/crypto/veiled/veiledTransfer.ts +++ b/src/core/crypto/veiled/veiledTransfer.ts @@ -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 = { @@ -444,7 +444,7 @@ export class VeiledTransfer { async genRangeProof(): Promise { 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(), @@ -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(), @@ -509,7 +509,7 @@ 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(), @@ -517,7 +517,7 @@ export class VeiledTransfer { }), ), ...opts.rangeProofNewBalance.map((proof, i) => - verifyRangeZKP({ + RangeProofExecutor.verifyRangeZKP({ proof, commitment: opts.encryptedActualBalanceAfterTransfer[i].C.toRawBytes(), valBase: RistrettoPoint.BASE.toRawBytes(), diff --git a/src/core/crypto/veiled/veiledWithdraw.ts b/src/core/crypto/veiled/veiledWithdraw.ts index e2da1016..e53096d7 100644 --- a/src/core/crypto/veiled/veiledWithdraw.ts +++ b/src/core/crypto/veiled/veiledWithdraw.ts @@ -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 = { @@ -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(), @@ -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(), diff --git a/tests/e2e/api/veiledCoin.test.ts b/tests/e2e/api/veiledCoin.test.ts index 2d977925..f617161a 100644 --- a/tests/e2e/api/veiledCoin.test.ts +++ b/tests/e2e/api/veiledCoin.test.ts @@ -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]; @@ -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 () => { @@ -366,7 +372,7 @@ describe("Veiled balance api", () => { tokenAddress: TOKEN_ADDRESS, }); - unnormalizedAliceEncryptedBalance = unnormalizedAliceBalances.pending; + unnormalizedAliceEncryptedBalance = unnormalizedAliceBalances.actual; } expect(isAliceBalanceNormalized).toBeTruthy(); diff --git a/tests/unit/veiled/api/normalize.test.ts b/tests/unit/veiled/api/normalize.test.ts index 7b07ebe9..5103482f 100644 --- a/tests/unit/veiled/api/normalize.test.ts +++ b/tests/unit/veiled/api/normalize.test.ts @@ -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, }); diff --git a/tests/unit/veiled/dkDerivation.test.ts b/tests/unit/veiled/dkDerivation.test.ts new file mode 100644 index 00000000..a60edc70 --- /dev/null +++ b/tests/unit/veiled/dkDerivation.test.ts @@ -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"); + }); +}); diff --git a/tests/unit/veiled/helpers.ts b/tests/unit/veiled/helpers.ts index 81d36681..25611269 100644 --- a/tests/unit/veiled/helpers.ts +++ b/tests/unit/veiled/helpers.ts @@ -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 @@ -102,3 +104,7 @@ export const getTestVeiledAccount = () => { return TwistedEd25519PrivateKey.generate(); }; + +/** !important: for testing purposes */ +RangeProofExecutor.setGenerateRangeZKP(generateRangeZKP); +RangeProofExecutor.setVerifyRangeZKP(verifyRangeZKP); diff --git a/tests/unit/veiled/veiledProofs.test.ts b/tests/unit/veiled/veiledProofs.test.ts index c7b2c462..835bd301 100644 --- a/tests/unit/veiled/veiledProofs.test.ts +++ b/tests/unit/veiled/veiledProofs.test.ts @@ -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; @@ -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(); }); @@ -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({ diff --git a/src/core/crypto/veiled/rangeProof.ts b/tests/unit/veiled/wasmRangeProof.ts similarity index 100% rename from src/core/crypto/veiled/rangeProof.ts rename to tests/unit/veiled/wasmRangeProof.ts