为什么相同的 HSM 密钥每次都会验证到不同的以太坊地址?



我正在与一个HSM接口,该HSM正在生成并使用以太坊标准(secp256k1)进行签名。我正在使用一个名为Graphene的软件包与HSM接口。我使用其"pointEC"属性提取公钥:0xc87c1d67c1909ebf8b54c9ce3d8e0f0cde41561c8115481321e45b364a8f3334b6e826363d8e895110fc9ca2d75e84cc7c56b8e9fbcd70c726cb44f5506848fa

我可以用来生成地址:0x21d20b04719f25d2ba0c68e851bb64fa570a9465

但是当我尝试使用密钥对来自 dApp 的个人信息进行签名时,签名的计算结果总是不同的地址。例如,随机数/消息:wAMqcOCD2KKz2n0Dfbu1nRYbeLw_qbLxrW1gpTBwkq具有以下签名:

0x2413f8d2ab4df2f3d87560493f21f0dfd570dc61136c53c236731bf56a9ce02cb23692e6a5cec96c62359f6eb4080d80328a567d14387f487f3c50d9ce61503b1c

但它恢复了一个有效的地址0xFC0561D848b0cDE5877068D94a4d803A0a933785

这大概都是使用相同的私钥/公钥。当然,我只是附加了"1c"恢复值,但即使我尝试使用其他值,我也没有运气。下面再举几个例子:

Nonce: WRH_ApTkfN7yFAEpbGwU9BiE2M6eKTZMklPYK50djnx
Sig: 0x70242adabfe27c12e54abced8de87b45f511a194609eb27b215b153594b5697b7fb5e7279285663f80c82c2a2f2920916f76fd845cdecb45ace19f76b0622ac41c
Address: 0x1A086eD40FF90E75764260E2Eb42fab4Db519E53
Nonce: TZV6qhplddJgcKaN7qtpcIhudFhiQ
Sig: 0x3607beb3d58ff35ca1059f3ea44f41e79e76d8ffe35a4f716e78030f0fe2ca1da51f138c31d4ec4b9fc3546c4de1185736a4c4c7030a8b1965e30cb0af6ba2ee1c
Address: 0xa61A518cf73163Fd92461942c26C67c203bda379

我的消息签名代码:

let alg: graphene.MechanismType;
alg = graphene.MechanismEnum.ECDSA;
const session = get_session();
let key: graphene.Key | null = null;
//#region Find signing key
const objects = session.find({label: GEN_KEY_LABEL});
for (let i = 0; i < objects.length; i++) {
const obj = objects.items(i);
if ((obj.class === graphene.ObjectClass.PRIVATE_KEY ||
obj.class === graphene.ObjectClass.SECRET_KEY) &&
obj.handle.toString('hex') == params.handle
) {
key = obj.toType<graphene.Key>();
break;
}
}
if (!key) {
throw new Error("Cannot find signing key");
}
var sign = session.createSign(alg,key);
if (!params.data) {
console.log("No data found. Signing 'test' string");
params.data = 'test';
}
sign.update(Buffer.from(params.data.toString().trim()));
var signature = sign.final();

console.log(signature.toString('hex'));

请记住,即使只有 1 个密钥存在,它也会失败。

地址只是通过公钥计算的,而签名是使用 ECDSA 生成的。ECDSA 由随机值 r 和特定于该随机值的签名 s 组成(当然还有私钥)。更多信息在这里(关于ECDSA的维基百科)。

您看不到这一点,因为它们只是简单地编码为静态大小(无符号的大整数)值,然后连接在一起称为"签名"(因此签名的大小是密钥大小的两倍,64字节而不是32字节)。验证将解析签名并再次使用单独的值。对于以太坊和比特币,可以在签名上加上一个额外的字节,以便可以找回公钥,然后重新计算地址。这也改变了签名生成,因此您不再谈论简单的 ECDSA。

还有X9.62签名格式,它仍然由两个单独的整数组成,使用ASN.1/DER编码进行编码。这些签名看起来只是部分随机的,因为分离/编码两个整数所需的开销。

事实证明,我使用的是已弃用的 Buffer.from 函数,因为更新版本要求您指定传入数据的格式。

E.g. Buffer.from("04021a","hex")

由于这是最终的"输入"和计算,我花了很长时间才意识到数据在这一点上被错误地转换了。我以为我已经多次检查和重新检查了每一步的数据,但错过了最面对面的部分。

此外,我了解到,要创建适当的签名并防止交易延展性,您必须不断辞职,以便"s"的值最终小于:(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141)/2

然后,当通过地址恢复函数放置"r"和"s"时,它应该尝试恢复v=27或v=28(0x1a或0x1b)的地址,基本上在这一点上它是反复试验。大多数情况下,它会恢复 v=27 的正确地址。

最新更新