以太坊:签名与验证

本文详细介绍了以太坊中的签名机制,包括签名的原理、实现和应用。文章通过代码示例展示了如何在Solidity中验证签名,以及如何在客户端生成签名并调用验证函数。

以太坊和其他加密货币的一个核心原语是能够签署任何人都可以验证的数据。这为区块链的分布式特性提供了动力。在比特币中,你签署一笔交易,表示你想要把4个比特币给萨莉。如果没有这个属性,任何人都可以伪造交易,让自己拥有所有的币。

TL;DR;

如果你想要查看完整的代码库,访问 ecrecover-example。只需按照 README.md 中的说明操作并在命令行中查看结果。

什么是签名?

签名是用户 A “签署” 数据的行为,任何人都可以验证该数据来自用户 A。这在交易中用于检查它们是否真实。

一个常见的问题是“你如何验证交易的真实性?”简短的回答是公共密钥密码学。这是一个包含三个部分的算法。

  1. 密钥创建
  2. 加密/签名
  3. 解密/验证

加密通常用于隐藏其他数据中的数据。如果你加密一个字符串,例如 'hello world',你将得到类似于 `dqE3gJz/+5CQHfSJwMP2nQ` 的字符串。其目的是隐藏消息 'hello world'。签名则用于生成一个不同的输出字符串,但你也会将原始消息公开。

密钥创建将输出两个字符串:一个公共密钥和一个私有密钥。它们通过一个具有签名和验证特性的算法连接。签名将接受一个公共密钥、一个私有密钥和消息。输出将是另一个字符串,即签名。

  1. 签名 = F(公共密钥, 私有密钥, 消息)
  2. 验证 = F(签名, 消息)
  3. 有效的条件:验证 = 公共密钥

请注意,验证并不需要知道私有密钥。这使得第三方可以验证信息。如果验证函数的输出等于公共密钥,则签名是真的,否则是假的。

签名由三个变量组成:v、r、s。以太坊采用了 椭圆曲线密码学,这些变量只是底层数学的一部分。

为什么要签名?

签名是确认某件事情由正确的人/合约进行的良好方式。这意味着我们可以相信某人实际上在做他们所说的事情。

与现实世界的签名可能会被伪造不同,数字签名不能被伪造。如果你想知道用户 A 做了某件事,请在继续之前让他们签署。一旦发生争议,请检查签名。

开发:

作为开发者,你希望用户签署一条消息。在你的 DApp(分布式应用程序)中有三个部分来创建此功能。

  1. Solidity 验证函数
  2. 用于签署消息的客户端代码
  3. 用于调用 Solidity 验证器的客户端代码

Solidity 验证器:

Solidity 提供了一个全局可用的方法 ecrecover,返回一个地址。如果返回地址与签名者相同,则签名有效。

一个验证签名的 Solidity 合约 – Medium


 pragma solidity^0.4.8; 
 contract Verifier { 
 function recoverAddr(bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) returns (address) { 
 return ecrecover(msgHash, v, r, s); 
 } 

 function isSigned(address _addr, bytes32 msgHash, uint8 v, bytes32 r, bytes32 s) returns (bool) { 
 return ecrecover(msgHash, v, r, s) == _addr; 
 } 
 } 

查看原始代码

上述代码创建了一个具有 recoverAddrisSigned 功能的 Verifier 合约。后者将返回一个地址。要求你作为开发者在 Solidity 之外验证该地址是否正确。第二种方法 isSigned 则在 Solidity 内部进行检查。如果 msgHash 是由 _addr 签名的,isSigned 将返回 true 或 false。

创建签名:

创建签名有两种方式:

  1. 使用 Web3 的 Javascript 函数 web3.eth.sign
  2. 调用以太坊节点的 RPC API

如果你使用 Javascript,只需引入 web3 并连接到以太坊节点。下面的代码中,我正在运行一个绑定到 localhost:8545 的私有以太坊节点。注意:这在 TESTRPC 上将无法工作。


 const Web3 = require('web3') 
 const provider = new Web3.providers.HttpProvider('http://localhost:8545') 
 const web3 = new Web3(provider) 

 function toHex(str) { 
 var hex = '' 
 for (var i = 0; i < str.length; i++) { 
 hex += '' + str.charCodeAt(i).toString(16) 
 } 
 return hex 
 } 

 let addr = web3.eth.accounts[0] 
 let msg = 'I really did make this message' 
 let signature = web3.eth.sign(addr, '0x' + toHex(msg)) 
 console.log(signature) 

查看原始代码

生成以太坊签名

没有内置函数可以将字符串转换为十六进制。因此,我使用了函数 toHex 进行转换。用户地址( web3.eth.accounts[0] )和带有 0x 前缀的消息被传递到 web3.eth.sign 函数中。

创建签名的另一种方法是调用以太坊 RPC API。通过 curl,你应该能够向以太坊节点发起请求。

调用以太坊 RPC API 使用 curl – Medium


 curl -X POST localhost:8545 --data '{ 
 "jsonrpc": "2.0", 
 "method": "eth_sign", 
 "params": ["0x2fb5949a6a1dc03b6d59d4723bc913230a155d0d", "0x0564b25c8fcd6766f672d43252c8ee2597ad6c7a35315cf13e3b4d00bafc2e9f"], 
 "id": 1 
 }' 

查看原始代码 eth_sign_RPC.sh 由 ❤ 提供支持的 GitHub

调用以太坊 RPC API eth_sign

params 中的第一个参数是用户地址,第二个是消息的十六进制值。请注意,要使 RPC API 工作,你的帐户必须解锁。你将会得到类似以下内容的返回结果:

0x9955af11969a2d2a7f860cb00e6a00cfa7c581f5df2dbe8ea16700b33f4b4b9b69f945012f7ea7d3febf11eb1b78e1adc2d1c14c2cf48b25000938cc1860c83e01

长签名编码了之前提到的 v、r、s 变量。要提取这些值,你需要将签名解析为子字符串。

给定一个签名,提取 r、s 和 v 值。 – Medium


 signature = signature.substr(2); //去掉 0x 
 const r = '0x' + signature.slice(0, 64) 
 const s = '0x' + signature.slice(64, 128) 
 const v = '0x' + signature.slice(128, 130) 
 const v_decimal = web3.toDecimal(v) 

查看原始代码 extract_signature_params_snippet.js 由 ❤ 提供支持的 GitHub

解析签名中的 r、s 和 v

注意:v 必须是一个十进制数,因此第二个 v_decimal 将十六进制 v 转换为十进制 v。

危险:生成的 v_decimal 必须是 27 或 28!

检查是否正确:

在完成验证器和签名后,剩下的就是实际检查签名是否真实。有一个小小的注意事项。请记住,在创建签名时使用了字符串 0x + toHex(msg)。好吧,这与你传入验证器的哈希并不相同!

再次强调,创建签名的哈希与验证器的哈希不同。这样做的原因是保护用户不签署 任意负载

解决方案是添加一个自定义的以太坊消息和长度。

// 签名 Hex:
I really did make this message // 对于验证器 sha3:
\x19Ethereum Signed Message:\n30I really did make this message

这一区分是非常必要的!请不要浪费时间遵循任何其他步骤。

最后一步是以某种方式调用你的 Solidity 代码。我正在使用 Truffle 3 来部署之前的智能合约。请注意,必须声明以太坊节点的位置( localhost:8545),否则它将无法按预期工作。

在 Javascript 中使用 Solidity 验证签名的有效性 – Medium


 //... 
 const SignVerifyArtifact = require('./contracts/SignAndVerifyExample') 
 const SignVerify = contract(SignVerifyArtifact) 
 SignVerify.setProvider(provider) 
 //... 
 SignVerify 
 .deployed() 
 .then(instance => { 
 let fixed_msg = `\\x19Ethereum Signed Message:\\n${msg.length}${msg}` 
 let fixed_msg_sha = web3.sha3(fixed_msg) 
 return instance.verify.call( 
 fixed_msg_sha, 
 v_decimal, 
 r, 
 s 
 ) 
 }) 
 .then(data => { 
 console.log('-----data------') 
 console.log(\`input addr ==> ${addr}\`) 
 console.log(\`output addr => ${data}\`) 
 }) 
 .catch(e => { 
 console.error(e) 
 }) 

查看原始代码 验证签名是否有效

Truffle 创建一个部署函数,返回合约实例。我创建我的 sha3 消息并将所需变量传递给 instance.verify.call。如果最后两行返回的地址相同,那么拥有者确实签署了该消息。否则,这是可以忽视的伪造。

结论:

签署数据对任何类型的 DApp 都可能很重要。一些明显的应用包括权利管理、版权、专利所有权。用户可以签署这些文件,任何人都可以验证他们确实创建了这些东西。你可以想到什么用例?

  • 原文链接: medium.com/@angellopozo/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
angellopozo
angellopozo
江湖只有他的大名,没有他的介绍。