Google Cloud KMS是否支持secp256k1的RFC6979签名用于以太坊密钥管理?
GCP KMS的secp256k1密钥完全支持RFC6979规范的确定性ECDSA签名,可以实现和ethers.js v5 wallet.signMessage(...)完全一致的签名结果。以下是具体的实现步骤和注意事项:
一、密钥配置要求
创建secp256k1密钥时,必须选择签名算法为EC_SIGN_SECP256K1_SHA256——这个算法默认遵循RFC6979生成确定性的k值,确保相同输入(消息+私钥)生成完全相同的签名,和ethers.js的行为一致。
二、核心实现步骤
ethers.js的signMessage方法会对消息做特殊预处理,再进行签名。用GCP KMS实现相同效果,必须复刻这个预处理流程:
1. 消息预处理(和ethers.js对齐)
ethers.js会自动给消息添加以太坊签名前缀:
"\x19Ethereum Signed Message:\n" + 消息长度 + 原始消息
之后对拼接后的内容做SHA256哈希。你可以直接用ethers.js v5的utils.hashMessage方法完成这一步,避免手动拼接出错:
const {utils} = require('ethers'); const prefixedMessageHash = utils.hashMessage('你的消息内容');
prefixedMessageHash是十六进制格式的哈希值,后续会作为GCP KMS签名的输入。
2. 调用GCP KMS签名API
使用GCP官方Node.js客户端库@google-cloud/kms发起签名请求,传入预处理后的哈希值:
const {KeyManagementServiceClient} = require('@google-cloud/kms'); const kmsClient = new KeyManagementServiceClient(); async function signMessageWithKMS(projectId, location, keyRing, keyId, versionId, message) { // 生成符合ethers.js格式的消息哈希 const hashHex = utils.hashMessage(message); const hashBuffer = Buffer.from(hashHex.slice(2), 'hex'); // 构建KMS签名请求 const keyVersionPath = kmsClient.cryptoKeyVersionPath(projectId, location, keyRing, keyId, versionId); const request = { name: keyVersionPath, digest: { sha256: hashBuffer } }; // 执行签名 const [response] = await kmsClient.asymmetricSign(request); return response.signature; }
3. 转换签名格式为ethers.js兼容格式
GCP KMS返回的签名是DER编码的二进制数据,需要转换成ethers.js使用的r/s/v格式:
async function convertKmsSignatureToEthersFormat(kmsSignature, message, publicKey) { // 解析DER签名为r和s const {r, s} = utils.splitSignature(kmsSignature); // 计算v值(以太坊恢复ID,0或1) const address = utils.computeAddress(publicKey); let recoveredAddr = utils.verifyMessage(message, {r, s, v: 0}, publicKey); let v = 0; if (recoveredAddr.toLowerCase() !== address.toLowerCase()) { recoveredAddr = utils.verifyMessage(message, {r, s, v: 1}, publicKey); v = 1; } // 合并成ethers.js兼容的签名字符串 return utils.joinSignature({r, s, v}); } // 获取KMS密钥的公钥 async function getKmsPublicKey(projectId, location, keyRing, keyId, versionId) { const keyVersionPath = kmsClient.cryptoKeyVersionPath(projectId, location, keyRing, keyId, versionId); const [response] = await kmsClient.getPublicKey({name: keyVersionPath}); // 转换PEM公钥为十六进制格式 const pem = response.pem.replace(/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----|\n/g, ''); return `0x${Buffer.from(pem, 'base64').toString('hex')}`; }
三、验证一致性
完成上述步骤后,用同一个消息分别调用wallet.signMessage和GCP KMS签名方法,得到的签名字符串应该完全一致,证明实现对齐。
四、权限说明
确保执行签名的服务账号拥有cloudkms.cryptoKeyVersions.signAsymmetric权限,否则会被KMS拒绝请求。
内容的提问来源于stack exchange,提问作者chrisb

