Skip to content
🎉 Welcome to the new Aptos Docs! Click here to submit an issue.
构建SDKsTypeScript SDKConfidential Asset (CA)

通过Typescript SDK与机密资产(CA)交互

你可以使用Aptos客户端的confidentialCoin属性来与CA进行交互

初始化

CA中的操作需要生成零知识证明(ZKPs),根据你的运行环境,需要定义范围证明的计算方式。

对于Web环境,可以使用confidential-asset-wasm-bindings/confidential-asset-wasm-bindings

让我们准备范围证明生成并配置SDK使用它:

import initWasm, {
  batch_range_proof as batchRangeProof,
  batch_verify_proof as batchVerifyProof,
  range_proof as rangeProof,
  verify_proof as verifyProof,
} from '@aptos-labs/confidential-asset-wasm-bindings/range-proofs'
import {
  BatchRangeProofInputs,
  BatchVerifyRangeProofInputs,
  RangeProofInputs,
  VerifyRangeProofInputs,
} from '@lukachi/aptos-labs-ts-sdk'
 
const RANGE_PROOF_WASM_URL =
  'https://unpkg.com/@aptos-labs/confidential-asset-wasm-bindings@0.3.16/range-proofs/aptos_rp_wasm_bg.wasm'
 
export async function genBatchRangeZKP(
  opts: BatchRangeProofInputs,
): Promise<{ proof: Uint8Array; commitments: Uint8Array[] }> {
  await initWasm({ module_or_path: RANGE_PROOF_WASM_URL })
 
  const proof = batchRangeProof(
    new BigUint64Array(opts.v),
    opts.rs,
    opts.val_base,
    opts.rand_base,
    opts.num_bits,
  )
 
  return {
    proof: proof.proof(),
    commitments: proof.comms(),
  }
}
 
export async function verifyBatchRangeZKP(
  opts: BatchVerifyRangeProofInputs,
): Promise<boolean> {
  await initWasm({ module_or_path: RANGE_PROOF_WASM_URL })
 
  return batchVerifyProof(
    opts.proof,
    opts.comm,
    opts.val_base,
    opts.rand_base,
    opts.num_bits,
  )
}

然后,只需将其放在应用的最顶部:

import { RangeProofExecutor } from '@aptos-labs/ts-sdk'
 
RangeProofExecutor.setGenBatchRangeZKP(genBatchRangeZKP);
RangeProofExecutor.setVerifyBatchRangeZKP(verifyBatchRangeZKP);
RangeProofExecutor.setGenerateRangeZKP(generateRangeZKP);
RangeProofExecutor.setVerifyRangeZKP(verifyRangeZKP);

对于原生应用:

这里生成androidios绑定,并按需集成到你的应用中。

最后但同样重要的是:

要获取机密资产的”数值”余额,还需要解决离散对数问题(DLP)。 CA实现了Pollard’s Kangaroo方法来解决Ristretto曲线上的DLP。 来源

因此我们还需要初始化解密函数:

// Copyright © Aptos Foundation
// SPDX-License-Identifier: Apache-2.0
 
import initWasm, {
  create_kangaroo,
  WASMKangaroo,
} from '@aptos-labs/confidential-asset-wasm-bindings/pollard-kangaroo'
import {
  ConfidentialAmount,
  TwistedEd25519PrivateKey,
  TwistedElGamal,
  TwistedElGamalCiphertext,
} from '@lukachi/aptos-labs-ts-sdk'
import { bytesToNumberLE } from '@noble/curves/abstract/utils'
 
const POLLARD_KANGAROO_WASM_URL =
  'https://unpkg.com/@aptos-labs/confidential-asset-wasm-bindings@0.3.15/pollard-kangaroo/aptos_pollard_kangaroo_wasm_bg.wasm'
 
export async function createKangaroo(secret_size: number) {
  await initWasm({ module_or_path: POLLARD_KANGAROO_WASM_URL })
 
  return create_kangaroo(secret_size)
}
 
export const preloadTables = async () => {
  const kangaroo16 = await createKangaroo(16)
  const kangaroo32 = await createKangaroo(32)
  const kangaroo48 = await createKangaroo(48)
 
  TwistedElGamal.setDecryptionFn(async pk => {
    if (bytesToNumberLE(pk) === 0n) return 0n
 
    let result = kangaroo16.solve_dlp(pk, 500n)
 
    if (!result) {
      result = kangaroo32.solve_dlp(pk, 1500n)
    }
 
    if (!result) {
      result = kangaroo48.solve_dlp(pk)
    }
 
    if (!result) throw new TypeError('Decryption failed')
 
    return result
  })
}

现在,将以下代码放在你的应用顶部:

const init = async () => {
  await preloadTables();
}

对于原生应用,你可以在此生成androidios绑定来代替WASM使用。


现在我们准备就绪。让我们定义Aptos客户端:

const APTOS_NETWORK: Network = NetworkToNetworkName[Network.TESTNET];
const config = new AptosConfig({ network: APTOS_NETWORK });
export const aptos = new Aptos(config);

创建解密密钥(DK)

要与保密资产交互,首先需要创建一个唯一的密钥对

生成新密钥:

const dk = TwistedEd25519PrivateKey.generate();

或导入现有密钥:

const dk = new TwistedEd25519PrivateKey("0x...");

你也可以使用签名派生密钥(仅用于测试目的,勿在生产环境使用):

const user = Account.generate()
 
const signature = user.sign(TwistedEd25519PrivateKey.decryptionKeyDerivationMessage);
 
const dk = TwistedEd25519PrivateKey.fromSignature(signature);

或者使用无密钥账户中的pepper

注册

接下来,你需要在合约中注册之前生成的加密密钥(EK):

export const registerConfidentialBalance = async (
  account: Account,
  publicKeyHex: string,
  tokenAddress = "0x...",
) => {
  const txBody = await aptos.confidentialAsset.deposit({
    sender: account.accountAddress,
    to: AccountAddress.from(to),
    tokenAddress: tokenAddress,
    amount: amount,
  })
 
  const txResponse = await aptos.signAndSubmitTransaction({ signer: user, transaction: userRegisterCBTxBody });
 
  const txReceipt = await aptos.waitForTransaction({ transactionHash: txResponse.hash });
 
  return txReceipt;
}

检查用户是否已注册特定代币:

export const getIsAccountRegisteredWithToken = async (
  account: Account,
  tokenAddress = "0x...",
) => {
  const isRegistered = await aptos.confidentialAsset.hasUserRegistered({
    accountAddress: account.accountAddress,
    tokenAddress: tokenAddress,
  })
 
  return isRegistered
}

存款

假设你已经拥有代币。

以下代码会将代币存入你的保密余额:

export const depositConfidentialBalance = async (
  account: Account,
  amount: bigint,
  to: string,
  tokenAddress = "0x...",
) => {
  const txBody = await aptos.confidentialAsset.deposit({
    sender: account.accountAddress,
    to: AccountAddress.from(to),
    tokenAddress: tokenAddress,
    amount: amount,
  })
  // 签名并发送交易
}

获取用户余额

存款后,让我们检查用户的余额。

const userConfidentialBalance = await aptos.confidentialAsset.getBalance({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS });
```该方法返回用户的[`pending`和`actual`](confidential-asset.mdx#confidential-asset-store)保密余额,要[解密](confidential-asset.mdx#encryption-and-decryption)这些余额,可以使用`ConfidentialAmount`类
 
```typescript
export const getConfidentialBalances = async (
  account: Account,
  decryptionKeyHex: string,
  tokenAddress = "0x...",
) => {
  const decryptionKey = new TwistedEd25519PrivateKey(decryptionKeyHex)
 
  const { pending, actual } = await aptos.confidentialAsset.getBalance({
    accountAddress: account.accountAddress,
    tokenAddress,
  })
 
  try {
    const [confidentialAmountPending, confidentialAmountActual] =
      await Promise.all([
        ConfidentialAmount.fromEncrypted(pending, decryptionKey),
        ConfidentialAmount.fromEncrypted(actual, decryptionKey),
      ])
 
    return {
      pending: confidentialAmountPending,
      actual: confidentialAmountActual,
    }
  } catch (error) {
    return {
      pending: ConfidentialAmount.fromAmount(0n),
      actual: ConfidentialAmount.fromAmount(0n),
    }
  }
}

余额结转

当您向用户的保密余额存款后,可以看到例如pending余额中有5n,而actual余额为0n

用户无法操作pending余额,因此您可以将其结转actual余额。

为此,请使用aptos.confidentialAsset.rolloverPendingBalance

⚠️

重要提示:在rollover操作前,用户的actual余额需要先进行标准化处理。

要同时处理标准化rollover操作,可以使用aptos.confidentialAsset.safeRolloverPendingCB

export const safelyRolloverConfidentialBalance = async (
  account: Account,
  decryptionKeyHex: string,
  tokenAddress = "0x...",
) => {
  const rolloverTxPayloads = await aptos.confidentialAsset.safeRolloverPendingCB({
    sender: account.accountAddress,
    tokenAddress,
    decryptionKey: new TwistedEd25519PrivateKey(decryptionKeyHex),
  })
 
  // 签名并发送批量交易
}

标准化

通常您不需要显式调用标准化操作

如果您需要手动操作:

⚠️

首先检查保密余额是否已标准化,因为尝试对已标准化的余额再次标准化会抛出异常

export const getIsBalanceNormalized = async (
  account: Account,
  tokenAddress = "0x...",
) => {
  const isNormalized = await aptos.confidentialAsset.isUserBalanceNormalized({
    accountAddress: account.accountAddress,
    tokenAddress: tokenAddress,
  })
 
  return isNormalized
}

获取余额后,最终调用aptos.confidentialAsset.normalizeUserBalance方法:

export const normalizeConfidentialBalance = async (
  account: Account,
  decryptionKeyHex: string,
  encryptedPendingBalance: TwistedElGamalCiphertext[],
  amount: bigint,
  tokenAddress = "0x...",
) => {
  const normalizeTx = await aptos.confidentialAsset.normalizeUserBalance({
    tokenAddress,
    decryptionKey: new TwistedEd25519PrivateKey(decryptionKeyHex),
    unnormalizedEncryptedBalance: encryptedPendingBalance,
    balanceAmount: amount,
 
    sender: account.accountAddress,
  })
 
  // 签名并发送交易
}

提现

要将资产从机密余额中提现:

export const withdrawConfidentialBalance = async (
  account: Account,
  receiver: string,
  decryptionKeyHex: string,
  withdrawAmount: bigint,
  encryptedActualBalance: TwistedElGamalCiphertext[],
  tokenAddress = '0x...',
) => {
  const withdrawTx = await aptos.confidentialAsset.withdraw({
    sender: account.accountAddress,
    to: receiver,
    tokenAddress,
    decryptionKey: decryptionKey,
    encryptedActualBalance,
    amountToWithdraw: withdrawAmount,
  })
 
  // 签名并发送交易
}

转账

进行机密转账需要知道接收方的加密密钥和aptos账户地址

假设您已有接收方账户地址,让我们获取其加密密钥:

export const getEkByAddr = async (addrHex: string, tokenAddress: string) => {
  return aptos.confidentialAsset.getEncryptionByAddr({
    accountAddress: AccountAddress.from(addrHex),
    tokenAddress,
  })
}

现在将所有步骤整合并进行转账:

export const transferConfidentialCoin = async (
  account: Account,
  decryptionKeyHex: string,
  encryptedActualBalance: TwistedElGamalCiphertext[],
  amountToTransfer: bigint,
  recipientAddressHex: string,
  auditorsEncryptionKeyHexList: string[],
  tokenAddress = "0x...",
) => {
  const decryptionKey = new TwistedEd25519PrivateKey(decryptionKeyHex)
 
  const recipientEncryptionKeyHex = await getEkByAddr(
    recipientAddressHex,
    tokenAddress,
  )
 
  const transferTx = await aptos.confidentialAsset.transferCoin({
    senderDecryptionKey: decryptionKey,
    recipientEncryptionKey: new TwistedEd25519PublicKey(
      recipientEncryptionKeyHex,
    ),
    encryptedActualBalance: encryptedActualBalance,
    amountToTransfer,
    sender: account.accountAddress,
    tokenAddress,
    recipientAddress: recipientAddressHex,
    auditorEncryptionKeys: auditorsEncryptionKeyHexList.map(
      hex => new TwistedEd25519PublicKey(hex),
    ),
  })
 
  // 签名并发送交易
}

密钥轮换

要进行密钥轮换,您需要创建新的解密密钥并使用aptos.confidentialAsset.rotateCBKey

⚠️

但请注意,key-rotation会检查待处理余额是否为0。 在这种情况下,我们可以使用带有freeze选项的rollover操作,将资产从待处理余额转移到实际余额并锁定余额。

aptos.confidentialAsset.safeRolloverPendingCB({
  ...,
  withFreezeBalance: false,
})

现在让我们创建新的解密密钥并轮换加密密钥:

const balances = await getBalances(user.accountAddress.toString(), myDecryptionKey, TOKEN_ADDRESS);
 
const NEW_DECRYPTION_KEY = TwistedEd25519PrivateKey.generate();
const keyRotationAndUnfreezeTxResponse = await ConfidentialCoin.safeRotateCBKey(aptos, user, {
  sender: user.accountAddress,
 
  currDecryptionKey: currentDecryptionKey,
  newDecryptionKey: NEW_DECRYPTION_KEY,
 
  currEncryptedBalance: balances.actual.amountEncrypted,
 
  withUnfreezeBalance: true, // 如果您想在之后解冻余额
  tokenAddress: TOKEN_ADDRESS,
});
 
// 保存:新的解密密钥
console.log(NEW_DECRYPTION_KEY.toString());
 
// 检查新的余额
const newBalance = await getBalances(user.accountAddress.toString(), NEW_DECRYPTION_KEY, TOKEN_ADDRESS);
 
console.log(newBalance.pending.amount);
console.log(newBalance.actual.amount);