输入/搜索内容
内置函数
结构体
内置变量与常量
通用约定
交易所
能力矩阵
中心化交易所
去中心化交易所
预测市场
证券与期货
链上与自定义接入

使用secp256k1私钥对32字节哈希进行签名,返回r、s、v等签名数据,适用于EIP-712结构化数据签名(如ERC-20 Permit授权、1inch限价单)等需要链下签名的场景。

exchange.IO(k, hash)
exchange.IO(k, hash, key)

示例

以以太坊主网USDC的ERC-20 Permit(EIP-2612)授权签名为例:首先读取链上参数,使用exchange.IO("encode", ...)和exchange.IO("hash", "keccak256", ...)计算EIP-712摘要,然后使用exchange.IO("sign", ...)对摘要进行签名,最后通过eth_call模拟调用USDC合约的permit方法以验证签名(仅模拟执行,不发送交易,不消耗gas)。bytes32类型的返回值以字节数组形式返回,范例中使用bytesToHex函数将其转换为十六进制字符串。

javascript
// 为以太坊主网 USDC 签署 EIP-2612 Permit,并通过 eth_call 由 USDC 合约验证签名(仅模拟执行,不发送交易、不消耗 gas) var USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // 以太坊主网 USDC 合约地址 var SPENDER = "0x1111111254EEB25477B68fb85Ed929f73A960582" // 被授权方地址(示例:1inch Router v5) var VALUE = "1000000" // 授权额度:1 USDC(精度为 6 位) // keccak256 → 以 0x 开头的 hex 字符串。fmt = "raw"(按字符串处理)或 "hex"(按十六进制数据处理) function keccak(fmt, data) { return "0x" + String(exchange.IO("hash", "keccak256", fmt, "hex", data)).replace(/^0x/, "") } function strip0x(h) { return String(h).replace(/^0x/, "") } // bytesN 类型的返回值为字节数组(与旧版一致),此处转换为以 0x 开头的 hex 字符串 function bytesToHex(v) { if (typeof v === "string") return "0x" + strip0x(v).toLowerCase() return "0x" + v.map(function (b) { return ("0" + b.toString(16)).slice(-2) }).join("") } // 函数选择器 + 标准 ABI 编码参数 → calldata function calldata(signature, types, values) { var args = exchange.IO.apply(exchange, ["encode", types].concat(values)) return keccak("raw", signature).slice(0, 10) + strip0x(args) } // eth_call 模拟执行:成功时返回 true,revert 时返回错误原因 function simulate(data) { var r = exchange.IO("api", "eth", "eth_call", { from: SPENDER, to: USDC, data: data }, "latest") return r === null ? GetLastError() : true } function main() { // 1. 注册所需的 ABI 片段,读取链上参数 exchange.IO("abi", USDC, JSON.stringify([ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] }, { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] }, { type: "function", name: "nonces", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] }, { type: "function", name: "DOMAIN_SEPARATOR", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] } ])) var owner = exchange.IO("address") var name = exchange.IO("api", USDC, "name") var version = exchange.IO("api", USDC, "version") var nonce = exchange.IO("api", USDC, "nonces", owner) var chainId = parseInt(exchange.IO("api", "eth", "eth_chainId"), 16) var deadline = Math.floor(Date.now() / 1000) + 3600 Log("owner:", owner, "| token:", name, "v" + version, "| nonce:", nonce, "| chainId:", chainId) // 2. 计算 EIP-712 域分隔符,并与合约的 DOMAIN_SEPARATOR() 返回值比对 var domainSeparator = keccak("hex", exchange.IO("encode", "bytes32,bytes32,bytes32,uint256,address", keccak("raw", "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak("raw", name), keccak("raw", version), chainId, USDC)) var onchain = bytesToHex(exchange.IO("api", USDC, "DOMAIN_SEPARATOR")) Log("domainSeparator:", domainSeparator, domainSeparator === onchain ? "与链上一致" : "与链上不一致: " + onchain) // 3. Permit 结构体哈希 → EIP-712 摘要 → 签名 var structHash = keccak("hex", exchange.IO("encode", "bytes32,address,address,uint256,uint256,uint256", keccak("raw", "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), owner, SPENDER, VALUE, nonce, deadline)) var digest = keccak("hex", "1901" + strip0x(domainSeparator) + strip0x(structHash)) var sig = exchange.IO("sign", digest) Log("digest:", digest) Log("sign:", sig) // 4. 由 USDC 合约验证签名:permit(owner, spender, value, deadline, v, r, s) Log("permit(v,r,s) 模拟:", simulate(calldata( "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", "address,address,uint256,uint256,uint8,bytes32,bytes32", [owner, SPENDER, VALUE, deadline, sig.v, sig.r, sig.s]))) // USDC v2.2 另提供接收 bytes 类型签名的重载方法:permit(owner, spender, value, deadline, signature) Log("permit(bytes) 模拟:", simulate(calldata( "permit(address,address,uint256,uint256,bytes)", "address,address,uint256,uint256,bytes", [owner, SPENDER, VALUE, deadline, sig.signature]))) // 5. 反例:篡改 v 值后,合约应当拒绝 Log("篡改 v 后模拟:", simulate(calldata( "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)", "address,address,uint256,uint256,uint8,bytes32,bytes32", [owner, SPENDER, VALUE, deadline, sig.v === 27 ? 28 : 27, sig.r, sig.s]))) }

返回值

类型描述

object

签名对象,包含以下字段:r(32字节,十六进制字符串)、s(32字节,十六进制字符串)、v(数值,27或28)、signature(65字节r‖s‖v,十六进制字符串)、vs(EIP-2098紧凑签名,32字节,十六进制字符串)。调用失败时返回空值。

参数

名称类型必填描述

k

string

是

固定为sign,用于对哈希进行签名。

hash

string

是

待签名的32字节哈希,格式为十六进制字符串,0x前缀可选。该函数不会对hash再次进行哈希处理,EIP-712摘要需预先计算完成,请参考范例。

key

string

否

签名所使用的私钥,未传入时使用exchange交易所对象配置的私钥。

参考

备注

EIP-712结构化数据签名可以直接使用exchange.IO("signTypedData", ...),传入domain、types、message即可,不需要自己计算摘要;EIP-191消息签名使用exchange.IO("signMessage", ...)。

返回的s均为低位值(不大于曲线阶的一半),符合EIP-2规范,可直接用于OpenZeppelin ECDSA.recover等校验场景。

合约接收(v, r, s)三个参数时(如ERC-20 Permit的permit(owner, spender, value, deadline, v, r, s)),使用v、r、s字段;接收bytes signature时,使用signature字段;接收EIP-2098紧凑签名(r, vs)时(如1inch限价单协议),使用r、vs字段。

exchange.IO("hash", "sign", ...)同样可以对32字节哈希进行签名,但其返回的是拼接后的65字节数据,且v为0或1,与旧版本行为保持一致;对于EIP-712等链上校验场景,建议使用exchange.IO("sign", ...)。

hash不是32字节时报错。签名与链无关,波场(TRON)的交易所对象同样可用。