You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在以太坊Solidity中验证Node.js签名并实现合约匹配

解决方法

你的Solidity验证逻辑基于以太坊ECDSA签名体系(secp256k1曲线),但当前Node.js代码使用的是RSA签名,两者算法完全不兼容。要让签名匹配,必须调整Node.js的签名流程,完全对齐Solidity的验证逻辑。

核心调整要点

1. 替换密钥类型

放弃RSA密钥,改用以太坊兼容的secp256k1私钥(格式为64字节十六进制字符串,可带0x前缀)。

2. 复刻Solidity的哈希计算流程

先明确Solidity中两个哈希函数的标准实现(如果你的实现不同,需同步调整Node.js代码):

// 计算原始消息的Keccak256哈希
function getMessageHash(string memory _message) public pure returns (bytes32) {
    return keccak256(abi.encodePacked(_message));
}

// 计算带以太坊签名前缀的哈希
function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
    return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash));
}

Node.js必须完全复刻这个哈希流程,否则签名无法被Solidity恢复。

3. 实现ECDSA签名并转换格式

使用secp256k1曲线进行签名,并将签名转换为Solidity期望的r+s+v十六进制格式。

完整Node.js代码示例

方案1:使用原生crypto模块

const crypto = require('crypto');
const keccak256 = require('keccak256'); // 需执行 npm install keccak256

// 替换为你的secp256k1私钥(64字节十六进制字符串,不带0x前缀)
const privateKey = Buffer.from('你的以太坊私钥十六进制字符串', 'hex');
const message = "Hello, world!";

// 1. 计算getMessageHash:对应Solidity的keccak256(abi.encodePacked(_message))
const messageBuffer = Buffer.from(message);
const messageHash = keccak256(messageBuffer);

// 2. 计算ethSignedMessageHash:带以太坊前缀的哈希
const prefix = Buffer.from("\x19Ethereum Signed Message:\n32");
const ethSignedMessageHash = keccak256(Buffer.concat([prefix, messageHash]));

// 3. 用ECDSA签名(secp256k1曲线)
const sign = crypto.createSign('sha256');
sign.update(ethSignedMessageHash);
const derSignature = sign.sign({ key: privateKey, format: 'der' });

// 4. 将DER格式签名转换为Solidity期望的r+s+v格式
const r = derSignature.slice(4, 36).toString('hex');
const s = derSignature.slice(38, 70).toString('hex');

// 计算v值:根据s是否大于secp256k1半阶调整为27(0x1b)或28(0x1c)
const secp256k1HalfOrder = Buffer.from('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex');
const sBuffer = Buffer.from(s, 'hex');
const v = sBuffer.compare(secp256k1HalfOrder) > 0 ? '1c' : '1b';

// 最终可直接传入Solidity验证的签名
const finalSignature = r + s + v;
console.log('匹配Solidity的签名:', finalSignature);

方案2:使用ethereum-cryptography库(更简洁)

const { secp256k1 } = require("@noble/curves/secp256k1");
const { keccak256 } = require("@noble/hashes/keccak");
const { hexToBytes, bytesToHex } = require("@noble/hashes/utils");

// 替换为你的secp256k1私钥(64字节十六进制字符串)
const privateKey = hexToBytes('你的以太坊私钥十六进制字符串');
const message = "Hello, world!";

// 计算带以太坊前缀的哈希
const messageHash = keccak256(new TextEncoder().encode(message));
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n32`);
const ethSignedMessageHash = keccak256(Buffer.concat([prefix, messageHash]));

// 签名并转换格式
const signature = secp256k1.sign(ethSignedMessageHash, privateKey);
const finalSignature = bytesToHex(signature.r) + bytesToHex(signature.s) + (signature.recovery + 27).toString(16);
console.log('匹配Solidity的签名:', finalSignature);

关键注意事项

  • 必须使用secp256k1曲线私钥:Solidity的recover函数仅支持该曲线的ECDSA签名,RSA签名无法兼容。
  • 严格对齐前缀规则:以太坊签名必须添加\x19Ethereum Signed Message:\n前缀(搭配消息哈希的固定长度32),否则签名无法被正确恢复。
  • 签名格式必须为r+s+v:Solidity期望的签名是由r(32字节)、s(32字节)、v(1字节)拼接而成的十六进制字符串,而非原生DER格式。

内容的提问来源于stack exchange,提问作者a_lucky_guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 02:50:39