1. 概览
在开始之前,掌握以下功能和概念的实际运用知识对于本 Codelab 会很有帮助。
学习内容
本实验提供了一个参考实现,介绍如何使用 Confidential Space 执行符合 MPC 要求的区块链签名。为了说明这些概念,我们将演示以下场景:Company Primus 希望将数字资产转移到 Company Secundus。在这种情况下,Company Primus 使用符合 MPC 标准的模型,这意味着它们使用分布式密钥共享,而不是使用单独的私钥。这些主要份额由多方持有,在本示例中为小红和小刚。这种方法为 Company Primus 提供了多项优势,包括简化用户体验、提高运营效率和控制私钥。
为了说明此流程的基本方面,我们将详细说明技术设置,并引导您完成审批和签署流程,此流程是将数字资产从 Company Primus 转移到 Company Secundus 的整个流程。请注意,Bob 和 Alice 都是 Company Primus 的员工,他们必须批准交易。
尽管此参考实现演示了签名操作,但它并未涵盖 MPC 密钥管理的所有方面。例如,我们不讨论密钥生成。此外,还有替代和互补的方法,例如使用非 Google Cloud 服务生成共同签名,或让共同签名者在自己的环境中构建区块链签名,这是一种更加分散的架构。希望本实验能够启发不同的方法在 Google Cloud 上构建 MPC。
您将使用一个简单的工作负载,使用共同签名者密钥材料在 Confidential Space 中对以太坊事务进行签名。以太坊交易签名是一个流程,用户可以通过该过程对以太坊区块链上的交易进行授权。如需发送以太坊交易,您需要使用私钥对其进行签名。这能证明您是账号的所有者并对交易进行授权。签名流程如下:
- 付款方创建一个交易对象,用于指定收款人地址、要发送的 ETH 金额以及任何其他相关数据。
- 付款方的私钥用于对交易数据进行哈希处理。
- 然后使用私钥对哈希值进行签名。
- 签名会附加到交易对象中。
- 交易会广播到以太坊网络。
当网络上的节点收到交易时,它会验证签名,确保是由账号所有者签署的。如果签名有效,节点会将交易添加到区块链中。
首先,您需要配置必要的 Cloud 资源。然后,您将在机密空间中运行工作负载。此 Codelab 将引导您完成以下简要步骤:
- 如何配置运行机密空间所需的 Cloud 资源
- 如何根据以下特性授予对受保护资源的访问权限:
- 定义:工作负载容器
- 位置:机密空间环境(机密虚拟机上的机密空间映像)
- 参与者:运行工作负载的账号
- 如何在运行 Confidential Space 虚拟机映像的机密虚拟机中运行工作负载
必需的 API
您必须在指定项目中启用以下 API 才能完成本指南。
API 名称 | API 标题 |
cloudkms.googleapis.com | Cloud KMS |
compute.googleapis.com | Compute Engine |
confidentialcomputing.googleapis.com | 机密计算 |
iamcredentials.googleapis.com | IAM |
artifactregistry.googleapis.com | Artifact Registry |
2. 设置 Cloud 资源
准备工作
- 使用以下命令克隆 此代码库,以获取此 Codelab 中使用的必需脚本。
git clone https://github.com/GoogleCloudPlatform/confidential-space.git
- 更改此 Codelab 的目录。
cd confidential-space/codelabs/digital_asset_transaction_codelab/scripts
- 确保您已按如下所示设置所需的项目环境变量。如需详细了解如何设置 GCP 项目,请参阅 此 Codelab。您可以参阅此文章,详细了解如何检索项目 ID 以及项目 ID 与项目名称和项目编号的不同之处。.
export PRIMUS_PROJECT_ID=<GCP project id>
- 为您的项目启用结算功能。
- 为这两个项目启用机密计算 API 和以下 API。
gcloud services enable \
cloudapis.googleapis.com \
cloudkms.googleapis.com \
cloudresourcemanager.googleapis.com \
cloudshell.googleapis.com \
container.googleapis.com \
containerregistry.googleapis.com \
iam.googleapis.com \
confidentialcomputing.googleapis.com
- 如需为资源名称设置变量,您可以使用以下命令。请注意,这将替换 A 公司的 GCP 项目特有的资源名称,例如
export PRIMUS_INPUT_STORAGE_BUCKET='primus-input-bucket'
- 您可以针对 A 公司的 GCP 项目设置以下变量:
$PRIMUS_INPUT_STORAGE_BUCKET | 存储已加密密钥的存储分区。 |
$PRIMUS_RESULT_STORAGE_BUCKET | 存储 MPC 交易结果的存储分区。 |
$PRIMUS_KEY | KMS 密钥用于加密 Primus Bank 存储在 $PRIMUS_INPUT_STORAGE_BUCKET 中的数据。 |
$PRIMUS_KEYRING | KMS 密钥环,将用于创建 Primus Bank 的加密密钥 $PRIMUS_KEY。 |
$PRIMUS_WIP_PROVIDER | 工作负载身份池提供方,其中包含用于由 MPC 工作负载服务签名的令牌的属性条件。 |
$PRIMUS_SERVICEACCOUNT | $PRIMUS_WORKLOAD_IDENTITY_POOL 用于访问受保护资源的服务账号。此服务账号将有权查看存储在 $PRIMUS_INPUT_STORAGE_BUCKET 存储分区中的加密密钥。 |
$PRIMUS_ARTIFACT_REPOSITORY | 用于存储工作负载容器映像的工件代码库。 |
$WORKLOAD_SERVICEACCOUNT | 有权访问运行工作负载的机密虚拟机的服务账号。 |
$WORKLOAD_CONTAINER | 运行工作负载的 Docker 容器。 |
$WORKLOAD_IMAGE_NAME | 工作负载容器映像的名称。 |
$WORKLOAD_IMAGE_TAG | 工作负载容器映像的标记。 |
- 运行以下脚本,根据资源名称的项目 ID,将其余变量名称设置为相应的值。
source config_env.sh
设置 Cloud 资源
在此步骤中,您将设置多方计算所需的云资源。在本实验中,您将使用以下私钥:0000000000000000000000000000000000000000000000000000000000000001
在生产环境中,您将生成自己的私钥。不过,在本实验中,我们会将此私钥拆分为两份,并分别加密。在生产环境中,密钥绝不应存储在明文文件中。相反,私钥可以在 Google Cloud 外部生成(或完全跳过并替换为自定义 MPC 密钥分片创建),然后进行加密,以确保任何人都无法访问私钥或密钥共享。在本实验中,我们将使用 Gcloud CLI。
运行以下脚本,设置所需的云资源。在这些步骤中,系统会创建下述资源:
- Cloud Storage 存储分区 (
$PRIMUS_INPUT_STORAGE_BUCKET
),用于存储已加密的私钥共享。 - 一个 Cloud Storage 存储分区 (
$PRIMUS_RESULT_STORAGE_BUCKET
),用于存储数字资产交易结果。 - KMS 中的加密密钥 (
$PRIMUS_KEY
) 和密钥环 ($PRIMUS_KEYRING
),用于对私钥共享进行加密。 - 工作负载身份池 (
$PRIMUS_WORKLOAD_IDENTITY_POOL
),用于根据其提供方下配置的特性条件验证声明。 - 一个与上述工作负载身份池 (
$PRIMUS_WORKLOAD_IDENTITY_POOL
) 关联的服务账号 ($PRIMUS_SERVICEACCOUNT
),具有以下 IAM 访问权限: roles/cloudkms.cryptoKeyDecrypter
,用于使用 KMS 密钥解密数据。objectViewer
,用于从 Cloud Storage 存储分区读取数据。roles/iam.workloadIdentityUser
(用于将此服务账号连接到工作负载身份池)。
./setup_resources.sh
3. 创建工作负载
创建工作负载服务账号
现在,您将为该工作负载创建具有所需角色和权限的服务账号。为此,请运行以下脚本,该脚本将为公司 A 创建一个工作负载服务账号。此服务账号将供运行工作负载的虚拟机使用。
工作负载服务账号 ($WORKLOAD_SERVICEACCOUNT
) 将具有以下角色:
confidentialcomputing.workloadUser
,用于获取证明令牌logging.logWriter
:用于将日志写入 Cloud Logging。objectViewer
,用于从$PRIMUS_INPUT_STORAGE_BUCKET
Cloud Storage 存储分区中读取数据。objectUser
- 将工作负载结果写入$PRIMUS_RESULT_STORAGE_BUCKET
Cloud Storage 存储分区。
./create_workload_service_account.sh
创建工作负载
此步骤涉及创建工作负载 Docker 映像。此 Codelab 中的工作负载是一个简单的 Node.js MPC 应用,可为数字交易签名,以使用加密的私钥共享传输资产。此处是工作负载项目代码。工作负载项目包含以下文件。
package.json::此文件包含应该用于工作负载 MPC 应用的软件包列表。在本例中,我们将使用 @google-cloud/kms、@google-cloud/storage、ethers 和 fast-crc32c 库。此处是我们将在此 Codelab 中使用的 package.json 文件。
index.js::这是工作负载应用的入口点,用于指定在工作负载容器启动时应运行的命令。我们还提供了一个未签名的交易示例,该交易通常由不受信任的应用提供,要求用户提供签名。此 index.js 文件还会从 mpc.js(我们将在稍后创建)导入函数。以下是 index.js 文件的内容,您也可以在此处找到该文件。
import {signTransaction, submitTransaction, uploadFromMemory} from './mpc.js';
const signAndSubmitTransaction = async () => {
try {
// Create the unsigned transaction object
const unsignedTransaction = {
nonce: 0,
gasLimit: 21000,
gasPrice: '0x09184e72a000',
to: '0x0000000000000000000000000000000000000000',
value: '0x00',
data: '0x',
};
// Sign the transaction
const signedTransaction = await signTransaction(unsignedTransaction);
// Submit the transaction to Ganache
const transaction = await submitTransaction(signedTransaction);
// Write the transaction receipt
uploadFromMemory(transaction);
return transaction;
} catch (e) {
console.log(e);
uploadFromMemory(e);
}
};
await signAndSubmitTransaction();
mpc.js::这是进行交易签名的位置。它会从 kms-decrypt 和 credential-config 导入函数,我们将在后面介绍相关内容。以下是 mpc.js 文件的内容,您也可以在此处找到该文件。
import {Storage} from '@google-cloud/storage';
import {ethers} from 'ethers';
import {credentialConfig} from './credential-config.js';
import {decryptSymmetric} from './kms-decrypt.js';
const providers = ethers.providers;
const Wallet = ethers.Wallet;
// The ID of the GCS bucket holding the encrypted keys
const bucketName = process.env.KEY_BUCKET;
// Name of the encrypted key files.
const encryptedKeyFile1 = 'alice_encrypted_key_share';
const encryptedKeyFile2 = 'bob_encrypted_key_share';
// Create a new storage client with the credentials
const storageWithCreds = new Storage({
credentials: credentialConfig,
});
// Create a new storage client without the credentials
const storage = new Storage();
const downloadIntoMemory = async (keyFile) => {
// Downloads the file into a buffer in memory.
const contents =
await storageWithCreds.bucket(bucketName).file(keyFile).download();
return contents;
};
const provider =
new providers.JsonRpcProvider(`http://${process.env.NODE_URL}:80`);
export const signTransaction = async (unsignedTransaction) => {
/* Check if Alice and Bob have both approved the transaction
For this example, we're checking if their encrypted keys are available. */
const encryptedKey1 =
await downloadIntoMemory(encryptedKeyFile1).catch(console.error);
const encryptedKey2 =
await downloadIntoMemory(encryptedKeyFile2).catch(console.error);
// For each key share, make a call to KMS to decrypt the key
const privateKeyshare1 = await decryptSymmetric(encryptedKey1[0]);
const privateKeyshare2 = await decryptSymmetric(encryptedKey2[0]);
/* Perform the MPC calculations
In this example, we're combining the private key shares
Alternatively, you could import your mpc calculations here */
const wallet = new Wallet(privateKeyshare1 + privateKeyshare2);
// Sign the transaction
const signedTransaction = await wallet.signTransaction(unsignedTransaction);
return signedTransaction;
};
export const submitTransaction = async (signedTransaction) => {
// This can now be sent to Ganache
const hash = await provider.sendTransaction(signedTransaction);
return hash;
};
export const uploadFromMemory = async (contents) => {
// Upload the results to the bucket without service account impersonation
await storage.bucket(process.env.RESULTS_BUCKET)
.file('transaction_receipt_' + Date.now())
.save(JSON.stringify(contents));
};
kms-decrypt.js::此文件包含使用 KMS 中管理的密钥进行解密的代码。以下是 kms-decrypt.js 文件的内容,您也可以在此处找到该文件。
import {KeyManagementServiceClient} from '@google-cloud/kms';
import crc32c from 'fast-crc32c';
import {credentialConfig} from './credential-config.js';
const projectId = process.env.PRIMUS_PROJECT_ID;
const locationId = process.env.PRIMUS_LOCATION;
const keyRingId = process.env.PRIMUS_ENC_KEYRING;
const keyId = process.env.PRIMUS_ENC_KEY;
// Instantiates a client
const client = new KeyManagementServiceClient({
credentials: credentialConfig,
});
// Build the key name
const keyName = client.cryptoKeyPath(projectId, locationId, keyRingId, keyId);
export const decryptSymmetric = async (ciphertext) => {
const ciphertextCrc32c = crc32c.calculate(ciphertext);
const [decryptResponse] = await client.decrypt({
name: keyName,
ciphertext,
ciphertextCrc32c: {
value: ciphertextCrc32c,
},
});
// Optional, but recommended: perform integrity verification on
// decryptResponse. For more details on ensuring E2E in-transit integrity to
// and from Cloud KMS visit:
// https://cloud.google.com/kms/docs/data-integrity-guidelines
if (crc32c.calculate(decryptResponse.plaintext) !==
Number(decryptResponse.plaintextCrc32c.value)) {
throw new Error('Decrypt: response corrupted in-transit');
}
const plaintext = decryptResponse.plaintext.toString();
return plaintext;
};
credential-config.js::此文件存储服务账号模拟的工作负载身份池路径和详细信息。此处是我们将在此 Codelab 中使用的 credential-config.js 文件。
Dockerfile:最后,我们将创建用于构建工作负载 Docker 映像的 Dockerfile。按照此处指定的方式定义 Dockerfile。
FROM node:16.18.0
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "package-lock.json*", "./"]
RUN npm install --production
COPY . .
LABEL "tee.launch_policy.allow_cmd_override"="true"
LABEL "tee.launch_policy.allow_env_override"="NODE_URL,RESULTS_BUCKET,KEY_BUCKET,PRIMUS_PROJECT_NUMBER,PRIMUS_PROJECT_ID,PRIMUS_WORKLOAD_IDENTITY_POOL,PRIMUS_WIP_PROVIDER,PRIMUS_SERVICEACCOUNT,PRIMUS_ENC_KEYRING,PRIMUS_ENC_KEY"
CMD [ "node", "index.js" ]
注意:LABEL "tee.launch_policy.allow_cmd_override"="true"是映像作者设置的启动政策它允许操作员在执行工作负载时替换 CMD。默认情况下,allow_cmd_override 设置为 false。LABEL "tee.launch_policy.allow_env_override"用于告知 Confidential Space 用户能够使用哪些环境变量映像。
运行以下脚本以创建将执行以下步骤的工作负载:
- 创建 Artifact Registry(
$PRIMUS_ARTIFACT_REPOSITORY
) 以存储工作负载 Docker 映像。 - 使用必需的资源名称更新工作负载代码。点击此处,了解此 Codelab 使用的工作负载代码。
- 创建 Dockerfile,用于构建工作负载代码的 Docker 映像。您可以在此处找到 Dockerfile。
- 构建 Docker 映像并将其发布到上一步中创建的 Artifact Registry (
$PRIMUS_ARTIFACT_REPOSITORY
)。 - 向“
$WORKLOAD_SERVICEACCOUNT
”授予“$PRIMUS_ARTIFACT_REPOSITORY
”的读取权限。只有这样,工作负载容器才能从 Artifact Registry 中拉取工作负载 Docker 映像。
./create_workload.sh
创建区块链节点
Ganache 以太坊节点
在授权工作负载之前,我们需要创建以太坊 Ganache 实例。已签名的交易将提交到此 Ganache 实例。请记下此实例的 IP 地址。运行以下命令后,您可能需要输入 y
以启用 API。
gcloud config set project $PRIMUS_PROJECT_ID
gcloud compute instances create-with-container mpc-lab-ethereum-node \
--zone=${PRIMUS_PROJECT_ZONE}\
--tags=http-server \
--shielded-secure-boot \
--shielded-vtpm \
--shielded-integrity-monitoring \
--container-image=docker.io/trufflesuite/ganache:v7.7.3 \
--container-arg=--wallet.accounts=\"0x0000000000000000000000000000000000000000000000000000000000000001,0x21E19E0C9BAB2400000\" \
--container-arg=--port=80
4. 授权和运行工作负载
为工作负载授权
在此步骤中,我们将在工作负载身份池 ($PRIMUS_WORKLOAD_IDENTITY_POOL
) 下设置工作负载身份池提供方。系统为工作负载身份配置了属性条件,如下所示。其中一个条件是验证是否从预期的工件代码库中拉取工作负载映像。
gcloud config set project $PRIMUS_PROJECT_ID
gcloud iam workload-identity-pools providers create-oidc ${PRIMUS_WIP_PROVIDER} \
--location="${PRIMUS_PROJECT_LOCATION}" \
--workload-identity-pool="$PRIMUS_WORKLOAD_IDENTITY_POOL" \
--issuer-uri="https://confidentialcomputing.googleapis.com/" \
--allowed-audiences="https://sts.googleapis.com" \
--attribute-mapping="google.subject='assertion.sub'" \
--attribute-condition="assertion.swname == 'CONFIDENTIAL_SPACE' && 'STABLE' in assertion.submods.confidential_space.support_attributes && assertion.submods.container.image_reference == '${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG' && '$WORKLOAD_SERVICEACCOUNT@$PRIMUS_PROJECT_ID.iam.gserviceaccount.com' in assertion.google_service_accounts"
运行工作负载
本部分介绍如何在机密虚拟机上运行工作负载。为此,我们将使用元数据标记传递必需的 TEE 参数。此外,我们还将使用“tee-env-*”为工作负载容器设置环境变量标志。该映像包含以下变量:
NODE_URL
:将处理已签名交易的以太坊节点的网址。RESULTS_BUCKET
:用于存储 mpc 交易结果的存储分区。KEY_BUCKET
:存储 mpc 加密密钥的存储分区。PRIMUS_PROJECT_NUMBER
:用于凭据配置文件的项目编号。PRIMUS_PROJECT_ID
:用于凭据配置文件的项目 ID。工作负载执行结果将发布到$PRIMUS_RESULT_STORAGE_BUCKET
。PRIMUS_WORKLOAD_IDENTITY_POOL
:用于验证声明的工作负载身份池。PRIMUS_WIP_POROVIDER
:工作负载身份池提供方,其中包含用于验证工作负载提供的令牌的特性条件。WORKLOAD_SERVICEACCOUNT
:工作负载的服务账号。
gcloud config set project $PRIMUS_PROJECT_ID
gcloud compute instances create $WORKLOAD_VM \
--confidential-compute-type=SEV \
--shielded-secure-boot \
--maintenance-policy=TERMINATE \
--scopes=cloud-platform \
--zone=${PRIMUS_PROJECT_ZONE} \
--image-project=confidential-space-images \
--image-family=confidential-space \
--service-account=$WORKLOAD_SERVICEACCOUNT@$PRIMUS_PROJECT_ID.iam.gserviceaccount.com \
--metadata "^~^tee-image-reference=${PRIMUS_PROJECT_REPOSITORY_REGION}-docker.pkg.dev/$PRIMUS_PROJECT_ID/$PRIMUS_ARTIFACT_REPOSITORY/$WORKLOAD_IMAGE_NAME:$WORKLOAD_IMAGE_TAG~tee-restart-policy=Never~tee-env-NODE_URL=$(gcloud compute instances describe mpc-lab-ethereum-node --format='get(networkInterfaces[0].networkIP)' --zone=${PRIMUS_PROJECT_ZONE})~tee-env-RESULTS_BUCKET=$PRIMUS_RESULT_STORAGE_BUCKET~tee-env-KEY_BUCKET=$PRIMUS_INPUT_STORAGE_BUCKET~tee-env-PRIMUS_PROJECT_ID=$PRIMUS_PROJECT_ID~tee-env-PRIMUS_PROJECT_NUMBER=$(gcloud projects describe $PRIMUS_PROJECT_ID --format="value(projectNumber)")~tee-env-PRIMUS_WORKLOAD_IDENTITY_POOL=$PRIMUS_WORKLOAD_IDENTITY_POOL~tee-env-PRIMUS_PROJECT_LOCATION=${PRIMUS_PROJECT_LOCATION}~tee-env-PRIMUS_WIP_PROVIDER=$PRIMUS_WIP_PROVIDER~tee-env-PRIMUS_SERVICEACCOUNT=$PRIMUS_SERVICEACCOUNT~tee-env-PRIMUS_KEY=${PRIMUS_KEY}~tee-env-PRIMUS_KEYRING=${PRIMUS_KEYRING}"
检查 Cloud Storage 结果
您可以在 Cloud Storage 中查看交易收据。机密空间可能需要几分钟时间才能启动并显示相关结果。当虚拟机处于停止状态时,您会知道容器已完成。
- 前往 Cloud Storage 浏览器页面。
- 点击
$PRIMUS_RESULT_STORAGE_BUCKET
。 - 点击
transaction_receipt
文件。 - 点击“下载”可下载并查看交易响应。
注意:如果结果未显示,您可以转到 Compute Engine Cloud 控制台页面中的 $WORKLOAD_VM,然后点击“串行端口 1(控制台)”查看日志。
查看 Ganache 区块链交易
您还可以在区块链日志中查看相应交易。
- 前往 Cloud Compute Engine 页面。
- 点击
mpc-lab-ethereum-node
VM
。 - 点击
SSH
以打开浏览器窗口中的 SSH 窗口。 - 在 SSH 窗口中,输入
sudo docker ps
以查看正在运行的 Ganache 容器。 - 查找
trufflesuite/ganache:v7.7.3
的容器 ID - 输入
sudo docker logs CONTAINER_ID
,将 CONTAINER_ID 替换为trufflesuite/ganache:v7.7.3
的 ID。 - 查看 Ganache 的日志,并确认日志中列出了事务。
5. 清理
此处是一个脚本,可用于清理我们在此 Codelab 中创建的资源。在此次清理过程中,系统将删除以下资源:
- 用于存储已加密的密钥共享的输入存储分区 (
$PRIMUS_INPUT_STORAGE_BUCKET)
. - 加密密钥和密钥环(
$PRIMUS_KEY
和$PRIMUS_KEYRING
)。 - 用于访问受保护资源的服务账号 (
$PRIMUS_SERVICEACCOUNT
)。 - 工作负载身份池 (
$PRIMUS_WORKLOAD_IDENTITY_POOL
)。 - 工作负载服务账号 (
$WORKLOAD_SERVICEACCOUNT
)。 - 工作负载计算实例。
- 用于存储交易结果的结果存储分区。(
$PRIMUS_RESULT_STORAGE_BUCKET
)。 - 用于存储工作负载映像 (
$PRIMUS_ARTIFACT_REPOSITORY
) 的 Artifact Registry。
./cleanup.sh
如果已完成探索,请考虑删除您的项目。
- 转到 Cloud Platform Console
- 选择要关停的项目,然后点击“删除”顶部。此操作会安排删除项目。
后续操作
查看一些类似的 Codelab...