PHP编程验证服务器上的以太坊签名

以太坊有一个非常强大的JavaScript生态系统。有一些很棒的开源项目,比如ethereumjs-util,它提供了一个用以太坊帐户签名的即插即用功能。

JavaScript的一个缺点是,在许多领域,它带来了安全问题。一个这样的安全风险是显而易见,这是由于我努力在EthTools.com上实现持久性认证(仍然是一个正在进行的被警告了的工作)。

利用开源项目(如ethereumjs-util)来签署任意的数据消息是相当容易的。然而,不容易的是告诉服务器有人已经成功地验证了某帐号的所有权。

当然这也不是绝对正确的,你也可以很容易做到这一点。简单地构建一个简单的API端点,并在成功认证后向其发出请求。

真正的问题是,创建一个“假”请求并将其发送到上述(易于识别 - 只需在控制台中查看)端点非常容易。 我可以轻松发出请求,说我已经验证了任何帐户的所有权。

凭借尖端技术……特别是处理“真实价值”的技术,尤其重要的是安全性体现出了它应有的重要性并被重视,特别是在历史上出现各种被利用的攻击的情况下。

而且即使是在初期,以太坊也吸引了最优秀的人——那些知道在做什么的人。如果有安全漏洞,有人会找到它。

现在。。虽然可以确保AJAX请求和伪造更难,但几乎不可能使交易100%的安全。我需要另一种方式。

我最终解决的方法是选择最简单的服务器端认证方式。

对每个人交易都是可视化的

在客户端中与区块链交互的一件大事是,在任何情况下,任何人都能清楚地看到你在做什么。他们可以自信地知道你没有把他们的私人钥匙发送给别人。怎样?他们可以查看控制台并查看每一个发出的请求。

如果一个服务在任何地方POSTing我的私钥,我会非常担心。

在我们实现的认证流中,用户可以看到我们没有在任何地方发送任何数据——所有的东西都是在客户端中完成的。

遗憾的是,我的身份验证方案中确实需要POSTing数据。但也不用担心(有些人可能不同意)。

我们POST身份验证的公钥到我们的API端点。虽然你不能用服务器上的公钥来验证我们所做的事情,但我们并没有用你的公钥做任何恶作剧——这就是为什么它是公开的。

在服务器上,我们使用提交的公钥来验证提交的签名是由具有相应私钥信息的人创建的。这里要明确指出,我们不知道你的私钥,但椭圆曲线加密允许我们通过简单地使用公钥来验证签名是否是使用它创建的。

在ethereumjs-util和solidity中,ecrecover方法是前提,除非这些工作分别在客户端和区块链中。

以太坊论坛上,chriseth给出了ecrecover的以下有用解释:

ecrecover的思想是,可以计算对应于用于创建ECDSA签名的私钥的公钥,这两个额外的字节通常是由签名提供的。签名本身是椭圆曲线点R和S的两个(编码),而V是恢复公钥所需的两个附加位。

这也解释了为什么返回类型是地址:它返回对应于恢复的公钥(即其sha3/keccak的哈希)的地址。这意味着要实际验证签名,检查返回的地址是否等于相应的私钥应该已经签署哈希的那个地址。

我们希望在服务器上有相同的功能。

注意:Solidity的ecrecover返回一个地址,而ethereumjs-utils的ecrecover返回一个公钥。

注:研究期间,我发现了一些有趣的StackExchange的问题主题。这些内容如下:

web3.js API文档还提供了一些关于ecrecover的参数的见解:

1
2
3
4
5
6
7
After the hex prefix, characters correspond to ECDSA values like this:

r = signature[0:64]
s = signature[64:128]
v = signature[128:130]

Note that if you are using ecrecover, v will be either "00" or "01". As a result, in order to use this value, you will have to parse it to an integer and then add 27. This will result in either a 27 or a 28.

PHP中怎么做

[EthTools.com]是建立在Phalcon PHP框架之上的。

没有真正意义上的以太坊PHP社区,PHP在处理数值表示方面有其缺点。

当然,椭圆曲线密码的问题是极其复杂的,而我对它也缺乏已经掌握的可用知识。

在大量的资料查询研究和大量的开发调试之后,我成功地实现了PHP中的ecrecover功能。

虽然我知道如何做到这一点,我写了一些“笔记”,我整理和包含在下面的内容,希望能帮助别人了解正确的方向。

我的行动逻辑是使用ethereumjs-util,使用已知的以太坊私钥签署交易。然后,我会模仿PHP中的ecrecover方法的代码路径,然后像宏播放一样执行,直到从签名返回的输出公共密钥与原始签名帐户匹配。

所以…

在Node中,缓存 Buffers 是无符号8位整数的数组。 digits 是它们的10进制(十进制)表示。

8位就会有2^8=255个十进制选项。这些整数是来自UTF-8字符集的字符的数字编码表示。

Node利用这些缓存来进行这些计算所需的排序的数据操作。

在服务器上,我们有不同的字符串(消息哈希和签名),但是PHP不知道这些字符串中的字节是base 16 numerical表示(十六进制)。

每个字符都是一个“小写”,它需要4个字节来表示(允许十六进制字符是0—9和A—F)。

这样,8位数据是两个十六进制字符。

在Node中,将字符串“61BF09”转换为一个buffer,通过将两个小写的集合转换成它的十进制形式。

  • 61转成97
  • bf转成191
  • 09转成9

要在PHP中执行等效,我们执行如下的操作:

1
$r_byte_array = unpack('C*', hex2bin($r));

我们调用hex2bin,它将十六进制字符串(不含0x)转换为二进制表示(base 2)。通过调用这个方法,我们隐式地说明初始格式是十六进制。

unpack然后将字符串转换为代码中的数组——我们的Buffer等价物。

最初PHP只是认为字符串是UTF-8。如果我们不先调用hex2bin,第一个int是54,效果是这样:

这是因为unpack只是将UTF8中的第一个字节(54)转换成二进制代码(6),64个字符=64个代码点。

当我们告诉unpack我们处理十六进制时,它将每个两个字节的十六进制集合(每个代表4位数据的字符)转换为它的十进制表示。61(0x61)变为97。我们的64个字节十六进制字符串变成32个8位整数,效果是这样:

你可以通过使用这个转换器来看这些不同的表示。

现在,你就有一个符合要求并且已经格式化了的消息哈希和签名表示,“你可以作弊了”。

我比较懒和喜欢自做聪明。也就是说,让我试图充分理解、欣赏和实施secp256k1椭圆曲线是根本不会发生的。此外…何苦?这是一个不需要重新发明的轮子。

我发现了一些与secp256k1有关的php库。例如:

我最终使用了所有三个库的组合,我喜欢知道我在使用什么,并且基本上(至少)理解我正在向服务器推送什么。上面的库是相当丰富的、复杂的,我只是简单提取我需要的相对简单的功能。

在花了大量的时间来了解我正在做的事情之后,我终于成功地实现了我想要达到的目标——我已经成功地验证了以太坊客户端中创建的签名是来自我的一个特定的私钥。

当我第一次爬进这个rabbit hole的时候,我会继续实施我所想到的功能。

注意事项。2018年又我写了第二篇文章,详细介绍了我如何验证PHP先前签署的消息的有效性。(注:后面也会翻译给大家)

如果大家在学习用php开发以太坊那我们推荐这个教程:

php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。

其他语言开发以太坊的教程也有一些:

  • web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。

汇智网原创翻译,转载请标明出处。这里是原文