Solidity中如何向智能合约函数传递经可信源验证的参数?
Solidity 验证链下传入参数可信来源的可行方案
Solidity 运行在链上沙盒环境,本身无法主动发起链下HTTP请求,也无法直接识别参数是从哪个服务端API获取的,要验证用户传入的claimDate确实来自你的可信后端,有三类成熟落地方案,可按安全需求选择:
- 服务端签名+链上验签(通用场景首选,成本最低)
实现逻辑非常直接:你的可信服务端持有一把独立保管的签名私钥,当接口返回claimDate给调用方时,同时返回私钥对(链ID, 合约地址, 用户id, claimDate, 防重放标识)的签名值。用户调用合约testFunction时,需要把id、claimDate、对应签名一同作为入参传入。合约内提前硬编码签名私钥对应的公钥地址,函数入口先做ECDSA签名校验,只有签名验证通过,才说明传入的claimDate是你的可信服务端签发的原始值,没有被调用方篡改。
参考实现代码:
注意签名规则一定要加链ID、合约地址、调用者地址,避免攻击者把其他链、其他合约的有效签名拿过来做重放攻击。contract TestSmartContract { // 提前写入可信服务端签名对应的公钥地址 address public immutable TRUSTED_SIGNER; constructor(address _trustedSigner) { TRUSTED_SIGNER = _trustedSigner; } function testFunction(uint256 id, uint256 claimDate, bytes calldata signature) public payable returns (uint) { // 构造和服务端签名规则完全一致的消息哈希 bytes32 msgHash = keccak256(abi.encodePacked( block.chainid, address(this), id, claimDate, msg.sender )); // 适配以太坊签名规则做验签 bytes32 ethSignedHash = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", msgHash )); (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); address recoveredSigner = ecrecover(ethSignedHash, v, r, s); require(recoveredSigner == TRUSTED_SIGNER, "claimDate source invalid"); // 以下编写业务逻辑 return 1; } // 签名拆分工具函数 function splitSignature(bytes calldata sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "invalid signature length"); assembly { r := calldataload(sig.offset) s := calldataload(add(sig.offset, 32)) v := byte(0, calldataload(add(sig.offset, 64))) } } } - 去中心化预言机写入(高安全场景可选)
如果不想依赖用户传参,可以直接通过去中心化预言机网络,把后端生成的claimDate经过多节点共识后直接写入合约存储,业务函数直接读取合约内存储的、经预言机认证的参数值即可,完全不需要接收用户传入的claimDate。这个方案安全性更高,但接入复杂度和Gas成本也更高。 - TLS原生证明(极端高安全需求可选)
如果需要完全去掉对服务端私钥保管的信任,可以用TLS证明类方案,要求调用方提供从你的官方API获取claimDate时的TLS会话证明,合约验证证明是和你服务端域名建立合法TLS连接拿到的原始返回值,全程不需要服务端额外做签名逻辑,但技术复杂度极高,普通业务场景不需要用到。
不要踩一个常见误区:不要试图靠校验
msg.sender做参数来源校验,用户完全可以在本地把从API拿到的claimDate改成任意值再签名发交易,链上只能看到发交易的地址,根本无法识别参数本身是不是从你API拿到的原始值。
内容的提问来源于stack exchange,提问作者haseebahmad
相关产品推荐
相关产品推荐

